TIFFFetchNormalTag 함수에서 TIFF_SETGET_C16ASCII 또는 TIFF_SETGET_C32_ASCII 태그 값을 통해 out-of-bounds read 취약점이 발생했습니다.mkdir fuzzing_tiff && cd fuzzing_tiff
wget https://download.osgeo.org/libtiff/tiff-4.0.4.tar.gz
tar -zxvf tiff-4.0.4.tar.gz
cd tiff-4.0.4/
./configure --enable-shared=no --prefix="$HOME/fuzzing_libtiff/install/"
make
make install
❯ $HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_tiff/tiff-4.0.4/test/images/palette-1c-1b.tiff
TIFF Directory at offset 0xbd4 (3028)
Image Width: 157 Image Length: 151
Bits/Sample: 1
Sample Format: unsigned integer
Compression Scheme: None
Photometric Interpretation: palette color (RGB from colormap)
Samples/Pixel: 1
Rows/Strip: 409
Planar Configuration: single image plane
Page Number: 0-1
Color Map:
0: 0 0 0
1: 65535 65535 65535
DocumentName: palette-1c-1b.tiff
Software: GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/
1 Strips:
0: [ 8, 3020]
rm -r $HOME/fuzzing_tiff/install
cd $HOME/fuzzing_tiff/tiff-4.0.4/
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --prefix="$HOME/fuzzing_tiff/install/" --disable-shared
AFL_USE_ASAN=1 make -j4
AFL_USE_ASAN=1 make install
afl-fuzz -m none -i $HOME/fuzzing_tiff/tiff-4.0.4/test/images/ -o $HOME/fuzzing_tiff/out/ -s 123 -- $HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w @@
ttifinfo 바이너리를 타겟으로 퍼징을 진행합니다.
afltriage를 이용해서 crash 파일을 정리하겠습니다.
afltriage -i . -o reports $HOME/fuzzing_tiff/debug/bin/tiffinfo -- @@
AFLTriage v1.0.0 by Grant Hernandez
[+] GDB is working (GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.2) 9.2 - Python 3.8.10 (default, Sep 11 2024, 16:02:53))
[+] Image triage cmdline: /home/ion/fuzzing_tiff/debug/bin/tiffinfo -- @@
[+] Will write text reports to directory "reports"
[+] Triaging plain directory . (140 files)
[+] Triage timeout set to 60000ms
[+] Profiling target...
[+] Target profile: time=33.886377ms, mem=1KB
[+] Debugged profile: t=288.078711ms (8.73x), mem=40896KB (40896.00x)
[+] System memory available: 7013200 KB
[+] System cores available: 16
[+] Triaging 140 testcases
[+] Using 16 threads to triage
[+] Triaging [140/140 00:00:05] [####################] ASAN detected heap-buffer-overflow in fprintf after a READ lead
[+] Triage stats [Crashes: 118 (unique 1), No crash: 22, Timeout: 0, Errored: 0]
❯ cd reports
❯ ls
afltriage_ASAN_heap-buffer-overflow_READ_fprintf_53bc4c3617bde448f0b24ca2ba59feae.txt
한 개의 유니크한 Heap Buffer Overflow 크래시가 발생했습니다.
$HOME/fuzzing_tiff/debug/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_tiff/out/default/crashes/id:000000,sig:06,src:000000,time:6594,execs:6651,op:havoc,rep:4
TIFFReadDirectoryCheckOrder: Warning, Invalid TIFF directory; tags are not sorted in ascending order.
TIFFReadDirectory: Warning, Unknown field with tag 3 (0x3) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 63253 (0xf715) encountered.
TIFFFetchNormalTag: Warning, IO error during reading of "Tag 3"; tag ignored.
TIFF Directory at offset 0x10 (16)
Image Width: 1 Image Length: 1
Bits/Sample: 769
Compression Scheme: SGILog
Photometric Interpretation: 11 (0xb)
Rows/Strip: 682
Planar Configuration: single image plane
=================================================================
==1700537==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000b1 at pc 0x0000002aee84 bp 0x7ffdfb687760 sp 0x7ffdfb686ee8
READ of size 2 at 0x6020000000b1 thread T0
#0 0x2aee83 in printf_common(void*, char const*, __va_list_tag*) (/home/ion/fuzzing_tiff/debug/bin/tiffinfo+0x2aee83)
#1 0x2b02ff in fprintf (/home/ion/fuzzing_tiff/debug/bin/tiffinfo+0x2b02ff)
#2 0x47b39c in _TIFFPrintField /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_print.c:127:4
#3 0x47810f in TIFFPrintDirectory /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_print.c:641:5
#4 0x33f642 in tiffinfo /home/ion/fuzzing_tiff/tiff-4.0.4/tools/tiffinfo.c:449:2
#5 0x33ee8c in main /home/ion/fuzzing_tiff/tiff-4.0.4/tools/tiffinfo.c:152:6
#6 0x7ff77a5b9082 in __libc_start_main /build/glibc-LcI20x/glibc-2.31/csu/../csu/libc-start.c:308:16
#7 0x290acd in _start (/home/ion/fuzzing_tiff/debug/bin/tiffinfo+0x290acd)
0x6020000000b1 is located 0 bytes to the right of 1-byte region [0x6020000000b0,0x6020000000b1)
allocated by thread T0 here:
#0 0x30ab3d in malloc (/home/ion/fuzzing_tiff/debug/bin/tiffinfo+0x30ab3d)
#1 0x49c949 in _TIFFmalloc /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_unix.c:283:10
#2 0x34d93c in setByteArray /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_dir.c:51:19
#3 0x35fae4 in _TIFFVSetField /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_dir.c:539:4
#4 0x421ce7 in LogLuvVSetField /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_luv.c:1605:10
#5 0x34def7 in TIFFVSetField /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_dir.c:820:6
#6 0x34ddbc in TIFFSetField /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_dir.c:764:11
#7 0x395e5d in TIFFFetchNormalTag /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_dirread.c:5164:8
#8 0x3844ae in TIFFReadDirectory /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_dirread.c:3810:12
#9 0x43f7fb in TIFFClientOpen /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_open.c:466:8
#10 0x49adf5 in TIFFFdOpen /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_unix.c:178:8
#11 0x49c860 in TIFFOpen /home/ion/fuzzing_tiff/tiff-4.0.4/libtiff/tif_unix.c:217:8
#12 0x33ed21 in main /home/ion/fuzzing_tiff/tiff-4.0.4/tools/tiffinfo.c:140:9
#13 0x7ff77a5b9082 in __libc_start_main /build/glibc-LcI20x/glibc-2.31/csu/../csu/libc-start.c:308:16
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/ion/fuzzing_tiff/debug/bin/tiffinfo+0x2aee83) in printf_common(void*, char const*, __va_list_tag*)
Shadow bytes around the buggy address:
0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff8000: fa fa 00 00 fa fa fd fa fa fa fd fa fa fa 00 fa
=>0x0c047fff8010: fa fa fd fa fa fa[01]fa fa fa fd fa fa fa 00 fa
0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1700537==ABORTING
TIFFFetchNormalTag 함수가 allocated 스택에 있습니다.fprintf() 함수로 동적 할당된 변수를 출력하는 과정에서 Out-of-Bound Read 취약점이 발생한거 같습니다.cd $HOME/fuzzing_tiff/tiff-4.0.4/
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto CFLAGS="-g -O0 -fno-inline -fno-omit-frame-pointer" ./configure --prefix="$HOME/fuzzing_tiff/debug/" --disable-shared
AFL_USE_ASAN=1 make -j4
AFL_USE_ASAN=1 make install
❯ ls $HOME/fuzzing_tiff/debug/bin
bmp2tiff gif2tiff ras2tiff thumbnail tiff2ps tiffcp tiffdump tiffset
fax2ps pal2rgb raw2tiff tiff2bw tiff2rgba tiffcrop tiffinfo tiffsplit
fax2tiff ppm2tiff rgb2ycbcr tiff2pdf tiffcmp tiffdither tiffmedian


pwndbg> b tif_print.c:127
Breakpoint 2 at 0x47b362: file tif_print.c, line 127.
pwndbg> r
_TIFFPrintField
else if(fip->field_type == TIFF_ASCII) {
fprintf(fd, "%s", (char *) raw_data);
break;
}
pwndbg> x/s 0x602000000090
0x602000000090: "0"
raw_data 값에 0 이 들어있습니다.pwndbg> next
next 를 입력하면 asan 로그가 출력됩니다.NULL 이 없습니다.frpintf 함수에서 %s 형식지정자로 문자열을 출력하면 NULL 문자를 만나기 전까지 문자열을 출력하기 때문에, raw_data 를 넘어선 범위까지 문자열을 출력하여 Out-of-Bounds 취약점이 발생합니다.해당 버그를 간단하게 재현해보겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void printRawData(char* raw_data) {
// raw_data를 문자열로 출력
fprintf(stdout, "%s\n", raw_data);
}
int main() {
// 크기가 1인 데이터 생성 (null 종료 문자가 없음)
char* raw_data = (char*)malloc(1);
if (raw_data == NULL) {
perror("Failed to allocate memory");
return 1;
}
// 데이터 초기화 (null 종료 문자가 없음)
raw_data[0] = 'A'; // 'A'라는 문자만 저장
raw_data[1] = 'B';
raw_data[2] = 'C';
raw_data[3] = '\0';
// printRawData 호출
printRawData(raw_data);
// 메모리 해제
free(raw_data);
return 0;
}
❯ ./normal
ABC
raw_data 에 NULL 을 빼고 문자열만 대입하면, fprintf 함수로 문자열을 출력할 때 NULL 앞 까지인 ABC 를 출력합니다.asan 로그를 봐보면
❯ ./sanitizer
=================================================================
==1702654==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000011 at pc 0x55c62e09f36d bp 0x7ffde03e0790 sp 0x7ffde03e0780
WRITE of size 1 at 0x602000000011 thread T0
#0 0x55c62e09f36c in main /home/ion/fuzzing_tiff/test/hello.c:20
#1 0x7fc9ff528082 in __libc_start_main ../csu/libc-start.c:308
#2 0x55c62e09f1ad in _start (/home/ion/fuzzing_tiff/test/sanitizer+0x11ad)
0x602000000011 is located 0 bytes to the right of 1-byte region [0x602000000010,0x602000000011)
allocated by thread T0 here:
#0 0x7fc9ff803808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
#1 0x55c62e09f2d4 in main /home/ion/fuzzing_tiff/test/hello.c:12
#2 0x7fc9ff528082 in __libc_start_main ../csu/libc-start.c:308
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ion/fuzzing_tiff/test/hello.c:20 in main
Shadow bytes around the buggy address:
0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[01]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1702654==ABORTING
이제 취약점이 왜 발생하게 되었는지 알려면 왜 raw_data 에 NULL 이 빠진 문자열이 들어갔는지 알아야합니다. 이를 위해 Malloc Allocation Trace를 디버깅 해보겠습니다.
TIFFFetchNormalTag
먼저 취약점이 발생한 함수를 디버깅해보겠습니다.
pwndbg> b tif_dirread.c:5164
Breakpoint 3 at 0x395d20: file tif_dirread.c, line 5164.
pwndbg> r
case TIFF_SETGET_C32_ASCII:
{
uint8* data;
assert(fip->field_readcount==TIFF_VARIABLE2);
assert(fip->field_passcount==1);
err=TIFFReadDirEntryByteArray(tif,dp,&data);
if (err==TIFFReadDirEntryErrOk)
{
int m;
m=TIFFSetField(tif,dp->tdir_tag,(uint32)(dp->tdir_count),data);
if (data!=0)
_TIFFfree(data);
if (!m)
return(0);
}
}
break;

data 변수를 보면 raw_data 에서 봤던 비슷한 대역의 주소가 보이고 raw_data 값인 0 이 들어있습니다.dp->tdir_tag 와 dp->tdir_count 가 각각 인자로 넘어가는데, tdir_tag 는 태그 식별자이고 tdir_count 는 특정 태그에 대해 설정될 항목의 개수를 의미합니다.TIFFSetField
int
TIFFSetField(TIFF* tif, uint32 tag, ...)
{
va_list ap;
int status;
va_start(ap, tag);
status = TIFFVSeField(tif, tag, ap);
va_end(ap);
return (status);
}
ap 를 인자로 TIFFVSeField 함수를 호출합니다.ap 에는 dp->tdir_count 와 data 값이 들어있습니다.TIFFVSeField
int
TIFFVSetField(TIFF* tif, uint32 tag, va_list ap)
{
return OkToChangeTag(tif, tag) ?
(*tif->tif_tagmethods.vsetfield)(tif, tag, ap) : 0;
}
vsetfield 함수형 포인터를 호출합니다._TIFFVSetField
static int
_TIFFVSetField(TIFF* tif, uint32 tag, va_list ap)
{
static const char module[] = "_TIFFVSetField";
TIFFDirectory* td = &tif->tif_dir;
int status = 1;
uint32 v32, i, v;
...
if (fip->field_type == TIFF_ASCII)
{
uint32 ma;
char* mb;
if (fip->field_passcount)
{
assert(fip->field_writecount==TIFF_VARIABLE2);
ma=(uint32)va_arg(ap,uint32);
mb=(char*)va_arg(ap,char*);
}
else
{
mb=(char*)va_arg(ap,char*);
ma=(uint32)(strlen(mb)+1);
}
tv->count=ma;
setByteArray(&tv->value,mb,ma,1);
}
mb 변수에 가변인자로 넘어온 data 값을 대입합니다.fip->field_passcount 변수가 True 이면 가변 인자로 판단하여 ma 에 가변 인자로 넘어온 dp->tdir_count 값을 대입합니다.ma 변수에 mb 변수의 길이 + 1을 대입합니다.tv->value, mb, ma, 1 로 setByteArray 함수를 호출합니다.data 와 dp->tdir_count 를 인자로 넘깁니다.1 을 인자로 넘기는데 이건 ASCII가 1 바이트라 1을 넘기는거 같습니다.setByteArray
static void
setByteArray(void** vpp, void* vp, size_t nmemb, size_t elem_size)
{
if (*vpp)
_TIFFfree(*vpp), *vpp = 0;
if (vp) {
tmsize_t bytes = (tmsize_t)(nmemb * elem_size);
if (elem_size && bytes / elem_size == nmemb)
*vpp = (void*) _TIFFmalloc(bytes);
if (*vpp)
_TIFFmemcpy(*vpp, vp, bytes);
}
}
tv->value 에 동적할당을 하고 data 값을 tv->value 에 복사합니다.vp 값은 0 이고 bytes 값은 1 입니다.vp 에 NULL 이 없이 0 만 대입되었는데 아무런 검사나 예외처리도 하고 있지 않습니다.vp 를 출력할 때 vp 변수의 크기는 1바이트라 한글자만 출력해야 하지만, 문자열 구조상 NULL 을 만날때 까지 출력하기 때문에 vp 변수의 범위를 벗어난 접근 및 출력을 하게 되어 Out-of-Bound Read 취약점이 발생합니다.data 변수 끝에 NULL 이 있는지 검사하고 없으면 NULL 을 삽입하는 코드를 추가해주면 됩니다.
case TIFF_SETGET_C16_ASCII:
{
uint8* data;
assert(fip->field_readcount==TIFF_VARIABLE);
assert(fip->field_passcount==1);
if (dp->tdir_count>0xFFFF)
err=TIFFReadDirEntryErrCount;
else
{
err=TIFFReadDirEntryByteArray(tif,dp,&data);
if (err==TIFFReadDirEntryErrOk)
{
int m;
m=TIFFSetField(tif,dp->tdir_tag,(uint16)(dp->tdir_count),data);
if (data[dp->tdir_count - 1] != '\0')
data[dp->tdir_count - 1] = '\0';
if (data!=0)
_TIFFfree(data);
if (!m)
return(0);
}
}
}
break;
case TIFF_SETGET_C32_ASCII:
{
uint8* data;
assert(fip->field_readcount==TIFF_VARIABLE2);
assert(fip->field_passcount==1);
err=TIFFReadDirEntryByteArray(tif,dp,&data);
if (err==TIFFReadDirEntryErrOk)
{
int m;
m=TIFFSetField(tif,dp->tdir_tag,(uint32)(dp->tdir_count),data);
if (data[dp->tdir_count - 1] != '\0')
data[dp->tdir_count - 1] = '\0';
if (data!=0)
_TIFFfree(data);
if (!m)
return(0);
}
}
break;