1. 환경설정 및 설치
대상(LibTIFF) 다운로드
# 디렉토리 생성
cd $HOME
mkdir fuzzing_tiff && cd fuzzing_tiff/
# 다운로드 및 압축해제
wget https://download.osgeo.org/libtiff/tiff-4.0.6.tar.gz
tar -xzvf tiff-4.0.6.tar.gz
여러가지 빌드 방법 (default, code coverage, ASAN)
# 빌드 수정 시 사용
rm -r $HOME/fuzzing_tiff/install
cd $HOME/fuzzing_tiff/tiff-4.0.6/
make clean
# LibTIFF 빌드 (default)
cd tiff-4.0.6/
./configure --prefix="$HOME/fuzzing_tiff/install/" --disable-shared
make
make install
software matric 중 하나인 code coverage설정
# lcov 설치
sudo apt install lcov
# libtiff 빌드 (--coverage flag 사용)
CFLAGS="--coverage" LDFLAGS="--coverage" ./configure --prefix="$HOME/fuzzing_tiff/install/" --disable-shared
make
make install
# libtiff 빌드 (ASAN 사용)
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
--prefix: 빌드 된 프로그램, 라이브러리가 설치될 경로를 설정한다.
--disable-shared: 공유 라이브러리를 사용하지 않는다. (정적 라이브러리만 생성)
CFLAGS: 컴파일러에게 code coverage를 수집하도록 flag를 전달한다
LDFLAGS: 링커에게 code coverage를 수집하도록 flag를 전달한다.
--coverage: gcc, clang이 code coverage를 수집하도록 지시한다.
CC=afl-clang-lto: 컴파일러를 afl-clang-lto로 설정한다.
AFL_USE_ASAN=1: AFL에서 ASAN을 사용하도록 지시한다.
-j4: 4개의 작업을 병렬로 빌드하도록 make에 지시한다.
+) Code coverage 데이터 수집 예시
1 cd $HOME/fuzzing_tiff/tiff-4.0.6/
2 lcov --zerocounters --directory ./
3 lcov --capture --initial --directory ./ --output-file app.info
4 $HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_tiff/tiff-5: 4.0.6/test/images/palette-1c-1b.tiff
5 lcov --no-checksum --directory ./ --capture --output-file app2.info
코드 설명:
- 2: 현재 디렉토리 coverage counter 초기화
- 3: 초기 coverage 정보를 app.info에 저장
- 4: 분석하려는 프로그램 이미지 실행
D: 디렉토리 정보
j: JPEG 태그 정보 - JPEG 이미지 설정, 품질, 색상 등
c: 컬러맵(이미지 색상정보를 저장하는 방식 중 하나) 정보
r: 래스터 데이터(이미지를 구성하는 데이터) 정보
s: 파일 및 스트립(strip) 정보
w: 파일 경로
- 5: 현재 coverage 상태를 app2.info에 저장
2. Fuzzing 단계

afl-fuzz -m none -i $HOME/fuzzing_tiff/tiff-4.0.6/test/images/ -o $HOME/fuzzing_tiff/out/ -s 123 -- $HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w @@
3. 결과 분석

main :
// tiffinfo.c - main()
tif = TIFFOpen(argv[optind], chopstrips ? "rC" : "rc");
if (tif != NULL) {
if (dirnum != -1) {
if (TIFFSetDirectory(tif, (tdir_t) dirnum))
tiffinfo(tif, order, flags, 1);
} else if (diroff != 0) {
if (TIFFSetSubDirectory(tif, diroff))
tiffinfo(tif, order, flags, 1);
} else {
do {
toff_t offset=0;
tiffinfo(tif, order, flags, 1);
if (TIFFGetField(tif, TIFFTAG_EXIFIFD,
&offset)) {
if (TIFFReadEXIFDirectory(tif, offset)) {
tiffinfo(tif, order, flags, 0);
}
}
}
- main()에서 인자로 TIFF 태그와 이 태그를 가져오는 IFD offset을 가지는데, 이 TIFFGetField() 함수는 TIFF 특정 태그의 ID를 가져온다.
- TIFFTAG_EXIFIFD() 함수는 EXIF 정보가 있는 디렉토리를 가리키고, 해당 TIFF 태그의 파일이 포함하는 데이터들의 정보를 저장한다.
TIFFPrintDirectory :
// tiffinfo.c - TIFFPrintDirectory()
static void
tiffinfo(TIFF* tif, uint16 order, long flags, int is_image)
{
TIFFPrintDirectory(tif, stdout, flags);
if (!readdata || !is_image)
return;
TIFFPrintField :
// tif_print.c - _TIFFPrintField()
if (!_TIFFPrettyPrintField(tif, fip, fd, tag, value_count, raw_data))
_TIFFPrintField(fd, fip, value_count, raw_data);
if(mem_alloc)
_TIFFfree(raw_data);
- TIFFPrintDirectory() 함수는 이름에서 유추할 수 있듯이 가져온 offset에 해당하는 디렉토리를 읽고 추가적인 정보를 출력한다.
- TIFFPrintField() 함수는 TIFFField 구조체에 대한 포인터와 값의 수, 데이터 포인터를 인자로 가지는데, fip를 통해 필드의 형식을 파악하고, raw_data를 해당 형식에 따라서 fd에 출력한다.
// tif_print.c
else if(fip->field_type == TIFF_ASCII) {
fprintf(fd, "%s", (char *) raw_data);
break;
}
// tiff.h
typedef enum {
TIFF_NOTYPE = 0, /* placeholder */
TIFF_BYTE = 1, /* 8-bit unsigned integer */
TIFF_ASCII = 2, /* 8-bit bytes w/ last byte null */
TIFF_SHORT = 3, /* 16-bit unsigned integer */
TIFF_LONG = 4, /* 32-bit unsigned integer */
- 여기 TIFFPrintField() 함수에서 문제가 발생하는데, 해당 코드는 NULL byte가 나올 때까지 문자열을 출력한다.
- 마지막 byte가 NULL일 때까지 출력하기 때문에, 만약 데이터의 마지막 byte가 NULL이 아니게 되면, 의도하지 않은 부분을 출력할 수 있어 문제가 발생한다.
4. 취약점 패치

- TIFF 파일을 읽을 때 ASCII이고 ASCII 타입의 필드 값을 다룰 때, 만약 마지막 byte가 NULL(‘\0’)로 끝나지 않으면 경고 메시지와 함께 강제로 마지막 byte에 NULL(‘\0’)을 삽입하도록 패치되었다.