CVE-2016-9297
The TIFFFetchNormalTag function in LibTiff 4.0.6 allows remote attackers to cause a denial of service (out-of-bounds read) via crafted TIFF_SETGET_C16ASCII or TIFF_SETGET_C32_ASCII tag values.
해석하자면 LibTiff 4.0.6의 TIFFFetchNormalTag 함수에서 발생하는 취약점으로 out-of-bound read 가 발생한다.
빌드를 해보자. 우선 LibTIFF 를 설치한다.
wget https://github.com/vadz/libtiff/archive/refs/tags/Release-v4-0-6.tar.gz
tar -zxvf Release-v4-0-6.tar.gz
그리고 빌드를 한다.
cd $HOME/f/fuzzing_LibTIFF/libtiff-Release-v4-0-6/
cd tiff-4.0.4/
./configure --prefix="$HOME/f/fuzzing_tiff/install/" --disable-shared
make
make install
잘 되는지 한 번 해 본다.
./install/bin/tiffinfo -D -j -c -r -s -w ./tiff-4.0.4/test/images/palette-1c-1b.tiff
옵션:
-D : data를 읽는다.
-j : JPEG table을 본다.
-c : grey/color response 혹은 colormap에 대한 표시를 한다.
-r : 디코드된 data 대신 raw image data로 표시한다.
-s : strip offset과 byte count를 표시한다.
-w : raw data를 bytes가 아닌 word로 표시한다.
-j -c -r -s -w 와 같은 옵션을 쓰는 이유는 code coverage를 높이고 bug를 찾는 가능성을 높이기 위해서이다.
code coverage
code coverage는 코드의 각 줄이 실행된 횟수를 보여주는 것이다. code coverage를 사용함으로써 우리는 코드의 어느 부분에 fuzzer가 도달했는지 알 수 있고 퍼징 과정을 시각화할 수 있다.
우선은 lcov를 설치해야한다.
sudo apt install lcov
이제는 --coverage 옵션과 함께 다시 rebuild를 한다.
rm -r $HOME/fuzzing_tiff/install
cd $HOME/fuzzing_tiff/tiff-4.0.4/
make clean
CFLAGS="--coverage" LDFLAGS="--coverage" ./configure --prefix="$HOME/f/fuzzing_LibTIFF/install/" --disable-shared
make
make install
그래서 우리는 다음과 같은 작업으로 code coverage data를 얻을 수 있다.
cd $HOME/fuzzing_tiff/tiff-4.0.4/
lcov --zerocounters --directory ./
lcov --capture --initial --directory ./ --output-file app.info
$HOME/f/fuzzing_LibTIFF/install/bin/tiffinfo -D -j -c -r -s -w $HOME/f/fuzzing_LibTIFF/tiff-4.0.4/test/images/palette-1c-1b.tiff
lcov --no-checksum --directory ./ --capture --output-file app2.info
lcov --zerocounters --directory ./ : 이전 counters를 reset 한다.
lcov --capture --initial --directory ./ --output-file app.info : 모든 instrumented line의 zero coverage가 기록된 초기 code coverage data file을 생성한다.
$HOME/f/fuzzing_LibTIFF/install/bin/tiffinfo -D -j -c -r -s -w $HOME/f/fuzzing_LibTIFF/tiff-4.0.4/test/images/palette-1c-1b.tiff : 분석하려는 application을 실행한다. 동시에 다양한 inputs을 실행시킬 수 있다.
lcov --no-checksum --directory ./ --capture --output-file app2.info : 최근 coverage를 app2.info file 에 저장한다.
마지막으로 우리는 HTML output을 만들어낸다.
genhtml --highlight --legend -output-directory ./html-coverage/ ./app2.info
code coverage report는 html-coverage folder에 만들어졌다. ./html-coverage/index.html 을 열고 아래와 같이 볼 수 있다.
fuzzing
이제 libtiff를 ASAN으로 compile 하자.
rm -rf ./install
cd ./tiff-4.0.4
make clean
export LLVM_CONFIG="llvm-config-15" ; CC=afl-clang-lto ./configure --prefix="$HOME/f/fuzzing_libtiff/install/" --disable-shared
AFL_USE_ASAN=1 make -j4
AFL_USE_ASAN=1 make install
여기서 make 명령어의 옵션으로 병렬 빌드를 수행하도록 한다. -j 뒤에 오는 숫자만큼 동시에 작업을 실행한다. 여기서는 동시에 4개의 작업을 수행한다.
fuzzing start
afl-fuzz -m none -i $HOME/f/fuzzing_LibTIFF/tiff-4.0.4/test/images/ -o $HOME/f/fuzzing_LibTIFF/out/ -s 123 -- $HOME/f/fuzzing_LibTIFF/install/bin/tiffinfo -D -j -c -r -s -w @@
0x6070000001b1 is located 0 bytes to the right of 65-byte region \[0x607000000170,0x6070000001b1)
이것을 보건데
[heap left redzone] + [사용 가능 공간 65] + [heap left redzone]
heap chunk 아마도 65크기의 chunk가 있을 것이다.
0x0c0e7fff802e - 0x7fff8000 = 0xc0e0000002e
0xc0e0000002e << 3 = 0x607000000170
TIFF란?
Tagged Image File Format(.tiff)
래스터 그래픽과 이미지 정보를 저장하는 데 사용되는 컴퓨터 파일이다.
이는 세가지 섹션으로 구성된다.
IFH (Image File Header)
IFD (Image File Directory)
Image data (생략 가능 - 이미지 데이터가 없는 파일 존재 가능)
IFD와 Bitmap Data는 이미지의 갯수에 따라 여러개가 될 수 있다.
TIFF의 가장 큰 특징은 이미지에 대한 정보를 태그의 형식으로 제공한다. 이미지 정보를 위해 메이커, 컴퓨터 종류 혹은 생성 날짜 및 시간, 사용 소프트웨어 등을 위한 상당한 많은 태그들을 정의하고 있다.
TIFF는 하나의 파일에 여러 개의 이미지가 함께 저장되는 것이 가능하다. 각 이미지는 하나의 IFD와 하나의 Image data로 구성되며 이를 TIFF 서브파일이라 부른다.
IFH는 파일의 처음 8 byte 라는 고정 위치를 갖는다.
아무튼 여기서는 TIFF는 tag로 이미지의 정보를 가지고 있다고 생각해두자.
6버전 이후 즉, 현재 버전은 tag가 아니라 field로 이름이 바뀌었다.
취약점 분석
static void
tiffinfo(TIFF* tif, uint16 order, long flags, int is_image)
{
TIFFPrintDirectory(tif, stdout, flags); // 시작
if (!readdata || !is_image)
return;
if (rawdata) {
if (order) {
//생략
tiffinfo (tiffinfo.c)
void //2_여기
TIFFPrintDirectory(TIFF* tif, FILE* fd, long flags)
//여기서 fd는 stdout
{
TIFFDirectory *td = &tif->tif_dir;
char *sep;
uint16 i;
long l, n;
// 생략
/*
** Custom tag support.
*/
{
int i;
short count;
count = (short) TIFFGetTagListCount(tif);
for(i = 0; i < count; i++) {
uint32 tag = TIFFGetTagListEntry(tif, i);
const TIFFField *fip;
uint32 value_count;
int mem_alloc = 0;
void *raw_data; //raw data
fip = TIFFFieldWithTag(tif, tag);
if(fip == NULL)
continue;
if(fip->field_passcount) {//else로 분기
if (fip->field_readcount == TIFF_VARIABLE2 ) {
if(TIFFGetField(tif, tag, &value_count, &raw_data) != 1)
continue;
} else if (fip->field_readcount == TIFF_VARIABLE ) {
uint16 small_value_count;
if(TIFFGetField(tif, tag, &small_value_count, &raw_data) != 1)
continue;
value_count = small_value_count;
} else {
assert (fip->field_readcount == TIFF_VARIABLE
|| fip->field_readcount == TIFF_VARIABLE2);
continue;
}
} else {//여기로 분기
if (fip->field_readcount == TIFF_VARIABLE
|| fip->field_readcount == TIFF_VARIABLE2)
value_count = 1; //실행
else if (fip->field_readcount == TIFF_SPP)
value_count = td->td_samplesperpixel;
else
value_count = fip->field_readcount;
if (fip->field_tag == TIFFTAG_DOTRANGE // 분기
&& strcmp(fip->field_name,"DotRange") == 0) {// 실행 x
/* TODO: This is an evil exception and should not have been
handled this way ... likely best if we move it into
the directory structure with an explicit field in
libtiff 4.1 and assign it a FIELD_ value */
static uint16 dotrange[2];
raw_data = dotrange;
TIFFGetField(tif, tag, dotrange+0, dotrange+1);
} else if (fip->field_type == TIFF_ASCII // 분기
|| fip->field_readcount == TIFF_VARIABLE
|| fip->field_readcount == TIFF_VARIABLE2
|| fip->field_readcount == TIFF_SPP
|| value_count > 1) {
if(TIFFGetField(tif, tag, &raw_data) != 1) //실행
continue;
} else {
raw_data = _TIFFmalloc(
_TIFFDataSize(fip->field_type)
* value_count);
mem_alloc = 1;
if(TIFFGetField(tif, tag, raw_data) != 1) {
_TIFFfree(raw_data);
continue;
}
}
}
/*
* Catch the tags which needs to be specially handled
* and pretty print them. If tag not handled in
* _TIFFPrettyPrintField() fall down and print it as
* any other tag.
*/
if (!_TIFFPrettyPrintField(tif, fip, fd, tag, value_count, raw_data))
_TIFFPrintField(fd, fip, value_count, raw_data); //1호출, fd는 stdout
//생략
TIFFPrintDirectory (tif_print.c)
static void //1_여기
_TIFFPrintField(FILE* fd, const TIFFField *fip, // fd는 stdout
uint32 value_count, void *raw_data)
{
uint32 j;
// 생략
else if(fip->field_type == TIFF_ASCII) { // field_type 이 ASCII인경우
fprintf(fd, "%s", (char *) raw_data); // 여기서 터짐
break;
}
// 생략
_TIFFPrintField (tif_print.c)
TIFFPrintDirectory 부분에서 raw_data는 다음과 같다. 여기서 봐야 할 곳은 분명 65만큼 할당을 받았지만 그 65바이트를 모두 채운 바람에 다음 chunk의 prev_size 부분을 덮었다.(그런데 값을 봐서는 prev_size는 아님)
그럼 결국 _TIFFPrintField의
fprintf(fd, "%s", (char *) raw_data*);
에서 터지게 된다.
이 CVE는 out-of-bound read로 허용되지 않는 영역에 읽기를 한 것이다. chunk의 크기는 65여서 overwrite는 하지 않고 딱 65만큼 채웠다. 하지만 마지막을 공백으로 하지 않아서 다음 chunk의 prev_size 부분에 있는 값과 연결이 되어버린다. 이 상태에서 fprintf를 할 경우 red zone에서 읽기 시도를 하기 때문에 오류가 발생하고 아래와 같이 이상한 문자가 같이 출력된다.
정상적인 test case를 가져와 crash를 비교해보았다. 정상적인 test case는
GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/
와 같은 문자열을 가지고 있다. 이는 64바이트로 문자열 마지막 공백포함 65바이트 딱 맞는 크기이다. 하지만 그림을 보면 알겠지만 crash에는 unreleased와 Q16에 어떤 값이 추가되었다. 그래서 해당 문자열이 길어지고 길어진 만큼 뒷 부분이 잘려서 overwrite는 발생하지 않았지만 문자열 끝 공백이 없어져서 out of bound read 가 발생했다.
패치
4.0.7 버전을 설치해서 crash를 넣어보았다.
Tag에서 정상적인 길이로 출력된다.
TIFFFetchNormalTag 함수에서 추가적인 패치가 이루어졌다.
기능을 요약하자면 마지막 값이 null이 아니면 ASCII 값을 가지는 tag가 끝 byte가 null이 아니라는 경고문과 함께 마지막 byte를 null로 바꿔버린다.
<틀린 부분이 있다면 비난과 욕설을 해주세요>
'Fuzzing > CVE 분석' 카테고리의 다른 글
Fuzzing101 Exercise5 (1) | 2024.06.14 |
---|---|
Fuzzing101 Exercise3 (1) | 2024.04.19 |
Fuzzing101 Exercise2_2 (0) | 2024.03.13 |
Fuzzing101 Exercise2_1 (1) | 2024.02.26 |
Fuzzing101 Exercise1 (1) | 2024.01.16 |