exif-entry.c 파일의 exif_entry_fix 함수에서 발생한 Heap-based buffer overflow 취약점exif-data.c 파일의 exif_data_load_data 함수에서 발생한 out-of-bounds read 취약점cd ~
mkdir fuzzing_libexif && cd fuzzing_libexif/
wget https://github.com/libexif/libexif/archive/refs/tags/libexif-0_6_14-release.tar.gz
tar -zxvf libexif-0_6_14-release.tar.gz
cd libexif-libexif-0_6_14-release
sudo apt-get install doxygen autopoint
autoreconf -i
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/"
make
make install
❯ ls
include lib share
❯ ls
libexif.a libexif.la libexif.so libexif.so.12 libexif.so.12.1.0 pkgconfig
FRONTENDS
---------
Here are a few frontends to libexif:
- exif: A small command-line utility to show EXIF information in JPEG
files (https://github.com/libexif/exif).
- gexif: A GTK+ frontend for editing EXIF data
(https://github.com/libexif/gexif).
- gphoto2: A command-line frontend to libgphoto2, a library to access a
wide range of digital cameras (http://www.gphoto.org).
- gtkam: A GTK+ frontend to libgphoto2 (http://www.gphoto.org).
- thirdeye: Digital photos organizer and driver for eComStation
(http://ecomstation.ru/thirdeye).
- digikam: digital photo management application for KDE
(https://www.digikam.org/)
wget https://github.com/libexif/exif/archive/refs/tags/exif-0_6_15-release.tar.gz
tar -zxvf exif-0_6_15-release.tar.gz
cd exif-exif-0_6_15-release
sudo apt-get update
sudo apt-get install libexif-dev libpopt-dev
autoreconf -i
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH="$HOME/fuzzing_libexif/install/lib/pkgconfig/"
make
make install
❯ ls
bin include lib share
❯ cd bin
❯ ls
exif
install 디렉터리에 가서 확인해보면 빌드된 exif 파일이 있습니다.❯ $HOME/fuzzing_libexif/install/bin/exif
Usage: exif [OPTION...] file
-v, --version Display software version
-i, --ids Show IDs instead of tag names
-t, --tag=tag Select tag
--ifd=IFD Select IFD
-l, --list-tags List all EXIF tags
-|, --show-mnote Show contents of tag MakerNote
--remove Remove tag or ifd
-s, --show-description Show description of tag
-e, --extract-thumbnail Extract thumbnail
-r, --remove-thumbnail Remove thumbnail
-n, --insert-thumbnail=FILE Insert FILE as thumbnail
-o, --output=FILE Write data to FILE
--set-value=STRING Value
-m, --machine-readable Output in a machine-readable (tab delimited) format
-x, --xml-output Output in a XML format
-d, --debug Show debugging messages
Help options:
-?, --help Show this help message
--usage Display brief usage message
rm -r $HOME/fuzzing_libexif/install
cd $HOME/fuzzing_libexif/libexif-libexif-0_6_14-release
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/"
make
make install
cd ../exif-exif-0_6_15-release
make clean
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
make install
❯ $HOME/fuzzing101/fuzzing_libexif/install/bin/exif $HOME/fuzzing101/fuzzing_libexif/exif-samples/jpg/Canon_40D_photoshop_import.jpg
EXIF tags in '/home/ion/fuzzing101/fuzzing_libexif/exif-samples/jpg/Canon_40D_photoshop_import.jpg' ('Motorola' byte order):
--------------------+----------------------------------------------------------
Tag |Value
--------------------+----------------------------------------------------------
Orientation |top - left
x-Resolution |300.00
y-Resolution |300.00
Resolution Unit |Inch
Software |GIMP 2.4.5
Date and Time |2008:07:31 10:05:49
Compression |JPEG compression
x-Resolution |72.00
y-Resolution |72.00
Resolution Unit |Inch
Color Space |sRGB
PixelXDimension |100
PixelYDimension |77
--------------------+----------------------------------------------------------
cd ..
git clone https://github.com/ianare/exif-samples.git
afl-fuzz -D -i $HOME/fuzzing_libexif/exif-samples/ -o $HOME/fuzzing_libexif/out/ -s 123 -- $HOME/fuzzing_libexif/install/bin/exif @@ $HOME/fuzzing_libexif/output

-D 옵션을 줘야 fuzzing strategy가 활성화 되기 때문에, -D 옵션을 줘서 실행시켜줍니다.❯ gdb-gef --args ~/fuzzing101/fuzzing_libexif/install/bin/exif ./crash0
gef➤ r
Program received signal SIGSEGV, Segmentation fault.
0x000055555557c7b0 in exif_get_sshort (buf=0x5556557adb95 <error: Cannot access memory at address 0x5556557adb95>, order=EXIF_BYTE_ORDER_MOTOROLA) at /root/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-utils.c:92
92 /root/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-utils.c: No such file or directory.
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x5556557adb95
$rbx : 0x00005555557acb80 → 0x00005555557acc10 → 0x0000000000000000
$rcx : 0x5556557adb96
$rdx : 0x5556557adb95
$rsp : 0x00007fffffffd120 → 0x0000000000000000
$rbp : 0x00005555557acbb8 → 0x00005555557acbd0 → 0x0000000000000000
$rsi : 0x0
$rdi : 0x00005555557acb80 → 0x00005555557acc10 → 0x0000000000000000
$rip : 0x000055555557c7b0 → <exif_data_load_data+0690> movzx edx, BYTE PTR [rdx]
$r8 : 0xffffffff
$r9 : 0x0
$r10 : 0x00005555557adb80 → 0x0000000000000df0
$r11 : 0x00007ffff7eacce0 → 0x00005555557ae010 → 0x0000000010004300
$r12 : 0x00005555557acbd0 → 0x0000000000000000
$r13 : 0xffffffff
$r14 : 0x00005555557adb90 → 0x4d4d000066697845 ("Exif"?)
$r15 : 0x484
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
─────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd120│+0x0000: 0x0000000000000000 ← $rsp
0x00007fffffffd128│+0x0008: 0x00005555557adb96 → 0xffffffff2a004d4d ("MM"?)
0x00007fffffffd130│+0x0010: 0x0000047e5556085a
0x00007fffffffd138│+0x0018: 0x00005555555a34d0 → 0x00005555555a6920 → <__afl_area_initial+0000> add BYTE PTR [rax], al
0x00007fffffffd140│+0x0020: 0x00005555557acb80 → 0x00005555557acc10 → 0x0000000000000000
0x00007fffffffd148│+0x0028: 0x00005555557ac130 → 0x0000000000000002
0x00007fffffffd150│+0x0030: 0x00005555557acb30 → 0x0000000100000006
0x00007fffffffd158│+0x0038: 0x000000000000009c
───────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555557c7a3 <exif_data_load_data+0683> mov BYTE PTR [rcx+0x446], dl
0x55555557c7a9 <exif_data_load_data+0689> lea rcx, [rax+0x1]
0x55555557c7ad <exif_data_load_data+068d> mov rdx, rax
→ 0x55555557c7b0 <exif_data_load_data+0690> movzx edx, BYTE PTR [rdx]
0x55555557c7b3 <exif_data_load_data+0693> movzx ecx, BYTE PTR [rcx]
0x55555557c7b6 <exif_data_load_data+0696> shl edx, 0xa
0x55555557c7b9 <exif_data_load_data+0699> lea ecx, [rdx+rcx*4]
0x55555557c7bc <exif_data_load_data+069c> lea ecx, [rcx+rcx*2]
0x55555557c7bf <exif_data_load_data+069f> jmp 0x55555557c7d9 <exif_data_load_data+1721>
───────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "exif", stopped 0x55555557c7b0 in exif_get_sshort (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x55555557c7b0 → exif_get_sshort(buf=0x5556557adb95 <error: Cannot access memory at address 0x5556557adb95>, order=EXIF_BYTE_ORDER_MOTOROLA)
[#1] 0x55555557c7b0 → exif_get_short(buf=0x5556557adb95 <error: Cannot access memory at address 0x5556557adb95>, order=EXIF_BYTE_ORDER_MOTOROLA)
[#2] 0x55555557c7b0 → exif_data_load_data(data=0x5555557acb80, d_orig=<optimized out>, ds_orig=<optimized out>)
[#3] 0x555555572a39 → exif_loader_get_data(loader=0x0)
[#4] 0x555555572a39 → main(argc=<optimized out>, argv=<optimized out>)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
SIGSEGV 가 발생했습니다.exif_data_load_data 함수가 존재합니다.main 함수부터 올라가면서 봐보겠습니다. if (args) {
while (*args) {
ExifLoader *l;
/*
* Try to read EXIF data from the file.
* If there is no EXIF data, exit.
*/
l = exif_loader_new ();
exif_loader_log (l, log);
exif_loader_write_file (l, *args);
ed = exif_loader_get_data (l);
exif_loader_unref (l);
if (!ed || ! (ed->data || ed->size ||
ed->ifd[EXIF_IFD_0]->count ||
ed->ifd[EXIF_IFD_1]->count ||
ed->ifd[EXIF_IFD_EXIF]->count ||
ed->ifd[EXIF_IFD_GPS]->count ||
ed->ifd[EXIF_IFD_INTEROPERABILITY]->count))
exif_log (log, -1, "exif", _("'%s' does not "
"contain EXIF data!"), *args);
exif_loader_new 함수를 통해 l 에 EXIF 데이터를 읽어오기 위한 EXIF 로더를 로드합니다.exif_loader_log 함수를 통해 logger를 붙입니다.exif_loader_write_file 함수를 통해 인자로 들어온 이미지 파일의 데이터를 로더에 씁니다.exif_loader_get_data 함수를 통해 EXIF 구조체에 EXIF 데이터를 얻어와 저장합니다.ExifData *
exif_loader_get_data (ExifLoader *loader)
{
ExifData *ed;
if (!loader)
return NULL;
ed = exif_data_new_mem (loader->mem);
exif_data_log (ed, loader->log);
exif_data_load_data (ed, loader->buf, loader->bytes_read);
return ed;
}
exif_data_new_mem 함수를 통해 로더에 메모리를 할당합니다.exif_data_log 함수를 통해 로더에 로거를 붙입니다.exif_data_load_data 함수를 통해 로더의 buf 에 있던 데이터를 ExifData 변수인 ed 에 저장합니다.exif_data_load_data (ExifData *data, const unsigned char *d_orig,
unsigned int ds_orig)
{
unsigned int l;
ExifLong offset;
ExifShort n;
const unsigned char *d = d_orig;
unsigned int ds = ds_orig, len;
if (!data || !data->priv || !d || !ds)
return;
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"Parsing %i byte(s) EXIF data...\n", ds);
/*
* It can be that the data starts with the EXIF header. If it does
* not, search the EXIF marker.
*/
if (ds < 6) {
LOG_TOO_SMALL;
return;
}
.
.
.
/*
* Verify the exif header
* (offset 2, length 6).
*/
if (ds < 6) {
LOG_TOO_SMALL;
return;
}
.
.
/* Byte order (offset 6, length 2) */
if (ds < 14)
return;
if (!memcmp (d + 6, "II", 2))
data->priv->order = EXIF_BYTE_ORDER_INTEL;
else if (!memcmp (d + 6, "MM", 2))
data->priv->order = EXIF_BYTE_ORDER_MOTOROLA;
else {
exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA,
"ExifData", _("Unknown encoding."));
return;
}
Exif.. 다음에 나오기 6 바이트를 더한 뒤 2 바이트를 살펴봅니다. /* IFD 0 offset */
offset = exif_get_long (d + 10, data->priv->order);
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"IFD 0 at %i.", (int) offset);
/* Parse the actual exif data (usually offset 14 from start) */
exif_data_load_data_content (data, EXIF_IFD_0, d + 6, ds - 6, offset, 0);
ExifData 에서 offset 10만큼 이동해서 IFD 0 offset을 찾아냅니다. /* IFD 1 offset */
if (offset + 6 + 2 > ds) {
return;
}
n = exif_get_short (d + 6 + offset, data->priv->order);
if (offset + 6 + 2 + 12 * n + 4 > ds) {
return;
}
offset = exif_get_long (d + 6 + offset + 2 + 12 * n, data->priv->order);
if (offset) {
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"IFD 1 at %i.", (int) offset);
/* Sanity check. */
if (offset > ds - 6) {
exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA,
"ExifData", "Bogus offset.");
return;
}
exif_get_short 함수에서 문제가 발생했습니다.ExifShort
exif_get_short (const unsigned char *buf, ExifByteOrder order)
{
return (exif_get_sshort (buf, order) & 0xffff);
}
buf에서 바이트 오더에 따라 2바이트의 짧은 정수를 읽어옵니다.


0xffffffff 로 이상한 값이 들어와 있습니다.다시 위로 올라가서 왜 offset 값에 이상한 값이 들어가게 되었는지 확인해보겠습니다.
/* IFD 0 offset */
offset = exif_get_long (d + 10, data->priv->order);
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"IFD 0 at %i.", (int) offset);
offset 에 저장합니다.crash 파일에서 이 위치에 무슨 값이 있는지 확인해보면

0xFFFF 값이 들어있습니다.정상 파일에서는 무슨 값이 있는지 확인을 해보면

0x0008 이 들어 있습니다.➡️ 한눈에 봐도 크래쉬 파일에 들어있는 오프셋 값이 비정상적으로 보입니다. 그래서 해당 함수에서 offset 값을 얻어오고 해당 값을 기준으로 데이터를 읽을 때, offset 이 전체 EXIF 데이터 크기인 ds 보다 큰지 검증을 하지만 offset 값에 0xffff 가 들어오면서 정수형 오버플로우가 발생하여 값이 -1 이 되었습니다. 그래서 검증 절차를 통과하여 그대로 실행이 되어 out-of-bound read 취약점이 발생했습니다.
offset 값이 정수형 오버플로우가 발생하지 않도록 타입을 변경해주면 됩니다.
// exif-data.c
ExifLong offset;
// exif-utils.h
typedef uint64_t ExifLong; /* 4 bytes */
uint32_t 타입이었는데, 좀 더 큰 범위까지 커버 할 수 있도록 uint64_t 로 타입을 변경하였습니다.❯ gdb-gef --args ~/fuzzing_libexif/install/bin/exif crash0
Reading symbols from /root/fuzzing_libexif/install/bin/exif...
Error while writing index for `/root/fuzzing_libexif/install/bin/exif': mkstemp: No such file or directory.
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
gef➤ r
Starting program: /root/fuzzing_libexif/install/bin/exif crash0
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
'crash0' does not contain EXIF data!
[Inferior 1 (process 12744) exited with code 01]
SIGSEGV 가 아닌 에러 처리로 인한 문자열이 출력되며 정상 종료되었습니다.Program received signal SIGSEGV, Segmentation fault.
0x0000555555578ed3 in exif_get_slong (b=<optimized out>, order=EXIF_BYTE_ORDER_INTEL) at /root/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-utils.c:135
135 /root/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-utils.c: No such file or directory.
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x1
$rbx : 0x0
$rcx : 0xd308
$rdx : 0x6985
$rsp : 0x00007fffffffd0d0 → 0x000000000000039a
$rbp : 0x80000001
$rsi : 0x1a610
$rdi : 0x00005555557ae9f0 → 0x0000000000000000
$rip : 0x0000555555578ed3 → <exif_content_fix+0603> movzx r9d, BYTE PTR [r9]
$r8 : 0x5555557c9000
$r9 : 0x5555557c9001
$r10 : 0xee
$r11 : 0xbfb9ea25ec6404aa
$r12 : 0x00005555555a34d0 → 0x00005555555a6920 → <__afl_area_initial+0000> add BYTE PTR [rax], al
$r13 : 0x00005555557ae990 → 0x000000040000a408
$r14 : 0xb
$r15 : 0x400
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd0d0│+0x0000: 0x000000000000039a ← $rsp
0x00007fffffffd0d8│+0x0008: 0x00005555557accb0 → 0x00005555557aea10 → 0x5555e5e05555e580
0x00007fffffffd0e0│+0x0010: 0x0000000000000701
0x00007fffffffd0e8│+0x0018: 0x0000555500000004
0x00007fffffffd0f0│+0x0020: 0x0000555500000002
0x00007fffffffd0f8│+0x0028: 0x00005555557acb80 → 0x00005555557acc10 → 0x00005555557ad290 → 0x00005555557acda0 → 0x000000020000010f
0x00007fffffffd100│+0x0030: 0x00005555557acb80 → 0x00005555557acc10 → 0x00005555557ad290 → 0x00005555557acda0 → 0x000000020000010f
0x00007fffffffd108│+0x0038: 0x00005555557ac130 → 0x0000000000000002
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x555555578ec4 <exif_content_fix+05f4> mov BYTE PTR [r9+0x323], r10b
0x555555578ecb <exif_content_fix+05fb> lea r9, [r8+0x2]
0x555555578ecf <exif_content_fix+05ff> add r8, 0x3
→ 0x555555578ed3 <exif_content_fix+0603> movzx r9d, BYTE PTR [r9]
0x555555578ed7 <exif_content_fix+0607> shl r9d, 0x8
0x555555578edb <exif_content_fix+060b> movzx ebx, BYTE PTR [r8]
0x555555578edf <exif_content_fix+060f> or ebx, r9d
0x555555578ee2 <exif_content_fix+0612> test eax, eax
0x555555578ee4 <exif_content_fix+0614> je 0x555555578f41 <exif_content_fix+1649>
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "exif", stopped 0x555555578ed3 in exif_get_slong (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555578ed3 → exif_get_slong(b=<optimized out>, order=EXIF_BYTE_ORDER_INTEL)
[#1] 0x555555578ed3 → exif_get_long(buf=<optimized out>, order=EXIF_BYTE_ORDER_INTEL)
[#2] 0x555555578ed3 → exif_entry_fix(e=0x5555557ae990)
[#3] 0x555555578ed3 → fix_func(e=0x5555557ae990, data=0x0)
[#4] 0x555555578ed3 → exif_content_foreach_entry(content=0x5555557accb0, data=0x0, func=<optimized out>)
[#5] 0x555555578ed3 → exif_content_fix(c=0x5555557accb0)
[#6] 0x55555557eb9b → exif_data_foreach_content(data=0x5555557acb80, user_data=0x0, func=<optimized out>)
[#7] 0x55555557eb9b → exif_data_fix(d=0x5555557acb80)
[#8] 0x555555572a39 → exif_loader_get_data(loader=0x0)
[#9] 0x555555572a39 → main(argc=<optimized out>, argv=<optimized out>)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
SIGSEGV 가 발생했습니다.exif_entry_fix 함수가 보입니다. /*
* If we got an EXIF_TAG_MAKER_NOTE, try to interpret it. Some
* cameras use pointers in the maker note tag that point to the
* space between IFDs. Here is the only place where we have access
* to that data.
*/
switch (exif_data_get_type_maker_note (data)) {
case EXIF_DATA_TYPE_MAKER_NOTE_OLYMPUS:
data->priv->md = exif_mnote_data_olympus_new (data->priv->mem);
break;
case EXIF_DATA_TYPE_MAKER_NOTE_PENTAX:
data->priv->md = exif_mnote_data_pentax_new (data->priv->mem);
break;
case EXIF_DATA_TYPE_MAKER_NOTE_CANON:
data->priv->md = exif_mnote_data_canon_new (data->priv->mem, data->priv->options);
break;
default:
break;
}
/*
* If we are able to interpret the maker note, do so.
*/
if (data->priv->md) {
exif_mnote_data_log (data->priv->md, data->priv->log);
exif_mnote_data_set_byte_order (data->priv->md,
data->priv->order);
exif_mnote_data_set_offset (data->priv->md,
data->priv->offset_mnote);
exif_mnote_data_load (data->priv->md, d, ds);
}
exif_mnote_data_log: 제조사 노트 데이터를 로그에 기록합니다.exif_mnote_data_set_byte_order: 바이트 오더를 설정합니다.exif_mnote_data_set_offset: 제조사 노트의 오프셋을 설정합니다.exif_mnote_data_load: 데이터를 메모리에서 로드합니다. if (data->priv->options & EXIF_DATA_OPTION_FOLLOW_SPECIFICATION)
exif_data_fix (data);
EXIF_DATA_OPTION_FOLLOW_SPECIFICATION 옵션이 설정되어 있으면 EXIF 데이터를 수정하는 함수를 호출합니다.void
exif_data_fix (ExifData *d)
{
exif_data_foreach_content (d, fix_func, NULL);
}
exif_data_foreach_content 함수를 호출합니다.void
exif_data_foreach_content (ExifData *data, ExifDataForeachContentFunc func,
void *user_data)
{
unsigned int i;
if (!data || !func)
return;
for (i = 0; i < EXIF_IFD_COUNT; i++)
func (data->ifd[i], user_data);
}
typedef struct _ByteOrderChangeData ByteOrderChangeData;
struct _ByteOrderChangeData {
ExifByteOrder old, new;
};
EXIF_IFD_COUNT 만큼 각 IFD에 대해 exif_content_fix 함수 호출을 반복합니다.void
exif_content_fix (ExifContent *c)
{
ExifIfd ifd = exif_content_get_ifd (c);
ExifDataType dt;
ExifTag t;
ExifEntry *e;
if (!c) return;
dt = exif_data_get_data_type (c->parent);
c 가 NULL인 경우 함수를 종료합니다.exif_data_get_data_type 을 호출하여 IFD를 가져오고, 데이터 타입을 결정합니다. /* First of all, fix all existing entries. */
exif_content_foreach_entry (c, fix_func, NULL);
exif_content_foreach_entry 함수를 호출하여 모든 기존 항목을 수정하는 fix_func 함수를 적용합니다.void
exif_content_foreach_entry (ExifContent *content,
ExifContentForeachEntryFunc func, void *data)
{
unsigned int i;
if (!content || !func)
return;
for (i = 0; i < content->count; i++)
func (content->entries[i], data);
}
content 또는 func 가 NULL 이면 함수를 종료합니다.content->count 만큼 반복하여 func 함수를 호출합니다. 여기서는 fix_func 를 호출합니다.static void
fix_func (ExifEntry *e, void *data)
{
exif_entry_fix (e);
}
exif_entry_fix 함수를 호출합니다.void
exif_entry_fix (ExifEntry *e)
{
unsigned int i;
ExifByteOrder o;
ExifRational r;
ExifSRational sr;
if (!e || !e->priv) return;
e 또는 e->priv 값이 NULL이면 함수를 종료합니다. switch (e->tag) {
/* These tags all need to be of format SHORT. */
case EXIF_TAG_YCBCR_SUB_SAMPLING:
case EXIF_TAG_SUBJECT_AREA:
case EXIF_TAG_COLOR_SPACE:
case EXIF_TAG_PLANAR_CONFIGURATION:
case EXIF_TAG_SENSING_METHOD:
case EXIF_TAG_ORIENTATION:
case EXIF_TAG_YCBCR_POSITIONING:
case EXIF_TAG_PHOTOMETRIC_INTERPRETATION:
case EXIF_TAG_CUSTOM_RENDERED:
case EXIF_TAG_EXPOSURE_MODE:
case EXIF_TAG_WHITE_BALANCE:
case EXIF_TAG_SCENE_CAPTURE_TYPE:
case EXIF_TAG_GAIN_CONTROL:
case EXIF_TAG_SATURATION:
case EXIF_TAG_CONTRAST:
case EXIF_TAG_SHARPNESS:
e->tag 에 따라 특정 태그로 분기합니다.switch (e->format) {
case EXIF_FORMAT_LONG:
if (!e->parent || !e->parent->parent) break;
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));
코드만 봐서는 감이 잘 오지 않습니다. 크래쉬 발생 상황에서의 context를 봐보겠습니다.

e->components 가 2147483649 입니다.i 가 27020 입니다.e->components 값이 비정상적으로 커서 ExifEntry 범위를 넘어선 곳까지 접근하여 값을 조작해서 Heap overflow가 발생한 것이 아닌가 하는 의심이듭니다.검증을 위해 최적화를 해제하고 다시 빌드한 이후 변환하려는 값의 주소를 분석해보겠습니다.
rm -r $HOME/fuzzing_libexif/install
cd $HOME/fuzzing_libexif/libexif-libexif-0_6_14-release
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto CFLAGS="-O0 -g" ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/test/"
make
make install
CFLAGS="-O0 -g" 옵션을 통해 최적화를 해제하고 빌드해줍니다.cd ../exif-exif-0_6_15-release
make clean
CC=afl-clang-lto CFLAGS="-O0 -g" ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/test/lib/pkgconfig
make
make install
다시 디버깅 context를 보면
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x55555558806b <exif_get_slong+00cb> add cl, dl
0x55555558806d <exif_get_slong+00cd> mov BYTE PTR [rax+0x51c], cl
0x555555588073 <exif_get_slong+00d3> mov rax, QWORD PTR [rbp-0x8]
→ 0x555555588077 <exif_get_slong+00d7> movzx eax, BYTE PTR [rax+0x3]
0x55555558807b <exif_get_slong+00db> shl eax, 0x18
0x55555558807e <exif_get_slong+00de> mov rcx, QWORD PTR [rbp-0x8]
0x555555588082 <exif_get_slong+00e2> movzx ecx, BYTE PTR [rcx+0x2]
0x555555588086 <exif_get_slong+00e6> shl ecx, 0x10
0x555555588089 <exif_get_slong+00e9> or eax, ecx
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "exif", stopped 0x555555588077 in exif_get_slong (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555588077 → exif_get_slong(b=0x5555557c4000 <error: Cannot access memory at address 0x5555557c4000>, order=EXIF_BYTE_ORDER_INTEL)
[#1] 0x555555587dbf → exif_get_long(buf=0x5555557c4000 <error: Cannot access memory at address 0x5555557c4000>, order=EXIF_BYTE_ORDER_INTEL)
[#2] 0x55555557b82e → exif_entry_fix(e=0x5555557a8380)
[#3] 0x55555557640d → fix_func(e=0x5555557a8380, data=0x0)
[#4] 0x555555575d42 → exif_content_foreach_entry(content=0x5555557a5840, func=0x5555555763d0 <fix_func>, data=0x0)
[#5] 0x55555557614d → exif_content_fix(c=0x5555557a5840)
[#6] 0x555555578be2 → fix_func(c=0x5555557a5840, data=0x0)
[#7] 0x555555578ca6 → exif_data_foreach_content(data=0x5555557a5710, func=0x555555578ab0 <fix_func>, user_data=0x0)
[#8] 0x555555578aa2 → exif_data_fix(d=0x5555557a5710)
[#9] 0x55555557780c → exif_data_load_data(data=0x5555557a5710, d_orig=0x5555557a78e0 "Exif", ds_orig=0x7e5)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ vmmap 0x5555557c4000
[ Legend: Code | Stack | Heap ]
Start End Offset Perm Path
e->components 에 비정상적으로 큰 값이 파싱되고 이후 해당 값을 기준으로 메모리에 접근하여 값을 조작하려고 하여 Heap Overflow가 발생하였습니다.e->components 의 값을 검사하는 코드를 추가하면 됩니다.
#define MAX_COMPONENTS 100
case EXIF_FORMAT_LONG:
if (!e->parent || !e->parent->parent) break;
if (e->components > MAX_COMPONENTS) break;
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->components 최대 값을 100 정도로 제한하겠습니다.❯ gdb-gef --args ~/fuzzing_libexif/install/bin/exif crash30
Reading symbols from /root/fuzzing_libexif/install/bin/exif...
Error while writing index for `/root/fuzzing_libexif/install/bin/exif': mkstemp: No such file or directory.
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
gef➤ r
Starting program: /root/fuzzing_libexif/install/bin/exif crash30
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
EXIF tags in 'crash30' ('Intel' byte order):
--------------------+----------------------------------------------------------
Tag |Value
--------------------+----------------------------------------------------------
Manufacturer |OLYMPUS CoR
Orientation |top - left
x-Resolution |72.00
y-Resolution |72.00
Resolution Unit |Inch
Software |GIMP 2.4.5
Date and Time |2008:07:31 13:03:
Artist |
YCbCr Positioning |centered
Exif Version |Exif Version 2.1
Date and Time (origi|
ISO Speed Ratings |50
Corrupt data (ExifEntry):
The tag 'FileSource' contains an invalid number of components (64, expected 1).
File Source |[Inferior 1 (process 31524) exited with code 01]