[Fuzzing101] Exercise 4

Sisyphus·2024년 11월 9일

Fuzzing101

목록 보기
4/7

Target

  • LibTIFF 4.0.4
    • TIFF 파일을 읽고 쓰기 위한 라이브러리
    • 다양한 이미지 포맷을 지원하며, TIFF 파일의 생성, 수정, 변환 등을 가능하게 함
  • tiffinfo
    • TIFF(Tagged Image File Format) 파일에 대한 정보를 출력하는 유틸리티입니다. 이 프로그램은 TIFF 파일의 구조와 내용을 분석하여, 각 TIFF 디렉토리의 태그 및 이미지 데이터에 대한 세부 정보를 표시합니다.
  • CVE-2016-9297
    • TIFFFetchNormalTag 함수에서 TIFF_SETGET_C16ASCII 또는 TIFF_SETGET_C32_ASCII 태그 값을 통해 out-of-bounds read 취약점이 발생했습니다.


Target Download & Build

Download

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

Build

cd tiff-4.0.4/
./configure --enable-shared=no --prefix="$HOME/fuzzing_libtiff/install/"
make
make install

Testing

$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]
  • 빌드가 잘 되었습니다.


Fuzzing

  • Fuzzing을 위해 바이너리를 재빌드 하겠습니다.
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와 asan을 포함시켜서 빌드해줍니다.

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 바이너리를 타겟으로 퍼징을 진행합니다.

  • 140개의 크래시가 발생했습니다.

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
  • Out-of-Bound Read 취약점이 발생한 TIFFFetchNormalTag 함수가 allocated 스택에 있습니다.
  • fprintf() 함수로 동적 할당된 변수를 출력하는 과정에서 Out-of-Bound Read 취약점이 발생한거 같습니다.


Root Cause Aanalysis

Build Debuging Binary

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

Eclipse Settings


Debugging Stack Trace

pwndbg> b tif_print.c:127
Breakpoint 2 at 0x47b362: file tif_print.c, line 127.
pwndbg> r
  • 취약점이 발생한 부분에 break point를 걸고 값들을 살펴보겠습니다.

_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 로그가 출력됩니다.
  • 왜 문자열을 출력했는데, Out-of-Bound Read 취약점이 발생한건지 봐보면, 문자열 끝에 NULL 이 없습니다.
  • frpintf 함수에서 %s 형식지정자로 문자열을 출력하면 NULL 문자를 만나기 전까지 문자열을 출력하기 때문에, raw_data 를 넘어선 범위까지 문자열을 출력하여 Out-of-Bounds 취약점이 발생합니다.

Reproduce

해당 버그를 간단하게 재현해보겠습니다.

#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_dataNULL 을 빼고 문자열만 대입하면, 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
  • 위 로그처럼 Heap Buffer Overflow가 발생했다는 로그가 나옵니다.

Debugging Malloc Allocation Trace

이제 취약점이 왜 발생하게 되었는지 알려면 왜 raw_dataNULL 이 빠진 문자열이 들어갔는지 알아야합니다. 이를 위해 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_tagdp->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_countdata 값이 들어있습니다.

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, 1setByteArray 함수를 호출합니다.
    • 이때 datadp->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 입니다.
    • vpNULL 이 없이 0 만 대입되었는데 아무런 검사나 예외처리도 하고 있지 않습니다.
    • 문자열 변수 vp 를 출력할 때 vp 변수의 크기는 1바이트라 한글자만 출력해야 하지만, 문자열 구조상 NULL 을 만날때 까지 출력하기 때문에 vp 변수의 범위를 벗어난 접근 및 출력을 하게 되어 Out-of-Bound Read 취약점이 발생합니다.


Patch

data 변수 끝에 NULL 이 있는지 검사하고 없으면 NULL 을 삽입하는 코드를 추가해주면 됩니다.

TIFFFetchNormalTag

		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;

0개의 댓글