Post

CVE-2016-9297(libtiff) 분석

개요


TIFF (Tagged Image File Format) : 비트맵 이미지, 이미지 정보를 저장하는 데 사용하는 이미지 포맷

  • 무손실 압축 파일 포맷이므로 원본 이미지의 디테일 등이 그대로 유지되므로 고품질 사진에 적합
  • 파일 크기가 큼
  • EXIF 포맷은 TIFF 포맷을 기반으로 제작되었음

CVE-2016-9297 : libtiff 4.0.6, TIFFFetchNormalTag 함수에서 발생하는 out-of-bounds read 취약점


TIFF 포맷 구조는 다음과 같다.

0

TIFF 포맷 구조

EXIF 포맷과 비슷하게 생겼으며, IFD의 구조는 동일하다.

1

TIFF entry 구조

크래시 분석


AFL에서 크래시 발견 후 ASAN 계측만 남겨두도록 컴파일 하였다.

1
2
3
CFLAGS="-fsanitize=address -g -O0" ./configure --prefix=/root/install/ --disable-shared
make
make install

2

콜스택을 따라 분석을 진행하였다.

_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 바이트를 제외시킨 파일을 전달할 경우 허가되지 않은 영역의 데이터를 출력시킬 수 있다.

3

4

따라서 해당 취약점은 ASCII 자료형이고, NULL 바이트가 포함되어있지 않을 때 발생하는 out-of-bounds read취약점이라는 것을 알 수 있다.


다음과 같이 NULL 바이트를 삽입시켜주면 크래시가 발생하지 않는 것을 확인할 수 있다.

5

NULL 바이트 삽입 전


6

7

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;
}
.
.
This post is licensed under CC BY 4.0 by the author.