1. 분석 대상
LibTIFF란?
태그 이미지 파일 형식의 파일을 읽고 쓰기 위한 라이브러리
tiff 파일을 생성하거나 확장자 정보를 얻는 등의 기능도 있다.
Main Topic
- Code coverage
- LCOV
2. CVE Code
CVE-2016-9297
- LibTIFF 4.0.6 버전의 TIFFetchNormalTag 함수를 사용하여 조작된 태그 파일을 통해 Out-of-Bounds Read, 서비스 거부 공격을 일으킬 수 있는 취약점
3. CVE 관련 정보
Reference
https://nvd.nist.gov/vuln/detail/CVE-2016-9297
https://mititch.tistory.com/114
https://docs.fileformat.com/ko/image/tiff/
4. 분석 환경
Ubuntu 20.04 환경에서 진행
- LibTiff 4.0.6 Download
wget https://download.osgeo.org/libtiff/tiff-4.0.6.tar.gz
tar -xzvf tiff-4.0.6.tar.gz
LibTiff Build
cd tiff-4.0.6
./configure --prefix="$HOME/fuzzing_libtiff/install"
make
make install
설치 확인
root@7216a6b36080:~/fuzzing_libtiff# ./install/bin/tiffinfo -v
LIBTIFF, Version 4.0.6
Copyright (c) 1988-1996 Sam Leffler
Copyright (c) 1991-1996 Silicon Graphics, Inc.
usage: tiffinfo [options] input...
where options are:
-D read data
-i ignore read errors
-c display data for grey/color response curve or colormap
-d display raw/decoded image data
-f lsb2msb force lsb-to-msb FillOrder for input
-f msb2lsb force msb-to-lsb FillOrder for input
-j show JPEG tables
-o offset set initial directory offset
-r read/display raw image data instead of decoded data
-s display strip offsets and byte counts
-w display raw data in words rather than bytes
-z enable strip chopping
-# set initial directory (first directory is # 0)
Test File
테스트 파일은 libtiff 디렉터리 내에 test/images 디렉터리에 있다.
root@7216a6b36080:~/fuzzing_libtiff# $HOME/fuzzing_libtiff/install/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_libtiff/tiff-4.0.6/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]
Code Coverage란?
퍼저가 코드의 도달한 부분과 퍼징 프로세스를 시각화 하여 나타내주는 도구
LCOV Install
sudo apt install lcov
LibTiff Build with lcov
rm -rf install/
cd tiff-4.0.6/
make clean
CFLAGS="--coverage" LDFLAGS="--coverage" ./configure --prefix="$HOME/fuzzing_libtiff/install/" --disable-shared
make
make install
Code Coverage enable
cd $HOME/fuzzing_libtiff/tiff-4.0.6/
lcov --zerocounters --directory ./
lcov --capture --initial --directory ./ --output-file app.info
$HOME/fuzzing_libtiff/install/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_libtiff/tiff-4.0.6/test/images/palette-1c-1b.tiff
lcov --no-checksum --directory ./ --capture --output-file app2.info
- lcov --zerocounters --directory ./
카운터 리셋 - lcov --capture --initial --directory ./ --output-file app.info
모든 계측 라인에 대해 제로 커버리지가 포함된 base line 커버리지 데이터 파일을 반환 - $HOME/fuzzing_libtiff/install/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_libtiff/tiff-4.0.6/test/images/palette-1c-1b.tiff
분석할 프로그램 실행 - lcov --no-checksum --directory ./ --capture --output-file app2.info
현재 커버리지 상태를 파일로 저장
Make HTML
genhtml --highlight --legend -output-directory ./html-coverage/ ./app2.info
커버리지 상태를 저장한 파일을 HTML로 만들어 시각화 한다.
HTML 파일을 열면 다음과 같은 화면을 볼 수 있다.
LibTiff Build with ASan
rm -rf install/
cd tiff-4.0.6/
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --prefix="$HOME/fuzzing_libtiff/install/" --disable-shared
AFL_USE_ASAN=1 make -j4
AFL_USE_ASAN=1 make install
Fuzzing
afl-fuzz -m none -i $HOME/fuzzing_libtiff/tiff-4.0.6/test/images/ -o $HOME/fuzzing_libtiff/out/ -s 123 -- $HOME/fuzzing_libtiff/install/bin/tiffinfo -D -j -c -r -s -w @@
- -m
프로세스 메모리 제한 - -i
입력 테스트 케이스를 넣을 디렉터리 - -o
결과를 저장할 디렉터리 - -s
사용할 정적 랜덤 시드 - --
실제 사용할 인자 - @@
타겟 프로그램이 파일을 입력으로 받는 경우에 사용한다. @@ 위치에 자동으로 대상 파일의 경로와 이름이 커맨드 라인으로 들어간다. 붙이지 않으면 표준 입력으로 처리하는 것으로 간주한다. - 이외, LibTiff 실행 인자
- 16분 정도 돌린 결과, 44개의 유니크 크래쉬를 찾고 퍼저를 멈췄다.
5. 루트 커즈 분석
~/fuzzing_libtiff/out 하위 디렉터리에 저장된 크래시 파일을 확인할 수 있다.
root@7216a6b36080:~/fuzzing_libtiff/out/default/crashes# ls
README.txt id:000022,sig:06,src:000423,time:547528,execs:466273,op:havoc,rep:4
id:000000,sig:11,src:000007,time:48622,execs:47079,op:havoc,rep:4 id:000023,sig:06,src:000423,time:549584,execs:467728,op:havoc,rep:5
id:000001,sig:06,src:000004,time:69329,execs:66194,op:havoc,rep:7 id:000024,sig:06,src:000423,time:550253,execs:468235,op:havoc,rep:6
id:000002,sig:06,src:000004,time:74483,execs:70597,op:havoc,rep:1 id:000025,sig:06,src:000526+000095,time:576186,execs:487726,op:splice,rep:3
id:000003,sig:06,src:000260,time:226892,execs:173954,op:havoc,rep:2 id:000026,sig:06,src:000539+000452,time:593172,execs:500876,op:splice,rep:2
id:000004,sig:06,src:000338,time:250617,execs:199022,op:havoc,rep:6 id:000027,sig:06,src:000499,time:610801,execs:506707,op:havoc,rep:13
id:000005,sig:06,src:000350,time:264775,execs:211068,op:havoc,rep:13 id:000028,sig:06,src:000499,time:640066,execs:513684,op:havoc,rep:8
...
두 번째 파일을 대상으로 디버깅 진행
Triger
fputs 함수 진행 도중 heap-buffer-overflow 및 ImageDescription 태그의 끝이 NULL이 아니라는 경고가 같이 뜬다.
코드 분석
tif.print.c 파일을 먼저 분석
static void
_TIFFPrintField(FILE* fd, const TIFFField *fip,
uint32 value_count, void *raw_data)
{
uint32 j;
fprintf(fd, " %s: ", fip->field_name);
for(j = 0; j < value_count; j++) {
if(fip->field_type == TIFF_BYTE)
fprintf(fd, "%u", ((uint8 *) raw_data)[j]);
else if(fip->field_type == TIFF_UNDEFINED)
fprintf(fd, "0x%x",
(unsigned int) ((unsigned char *) raw_data)[j]);
else if(fip->field_type == TIFF_SBYTE)
fprintf(fd, "%d", ((int8 *) raw_data)[j]);
else if(fip->field_type == TIFF_SHORT)
fprintf(fd, "%u", ((uint16 *) raw_data)[j]);
else if(fip->field_type == TIFF_SSHORT)
fprintf(fd, "%d", ((int16 *) raw_data)[j]);
else if(fip->field_type == TIFF_LONG)
fprintf(fd, "%lu",
(unsigned long)((uint32 *) raw_data)[j]);
else if(fip->field_type == TIFF_SLONG)
fprintf(fd, "%ld", (long)((int32 *) raw_data)[j]);
else if(fip->field_type == TIFF_IFD)
fprintf(fd, "0x%lx",
(unsigned long)((uint32 *) raw_data)[j]);
else if(fip->field_type == TIFF_RATIONAL
|| fip->field_type == TIFF_SRATIONAL
|| fip->field_type == TIFF_FLOAT)
fprintf(fd, "%f", ((float *) raw_data)[j]);
else if(fip->field_type == TIFF_LONG8)
...
문제가 발생한 _TIFFPrintField 함수의 진입점이다. fd에 파일 이름과 필드 타입에 따라 value_count 만큼 데이터를 형식에 맞추어 저장한다.
else if(fip->field_type == TIFF_ASCII) {
fprintf(fd, "%s", (char *) raw_data);
break;
heap buffer overflow 가 발생한 127번 줄 코드이다. field_type이 ASCII 이면 fd에 "%s" 형식으로 데이터를 저장한다.
이때, printf 는 NULL 바이트를 만날때 까지 출력을 진행하기 때문에 태그를 조작하여 NULL 바이트를 삭제 시켰을 경우 뒤에 저장되어있는 임의 값이 출력되어지는 것이다.
위와 같이 ASCII 타입으로 지정되어 있지만 NULL 바이트가 존재하지 않으면 printf의 동작에 따라 NULL 바이트를 만날때 까지 fd에 출력하게 되므로 메모리안에 남아있는 쓰레기값 혹은 중요 주소 등이 출력될 수 있는 것이다.
6. 패치 파악
다음은 4.0.7 버전에서 패치된 커스텀 태그 ascii 데이터를 읽어오는 함수이다.
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;
if( dp->tdir_count > 0 && data[dp->tdir_count-1] != '\0' )
{
TIFFWarningExt(tif->tif_clientdata,module,"ASCII value for tag \"%s\" does not end in null byte. Forcing it to be null",fip->field_name);
data[dp->tdir_count-1] = '\0';
}
m=TIFFSetField(tif,dp->tdir_tag,(uint16)(dp->tdir_count),data);
if (data!=0)
_TIFFfree(data);
if (!m)
return(0);
}
}
}
break;
데이터 배열의 마지막 값이 NULL이 아니면 경고 메시지와 함께 마지막 값을 NULL로 바꿔주는 코드가 추가되어 패치 되었다.
전체 코드는 아래 취약점 패치 공식 문서에서 확인할 수 있다.
https://github.com/vadz/libtiff/commit/30c9234c7fd0dd5e8b1e83ad44370c875a0270ed
정리
- tiff 파일의 태그가 조작되어 NULL 바이트가 삽입되지 않은 문자열 데이터가 존재할 경우 printf 함수의 동작에 따라 NULL 바이트를 만날 때 까지 값이 출력되는 취약점이 존재 (Out-of-Bounds Read)
- 데이터의 마지막 값이 NULL이 아닐 경우 강제적으로 NULL 값을 삽입하는 코드가 추가되어 패치되었다.
'Fuzzing' 카테고리의 다른 글
[Fuzzing101] 5. Libxml2 (CVE-2017-9048) (0) | 2024.01.12 |
---|---|
[Fuzzing101] 3. TCPdump (CVE-2017-13028) (1) | 2023.12.26 |
[Fuzzing101] 2. libexif (CVE-2009-3895) (0) | 2023.11.07 |
[Fuzzing101] 1. Xpdf (CVE-2019-13288) (1) | 2023.10.30 |