网易首页 > 网易号 > 正文 申请入驻

Emoji 突变「U盘」?表情符号被曝能“走私”数据,程序员亲测:真的可以!

0
分享至

【CSDN 编者按】在数字通信的世界里,人们经常使用表情符号来让对话变得更加生动有趣。但你是否曾想过,这些看似简单的表情符号背后可能隐藏着一些秘密数据?本文作者听闻这一可能性后,亲测发现:在普通文本或表情符号中,真的可以嵌入不可见的数据,实现信息的隐蔽传输。

作者 | Paul Butler 翻译 | 郑丽媛

出品 | CSDN(ID:CSDNnews)

最近,有个网友在 Hacker News 上的评论引起了我的兴趣:

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

那么,真的可以在一个表情符号中编码任意数据吗?

我试了一下,真的可以——只不过我用的方法并不需要 ZWJ,而且在任何 Unicode 字符中都可以编码数据。

背景介绍

Unicode 以代码点的序列形式表示文本,每个代码点基本上只是一个数字,由 Unicode 联盟为其赋予特定含义。通常来说,一个具体的代码点会写成 U+XXXXXXXX,其中 XXXXXXXX 是用大写的十六进制表示的数字。

对于简单的拉丁字母文本,Unicode 代码点和屏幕上显示的字符之间存在一一对应的关系。例如,U+0067 代表字符“g”。

对于其他书写系统,某些屏幕上显示的字符可能由多个代码点表示。例如,印地语中的 की 字符就是由连续的两个代码点 U+0915 和 U+0940 组合而成。

变体选择器

Unicode 指定了 256 个代码点作为“变体选择器”,从 VS-1 到 VS-256。这些选择器本身没有屏幕显示的表现形式,而是用于修改前一个字符的显示效果。

大多数 Unicode 字符并没有与之关联的变体。由于 Unicode 是一个不断发展的标准,并且旨在保持未来兼容性,因此即使代码处理程序不了解其含义,也应保留变体选择器。例如,代码点“g”(U+0067)后面跟着 VS-2(U+FE01)时,显示为小写的“g”,与单独的“g”(U+0067)完全相同。但如果你复制并粘贴它,变异选择器会随之一起被复制。

既然 256 正好是一个字节的变体数量,这就为我们提供了一种方法,可以在任何其他 Unicode 代码点中“隐藏”一个字节的数据。

实际上,Unicode 规范中并未明确提到多个变体选择器的序列,只是暗示在渲染过程中应该忽略它们——所以,你明白我的意思了吗?

我们可以将一系列变体选择器连接起来,表示任意的字节字符串。

例如,假设我们想要编码数据“hello”,它对应的字节值是 [0x68, 0x65, 0x6c, 0x6c, 0x6f]。我们可以通过将每个字节转换为对应的变体选择器,然后将它们串联起来。

变体选择器的代码点被分为两段:最初的 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()
}
}

然后,要编码一系列字节,我们可以在一个基础字符后面连接多个这样的变体选择器:

fn encode(base: char, bytes: &[u8]) -> String {
let mut result = String::new();
result.push(base);
for byte in bytes {
result.push(byte_to_variation_selector(*byte));
}
result
}

为了编码字节 [0x68, 0x65, 0x6c, 0x6c, 0x6f],我们可以运行以下代码:

fn main() {
println!("{}", encode('', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]));
}

这将输出:

乍看之下,它就像一个普通的表情符号,但试着将其粘贴到解码器中看看。

如果我们改用调试格式化器,就可以看到发生了什么:

fn main() {
println!("{:?}", encode('', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]));
}

输出为:

"\u{e0158}\u{e0155}\u{e015c}\u{e015c}\u{e015f}"

很显然,这揭示了在原始输出中“隐藏”的字符。

如何解码?

解码过程同样也非常简单:

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;
}
// 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)给文本添加水印:有些技术可以利用文本中的微妙变化给消息添加“水印”,以便在消息被发送给多人后泄露时,可以追踪到原始接收者。变体选择器序列提供了一种持久化的方式,能够经受住大多数复制/粘贴操作,且允许存储任意密度的数据。如果你愿意,甚至可以对每个字符进行标记。

LLM 能解码吗?

自从这篇文章出现在 Hacker News 后,有些人开始问 LLM(大语言模型)能否处理这种隐藏数据。

一般来说,分词器确实似乎会将变体选择器作为标记保留下来,因此理论上模型是可以访问它们的。OpenAI 的分词器就是一个很好的例子:

可总体来说,模型并不会主动解码这些数据——不过当与代码解释器结合使用时,有一些模型能够成功解码它们。以下是 Gemini 2 Flash 在 7 秒内成功解码的示例,使用了 Codename Goose 和 foreverVM(免责声明:我在 foreverVM 团队中工作)。

原文链接:https://paulbutler.org/2025/smuggling-arbitrary-data-through-an-emoji/

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
上交所公布2026年春节休市安排

上交所公布2026年春节休市安排

界面新闻
2026-02-05 17:27:24
突发!巨头宣布重大收购,盘前大涨!

突发!巨头宣布重大收购,盘前大涨!

中国基金报
2026-02-05 19:00:20
年过60岁要远离喝茶?医生直言:不想早进医院,5种茶类避免饮用

年过60岁要远离喝茶?医生直言:不想早进医院,5种茶类避免饮用

路医生健康科普
2026-02-04 21:09:50
殡仪馆烧尸人:一具遗体最少卖八千块,年轻漂亮的女尸卖得更贵

殡仪馆烧尸人:一具遗体最少卖八千块,年轻漂亮的女尸卖得更贵

吴学华看天下
2024-08-15 14:45:07
狂秀操作!3笔交易送走半支队,整整省了1亿啊,冠军和薪资全都要

狂秀操作!3笔交易送走半支队,整整省了1亿啊,冠军和薪资全都要

球童无忌
2026-02-05 11:41:16
4换1!正式达成交易!雷霆又拿下一个新星

4换1!正式达成交易!雷霆又拿下一个新星

篮球实战宝典
2026-02-05 18:09:10
这是萝莉岛内部,看着确实有点诡异,而且还有这凳子和照射灯

这是萝莉岛内部,看着确实有点诡异,而且还有这凳子和照射灯

静若梨花
2026-02-05 11:59:15
天生“苦相脸”的7位女星,个个愁容满面,面无表情都像是要哭了

天生“苦相脸”的7位女星,个个愁容满面,面无表情都像是要哭了

白宸侃片
2026-02-04 13:34:53
普京真急了,派两员大将访华不放心,突然会晤中方,有大事发生?

普京真急了,派两员大将访华不放心,突然会晤中方,有大事发生?

安珈使者啊
2026-02-05 16:53:31
武汉新娘给黑人DJ留言事件,把我人都看麻了

武汉新娘给黑人DJ留言事件,把我人都看麻了

听风听你
2026-02-03 21:37:44
天生丽质高圆圆:国色天香,蕙质兰心。顾盼生姿,绝代芳华

天生丽质高圆圆:国色天香,蕙质兰心。顾盼生姿,绝代芳华

十为先生
2026-02-05 20:36:15
人老有3怕!一怕跌倒,另外2怕,可惜多数老人都没意识到!

人老有3怕!一怕跌倒,另外2怕,可惜多数老人都没意识到!

健身狂人
2026-02-05 19:26:12
母亲住院是我掏的钱,出院后,妈问我:你嫂子给钱没

母亲住院是我掏的钱,出院后,妈问我:你嫂子给钱没

媛来这样
2026-01-23 17:37:18
三只羊和“无语哥”的电商公司,9.75亿美元卖给了美股上市公司

三只羊和“无语哥”的电商公司,9.75亿美元卖给了美股上市公司

大风新闻
2026-02-05 17:17:02
微博之夜红毯,肖战腼腆笑,杨紫宣传《生命树》,娜扎被赞维纳斯

微博之夜红毯,肖战腼腆笑,杨紫宣传《生命树》,娜扎被赞维纳斯

露珠聊影视
2026-02-05 19:28:37
大S雕像揭幕后,张兰说:回想起年初的风波,想起来还真是不容易

大S雕像揭幕后,张兰说:回想起年初的风波,想起来还真是不容易

向天祈福
2026-02-04 18:16:08
夺冠后却被开除,每月拿600,如今为法国14次夺冠,已成法国媳妇

夺冠后却被开除,每月拿600,如今为法国14次夺冠,已成法国媳妇

古木之草记
2025-12-15 14:31:43
爱泼斯坦这把“火”烧到欧洲了,法国马克龙、罗斯柴尔德家族、英国王子、德银纷纷中招...

爱泼斯坦这把“火”烧到欧洲了,法国马克龙、罗斯柴尔德家族、英国王子、德银纷纷中招...

鲁晓芙看欧洲
2026-02-05 19:20:52
江西官方通报:成立联合调查组,将问责责任人

江西官方通报:成立联合调查组,将问责责任人

澎湃新闻
2026-02-05 00:54:05
1961年周总理见陈洁如,其见面便称女婿是好人且可能是共产党

1961年周总理见陈洁如,其见面便称女婿是好人且可能是共产党

唠叨说历史
2026-02-03 11:16:37
2026-02-05 22:03:00
CSDN incentive-icons
CSDN
成就一亿技术人
26304文章数 242229关注度
往期回顾 全部

科技要闻

美团7.17亿元收购叮咚买菜

头条要闻

多家医美机构可"造腹肌" 有人花7万元打了83支玻尿酸

头条要闻

多家医美机构可"造腹肌" 有人花7万元打了83支玻尿酸

体育要闻

奇才:我学生……独行侠:成交!

娱乐要闻

微博之夜卷入座位风波!杨幂超话沦陷

财经要闻

中美"只会有好消息" 经济冷暖看房价

汽车要闻

李想为全新L9预热 all in AI造更好的车

态度原创

艺术
旅游
健康
公开课
军事航空

艺术要闻

他热爱绘画,生活中的每一刻都充满激情!

旅游要闻

2月11日启幕!苏州虎丘山灯会邀您入画游

耳石症分类型,症状大不同

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

54岁荷兰王后以预备役军人身份参军 王室解释原因

无障碍浏览 进入关怀版