CVE-2016-9297(libtiff) 분석
개요
TIFF (Tagged Image File Format) : 비트맵 이미지, 이미지 정보를 저장하는 데 사용하는 이미지 포맷
- 무손실 압축 파일 포맷이므로 원본 이미지의 디테일 등이 그대로 유지되므로 고품질 사진에 적합
- 파일 크기가 큼
- EXIF 포맷은 TIFF 포맷을 기반으로 제작되었음
CVE-2016-9297 : libtiff 4.0.6, TIFFFetchNormalTag
함수에서 발생하는 out-of-bounds read
취약점
TIFF 포맷 구조는 다음과 같다.
TIFF 포맷 구조
EXIF 포맷과 비슷하게 생겼으며, IFD의 구조는 동일하다.
TIFF entry 구조
크래시 분석
AFL
에서 크래시 발견 후 ASAN
계측만 남겨두도록 컴파일 하였다.
1
2
3
CFLAGS="-fsanitize=address -g -O0" ./configure --prefix=/root/install/ --disable-shared
make
make install
콜스택을 따라 분석을 진행하였다.
_TIFFPrintField
에서 크래시가 발생한 것으로 보인다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
}
} while (TIFFReadDirectory(tif));
}
TIFFClose(tif);
}
프로그램 실행 후 인자로 IFD의 오프셋, 번호를 입력하였을 때 디렉토리를 지정하는 작업을 수행한다.
크래시가 터진 함수는 tiffinfo
이므로 옵션이 주어지더라도 상관 없다.
1
2
3
4
5
6
7
static void
tiffinfo(TIFF* tif, uint16 order, long flags, int is_image)
{
TIFFPrintDirectory(tif, stdout, flags);
.
.
.
tiffinfo
함수는 TIFFPrintDirectory
함수를 호출한다.
해당 함수는 출력을 위한 함수로 디렉토리내의 엔트리를 가져온 후 출력한다.
1
2
3
4
5
6
7
8
9
10
11
12
void
TIFFPrintDirectory(TIFF* tif, FILE* fd, long flags)
{
TIFFDirectory *td = &tif->tif_dir;
char *sep;
uint16 i;
long l, n;.
.
if (!_TIFFPrettyPrintField(tif, fip, fd, tag, value_count, raw_data))
_TIFFPrintField(fd, fip, value_count, raw_data);
.
.
TIFFPrintDirectory
함수는 _TIFFPrettyPrintField
에서 0이 반환되었을 때 즉, 커스텀 태그일 경우 자료형에 맞게 데이터를 가져와 출력해주는 _TIFFPrintDirectory
함수를 호출한다.
_TIFFPrintDirectory
함수는 다음과 같은 작업을 수행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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]);
.
.
크래시 파일을 보면 해당 필드의 데이터 타입이 TIFF_ASCII
형이다.
해당 자료형을 출력해주는 소스코드는 다음과 같다.
1
2
3
4
else if(fip->field_type == TIFF_ASCII) {
fprintf(fd, "%s", (char *) raw_data);
break;
}
printf
함수는 NULL 바이트까지 나올 때가지 출력한다.
하지만 다음과 같이 NULL 바이트를 제외시킨 파일을 전달할 경우 허가되지 않은 영역의 데이터를 출력시킬 수 있다.
따라서 해당 취약점은 ASCII 자료형이고, NULL 바이트가 포함되어있지 않을 때 발생하는 out-of-bounds read
취약점이라는 것을 알 수 있다.
다음과 같이 NULL 바이트를 삽입시켜주면 크래시가 발생하지 않는 것을 확인할 수 있다.
NULL 바이트 삽입 전
NULL 바이트 삽입 후
취약점 패치
해당 취약점은 TIFF 파일을 읽는 과정에서 ASCII 자료형을 가진 entry의 마지막 바이트가 NULL이 아닐 경우 강제로 NULL 바이트를 삽입하도록 패치되었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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( 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’;
}
.
.