Smuggling arbitrary data through an emoji | 通过emoji走私任意数据

https://paulbutler.org/2025/smuggling-arbitrary-data-through-an-emoji/

写了个python版本,感觉有人是会拿来比赛出题的。

Unicode 码点

Unicode 是计算机科学领域的一项业界标准,包括字符集和编码方案。它为每种语言中的每个字符设定了统一且唯一的二进制编码,通常用两个字节表示一个字符。Unicode 码点(Code Point)是指 Unicode 字符在 Unicode 字符集中的唯一编号。

Unicode 编码格式

Unicode 编码格式有多种,其中最常见的是 UTF-8UTF-16。UTF-8 是一种可变长度的编码方式,根据字符的不同范围使用不同长度的编码。例如:

  • 000000-00007F: 0xxxxxxx
  • 000080-0007FF: 110xxxxx 10xxxxxx
  • 000800-00FFFF: 1110xxxx 10xxxxxx 10xxxxxx
  • 010000-10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Unicode 定义了 256 个代码点作为“变体选择器”,从 VS-1 到 VS-256。这些变体选择器本身没有可见的显示效果,但用于修改前一个字符的显示方式。例如,代码点 U+0067​(字符g​)后跟 U+FE01​(VS-2)仍然显示为小写g​,与单独的g​相同。但如果复制并粘贴该字符,变体选择器也会随之携带。

由于 256 足以表示一个字节,这为我们提供了一种在任何其他 unicode 代码点中“隐藏”一个字节数据的方法。

变体选择器

按照Paul Butler的思路,假设要编码字符串 "hello",其 ASCII 码为 [0x68, 0x65, 0x6c, 0x6c, 0x6f]​。可以将每个字节映射到一个变体选择器,然后将这些变体选择器附加到一个基字符(如空格或emoji)后面。例如,使用emoji符作为基字符,编码后的字符串可能看起来像一个普通的表情符号,但实际上包含了隐藏的数据。

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

基字符(如空格或表情符号)不需要一定是emoji符号,任何 Unicode 字符都可以作为基字符。

image

python示例

encode

将字节(0-255​)转换为对应的变体选择器字符,判断字节大小并选择对应的变体选择器范围。将基字符和字节数组进行组合,依次生成编码后的字符串。

def byte_to_variation_selector(byte: int) -> str:
    """Converts a byte to a variation selector character."""
    if byte < 16:
        return chr(0xFE00 + byte)
    else:
        return chr(0xE0100 + (byte - 16))

def encode(base: str, bytes: list) -> str:
    """Encodes a base character with a list of bytes as variation selectors."""
    result = [base]
    for byte in bytes:
        result.append(byte_to_variation_selector(byte))
    return ''.join(result)

if __name__ == "__main__":
    base = ' '
    byte_sequence = [0x41, 0x6e, 0x64, 0x79, 0x4e, 0x6f, 0x65, 0x6c]
    encoded_str = encode(base, byte_sequence)
    print(encoded_str)

decode

解密思路就是将每个变体选择器字符映射回其对应的字节值,然后,编写一个函数,遍历输入字符串,提取所有有效的变体选择器字符,并将其转换为字节值。

def variation_selector_to_byte(variation_selector: str) -> int:
    code_point = ord(variation_selector)
    if 0xFE00 <= code_point <= 0xFE0F:
        return code_point - 0xFE00
    elif 0xE0100 <= code_point <= 0xE01EF:
        return code_point - 0xE0100 + 16
    else:
        raise ValueError(f"无效的变体选择器字符:{variation_selector}")
def decode(variation_selectors: str) -> bytes:
    result = bytearray()
    for char in variation_selectors:
        try:
            byte = variation_selector_to_byte(char)
            result.append(byte)
        except ValueError:
            if result:
                break
    return bytes(result)
encoded_str = ' 󠄱󠅞󠅔󠅩󠄾󠅟󠅕󠅜'
decoded_bytes = decode(encoded_str)
decoded_str = decoded_bytes.decode('utf-8')
print(decoded_str) 

发布者

AndyNoel

一杯未尽,离怀多少。