반응형
Recent Posts
Recent Comments
Link
«   2024/07   »
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 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

ZZoMb1E_PWN

[Fuzzing 101] libexif (CVE-2009-3895) 본문

STUDY/CVE && Fuzzing

[Fuzzing 101] libexif (CVE-2009-3895)

ZZoMb1E 2024. 1. 16. 21:16
728x90
반응형

 자료들을 참고하여 분석을 진행하였기에 잘못된 부분이 있을지도 모릅니다. 

※ 보완 혹은 수정해야 되는 부분이 있다면 알려주시면 확인 후 조치하도록 하겠습니다.
 

이전 게시글과 Target이 같기 때문에 분석을 제외한 부분은 유사/동일 합니다.

1. 분석대상


libexif란?

  사진 파일의 메타데이터인 EXIF 형식을 볼 수 있게 해주는 라이브러이다.

EXIF 형식에는 카메라의 설정 정보, gps 정보 등이 포함되어 있다.

 

2. CVE Code


CVE-2012-2836

libexif/exif-entry.c의 exif_entry_fix()에서 발생하는 Heap Buffer Overflow 취약점으로 공격자가 유효하지 않은 EXIF 이미지를 통해 서비스 거부를 발생시키거나 임의의 코드를 실행시킬 수 있다.

CWE-122

Heap-based Buffer Overflow
힙 영역에서 발생하는 Buffer Overflow이다.
 

3. CVE 관련 정보



 
https://nvd.nist.gov/vuln/detail/CVE-2009-3895

 

NVD - CVE-2009-3895

CVE-2009-3895 Detail Modified This vulnerability has been modified since it was last analyzed by the NVD. It is awaiting reanalysis which may result in further changes to the information provided. Description Heap-based buffer overflow in the exif_entry_fi

nvd.nist.gov

4. 분석  환경 및 구현


분석 환경 : Ubuntu 20.04
 
 

실습을 위한 설치 과정


 
libexif 파일 실습할 디렉터리 경로 생성

cd $HOME
mkdir fuzzing_libexif && cd fuzzing_libexif/

  
취약한 libexif 버젼 설치 과정

wget https://github.com/libexif/libexif/archive/refs/tags/libexif-0_6_14-release.tar.gz
tar -xzvf libexif-0_6_14-release.tar.gz

 
일반적인 build

cd libexif-libexif-0_6_14-release/
sudo apt-get install autopoint libtool gettext libpopt-dev
autoreconf -fvi
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/"
make
make install

 


 

TARGET 프로그램 설치

cd $HOME/fuzzing_libexif
wget https://github.com/libexif/exif/archive/refs/tags/exif-0_6_15-release.tar.gz
tar -xzvf exif-0_6_15-release.tar.gz


 build

cd exif-exif-0_6_15-release/
autoreconf -fvi
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
make install

실습에 사용할 예제 파일 설치

cd $HOME/fuzzing_libexif
wget https://github.com/ianare/exif-samples/archive/refs/heads/master.zip
unzip master.zip

 
 
예제 파일 확인 및 exif 실행

$HOME/fuzzing_libexif/install/bin/exif $HOME/fuzzing_libexif/exif-samples-master/jpg/Canon_40D_photoshop_import.jpg

 


  
libexif/exif를 AFL++ 이용하여 빌드 - 계측코드 삽입
 
recompile

sudo rm -rf $HOME/fuzzing_libexif/install
cd $HOME/fuzzing_libexif/libexif-libexif-0_6_14-release/
make clean
export LLVM_CONFIG="llvm-config-11"
export CC=afl-clang-fast
export CXX=afl-clang-fast++
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" 
make
make install


cd $HOME/fuzzing_libexif/exif-exif-0_6_15-release
make clean
export LLVM_CONFIG="llvm-config-11"
export CC=afl-clang-fast
export CXX=afl-clang-fast++
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig 
make
make install

 
※ 제 환경에서 AFL에 문제가 있는지 afl-clang-lto에서 오류가 발생하여 afl-clang-fast로 진행했습니다. 
실습하실 때 afl-clang-lto로 하시면 됩니다.

※ 지금은 해당 문제를 해결했습니다.


fuzzer 수행
 

afl-fuzz -i $HOME/fuzzing_libexif/exif-samples-master/jpg/ -o $HOME/fuzzing_libexif/out/ -s 123 -- $HOME/fuzzing_libexif/install/bin/exif @@

-i : 입력에 사용할 예제의 디렉터리 경로 설정 옵션
-o : AFL++의 결과를 저장할 디렉터리 경로 설정 옵션
-s : 무작위 정적 시드 사용
@@ : 입력 파일 이름으로 대체할 대상 표시
 


 
 

5. 루크 커즈 분석


out/default/crashes/을 살펴보면 crash파일이 들어있다.
 
해당 crash 파일은 인자로 gdb를 다음과 같이 실행한다.

gdb -q --args $HOME/fuzzing_libexif/install/bin/exif id:000014,sig:11,src:000564,time:7806921,execs:430230,op:havoc,rep:12

 

 

분석



bt로 살펴보면 main()  exif_loader_get_data()  exif_data_load_data()  exif_data_fix()  exif_data_foreach_content() → fix_func() → exif_content_fix() → exif_content_foreach_entry() → fix_func() exif_entry_fix() → exif_get_long() → exif_get_slong()  순으로 호출되는 것을 확인할 수 있다.

 

 

코드 분석


main()에서 EXIF 데이터를 불려오기 위해 libexif/exif-loader.c에 있는 exif_loader_get_data()를 호출한다.


exif-loader.c:exif_loader_get_data()

ExifData *
exif_loader_get_data (ExifLoader *loader)
{
	ExifData *ed;

	if (!loader) 
		return NULL;

	ed = exif_data_new_mem (loader->mem);
	exif_data_log (ed, loader->log);
	exif_data_load_data (ed, loader->buf, loader->bytes_read);

	return ed;
}

불려온 데이터를 가지고 exif_data_load_data()를 호출한다.


 exif-data.c:exif_data_load_data()

if (data->priv->md) {
		exif_mnote_data_log (data->priv->md, data->priv->log);
		exif_mnote_data_set_byte_order (data->priv->md,
						data->priv->order);
		exif_mnote_data_set_offset (data->priv->md,
					    data->priv->offset_mnote);
		exif_mnote_data_load (data->priv->md, d, ds);
	}

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

EXIF_DATA_OPTION_FOLLOW_SPECIFICATION 옵션이 설정되어 있으면 exif_data_fix() 호출하여 데이터 수정한다.

→ EXIF 사양에 따라 데이터를 수정하는 역할을 수행한다.

 

※ EXIF_DATA_OPTION_FOLLOW_SPECIFICATION : EXIP 데이터가 특정 사양을 따르도록 강제하는 역할을 수행한다.

고정된 사양을 따르는 이미지를 처리하거나 일괄된 결과를 얻기 위해서 사용한다.

 

 

 

exif-data.c:exif_data_fix()

void
exif_data_fix (ExifData *d)
{
	exif_data_foreach_content (d, fix_func, NULL);
}

EXIF 데이터에 대해 수정 작업을 수행한다.

 

exif-data.c:exif_data_foreach_content()

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);
}

 

ExifData 구조체에 저장된 모든 EXIF 데이터에 대해 지정된 콜백 함수를 수행하는 역할을 한다.

이때 콜백 함수는 exif_data_fix()의 2번째 인자로 받은 값으로 EXIF_IFD_COUNT 만큼 반복을 수행한 후 콜백 함수를 호출한다.

 

exif-data.c:fix_func()

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);
	}
}

 

EXIF 데이터의 IFD에 따라 수정 작업을 수행한다.

 

 

exif-content.c:exif_content_fix()

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);

	/*
	 * Then check for existing tags that are not allowed and for
	 * non-existing mandatory tags.
	 */
	for (t = 0; t <= 0xffff; t++) {
		switch (exif_tag_get_support_level_in_ifd (t, ifd, dt)) {
		case EXIF_SUPPORT_LEVEL_MANDATORY:
			if (exif_content_get_entry (c, t)) break;
			exif_log (c->priv->log, EXIF_LOG_CODE_DEBUG, "exif-content",
					"Tag '%s' is mandatory in IFD '%s' and has therefore been added.",
					exif_tag_get_name_in_ifd (t, ifd), exif_ifd_get_name (ifd));
			e = exif_entry_new ();
			exif_content_add_entry (c, e);
			exif_entry_initialize (e, t);
			exif_entry_unref (e);
			break;
		case EXIF_SUPPORT_LEVEL_NOT_RECORDED:
			e = exif_content_get_entry (c, t);
			if (!e) break;
			exif_log (c->priv->log, EXIF_LOG_CODE_DEBUG, "exif-content",
					"Tag '%s' is not recoreded in IFD '%s' and has therefore been "
					"removed.", exif_tag_get_name_in_ifd (t, ifd),
					exif_ifd_get_name (ifd));
			exif_content_remove_entry (c, e);
			break;
		case EXIF_SUPPORT_LEVEL_OPTIONAL:
		default:
			break;
		}
	}
}

필수로 있어야 하는 TAG 검사 및 허용되지 않은 것들은 제거하는 함수이다.

 

 

exif-content.c:fix_func()

static void
fix_func (ExifEntry *e, void *data)
{
	exif_entry_fix (e);
}

ExifEntry를 수정하는 함수이다.

 

 

exif-entry.c:exif_entry_fix()

case EXIF_TAG_SHARPNESS:
		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;
		case EXIF_FORMAT_SHORT:
		default:
			break;
		}
		break;

EXIF의 TAG가 EXIF_TAG_SHARPNESS인 경우 처리하는 함수이다.

EXIF_FORMAT_LONG인 경우 exif_set_short()에서 exif_get_long()을 호출하여 long 형식의 데이터를 가져오고 이후 exif_set_short()에 의해서 short 형식으로 변환한다.

 

 

exif-utils.c:exif_get_long()

ExifLong
exif_get_long (const unsigned char *buf, ExifByteOrder order)
{
        return (exif_get_slong (buf, order) & 0xffffffff);
}

주어진 배열에서 long 타입의 값을 읽고 반환하는 함수이다.

반환하는 값의 연산을 살펴보면 exif_get_slong()를 이용하여 Slong 타입의 값을 가져와서 연산 수행한 결과를 반환한다.

→ exif_get_slog()가 반환하는 값이 부호 있는 32비트 정수로 해석될 때 발생할 수 있는 부호 확장 방지 및 부호없는 32비트로 올바르게 해석되도록 설정한다. → 주어진 배열에서 Long 타입의 값을 올바르게 읽어오는 역할

 

 

exif-utils.c:exif_get_slong()

ExifSLong
exif_get_slong (const unsigned char *b, ExifByteOrder order)
{
	if (!b) return 0;
        switch (order) {
        case EXIF_BYTE_ORDER_MOTOROLA:
                return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
        case EXIF_BYTE_ORDER_INTEL:
                return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
        }

	/* Won't be reached */
	return (0);
}

빅 엔디언/리틀 엔디언으로 읽을 지 확인 후 읽어온다.

 


동적 디버깅을 통하여 분석한 부분들을 살펴보겠다.

 

해당 crash 파일을 가지고 살펴보면 마지막 청크의 size가 변조되어 TOP chunk까지 포함시켜버린 것을 살펴볼 수 있다.

 

backtrace로 확인했을 때 문제가 되는 주소는 0x477000이라고 한다.

해당 주소는 Heap 영역의 마지막 주소이다.

TOP chunk의 크기를 침범한 이후로 heap 영역으로 할당된 영역이 아닌 부분에 값을 입력할려고 해서 발생하는 취약점이다.

 

(ExifShort) exif_get_long (
        e->data + i *
        exif_format_get_size (
        EXIF_FORMAT_LONG), o)

EXIF_FORMAT_LONG 형식의 데이터를 가져오고,  데이터를 가져올 위치를 지정한다.

i *exif_format_get_size (EXIF_FORMAT_LONG) 에 해당하는 위치의 데이터를 가져온다.

 

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))

 

ExifShort 데이터를 원래의 위치에 다시 재할당한다.

 

위 코드에서 Long 형식이면 4바이트의 데이터를, Short 형식이면 2바이트의 데이터를 반환한다.

이후 원래 위치에 저장하는데 4바이트의 공간에 2바이트의 데이터를 저장한다.

 

이 코드가 반복되면서 다시 4바이트의 크기로 데이터를 읽게되는데 이때 오버런 취약점이 발생하게 된다.

 

계속 반복되다 보면 위와 같이 인자가 들어간다.

데이터를 가져오는 부분 및 저장하는 부분에 대한 값이다.

 

위의 코드를 수행하기 전의 모습이다.

 

여기서 값을 실행하게 되면 아래와 같이 메모리에 0x45afe4의 0x45가 들어간 것을 확인할 수 있다.

 

위와 같은 작업이 계속 수행되면 아래와 같은 모습이 만들어진다.

0x45b008은 한 chunk의 size이다.

계속 데이터를 읽어오고 재할당하는 작업을 반복하다보면 다음 chunk에 Buf Overflow가 발생하게 된다.

 

이후 계속해서 수행하다보면 heap 영역의 마지막 주소로 참조할 수 없는 주소를 참조할려고 해서 Segmentation fault가 발생하게 된다.

 

 

 

6. 패치 파악


패치된 버젼과의 비교를 수행했다.

ds가 64KiB를 넘어갈 수 없도록 제한을 두는 방법으로 패치가 되었다.


 

728x90
반응형

'STUDY > CVE && Fuzzing' 카테고리의 다른 글

[Fuzzing 101] libtiff (CVE-2016-9297)  (1) 2024.06.01
[Fuzzing 101] TCPdump (CVE-2017-13028)  (0) 2024.01.19
[Fuzzing 101] libexif (CVE-2012-2836)  (0) 2024.01.08
[Fuzzing 101] Xpdf (CVE-2019-13288)  (0) 2023.11.18
[CVE] Curl (CVE-2023-38545)  (0) 2023.11.16