Post

CVE-2006-4168(libexif) 분석

개요


  • CVE-2006-4168 : 0.6.14, exif-data.cexif_data_load_data_entry 함수에서 발생하는 Integer overflow취약점

취약점 분석


먼저 IFD 구조는 다음과 같다.

0https://www.media.mit.edu/pia/Research/deepview/exif.html

  • EEEE (2bytes) : 해당 IFD 내의 엔트리 개수
  • TTTT (2bytes) : 태그번호
  • ffff (2bytes) : 데이터 형식
  • NNNNNNNN (4bytes) : Data가 가지고 있는 components의 개수
  • DDDDDDDD (4bytes) : 데이터 또는 데이터에 대한 오프셋 데이터가 4bytes 이하라면 해당 위치에 저장하지만, 4bytes가 넘는다면 IFDData 영역에 저장한 후 오프셋 값을 해당 위치에 저장한다.


콜스택을 확인해보면, exif_entry_fix에서 전달하는 buf를 찾을 수 없다고 나온다.

1

콜스택을 따라 함수를 분석하였다.

exif_data_fix 함수는 EXIF_DATA_OPTION_FOLLOW_SPECIFICATION가 설정된 경우 호출되어 기존 엔트리를 수정하고, 허용되지 않는 엔트리는 제거하고, 필요한 엔트리는 추가하는 과정을 거친다.

1
2
if (data->priv->options & EXIF_DATA_OPTION_FOLLOW_SPECIFICATION)
		exif_data_fix (data);

해당 옵션은 exif_data_new_mem 함수에서 기본으로 설정된다.

1
	exif_data_set_option (data, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION);

따라서 커맨드라인에서 특별한 옵션이 주어진게 아니라면 기본적으로 exif_entry_fix함수를 호출한다.

1
2
3
4
5
void
exif_data_fix (ExifData *d)
{
	exif_data_foreach_content (d, fix_func, NULL);
}
1
2
3
4
5
6
7
8
9
10
11
12
void
exif_data_foreach_content (ExifData *data, ExifDataForeachContentFunc func,
			   void *user_data)
{
	unsigned int i;

	if (!data || !func)
		return;

	for (i = 0; i < EXIF_IFD_COUNT; i++)
		func (data->ifd[i], user_data);
}

모든 IFD에 대해 반복문을 통해 fix_func 함수를 호출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void
fix_func (ExifContent *c, void *data)
{
	switch (exif_content_get_ifd (c)) {
	case EXIF_IFD_1:
		if (c->parent->data)
			exif_content_fix (c);
		else {
			exif_log (c->parent->priv->log, EXIF_LOG_CODE_DEBUG, "exif-data",
				  "No thumbnail but entries on thumbnail. These entries have been "
				  "removed.");
			while (c->count)
				exif_content_remove_entry (c, c->entries[c->count - 1]);
		}
		break;
	default:
		exif_content_fix (c);
	}
}

fix_func 함수내에선 exif_content_fix 함수를 호출한다.

exif_content_fix 함수가 엔트리의 수정을 담당한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void
exif_content_fix (ExifContent *c)
{
	ExifIfd ifd = exif_content_get_ifd (c);
	ExifDataType dt;
	ExifTag t;
	ExifEntry *e;

	if (!c) return;

	dt = exif_data_get_data_type (c->parent);

	/* First of all, fix all existing entries. */
	exif_content_foreach_entry (c, fix_func, NULL);
.
.

exif_content_foreach_entry 함수를 호출하여 해당 IFD에 담긴 엔트리를 수정한다.

여기서의 fix_func 함수와 exif_data_fix에서 호출되는 fix_func 함수는 다른 함수이다.


1
2
3
4
5
6
7
8
9
10
11
12
void
exif_content_foreach_entry (ExifContent *content,
			    ExifContentForeachEntryFunc func, void *data)
{
	unsigned int i;

	if (!content || !func)
		return;

	for (i = 0; i < content->count; i++)
		func (content->entries[i], data);
}
1
2
3
4
5
static void
fix_func (ExifEntry *e, void *data)
{
	exif_entry_fix (e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void
exif_entry_fix (ExifEntry *e)
{
	unsigned int i;
	ExifByteOrder o;
	ExifRational r;
	ExifSRational sr;

	if (!e || !e->priv) return;

	switch (e->tag) {
	
	/* These tags all need to be of format SHORT. */
	case EXIF_TAG_YCBCR_SUB_SAMPLING:
	case EXIF_TAG_SUBJECT_AREA:
	case EXIF_TAG_COLOR_SPACE:
	case EXIF_TAG_PLANAR_CONFIGURATION:
	case EXIF_TAG_SENSING_METHOD:
.
.

exif_entry_fix 함수는 태그가 잘못된 데이터 유형을 가지고 있거나 잘못된 포맷일 경우 유효하도록 변경한다.

예를 들어 EXIF_FORMAT_LONGEXIF_FORMAT_SHORT로 변환하는 작업을 수행한다.


exif_entry_fix에서 호출하는 exif_get_srational에 전달되는 인자가 올바르지 않아 크래시가 발생하였기 때문에, 해당 인자 값을 확인해보았다.

2

3


먼저 size의 값은 components * format의 실제사이즈로 계산된다.

그러나 components 값에 비해 size의 값은 터무니 없이 작다는 것을 알 수 있다.

이렇게 된다면 exif_entry_fix에서 components 값 만큼 반복을 수행하는 루틴에서 정의되지 않은 영역에 도달할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
switch (e->format) {
		case EXIF_FORMAT_LONG:
			if (!e->parent || !e->parent->parent) break;
			o = exif_data_get_byte_order (e->parent->parent);
			for (i = 0; i < e->components; i++)
				exif_set_short (
					e->data + i *
					exif_format_get_size (
					EXIF_FORMAT_SHORT), o,
					(ExifShort) exif_get_long (
					e->data + i *
					exif_format_get_size (
					EXIF_FORMAT_LONG), o));
			e->format = EXIF_FORMAT_SHORT;
			e->size = e->components *
				exif_format_get_size (e->format);
			e->data = exif_entry_realloc (e, e->data, e->size);
			exif_entry_log (e, EXIF_LOG_CODE_DEBUG,
				_("Tag '%s' was of format '%s' (which is "
				"against specification) and has been "
				"changed to format '%s'."),
				exif_tag_get_name (e->tag), 
				exif_format_get_name (EXIF_FORMAT_LONG),
				exif_format_get_name (EXIF_FORMAT_SHORT));
			break;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
switch (e->format) {
		case EXIF_FORMAT_SRATIONAL:
			if (!e->parent || !e->parent->parent) break;
			o = exif_data_get_byte_order (e->parent->parent);
			for (i = 0; i < e->components; i++) {
				sr = exif_get_srational (e->data + i * 
					exif_format_get_size (
						EXIF_FORMAT_SRATIONAL), o);
				r.numerator = (ExifLong) sr.numerator;
				r.denominator = (ExifLong) sr.denominator;
				exif_set_rational (e->data + i *
					exif_format_get_size (
						EXIF_FORMAT_RATIONAL), o, r);
			}
			e->format = EXIF_FORMAT_RATIONAL;
			exif_entry_log (e, EXIF_LOG_CODE_DEBUG,
				_("Tag '%s' was of format '%s' (which is "
				"against specification) and has been "
				"changed to format '%s'."),
				exif_tag_get_name (e->tag),
				exif_format_get_name (EXIF_FORMAT_SRATIONAL),
				exif_format_get_name (EXIF_FORMAT_RATIONAL));
			break;


data 할당과 size 정의를 담당하는 exif_data_load_data_entry 함수를 분석하였다.

1
2
3
4
5
6
7
8
9
10
11
/*
	 * Size? If bigger than 4 bytes, the actual data is not
	 * in the entry but somewhere else (offset).
	 */
	s = exif_format_get_size (entry->format) * entry->components;
	if (!s)
		return 0;
	if (s > 4)
		doff = exif_get_long (d + offset + 8, data->priv->order);
	else
		doff = offset + 8;

format은 열거형으로 정의되어 있으므로 exif_format_get_size 함수를 통해 실제 사이즈를 가져온 후 components 값과 곱한다.


예를 들어 components의 값을 INT_MAX 보다 2만큼 큰 값인 2147483649로 설정하고 format을 조건에 부합하는 값 중에서 가장 큰 값인 SRational(부호 있는 정수부 4byte, 부호 있는 소수부 4byte)로 설정하였다.

SRational은 총 8byte를 가지므로 s의 연산은 8 * 2147483649이 된다.

위의 연산 결과는 0x400000008이 되며, 변수 sunsigned int형으로 선언되어 있으므로 최상위 바이트 0x04는 overflow되어 s의 값은 0x8이된다.

4

이렇게 연산된 sentrydata를 할당하며, 이후 로직에서 components 값을 data의 오프셋으로 연산하는 과정에서 정의되지 않은 영역에 도달하게 된다.

components의 값만큼 반복하는 루틴은 올바르지 않은 태그를 변경하는 루틴이기 때문에, 의도적으로 태그를 올바르지 않도록 변경해주어야 해당 루틴을 수행할 수 있다.

위에서 components의 값을 2147483649로 설정한 이유는 2147483648으로 설정할 경우 s의 값이 0이 되어 리턴되며, 2147483647의 경우에는 s의 값이 4294967288이 되어 정상적인 루틴을 이어갈 수 있다.

굳이 2147483649이 아니더라도 components값에 비해 size 값을 작게 만든다면 취약점을 트리거할 수 있다.

1
2
>>> hex(0x20000001 * 0x8)
'0x100000008'

5

6

취약점 패치


해당 취약점은 scomponents 값 보다 작으면 해당 엔트리를 불러오지 않는 식으로 패치되었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* {0,1,2,4,8} x { 0x00000000 .. 0xffffffff } 
	 *   -> { 0x000000000 .. 0x7fffffff8 } */
	s = exif_format_get_size(entry->format) * entry->components;
	if (s < entry->components) {
		return 0;
	}
	if (0 == s)
		return 0;
	/*
	 * Size? If bigger than 4 bytes, the actual data is not
	 * in the entry but somewhere else (offset).
	 */
	if (s > 4)
		doff = exif_get_long (d + offset + 8, data->priv->order);
	else
		doff = offset + 8;
This post is licensed under CC BY 4.0 by the author.