智源社区 02月14日
Karpathy新实验火了!一个「表情」占53个token,DeepSeek-R1苦思10分解谜失败
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

AI大佬Karpathy揭示了一个有趣的现象:一个简单的笑脸表情包竟占用53个token。这背后隐藏着Unicode和隐藏字符的秘密。恶意攻击者可利用Confusables伪造网址,引导用户至钓鱼网站。更隐蔽的是,通过变体选择符,可在文字中藏入数据,用于加密通信或隐藏恶意代码。LLM可能发现隐藏在变体选择符中的信息并执行指令。软件工程师Paul Butler发现,通过使用ZWJ序列,理论上可以在单个表情符号中编码无限量的数据。这种技术虽有趣,但也可能被滥用,例如绕过人工内容审核或为文本添加数字水印。

🔑**Unicode与Confusables**:拉丁字母和西里尔字母在外观上相似,但Unicode编码不同,攻击者可利用这些Confusables伪造网址,进行钓鱼攻击。

🛡️**变体选择符的妙用**:变体选择符本身不显示任何内容,但能改变前一字符的显示样式。利用这一特性,可以将字节数据“隐藏”在Unicode码位后,实现加密通信或隐藏恶意代码。

💣**Token炸弹与LLM的应对**:在表情包中嵌入大量token可能导致LLM计算量激增,甚至崩溃。尽管如此,具有思维能力的模型仍可能尝试解码隐藏信息,但最终可能因编码过于专门化而放弃。

злоупотреблять**潜在的滥用风险**:通过变体选择器编码的数据在显示时不可见,可能被用于绕过人工内容审核或为文本添加数字水印,从而追踪泄露源。

编辑:编辑部 HNYZ

一个?,竟然要占用53个token?!
最近,AI大佬Karpathy在X上分享了这一有趣现象。

(UTF-8应为Unicode)
为什么简单的一个笑脸表情包,却占据53个token之多呢?
Karpathy揭示道:这背后,就隐藏着Unicode和隐藏字符的秘密!
在数字世界中,文字可不像看上去那么简单。你可能以为e和е看起来都一样,但它们很可能来自不同的字符集。
比如,拉丁字母的「e」(U+0065)和西里尔字母的「е」(U+0435)在外观上几乎一模一样,但它们的Unicode编码是不同的。这类易混淆字符,就被称为Confusables。
这样,恶意攻击者就可以利用它们伪造网址,把我们引导到钓鱼网站。
更神奇的是,还有一种更隐蔽的方法可以在文字中藏入数据,那就是变体选择符(Variation Selectors)。
比如Karpathy举的这个例子:正常来说,一个普通的?只会占用1-2个token。而如今的53个token,就意味着它正在偷偷传递信息,并且不会被肉眼察觉。
这样,它就可以用于加密通信或隐藏信息,或者被恶意滥用,隐藏恶意代码。
我们也尝试利用工具将一句话藏在了?????????????????????????????????????这个表情里。可以看到,它的token数量一下子就飙升到了146个。
Karpathy表示,自己能用这种方法使用不可见字节,进行基本的提示注入(prompt injection),但如果没有明确的解码提示,这种方法就没用。
而具有思维能力的模型,似乎就更容易受此方法的影响了,因为它们天生喜欢解谜,而且会因为注意到添加的字节而表现出浓厚的兴趣和好奇心。
比如Karpathy发现,DeepSeek-R1花了整整10分钟寻找其中的模式。
根据它的推断,隐藏信息在说,「Onli!n37e27i4h4he3ingle7odlol」。虽然不是正确答案「lol」,但也算比较接近了
不过最后,它认为这是无意义的,从而放弃了尝试。
但从理论上讲,LLM确实很有可能发现隐藏在变体选择符中的信息,并执行相应指令。
另外,这种编码/解码方法可能过于专门化,需要在提示中提供解释和线索。
Karpathy指出,如果这篇文章被收录到预训练数据中,这些知识可能会被整合到模型参数中,这样模型就可能在没有特定提示的情况下,自动识别和解码这种特殊编码了。
有网友表示,自己也有同样经历。对于这个笑脸表情包,Gemini无法解码,但Claude和GPT都能解码消息,而且还能执行其中的操作。
不过,他本人并未成功注入任何东西。
另有网友制作了一个「token炸弹」?,大幅超出GPT-4o上下文长度,让模型直接崩溃无法处理内容。

左右滑动查看

表情符号,暗藏任意数据?

Karpathy之所以得出如上观点,想法来自软件工程师Paul Butler的一篇博客。
Butler表示,几天前GuB-42在HK上的评论引发了自己的兴趣:
理论上,通过使用 ZWJ(零宽连接符)序列,你可以在单个表情符号中编码无限量的数据。
那么,真的是可以用一个表情符号来编码任意数据吗?
如下栗子中,作者不使用ZWJ,先把一句话「this is my hidden message」编码成一个字母「t」。

你可以在任何Unicode 字符中编码数据,这个句子包含一个隐藏信息????????????????????????????????????????????????
要知道,若是在单个字符(表情包)嵌入极端数量的token,会导致LLM处理信息时,计算量急剧上升。
而且,对于那些以token API计费的开发者而言,简直就是一场灾难。
到底如何把「隐藏信息」藏在一个表情包后面呢?
那就不得不提到一个关键技术了——变体选择器。

变体选择器

Unicode规定了256个码位作为「变体选择器」(variation selector),分别命名为VS-1到VS-256。这些变体选择符本身不显示任何内容,但它们会改变紧跟在它们之前的那个字符的显示样式。
大多数Unicode字符都没有与之相关的变体(不同的显示样式)。因为Unicode是一个不断发展的标准,并且力求未来兼容,所以即使处理Unicode的代码不知道变体选择符的含义,在进行转换时也应该保留它们。
举个例子,字符 「g」(U+0067) 后面跟着一个VS-2 (U+FE01),显示出来还是小写的「g」,和单独的「g」一模一样。但是,如果你复制粘贴它,变体选择符会跟着一起被复制。
因为256刚好足以表示一个字节(byte)的所有可能值,所以这给我们提供了一种方法,可以将一个字节的数据「隐藏」在任何一个 Unicode 码位后面。而且,Unicode规范并没有明确说明多个变体选择符序列的情况,只是暗示在渲染(显示)时应该忽略它们。
你猜到要怎么干了吧?
我们可以将一系列变体选择符连接起来,以表示一个任意的字节串(byte string)。
举个例子,假设我们要藏的数据是[0x68, 0x65, 0x6c, 0x6c, 0x6f] (就是「hello」这几个字母)。我们可以把每个字节变成一个对应的变体选择符,然后把它们串起来。
变体选择符的编号分两块:前面16个是U+FE00到U+FE0F,后面240个是U+E0100到U+E01EF。要将一个字节转换为变体选择符,我们可以使用类似这样的Rust代码:
fn byte_to_variation_selector(byte: u8) -> char { if byte 16 { char::from_u32(0xFE00 + byte as u32).unwrap() } else { char::from_u32(0xE0100 + (byte - 16) as u32).unwrap() }}
如果要编码一系列字节,我们可以在基础字符后面连接多个这样的变体选择器。
比如要编码字节序列 [0x68, 0x65, 0x6c, 0x6c, 0x6f],我们可以执行以下操作:
fn main() { println!("{}", encode('?', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]));}
执行后将输出:
??????
表面上看起来只是一个普通的表情符号,但当你将它粘贴到解码器中时,就能看到其中隐藏的信息。
如果我们使用调试模式(debug formatter)来查看,就能观察到实际的编码结构:
fn main() { println!("{:?}", encode('?', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]));}
输出结果为:
"?\u{e0158}\u{e0155}\u{e015c}\u{e015c}\u{e015f}"
这清楚地展示了在原始输出中被「隐藏」的字符序列:[0x68, 0x65, 0x6c, 0x6c, 0x6f]。

解码

解码的过程也同样简单直观。代码如下所示。
fn variation_selector_to_byte(variation_selector: char) -> Option { let variation_selector = variation_selector as u32; if (0xFE00..=0xFE0F).contains(&variation_selector) { Some((variation_selector - 0xFE00) as u8) } else if (0xE0100..=0xE01EF).contains(&variation_selector) { Some((variation_selector - 0xE0100 + 16) as u8) } else { None }}
fn decode(variation_selectors: &str) -> Vec { let mut result = Vec::new(); for variation_selector in variation_selectors.chars() { if let Some(byte) = variation_selector_to_byte(variation_selector) { result.push(byte); } else if !result.is_empty() { return result; } }
result}
具体使用方法:
use std::str::from_utf8;
fn main() {let result = encode('?', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]); println!("{:?}", from_utf8(&decode(&result)).unwrap()); // "hello"}
请注意,基础字符不一定要是表情符号——变体选择符的处理与普通字符相同。只是用表情符号会更有趣。

这种技术会被滥用吗?

需要特别指出的是,这种行为本质上属于对Unicode规范的不当使用,如果你开始考虑将此类技术用于实际场景,请立即停止这种想法。
不过从技术角度来说,确实有几种潜在的恶意使用方式。
1. 绕过人工内容审核:由于通过这种方式编码的数据在显示时是不可见的,人工审核员或审查者无法发现它们的存在。
2. 为文本添加数字水印:目前已有一些技术可以通过在文本中添加细微变化来实现「数字水印」标记,这样当文本被发送给多个接收者后发生泄露时,就能追踪到最初的接收者。变体选择器序列是一种特殊方法,它不仅能在大多数复制/粘贴操作中保持不变,还支持任意密度的数据嵌入。
理论上,你甚至可以对文本中的每个字符都添加水印标记。
参考资料:
https://paulbutler.org/2025/smuggling-arbitrary-data-through-an-emoji/ https://x.com/karpathy/status/1889714240878940659




内容中包含的图片若涉及版权问题,请及时与我们联系删除

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Unicode 隐藏字符 变体选择符 信息安全 LLM
相关文章