# Target CVEs
## Description
CVE: https://www.cve.org/CVERecord?id=CVE-2009-3895
CVE: https://www.cve.org/CVERecord?id=CVE-2012-2836
# Compile
## Download
```shellsession
git clone https://github.com/libexif/libexif.git
cd libexif && git checkout libexif-0_6_14-release
```
## Build
这里由于我在 make 的时候有关生成文档的地方报错了,所以我手动将文档部分 patch 掉
:
```diff title="Makefile.am"
- SUBDIRS = m4m po libexif test doc binary
+ SUBDIRS = m4m po libexif test binary
```
```shellsession
autoreconf -fvi
CC=clang CXX=clang++ CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-poi
nter" CXXFLAGS="$CFLAGS" ./configure --enable-shared=no --prefix="$PWD/../worksh
op/lib-debug/"
make -j`nproc` && make install
make clean
CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --enable-shared=no --prefix="$P
WD/../workshop/lib-fuzz/"
make -j`nproc` && make install
```
由于 libexif 只是一个库,所以我们要 fuzz 它需要自己找个前端,或者手写 harness 。
方便起见,我直接使用 [exif](https://github.com/libexif/exif) 作为前端。
```shellsession
git clone https://github.com/libexif/exif.git
cd exif && git checkout exif-0_6_15-release
autoreconf -fvi
CC=clang CXX=clang++ CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-poi
nter" CXXFLAGS="$CFLAGS" PKG_CONFIG_PATH="$PWD/../workshop/lib-debug/lib/pkgconf
ig/" ./configure --enable-shared=no --prefix="$PWD/../workshop/exif-debug"
make -j`nproc` && make install
make clean
CC=afl-clang-lto CXX=afl-clang-lto++ PKG_CONFIG_PATH="$PWD/../workshop/lib-fuzz/
lib/pkgconfig/" ./configure --enable-shared=no --prefix="$PWD/../workshop/exif-f
uzz"
make -j`nproc` && make install
```
## Samples
```python title="gen_corpus.py"
#!/usr/bin/env python3
import struct
import os
def pack_data(fmt, endian, *args):
return struct.pack(('<' if endian == 'LE' else '>') + fmt, *args)
class ExifGenerator:
def __init__(self, endian='LE'):
self.endian = endian
self.entries = []
self.data_blobs = b''
def add_entry(self, tag, data_type, count, value):
self.entries.append({'tag': tag, 'type': data_type, 'count': count, 'val
ue': value})
def build_ifd(self, start_offset, next_ifd_offset=0):
ifd_data = pack_data('H', self.endian, len(self.entries))
data_offset = start_offset + 2 + (len(self.entries) * 12) + 4
entry_bytes = b''
blob_bytes = b''
for e in self.entries:
val_bytes = b''
raw_blob = None
if e['type'] == 1: # BYTE
if isinstance(e['value'], int): raw_blob = pack_data('B', self.e
ndian, e['value'])
else: raw_blob = e['value']
elif e['type'] == 2: # ASCII
raw_blob = e['value'].encode('ascii') + b'x00'
elif e['type'] == 3: # SHORT
if e['count'] == 1: raw_blob = pack_data('H', self.endian, e['va
lue'])
else: raw_blob = pack_data('H'*e['count'], self.endian, *e['valu
e'])
elif e['type'] == 4: # LONG
if e['count'] == 1: raw_blob = pack_data('I', self.endian, e['va
lue'])
else: raw_blob = pack_data('I'*e['count'], self.endian, *e['valu
e'])
elif e['type'] == 5: # RATIONAL
raw_blob = pack_data('II', self.endian, e['value'][0], e['value'
][1])
elif e['type'] == 7: # UNDEFINED
raw_blob = e['value']
elif e['type'] == 10: # SRATIONAL
raw_blob = pack_data('ii', self.endian, e['value'][0], e['value'
][1])
if len(raw_blob) <= 4:
val_bytes = raw_blob.ljust(4, b'x00')
else:
off = data_offset + len(blob_bytes)
val_bytes = pack_data('I', self.endian, off)
blob_bytes += raw_blob
entry_bytes += pack_data('HHII', self.endian, e['tag'], e['type'], e
['count'], struct.unpack('<I' if self.endian == 'LE' else '>I', val_bytes)[0])
return ifd_data + entry_bytes + pack_data('I', self.endian, next_ifd_off
set) + blob_bytes
def create_complex_exif(endian='LE'):
# 1. Build Exif SubIFD
exif = ExifGenerator(endian)
exif.add_entry(0x9003, 2, 20, "2026:02:24 14:00:00")
exif.add_entry(0x829a, 5, 1, (1, 100))
exif.add_entry(0x829d, 5, 1, (28, 10))
exif.add_entry(0x9204, 10, 1, (-1, 3))
exif.add_entry(0x9286, 7, 8, b"USERCOMx00")
exif_sub_data = exif.build_ifd(0) # Length check only first
# 2. Build GPS IFD
gps = ExifGenerator(endian)
gps.add_entry(0x0000, 1, 4, b'x02x02x00x00')
gps.add_entry(0x0002, 5, 3, (1, 1)) # This will actually need 3 rationals, b
ut let's keep it simple
# Fix: GPS Latitude is 3 rationals
gps.entries[-1] = {'tag': 0x0002, 'type': 5, 'count': 3, 'value': (40, 1, 30
, 1, 15, 1)}
gps_data = gps.build_ifd(0)
# 3. Build IFD0
ifd0 = ExifGenerator(endian)
ifd0.add_entry(0x010e, 2, 11, "Fuzz Test")
ifd0.add_entry(0x0110, 2, 11, "Gemini Cam")
ifd0.add_entry(0x8769, 4, 1, 0) # ExifOffset
ifd0.add_entry(0x8825, 4, 1, 0) # GPSInfo
# IFD0 fixed size: 2 + 4*12 + 4 = 54. Blobs: 11 + 11 = 22. Total = 76.
ifd0_total_len = 76
exif_offset = 8 + ifd0_total_len
gps_offset = exif_offset + len(exif_sub_data)
for e in ifd0.entries:
if e['tag'] == 0x8769: e['value'] = exif_offset
if e['tag'] == 0x8825: e['value'] = gps_offset
final_ifd0 = ifd0.build_ifd(8)
final_exif = exif.build_ifd(exif_offset)
final_gps = gps.build_ifd(gps_offset)
header = b'IIx2ax00x08x00x00x00' if endian == 'LE' else b'MMx00x2ax00x00x00x
08'
return header + final_ifd0 + final_exif + final_gps
def save_corpus(name, data, add_exif_header=False):
os.makedirs('corpus/exif', exist_ok=True)
with open(os.path.join('corpus/exif', name), 'wb') as f:
if add_exif_header: f.write(b'Exifx00x00')
f.write(data)
print(f"Created {name}")
if __name__ == "__main__":
save_corpus('rich_le.exif', create_complex_exif('LE'), True)
save_corpus('rich_be.exif', create_complex_exif('BE'), True)
SOI, APP1 = b'xffxd8', b'xffxe1'
exif_payload = b'Exifx00x00' + create_complex_exif('LE')
app1_data = APP1 + struct.pack('>H', len(exif_payload) + 2) + exif_payload
save_corpus('rich_jpeg.jpg', SOI + app1_data + b'xffxd9')
save_corpus('raw_tiff.exif', create_complex_exif('LE'), False)
```
# Fuzzing
AFLplusplus go work: `afl-fuzz -i - -o out/ -s 1337 -- ./exif-fuzz/bin/exif @@`.
这个 Gemini Cam 还挺滑稽的哈哈哈:
13 个 crashes,4 个 hangs,其中有这几种独立的 crash 情况:
```plaintext showLineNumbers=false
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e595c1 in memcpy () from /usr/lib64/libc.so.6
#0 0x00007ffff7e595c1 in memcpy () from /usr/lib64/libc.so.6
#1 0x0000555555565553 in exif_data_load_data_thumbnail (data=0x5555555931e0, d=
0x555555595986 "II*", ds=256, offset=4294967168, size=232) at exif-data.c:292
#2 0x0000555555563d05 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_EXIF, d=0x555555595986 "II*", ds=256, offset=86, recursion_depth=1) at
exif-data.c:381
#3 0x0000555555563b36 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_0, d=0x555555595986 "II*", ds=256, offset=10, recursion_depth=0) at exi
f-data.c:361
#4 0x0000555555563621 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "Exif", ds_orig=262) at exif-data.c:813
#5 0x000055555556cb9b in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:387
#6 0x000055555555f73e in main (argc=2, argv=0x7fffffffdca8) at main.c:438
Program received signal SIGSEGV, Segmentation fault.
0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access mem
ory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c:129
129 return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
#0 0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access
memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c
:129
#1 0x000055555556e49f in exif_get_long (buf=0x5555555b2000 <error: Cannot acces
s memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.
c:156
#2 0x0000555555566026 in exif_entry_fix (e=0x555555593460) at exif-entry.c:189
#3 0x0000555555562d6d in fix_func (e=0x555555593460, data=0x0) at exif-content.
c:211
#4 0x0000555555562a13 in exif_content_foreach_entry (content=0x555555593270, fu
nc=0x555555562d50 <fix_func>, data=0x0) at exif-content.c:185
#5 0x0000555555562bdc in exif_content_fix (c=0x555555593270) at exif-content.c:
225
#6 0x0000555555565481 in fix_func (c=0x555555593270, data=0x0) at exif-data.c:1
082
#7 0x00005555555650ca in exif_data_foreach_content (data=0x5555555931e0, func=0
x5555555653e0 <fix_func>, user_data=0x0) at exif-data.c:965
#8 0x00005555555643d4 in exif_data_fix (d=0x5555555931e0) at exif-data.c:1087
#9 0x0000555555563886 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "", ds_orig=82) at exif-data.c:826
#10 0x000055555556cbeb in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:368
#11 0x000055555555f75e in main (argc=2, argv=0x7fffffffdbe8) at main.c:438
Program received signal SIGSEGV, Segmentation fault.
0x000055555556e3d2 in exif_get_sshort (buf=0x555655595985 <error: Cannot access
memory at address 0x555655595985>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c:
92
92 return ((buf[1] << 8) | buf[0]);
#0 0x000055555556e3d2 in exif_get_sshort (buf=0x555655595985 <error: Cannot acc
ess memory at address 0x555655595985>, order=EXIF_BYTE_ORDER_INTEL) at exif-util
s.c:92
#1 0x000055555556e33f in exif_get_short (buf=0x555655595985 <error: Cannot acce
ss memory at address 0x555655595985>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils
.c:100
#2 0x0000555555563671 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "Exif", ds_orig=61) at exif-data.c:775
#3 0x000055555556cbeb in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:368
#4 0x000055555555f75e in main (argc=2, argv=0x7fffffffdbe8) at main.c:438
```
hang 的情况比较唯一,全是卡在 `exif_loader_write` 这里:
这里附赠一个我看各个崩溃回溯的脚本:
```bash
#!/usr/bin/env bash
TARGET="./exif-debug/bin/exif"
CRASH_DIR="./out/default/crashes"
OUTPUT_LOG="crash_reports.txt"
>"$OUTPUT_LOG"
echo "[+] Analysing crashes in $CRASH_DIR..."
for crash_file in "$CRASH_DIR"/id:*; do
[ -e "$crash_file" ] || continue
echo "--------------------------------------------------" >>"$OUTPUT_LOG"
echo "File: $(basename "$crash_file")" >>"$OUTPUT_LOG"
gdb --batch
--ex "run"
--ex "bt"
--ex "quit"
--args "$TARGET" "$crash_file" >>"$OUTPUT_LOG" 2>&1
echo -e "n" >>"$OUTPUT_LOG"
done
echo "[+] Done! Result in: $OUTPUT_LOG"
```
# Analysis
这个项目有 API 文档,不用自己猜函数功能了:[libexif Project Documentation](https
://libexif.github.io/docs.html).
~~好的,继续玩 MC,快开学了,谁学啊!?~~
## CVE-2012-2836
先分析一下这个样本,栈回溯如下:
```plaintext showLineNumbers=false
#0 0x00007ffff7e595c1 in memcpy () from /usr/lib64/libc.so.6
#1 0x0000555555565553 in exif_data_load_data_thumbnail (data=0x5555555931e0, d=
0x555555595986 "II*", ds=256, offset=4294967168, size=232) at exif-data.c:292
#2 0x0000555555563d05 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_EXIF, d=0x555555595986 "II*", ds=256, offset=86, recursion_depth=1) at
exif-data.c:381
#3 0x0000555555563b36 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_0, d=0x555555595986 "II*", ds=256, offset=10, recursion_depth=0) at exi
f-data.c:361
#4 0x0000555555563621 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "Exif", ds_orig=262) at exif-data.c:813
#5 0x000055555556cb9b in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:387
#6 0x000055555555f73e in main (argc=2, argv=0x7fffffffdca8) at main.c:438
```
`exif_loader_get_data` 负责为 `ExifData` 结构体开辟空间,然后使用 `exif_data_loa
d_data` 将内存中的 JPEG / EXIF _header_, _byte order_, _fixed value_ 等信息加载
到 ExifData 结构体中,进行一些简单的校验,并初始化 _IFD 0_ 和 _IFD 1_。初始化 IF
D 字段是通过调用 `exif_data_load_data_content` 实现的。
根据这个 switch 就可以看出来,很显然,load data content 大致应该就是将 exif 图片
的各个字段信息加载进去。
继续调试,我们在 `EXIF_TAG_EXIF_IFD_POINTER` 这个 case 调用了 `exif_data_load_da
ta_content`,此时传入的 ifd 为 `EXIF_IFD_EXIF`,而我们后续也是在这里面执行 `exif
_data_load_data_thumbnail` 时崩溃的,所以要重点分析一下这部分。
首先确定一下在第几次循环的时候崩溃,我们直接 `c` 让它崩,然后切换到对应栈帧查看
`i`,发现是第 13 次调用了 `exif_data_load_data_thumbnail`:
然后通过 `if i == 13` 下条件断点,定位到触发崩溃的循环索引继续分析。由于这里有两
个调用 `exif_data_load_data_thumbnail` 的位置,而我们不知道具体会调用哪一个,因
此如果我们下的那个断点不是实际的调用点,那就停不下来了。所以这里两个位置都需要下
一个断点:
最后我们发现它执行的是 381 行处的代码,整洁起见,之前 374 行处的断点就可以删掉了
。
步入继续,发现致使 `memcpy` 崩溃的原因是 `rsi` 无法解引用:
接下来重点看这个 `exif_data_load_data_thumbnail` 的逻辑:
```c
// -> 0x555555563d00 <exif_data_load_data_content+1168> call exif_data_load
_data_thumbnail <exif_data_load_data_thumbnail>
// rdi: 0x555555593240 -> 0x5555555932d0 -> 0x5555555934a0 -> 0x555555593
420 <- 0x200000110
// rsi: 0x555555595986 <- 0x8002a4949 /* 'II*' */
// rdx: 0x100
// rcx: 0xffffff80
// r8: 0xe8
static void
exif_data_load_data_thumbnail (ExifData *data, const unsigned char *d,
unsigned int ds, ExifLong offset, ExifLong size)
{
if (ds < offset + size) {
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"Bogus thumbnail offset and size: %i < %i + %i.",
(int) ds, (int) offset, (int) size);
return;
}
if (data->data)
exif_mem_free (data->priv->mem, data->data);
data->size = size;
data->data = exif_data_alloc (data, data->size);
if (!data->data)
return;
memcpy (data->data, d + offset, data->size);
}
```
通过调试,我们可知它根本不会进入那三个 if branch,也就是说,这个函数只执行了如下
三行代码:
```c
data->size = size;
data->data = exif_data_alloc (data, data->size);
memcpy (data->data, d + offset, data->size);
```
即将 _size_ 设置为 `r8`,将 _data_ 设置为 `exif_data_alloc` 分配出来的值,而 _sr
c_ 则是 `d + offset` 的值,即 `rsi + 0xffffff80`:
显然,这个 `offset` 特别大,而经调试,我们的 rsi 其实是合法值,那么问题就处在 of
fset 上了。
回溯发现,在给 `exif_data_load_data_thumbnail` 传参时,offset 的值就有问题了:
通过查看二进制数据,我们很容易定位到一个类似的数据:
尝试直接将其修改为 `0xCAFEBABE`,然后再调试一下。调试之前我们可以先使用 `save br
eakpoints bps` 将断点信息保存到 `bps` 文件,之后通过 `source bps` 加载。
可见,我们可以通过控制图片的内部数据信息来控制 `memcpy` 的行为,至于具体怎么利用
,利用后有什么效果,我就不分析了。直接确定一下 CVE ID,发现这个 `CVE-2012-2836`
的描述比较符合我们的分析结果。
CVE: https://www.cve.org/CVERecord?id=CVE-2012-2836
## CVE-2009-3895
```plaintext showLineNumbers=false
Program received signal SIGSEGV, Segmentation fault.
0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access mem
ory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c:129
129 return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
#0 0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access
memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c
:129
#1 0x000055555556e49f in exif_get_long (buf=0x5555555b2000 <error: Cannot acces
s memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.
c:156
#2 0x0000555555566026 in exif_entry_fix (e=0x555555593460) at exif-entry.c:189
#3 0x0000555555562d6d in fix_func (e=0x555555593460, data=0x0) at exif-content.
c:211
#4 0x0000555555562a13 in exif_content_foreach_entry (content=0x555555593270, fu
nc=0x555555562d50 <fix_func>, data=0x0) at exif-content.c:185
#5 0x0000555555562bdc in exif_content_fix (c=0x555555593270) at exif-content.c:
225
#6 0x0000555555565481 in fix_func (c=0x555555593270, data=0x0) at exif-data.c:1
082
#7 0x00005555555650ca in exif_data_foreach_content (data=0x5555555931e0, func=0
x5555555653e0 <fix_func>, user_data=0x0) at exif-data.c:965
#8 0x00005555555643d4 in exif_data_fix (d=0x5555555931e0) at exif-data.c:1087
#9 0x0000555555563886 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "", ds_orig=82) at exif-data.c:826
#10 0x000055555556cbeb in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:368
#11 0x000055555555f75e in main (argc=2, argv=0x7fffffffdbe8) at main.c:438
```
直接调,调就完了。
一开始都是差不多的,`exif_data_fix` 的作用是将不符合规格的 exif 数据修复,使其符
合规范,内部其实就是对 `exif_data_foreach_content` 的包装,而 `exif_data_foreach
_content` 则是对每一个 _IFD_ 执行指定的函数,这里是 `fix_func` ……
其实我觉得没必要从 main 开始分析,直接从崩溃边界开始分析就好了,所以接下来我会从
后往前分析。
可以看到,是 rax 无法解引用导致的问题。入口是 `exif_entry_fix` 之后的 `exif_get_
long`。
回溯到调用 `exif_get_long` 的函数 `exif_entry_fix`:
我们发现这里是一个 for 循环,并且此时的索引是 `31440`,巨大无比,显然不正常,而
索引范围是 `e->components` 指定的:
这都十亿多次了,所以问题就是没有验证这个 _component_ 大小的合法性。此外,这个大
小也是攻击者可控的:
_components_ 肯定代表了组件数量,什么东西的组件数量?翻代码,发现是 `ExifEntry`
:
通过查询 exif 标准 [^1] 我们可知,一个 `ExifEntry` 由 `Format`,`Components` 和
`Data` 组成。如果 _format_ 类型是 `unsigned short`,则它代表每个 _component_ 占
用 2 bytes,而 _components_ 则代表了这样的组件有几个,将它和 _format_ 相乘即是这
一 _entry_ 总共占用的字节数。
所以这里的问题显然就是 `e->components` 很大,而 `e->data` 分配的内存很小,导致 O
OB 。而在计算 `e->size = e->components * exif_format_get_size(e->format)` 时,如
果 _components_ 特别大也有可能发生溢出,导致最后得到一个特别小的值。如果后续用这
个特别小的 _size_ 去申请内存,就会有申请大小远小于实际需求的问题,写入将直接崩溃
或允许执行任意代码。
定位一下 CVE,发现符合 CVE-2009-3895 的描述。至此,这个 chall 要求的两个 CVE 我
们均已找齐,剩下那几个 crashes 和 hang 我就不分析了,懒(
CVE: https://www.cve.org/CVERecord?id=CVE-2009-3895
# Fix
## CVE-2012-2836
调试发现,这个非法 offset 来自 `exif_get_long` 函数,我们深入分析一下它的逻辑。
首先调用入口是:
```c
case EXIF_TAG_JPEG_INTERCHANGE_FORMAT:
o = exif_get_long (d + offset + 12 * i + 8,
data->priv->order);
```
所以它是取 `d + offset + 12 * i + 8` 这个偏移处的值。查阅文档,我们可知它就是返
回一个 `uint32_t` 类型,而它内部使用的 `exif_get_slong` 会返回一个 `int32_t` 类
型:
```c
ExifLong
exif_get_long (const unsigned char *buf, ExifByteOrder order)
{
return (exif_get_slong (buf, order) & 0xffffffff);
}
ExifSLong
exif_get_slong (const unsigned char *b, ExifByteOrder order)
{
if (!b) return 0;
switch (order) {
case EXIF_BYTE_ORDER_MOTOROLA:
return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
case EXIF_BYTE_ORDER_INTEL:
return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
}
/* Won't be reached */
return (0);
}
```
虽然通过 `& 0xffffffff` 截断处理了整数溢出问题,但是 `0xffffffff` 依旧可以表示一
个巨大的范围,大概 4 GB?所以问题就在于,它没检查偏移是否合理。
解决方法很简单,只要将 `offset` 限制在 `ds` 的范围内即可。`ds` 代表了 _data size
_, 也就是整个图像数据的大小边界。超过 ds 肯定是不对的。
```diff lang="c" title="exif-data.c"
case EXIF_TAG_JPEG_INTERCHANGE_FORMAT:
o = exif_get_long(d + offset + 12 * i + 8, data->priv->order);
+ if (o >= ds) {
+ exif_log(data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData",
+ "Illegal offset value detected!");
+ abort();
}
```
## CVE-2009-3895
分析的时候已经写过问题了,修复方法就是校验 _components_ 与当前 _size_ 是否匹配:
```diff lang="c" title="exif-entry.c"
case EXIF_TAG_SHARPNESS:
switch (e->format) {
case EXIF_FORMAT_LONG:
if (!e->parent || !e->parent->parent)
break;
+ if (e->components <= 0 || e->components > 65535) {
+ exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+ "Component size is too large!");
+ abort();
+ }
+ if (e->size < e->components * exif_format_get_size(EXIF_FORMAT_LONG)) {
+ exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+ "Corrupted data size detected!");
+ abort();
+ };
o = exif_data_get_byte_order(e->parent->parent);
for (i = 0; i < e->components; i++)
exif_set_short(
e->data + i * exif_format_get_size(EXIF_FORMAT_SHORT), o,
(ExifShort)exif_get_long(
e->data + i * exif_format_get_size(EXIF_FORMAT_LONG), o));
e->format = EXIF_FORMAT_SHORT;
e->size = e->components * exif_format_get_size(e->format);
e->data = exif_entry_realloc(e, e->data, e->size);
exif_entry_log(e, EXIF_LOG_CODE_DEBUG,
_("Tag '%s' was of format '%s' (which is "
"against specification) and has been "
"changed to format '%s'."),
exif_tag_get_name(e->tag),
exif_format_get_name(EXIF_FORMAT_LONG),
exif_format_get_name(EXIF_FORMAT_SHORT));
break;
```
成功修复:
但是发现 rational 类型也有问题,不过直接使用和上面一样的 patch 方案就能解决。
```diff lang="c"
case EXIF_TAG_FOCAL_LENGTH:
switch (e->format) {
case EXIF_FORMAT_SRATIONAL:
if (!e->parent || !e->parent->parent)
break;
+ if (e->components <= 0 || e->components > 65535) {
+ exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+ "Component size is too large!");
+ abort();
+ }
+ if (e->size < e->components * exif_format_get_size(EXIF_FORMAT_LONG)) {
+ exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+ "Corrupted data size detected!");
+ abort();
+ };
o = exif_data_get_byte_order(e->parent->parent);
for (i = 0; i < e->components; i++) {
sr = exif_get_srational(
e->data + i * exif_format_get_size(EXIF_FORMAT_SRATIONAL), o);
r.numerator = (ExifLong)sr.numerator;
r.denominator = (ExifLong)sr.denominator;
exif_set_rational(
e->data + i * exif_format_get_size(EXIF_FORMAT_RATIONAL), o, r);
}
e->format = EXIF_FORMAT_RATIONAL;
exif_entry_log(e, EXIF_LOG_CODE_DEBUG,
_("Tag '%s' was of format '%s' (which is "
"against specification) and has been "
"changed to format '%s'."),
exif_tag_get_name(e->tag),
exif_format_get_name(EXIF_FORMAT_SRATIONAL),
exif_format_get_name(EXIF_FORMAT_RATIONAL));
break;
```
也算是在开学前完成了这两个 CVE 的复现,还没懒到啥也不干……
# References
[^1]: [Description of Exif file format](https://www.media.mit.edu/pia/Research/d
eepview/exif.html)