研究方向: 二进制安全、文件格式解析、内存漏洞、漏洞分析、Fuzzing
实验环境: Kali Linux、GCC、GDB、AddressSanitizer、FFmpeg、AFL++
适用对象: Linux 安全研究、漏洞挖掘、逆向分析学习者
实验原则: 使用自编的教学解析器研究内存错误,不运行未知恶意样本。
正常情况下,图片、视频、音频只是二进制数据,不具备直接执行代码的能力。
常见媒体文件包括:
但是,打开这些文件时,操作系统或应用程序必须解析文件内容。
例如:
用户打开 image.jpg
|
v
图片查看器读取文件
|
v
调用图片解析库
|
v
解析文件头、尺寸、元数据
|
v
解码压缩数据
|
v
分配内存并写入像素
|
v
显示图片
如果解析库存在漏洞,攻击者可以构造特殊文件触发异常内存操作。
攻击过程:
攻击者
|
| 构造特殊媒体文件
v
malicious.jpg / malicious.mp4 / malicious.mp3
|
| 用户打开或软件自动预览
v
存在漏洞的媒体解析器
|
| 处理异常长度、索引、压缩数据
v
内存安全漏洞
|
+----> 程序崩溃
|
+----> 内存信息泄露
|
+----> 内存破坏
|
v
满足特定利用条件
|
v
潜在的代码执行
核心结论:恶意文件本身通常不是执行主体,真正执行代码的是存在漏洞的解析程序。
| 技术 | 原理 | 是否能直接触发代码执行 |
|---|---|---|
| Buffer Overflow | 缓冲区越界写入 | 特定条件下可以 |
| Integer Overflow | 整数运算溢出 | 通常需要进一步触发内存错误 |
| Out-of-Bounds Read | 越界读取 | 通常导致崩溃或信息泄露 |
| Use-After-Free | 释放后继续访问内存 | 特定条件下可以 |
| Type Confusion | 错误解释对象类型 | 特定条件下可以 |
| 文件隐写 | 将数据隐藏在媒体内容中 | 通常不能独立执行 |
| Polyglot File | 同一文件满足多种格式结构 | 取决于解析器和执行环境 |
| 文件伪装 | 将可执行程序伪装成媒体文件 | 通常需要用户实际执行程序 |
需要特别区分:
将木马隐藏在图片中,不等于打开图片就能执行木马。
隐写技术通常还需要额外的加载器提取并运行隐藏的数据。
Linux 程序运行时,操作系统会为进程提供虚拟地址空间。
典型结构如下:
高地址
+-----------------------------+
| Stack(栈) |
| |
| 局部变量 |
| 函数调用栈帧 |
| 返回地址 |
+-----------------------------+
| |
| Memory Mapping |
| |
| 共享库 |
| 文件映射 |
| 匿名映射 |
+-----------------------------+
| |
| Heap(堆) |
| |
| malloc() |
| calloc() |
| new |
+-----------------------------+
| BSS |
| 未初始化全局变量 |
+-----------------------------+
| Data |
| 已初始化全局变量 |
+-----------------------------+
| Text |
| 程序机器指令 |
+-----------------------------+
低地址
注意:这是概念示意图,实际布局会受到操作系统、CPU 架构、ASLR 和程序链接方式影响。
栈通常用于保存:
例如:
void parse_image()
{
char buffer[16];
}
buffer 通常位于函数的栈帧中。
如果程序向其中写入超过 16 字节的数据,就可能发生栈缓冲区溢出。
合法栈缓冲区:
+----+----+----+----+
| A | A | A | A |
+----+----+----+----+
16 Bytes
越界写入:
+-------------------+------------------+
| 合法的 16 字节 | 超出边界的数据 |
+-------------------+------------------+
^
|
内存越界
越界写入可能破坏相邻局部变量或其他栈数据。
某些情况下,关键调用控制数据也可能受到影响,但并非所有栈溢出都能够覆盖返回地址。
堆用于动态分配内存。
例如:
unsigned char *pixels = malloc(64);
程序申请了 64 字节。
合法访问范围:
pixels[0] ~ pixels[63]
如果执行:
pixels[80] = 0x41;
就发生越界写入。
这种行为可能破坏其他堆对象或内存管理状态。
在 x86-64 架构中:
| 寄存器 | 作用 |
|---|---|
| RIP | 指令指针 |
| RSP | 栈指针 |
| RBP | 常用于栈帧管理 |
| RAX | 通用寄存器 |
| RDI | 通用寄存器,常用于传递参数 |
| RSI | 通用寄存器,常用于传递参数 |
在 ARM64 架构中:
| 寄存器 | 作用 |
|---|---|
| PC | 程序计数器 |
| SP | 栈指针 |
| X0–X7 | 常用于参数传递 |
| X29 | 帧指针 |
| X30 | 链接寄存器 |
正常情况下,CPU 按照程序控制流执行指令。
如果内存破坏影响了程序使用的函数指针、返回地址或其他控制数据,就可能出现控制流劫持。
但是,现代操作系统通常有 ASLR、NX/DEP、Stack Canary、CFI、沙箱等保护措施。
因此:
缓冲区溢出 ≠ 一定能够执行任意代码。
JPEG 通常包含:
JPEG File
|
+-- SOI
| 文件起始标记
|
+-- APP0 / APP1
| JFIF / EXIF 等信息
|
+-- DQT
| 量化表
|
+-- DHT
| 霍夫曼编码表
|
+-- SOF
| 图像尺寸与编码参数
|
+-- SOS
| 扫描开始
|
+-- Compressed Image Data
| 压缩图像数据
|
+-- EOI
文件结束标记
图片解析器需要处理:
如果长度计算、内存分配或解码过程存在错误,就可能触发漏洞。
PNG 文件通常由多个 Chunk 组成。
PNG Signature
|
v
IHDR
|
v
IDAT
|
v
IEND
常规 Chunk 结构:
+-------------------------+
| Length 4 Bytes |
+-------------------------+
| Type 4 Bytes |
+-------------------------+
| Data N Bytes |
+-------------------------+
| CRC 4 Bytes |
+-------------------------+
如果解析器没有正确验证 Length,可能发生:
假设 RGBA 图片每个像素需要 4 字节。
所需内存:
MemorySize = Width × Height × 4
错误代码:
uint32_t width = get_width();
uint32_t height = get_height();
uint32_t size = width * height * 4;
unsigned char *pixels = malloc(size);
问题在于:
uint32_t 最大值为:
4294967295
如果计算结果超过该范围,就会发生无符号整数回绕。
例如:
Width = 65536
Height = 65536
实际计算:
65536 × 65536 × 4
= 17179869184
在 32 位无符号整数中:
17179869184 % 4294967296 = 0
如果程序分配的内存小于后续实际写入量,就可能产生堆缓冲区溢出。
这只是漏洞原理示例,真实图片格式通常还会对尺寸和其他字段施加限制。
漏洞名称:
libwebp Heap Buffer Overflow
披露时间:2023 年。
漏洞涉及 WebP 无损图像解码中的霍夫曼解码表构建。
简化流程:
特殊构造的 WebP 文件
|
v
libwebp 解码
|
v
霍夫曼解码表处理
|
v
内存边界处理错误
|
v
Heap Buffer Overflow
|
v
潜在的代码执行
这个漏洞影响多个使用存在漏洞的 libwebp 版本的软件,并有真实攻击利用记录。
参考:
https://nvd.nist.gov/vuln/detail/CVE-2023-4863
漏洞编号:
CVE-2021-30860
攻击目标涉及 Apple iMessage 的附件处理流程。
攻击者发送看似 GIF、实际包含 PDF 数据的附件。
PDF 内部使用 JBIG2 图像压缩结构。
攻击流程:
攻击者发送附件
|
v
iMessage 自动处理
|
v
CoreGraphics 解析 PDF/JBIG2
|
v
整数溢出
|
v
堆内存破坏
|
v
构造复杂计算能力
|
v
进一步实现攻击
该案例的特殊之处在于 JBIG2 Weird Machine。
攻击者利用 JBIG2 的位操作能力,构造出类似逻辑电路的计算结构。
这说明:图像处理操作在特定漏洞条件下,也可能被组合成非预期的计算机制。
参考:
https://projectzero.google/2021/12/a-deep-dive-into-nso-zero-click.html
MP4 文件可以包含:
典型结构:
movie.mp4
|
+-- ftyp
| 文件类型
|
+-- moov
| 影片元数据
| |
| +-- mvhd
| |
| +-- trak
| |
| +-- mdia
| |
| +-- minf
| |
| +-- stbl
|
+-- mdat
|
+-- 视频编码数据
|
+-- 音频编码数据
播放器通常先通过 Demuxer 解析容器,再通过 Decoder 解码音视频数据。
普通 MP4 Box 头部通常包含:
4 Bytes: size
4 Bytes: type
错误代码:
uint32_t size = read_be32(data);
uint32_t payload_size = size - 8;
如果:
size = 4
那么:
4 - 8
在 32 位无符号整数中会得到:
4294967292
这种情况称为整数下溢或无符号整数回绕。
如果后续程序错误使用该值,就可能造成:
需要注意,MP4 还支持扩展长度和特殊长度值,真实解析器必须按格式规范处理。
视频解码器需要处理:
例如:
uint8_t pixel = reference_frame[y * stride + x];
如果 x、y、stride 或缓冲区范围验证错误,可能出现越界访问。
真实漏洞还可能涉及参考帧生命周期、对象释放、数组索引及复杂的状态管理。
2015 年,Android 媒体框架 Stagefright 被发现存在一系列严重漏洞。
其中:
CVE-2015-1538
涉及 MP4 Atom 解析中的整数溢出问题。
部分攻击场景可以通过 MMS 媒体处理触发,不一定需要用户主动播放视频。
简化流程:
恶意媒体内容
|
v
Android 媒体框架
|
v
MP4 Atom 解析
|
v
整数溢出
|
v
内存破坏
|
v
潜在的远程代码执行
参考:
https://source.android.com/docs/security/bulletin/2015-08-01
典型 MP3 文件可能包含:
music.mp3
|
+-- ID3v2
| |
| +-- 歌曲名称
| +-- 艺术家
| +-- 专辑
| +-- 封面图片
|
+-- MPEG Audio Frame 1
|
+-- MPEG Audio Frame 2
|
+-- MPEG Audio Frame 3
|
+-- ...
|
+-- 可选尾部标签
MP3 解码器需要处理:
如果实现存在边界检查错误,就可能发生内存安全问题。
例如,解析器使用固定长度数组保存歌曲标题:
char title[64];
memcpy(title, tag_data, tag_length);
如果 tag_length 超过 64,可能导致栈缓冲区溢出。
攻击者不一定需要修改实际的 MP3 音频数据,也可能利用元数据解析过程中的漏洞。
音频解码器可能需要计算 PCM 缓冲区大小:
BufferSize =
Samples × Channels × BytesPerSample
如果乘法发生整数溢出,可能分配不足的内存。
后续写入音频样本时,就可能发生堆缓冲区溢出。
MP3 可以嵌入专辑封面。
例如:
MP3 文件
|
v
ID3 元数据
|
v
APIC 封面字段
|
v
JPEG / PNG 图片
|
v
图片解码器
因此,播放 MP3 时发生的漏洞,不一定来自 MP3 解码器,也可能来自封面图片解析组件。
第一阶段:输入控制
攻击者构造特殊媒体数据
|
v
第二阶段:触发漏洞
解析器发生越界访问
|
v
第三阶段:内存破坏
对象、长度或控制数据受损
|
v
第四阶段:利用条件
攻击者需要满足特定内存布局、
控制能力及防护绕过条件
|
v
第五阶段:控制流异常
程序可能执行非预期路径
|
v
第六阶段:潜在代码执行
在成功利用时获得受影响进程
权限范围内的执行能力
现代系统存在多种防护机制。
| 防护技术 | 作用 |
|---|---|
| ASLR | 随机化内存地址 |
| NX / DEP | 防止普通数据页直接执行机器代码 |
| Stack Canary | 检测部分栈内存破坏 |
| CFI | 限制控制流跳转 |
| Sandbox | 限制进程访问系统资源 |
| ARM PAC | 保护部分指针完整性 |
因此:
程序崩溃
≠
控制流劫持
≠
任意代码执行
≠
获得系统最高权限
每一步都需要独立的技术证据。
sudo apt update
sudo apt install -y \
build-essential \
gcc \
clang \
gdb \
valgrind \
ffmpeg \
exiftool \
xxd \
file \
binutils \
afl++
检查:
gcc --version
clang --version
gdb --version
ffprobe -version
afl-fuzz -V
建议使用专用 Kali Linux 虚拟机,提前创建快照。
mkdir -p ~/media-security-lab/{src,build,samples,reports}
cd ~/media-security-lab
目录:
media-security-lab/
|
+-- src/
| +-- image_parser.c
| +-- mp4_parser.c
| +-- audio_parser.c
| +-- fuzz_box.c
|
+-- build/
|
+-- samples/
|
+-- reports/
nano src/image_parser.c
代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
int main(void)
{
size_t buffer_size = 16;
uint8_t *pixels = malloc(buffer_size);
if (pixels == NULL) {
return 1;
}
uint8_t image_data[24];
memset(image_data, 0x41, sizeof(image_data));
// 故意缺少长度检查
memcpy(pixels, image_data, sizeof(image_data));
printf("Image decoding finished\n");
free(pixels);
return 0;
}
这是模拟图片解码器内存写入的教学程序,不是实际 JPEG 解码器。
gcc -g -O0 \
-fsanitize=address \
-fno-omit-frame-pointer \
-o build/image_parser \
src/image_parser.c
./build/image_parser
预期出现类似:
ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 24
0x... is located 0 bytes after
16-byte region
程序分配:
16 Bytes
实际写入:
24 Bytes
内存示意:
+----------------------+----------------+
| 合法的 16 字节 | 越界的 8 字节 |
+----------------------+----------------+
^
|
越界位置
AddressSanitizer 检测到了超出分配边界的写入。
编译独立调试版本:
gcc -g -O0 \
-o build/image_parser_debug \
src/image_parser.c
启动:
gdb ./build/image_parser_debug
常用命令:
break main
run
next
info locals
print pixels
print buffer_size
x/16xb pixels
backtrace
由于存在未定义行为,未使用 Sanitizer 的程序可能崩溃,也可能看似正常运行。
nano src/mp4_parser.c
代码:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void)
{
uint32_t box_size = 4;
uint32_t header_size = 8;
// 故意缺少最小长度检查
uint32_t payload_size = box_size - header_size;
printf("Box size: %" PRIu32 "\n", box_size);
printf("Header size: %" PRIu32 "\n", header_size);
printf("Payload size: %" PRIu32 "\n", payload_size);
return 0;
}
gcc -g -O0 \
-o build/mp4_parser \
src/mp4_parser.c
./build/mp4_parser
输出:
Box size: 4
Header size: 8
Payload size: 4294967292
32 位无符号整数计算:
4 - 8
发生回绕:
4294967292
如果解析器后续错误使用这个长度,可能引发其他安全问题。
本实验只证明整数下溢,不代表已经发生内存破坏。
if (box_size < header_size) {
fprintf(stderr, "Invalid MP4 box size\n");
return 1;
}
uint32_t payload_size = box_size - header_size;
nano src/audio_parser.c
代码:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
void parse_title(const uint8_t *data, size_t length)
{
char title[16];
// 故意缺少长度检查
memcpy(title, data, length);
printf("Audio title parsed\n");
}
int main(void)
{
uint8_t metadata[32];
memset(metadata, 'A', sizeof(metadata));
parse_title(metadata, sizeof(metadata));
return 0;
}
gcc -g -O0 \
-fsanitize=address \
-fno-omit-frame-pointer \
-o build/audio_parser \
src/audio_parser.c
./build/audio_parser
预期输出包含:
ERROR: AddressSanitizer: stack-buffer-overflow
WRITE of size 32
目标缓冲区:
16 Bytes
实际复制:
32 Bytes
超出了合法栈数组范围。
void parse_title(const uint8_t *data, size_t length)
{
char title[16];
if (length >= sizeof(title)) {
fprintf(stderr, "Audio title too long\n");
return;
}
memcpy(title, data, length);
title[length] = '\0';
printf("Title: %s\n", title);
}
注意:下面的工具也需要读取或解析文件。真正可疑的样本应在更新过软件的隔离虚拟机中分析,不要直接在主机上打开。
file suspicious.jpg
file suspicious.mp4
file suspicious.mp3
作用:
识别文件内容特征,而不只是查看扩展名。
xxd -l 256 suspicious.jpg
xxd -l 256 suspicious.mp4
xxd -l 256 suspicious.mp3
常见特征:
| 格式 | 特征 |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| WebP | RIFF,偏移 8 为 WEBP |
| MP4 | 常见为偏移 4 的 ftyp |
| MP3 | 可能以 ID3 或 MPEG 音频帧开始 |
文件头正常不代表文件安全。
exiftool suspicious.jpg
关注:
ffprobe -v error \
-show_format \
-show_streams \
suspicious.mp4
查看视频信息:
ffprobe -v error \
-select_streams v:0 \
-show_entries stream=codec_name,width,height,pix_fmt \
-of json \
suspicious.mp4
查看音频信息:
ffprobe -v error \
-select_streams a \
-show_entries stream=codec_name,sample_rate,channels \
-of json \
suspicious.mp4
exiftool suspicious.mp3
ffprobe -v error \
-show_format \
suspicious.mp3
重点关注:
sha256sum suspicious.jpg
sha256sum suspicious.mp4
sha256sum suspicious.mp3
哈希可以用于样本识别和记录,但不能单独判断文件是否恶意。
gdb ./build/image_parser_debug
| 命令 | 作用 |
|---|---|
run |
运行程序 |
break main |
在 main 设置断点 |
next |
单步执行,不进入函数 |
step |
单步进入函数 |
continue |
继续执行 |
backtrace |
查看调用栈 |
info registers |
查看寄存器 |
info locals |
查看局部变量 |
x/32xb 地址 |
查看 32 字节内存 |
disassemble |
查看反汇编 |
x86-64:
info registers rip rsp rbp
ARM64:
info registers pc sp x29 x30
如果 Kali Linux 运行在 ARM64 设备或虚拟机中,应使用 ARM64 寄存器名称。
初始样本
|
v
AFL++ 变异输入
|
v
运行目标解析器
|
v
收集执行覆盖率
|
v
发现崩溃或超时
|
v
保存异常输入
|
v
GDB / Sanitizer 分析
nano src/fuzz_box.c
代码:
#include <stdio.h>
#include <stdint.h>
int main(int argc, char **argv)
{
if (argc != 2) {
return 1;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
return 1;
}
uint8_t header[8];
if (fread(header, 1, 8, fp) != 8) {
fclose(fp);
return 0;
}
fclose(fp);
uint32_t size =
((uint32_t)header[0] << 24) |
((uint32_t)header[1] << 16) |
((uint32_t)header[2] << 8) |
(uint32_t)header[3];
// 教学用异常触发点
if (size < 8) {
fprintf(stderr, "Invalid box size\n");
__builtin_trap();
}
printf("Payload size: %u\n", size - 8);
return 0;
}
这里使用 __builtin_trap() 人为触发异常,不是真实内存漏洞。
afl-clang-fast \
-g -O1 \
-o build/fuzz_box \
src/fuzz_box.c
mkdir -p samples/seed
python3 - <<'PY'
from pathlib import Path
Path("samples/seed/box.bin").write_bytes(
b"\x00\x00\x00\x08ftyp"
)
PY
afl-fuzz \
-i samples/seed \
-o reports/afl \
-- ./build/fuzz_box @@
AFL++ 会不断改变输入数据,寻找能够触发异常的测试样本。
find reports/afl -type f -path '*/crashes/id:*'
查看样本:
xxd <样本路径>
使用 GDB:
gdb --args ./build/fuzz_box <样本路径>
执行:
run
backtrace
通过该实验可以学习:
不能仅凭文件异常或播放器崩溃就判断存在可利用漏洞。
建议分为五个阶段。
文件类型
|
文件头
|
元数据
|
容器结构
|
编码信息
在虚拟机中使用更新过的解析工具处理文件,记录异常行为。
使用:
确定程序出现异常的位置。
重点研究:
区分:
文件格式异常
|
v
解析失败
|
v
程序崩溃
|
v
确认内存安全漏洞
|
v
评估可利用性
|
v
潜在代码执行
这些阶段不能简单画等号。
学习:
目标:理解内存访问错误如何产生。
学习:
目标:能够使用十六进制工具分析文件结构。
学习:
目标:能够定位简单的内存安全漏洞。
学习:
目标:能够解释程序崩溃的原因。
学习:
目标:能够对自编解析器进行自动化安全测试。
重点案例:
目标:能够阅读公开漏洞分析、理解补丁修改,并在隔离环境中验证已修复的缺陷。
CVE-2023-4863
https://nvd.nist.gov/vuln/detail/CVE-2023-4863
FORCEDENTRY 技术分析
https://projectzero.google/2021/12/a-deep-dive-into-nso-zero-click.html
Android Stagefright 安全公告
https://source.android.com/docs/security/bulletin/2015-08-01
FFmpeg / FFprobe
https://ffmpeg.org/documentation.html
GDB
https://www.sourceware.org/gdb/documentation/
AddressSanitizer
https://clang.llvm.org/docs/AddressSanitizer.html
AFL++
ExifTool
图片、视频、音频文件触发恶意代码执行的核心机制:
攻击者构造特殊媒体文件
|
v
用户打开文件或软件自动处理
|
v
媒体解析器读取不可信数据
|
v
长度、索引、内存分配等处理错误
|
v
触发内存安全漏洞
|
v
可能造成崩溃、信息泄露或内存破坏
|
v
在满足特定利用条件时
|
v
可能实现代码执行
必须掌握的五个核心知识点:
最重要的结论:
图片、视频和音频文件通常不会自行执行恶意代码。攻击者利用的是处理这些文件的软件中的漏洞。媒体文件是输入载体,解析器是漏洞触发点,内存破坏是常见中间环节,而代码执行只是满足进一步利用条件后可能出现的结果。
理解这一完整过程,是学习文件解析漏洞、二进制安全、媒体解码器安全和漏洞挖掘的重要基础。