识别文件中隐藏图片需按字节扫描固定头特征:png为89 50 4e 47,jpeg为ff d8 ff(需验证合法marker及ff d9结尾),gif为47 49 46 38;须二进制读取、避免截断、分块滑动扫描并校验结构完整性。

怎么识别文件里藏着的图片头(PNG/JPEG/GIF)
二进制文件里混着图片,但没索引、没资源表,只能靠“头特征”硬扫——本质是找固定字节序列。PNG 以 89 50 4E 47 0D 0A 1A 0A 开头,JPEG 是 FF D8 FF(可能带 E0/E1 等 APP 段),GIF 是 47 49 46 38(即 "GIF8")。别用字符串比较,直接按 unsigned char 数组比字节。
常见错误:把文件当文本读(用了 std::string + find()),遇到 \x00 就截断;或用 std::ifstream::read() 但没检查实际读取长度,导致末尾越界比对。
- 用
std::vector<unsigned char></unsigned>一次性读全文件(小文件)或分块滑动扫描(大文件) - 每找到一个疑似头,至少验证前 4–8 字节,JPEG 还要跳过 APP 段再确认
FF D9结尾是否合理 - 注意:
WEBP头是52 49 46 46 ?? ?? ?? ?? 57 45 42 50,中间长度字段得解析,不能只盯开头 4 字节
滑动窗口扫描时怎么避免漏图和误报
暴力从 offset 0 扫到 size-8 效率低还容易误触发——比如 FF D8 FF 在 JPEG 注释段里重复出现,或 PNG 头被压缩数据偶然凑出。关键不是“扫得快”,是“扫得准”。
典型坑:在 ZIP 或 EXE 文件里扫,FF D8 可能出现在代码段或资源 ID 中,根本不是图片;或者两个图片紧挨着,后一个的头被前一个的尾部覆盖(如前图未对齐结束)。
- 设置最小有效偏移间隔:找到一个头后,跳到它预估的结尾(需解析长度字段或找
FF D9)再继续扫,别挨个字节挪 - 对 JPEG,必须检查 SOI(
FF D8)后是否紧跟合法 marker(FF E0~FF EF或FF DB等),跳过非法组合 - PNG 要验证 IHDR 块是否存在且长度字段不为 0,否则可能是伪造头
如何安全提取出完整图片数据(不截断、不溢出)
光找到头不够,得确定边界。JPEG 靠 FF D9 结尾最可靠,但有些损坏图会缺失;PNG 必须解析 chunk 结构,累加每个 chunk 的 length + 12 字节(含 type/crc);GIF 要看逻辑屏幕描述符里的 width/height 和后续图像数据块。
最容易翻车的是:把整个文件当 JPEG 处理,结果在 FF D9 后面又扫到另一个 FF D8,就以为是两张图——其实第二张可能只是嵌入的缩略图或 APP1 里的 Exif 数据。
- 对 JPEG:从
FF D8开始,逐 marker 解析,遇FF D9停,但若超 10MB 还没停,强制截断并标记“可能损坏” - 对 PNG:读完
89 50 4E 47后,跳 4 字节,读 4 字节 length(big-endian),再读 4 字节 type,校验 type ==IHDR(49 48 44 52),才认为是真 PNG - 写提取结果时,用
std::ofstream以std::ios::binary模式打开,别用默认文本模式
C++ 里哪些库或工具能省事但别乱用
libpng 和 libjpeg 是解码用的,不是头扫描用的——它们要求输入是完整合法图片,你传进去一段乱扫出来的二进制,大概率直接崩溃或返回 NULL。真正能帮上忙的只有底层字节操作工具。
有人试过用 boost::iostreams 做内存映射扫描,结果在 Windows 上遇到 MAP_FAILED 不报错,静默失败;还有人用 std::mmap(C++17 未标准化)写跨平台代码,编译就跪。
- 坚持手写
std::ifstream+read()+memcmp(),控制力最强,也最易调试 - 需要快速原型?用 Python 先写逻辑(
open(..., 'rb')+find()),验证头规律后再 C++ 落地 - 别碰
stb_image的stbi_info_from_memory——它内部会尝试解码,对非图数据极不友好,且不暴露原始偏移
边界永远比想象中模糊:EXE 里可能有图标资源(ICONS、CURS),它们是多张 PNG 连在一起;APK 是 ZIP,里面 res/drawable/ 下的文件名带扩展名,但资源表可能被混淆。头扫描只是起点,后面得结合上下文判断是不是真想提取的图。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











