
新智元报道
新智元报道
【新智元导读】一个简单的笑脸😀可能远不止这么简单?最近,AI大神Karpathy发现,一个😀竟然占用了多达53个token!这背后隐藏着Unicode编码的哪些秘密?如何利用这些「隐形字符」在文本中嵌入、传递甚至「隐藏」任意数据。更有趣的是,这种「数据隐藏术」甚至能对AI模型进行「提示注入」!






左右滑动查看
表情符号,暗藏任意数据?

理论上,通过使用 ZWJ(零宽连接符)序列,你可以在单个表情符号中编码无限量的数据。

变体选择器
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<u8> {
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<u8> {
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;
}
// note: we ignore non-variation selectors until we have
// encountered the first one, as a way of skipping the "base
// character".
}
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. 为文本添加数字水印:目前已有一些技术可以通过在文本中添加细微变化来实现「数字水印」标记,这样当文本被发送给多个接收者后发生泄露时,就能追踪到最初的接收者。变体选择器序列是一种特殊方法,它不仅能在大多数复制/粘贴操作中保持不变,还支持任意密度的数据嵌入。 理论上,你甚至可以对文本中的每个字符都添加水印标记。 (文:新智元)