CVE-2006-4168(libexif) 분석
개요
- CVE-2006-4168 : 0.6.14,
exif-data.c
의exif_data_load_data_entry
함수에서 발생하는Integer overflow
취약점
취약점 분석
먼저 IFD
구조는 다음과 같다.
https://www.media.mit.edu/pia/Research/deepview/exif.html
EEEE
(2bytes) : 해당 IFD 내의 엔트리 개수TTTT
(2bytes) : 태그번호ffff
(2bytes) : 데이터 형식NNNNNNNN
(4bytes) :Data
가 가지고 있는components
의 개수DDDDDDDD
(4bytes) : 데이터 또는 데이터에 대한 오프셋 데이터가 4bytes 이하라면 해당 위치에 저장하지만, 4bytes가 넘는다면IFD
의Data
영역에 저장한 후 오프셋 값을 해당 위치에 저장한다.
콜스택을 확인해보면, exif_entry_fix
에서 전달하는 buf
를 찾을 수 없다고 나온다.
콜스택을 따라 함수를 분석하였다.
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_LONG
을 EXIF_FORMAT_SHORT
로 변환하는 작업을 수행한다.
exif_entry_fix
에서 호출하는 exif_get_srational
에 전달되는 인자가 올바르지 않아 크래시가 발생하였기 때문에, 해당 인자 값을 확인해보았다.
먼저 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
이 되며, 변수 s
는 unsigned int
형으로 선언되어 있으므로 최상위 바이트 0x04
는 overflow되어 s
의 값은 0x8
이된다.
이렇게 연산된 s
로 entry
의 data
를 할당하며, 이후 로직에서 components
값을 data
의 오프셋으로 연산하는 과정에서 정의되지 않은 영역에 도달하게 된다.
components
의 값만큼 반복하는 루틴은 올바르지 않은 태그를 변경하는 루틴이기 때문에, 의도적으로 태그를 올바르지 않도록 변경해주어야 해당 루틴을 수행할 수 있다.
위에서 components
의 값을 2147483649
로 설정한 이유는 2147483648
으로 설정할 경우 s
의 값이 0
이 되어 리턴되며, 2147483647
의 경우에는 s
의 값이 4294967288이
되어 정상적인 루틴을 이어갈 수 있다.
굳이 2147483649
이 아니더라도 components
값에 비해 size
값을 작게 만든다면 취약점을 트리거할 수 있다.
1
2
>>> hex(0x20000001 * 0x8)
'0x100000008'
취약점 패치
해당 취약점은 s
가 components
값 보다 작으면 해당 엔트리를 불러오지 않는 식으로 패치되었다.
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;