首页 / 资讯中心 / 文章详情

CTF学习/crypto/零宽字符与 Unicode 隐写

CTF学习/crypto/零宽字符与 Unicode 隐写 ★ FEATURED ARTICLE
零宽字符与 Unicode 隐写考点概念零宽字符是宽度为零、肉眼不可见的 Unicode 字符包括零宽空格U200B、零宽非连接符U200C、零宽连接符U200D、零宽不中断空格UFEFF不占显示宽度却在字节流中真实存在。可以在看起来完全正常的文本中隐藏信息通常把 3 个字符映射为 0/1/2 的进位制来编码数据。Unicode 隐写还包括同形异义字Cyrillic а 与 Latin a 视觉相同但码点不同与不可见控制符右至左标记 U202E 可颠倒显示顺序等变体识别难度略高于零宽。把不可见字符序列还原为比特流再分组统计可见文本之外的码点按约定进制重组。工具层面 vim 与 hexdump 均可暴露零宽字符。四字符二进制映射方案零宽空格 U200B 记 00零宽非连接符 U200C 记 01零宽连接符 U200D 记 10零宽不中断空格 UFEFF 记 11。映射表由出题工具自定解码遇到未知约定时穷举排列运算规则编码端先把明文按字符集转成字节序列每个字节 8 位。四字符方案下把位流按两位为一组查表转成对应零宽字符两字符方案只保留两种码点逐位记 0 与 1两者只是分组粒度不同。转换出的零宽字符序列插入任意正常文本屏幕显示不变。三字符三进制方案把每段数据映射到 0/1/2 三种状态原理一致仅基数不同。解码端遍历文本只保留零宽码点逆查映射表还原位流每 8 位一组转回字节按原字符集还原明文。以字母 a 完整演算四字符方案的编码与解码。a 的 ASCII 码点为 97二进制 01100001。编码按两位切分为 01、10、00、01 四段查表 01 对应 U200C 零宽非连接符10 对应 U200D 零宽连接符00 对应 U200B 零宽空格01 再次对应 U200C把 U200C、U200D、U200B、U200C 依次插入任意正常句子即完成隐藏显示与原句完全一致。解码扫描载体只收集零宽字符得到同一序列逆映射得到 01、10、00、01拼接为 01100001转十进制 97 查 ASCII 得 a闭环还原成功。识别特征文件特征文本肉眼正常但文件字节数异常如 100 字符的 txt 占 2KB。 编辑器特征vim 中显示为^或高亮不可见符hexdump 可见 E2 80 8B/8C/8DUTF-8 编码的 200B/200C/200D。 字符串特征用 Pythonlen(s)与可见字符数不符或去空白后re仍多出字符。 提示语题目描述出现隐藏、看不见、间谍、句子中藏了东西等词。靶场题目[UTCTF2020]zeroBUUCTF另见 NSSCTF 收录 zero-width stegoNSSCTF [安洵杯 2020]BeCare4NSSCTFWriteup题目[UTCTF2020]zeroBUUCTF 识别文本肉眼正常但字节数与可见字符数严重不符用 PyCharm 或 vim 打开可见大量 ZWJ零宽连接符 U200D字符夹在字母之间binwalk 与 foremost 分离不到文件排除文件级隐藏解题把文本提交零宽字符解码工具即可用在线工具一次读出 flag脚本做法是遍历文本统计零宽码点出现次序按常用映射还原位流每 8 位一组转字节成 ASCII。若工具默认映射与出题映射不符交换 0/1 或换四字符映射重试flag{...} 出现即停止。 脚本ZWSP \u200b ZWNJ \u200c ​ def extract(s: str) - bytes: bits [] for ch in s: if ch ZWSP: bits.append(0) elif ch ZWNJ: bits.append(1) out bytearray() for i in range(0, len(bits) - len(bits) % 8, 8): out.append(int(.join(bits[i:i 8]), 2)) return bytes(out) ​ carrier It looks like a perfectly normal English novel sentence. secret bflag{zwsp_demo} bits .join(format(b, 08b) for b in secret) hidden .join(ZWNJ if x 1 else ZWSP for x in bits) text carrier[:20] hidden carrier[20:] print(可见字符 %d 个, len() %d % (len(carrier), len(text))) print(extract(text)) assert extract(text) secret工具https://330k.github.io/misc_tools/unicode_steganography.html题目[安洵杯 2020]BeCare4NSSCTF 识别txt 打开空白但文件字节数不为零hexdump 可见 E2 80 8B/8C/8D 序列判定零宽字符携带信息顺带排除 binwalk 分离。 解题txt 交零宽解码工具还原得到压缩包密码 RealV1siBle解压得到一张 jpgjpg 用 SilentEye 解密提取出隐藏信息读出 flag{1nV1sible_flag_Can_You_find?}脚本ZWSP, ZWNJ \u200b, \u200c password RealV1siBle bits .join(format(b, 08b) for b in password.encode()) embedded \u8fd9\u884c\u770b\u4e0d\u51fa\u4efb\u4f55\u4e1c\u897f \ .join(ZWNJ if x 1 else ZWSP for x in bits) \u3002 got .join(1 if c ZWNJ else 0 for c in embedded if c in (ZWSP, ZWNJ)) out bytes(int(got[i:i 8], 2) for i in range(0, len(got), 8)).decode() print(out) assert out password工具Unicode Steganography with Zero-Width CharactersSilentEye 为本地图形软件脚本ZWSP \u200b ZWNJ \u200c ​ def hide(data: bytes, carrier: str) - str: bits [] for byte in data: for i in range(8): bits.append(1 if (byte (7 - i)) 1 else 0) hidden .join(ZWNJ if b 1 else ZWSP for b in bits) return carrier hidden 。结尾正常文本 ​ def extract(s: str) - bytes: bits [] for ch in s: if ch ZWSP: bits.append(0) elif ch ZWNJ: bits.append(1) out bytearray() for i in range(0, len(bits), 8): out.append(int(.join(bits[i:i 8]), 2)) return bytes(out) ​ cipher hide(bflag{zero_width_ok}, 这是看起来完全正常的句子。) flag extract(cipher) print(提取结果: %s % flag.decode()) assert flag bflag{zero_width_ok} print(SUCCESS: flag{zero_width_ok})
阅读完成 · 觉得有帮助?
咨询建站