1. 분석 대상
libexif란?
C언어 기반의 리눅스 라이브러리이다.
exif file을 parsing 해주는 기능을 가지고 있다.
How to Fuzz?
libexif는 라이브러리이기 때문에 libexif를 사용하는 프로그램을 설치하여 fuzzing 해야 한다.
Main Topic
- Afl-clang-Ito
- Fuzz libraries
- Eclipse IDE
2. CVE Code
CVE-2009-3895
- libexif 0.6.14 버전에서 exif-entry.c 파일에 exif_entry_fix() 함수가 올바르지 않은 EXIF 이미지 파일 사용시 Heap Base Buffer Overflow를 통해 공격자가 임의의 코드를 삽입 혹은 DoS 공격을 할 수 있는 취약점
3. CVE 관련 정보
Reference
https://nvd.nist.gov/vuln/detail/CVE-2009-3895
https://nvd.nist.gov/vuln/detail/CVE-2012-2836
https://lourcode.kr/posts/CVE-2012-2836(libexif)-%EB%B6%84%EC%84%9D/
4. 분석 환경
Ubuntu 20.04 환경에서 진행
- libexif 0.6.14 Download
cd $HOME
mkdir fuzzing_libexif && cd fuzzing_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
libexif 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
Exif command-line Download
- 여기서 우리는 libexif 라이브러리를 사용할 프로그램을 찾아야 한다.
- 단순히 라이브러리 취약점에대한 퍼징을 진행할 것이므로 가벼운 프로그램인 exif command-line을 설치하였다.
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
Exif command-line 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
$HOME/fuzzing_libexif/install/bin/exif
Exif Example file download
cd $HOME/fuzzing_libexif
wget https://github.com/ianare/exif-samples/archive/refs/heads/master.zip
unzip master.zip
$HOME/fuzzing_libexif/install/bin/exif $HOME/fuzzing_libexif/exif-samples-master/jpg/Canon_40D_photoshop_import.jpg
EXIF tags in '/root/fuzzing_libexif/exif-samples-master/jpg/Canon_40D_photoshop_import.jpg' ('Motorola' byte order):
--------------------+----------------------------------------------------------
Tag |Value
--------------------+----------------------------------------------------------
Orientation |top - left
x-Resolution |300.00
y-Resolution |300.00
Resolution Unit |Inch
Software |GIMP 2.4.5
Date and Time |2008:07:31 10:05:49
Compression |JPEG compression
x-Resolution |72.00
y-Resolution |72.00
Resolution Unit |Inch
Color Space |sRGB
PixelXDimension |100
PixelYDimension |77
--------------------+----------------------------------------------------------
EXIF data contains a thumbnail (2022 bytes).
Afl-clang-Ito 로 Exif 재컴파일
rm -r $HOME/fuzzing_libexif/install
cd $HOME/fuzzing_libexif/libexif-libexif-0_6_14-release/
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./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"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
make install
Fuzzing
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
결과를 저장할 디렉터리 - -s
사용할 정적 랜덤 시드 - --
-- 뒤에 실제로 넣을 인자들을 작성 - @@
타겟 프로그램이 파일을 입력으로 받는 경우에 사용한다. @@ 위치에 자동으로 대상 파일의 경로와 이름이 커맨드 라인으로 들어간다. 붙이지 않으면 표준 입력으로 처리하는 것으로 간주한다.
- 15분 정도 돌린결과 49개의 크래쉬 중 17개의 유니크 크래쉬를 찾고 퍼저를 멈췄다.
5. 루트 커즈 분석
~/fuzzing_libexif/out 하위 디렉터리에 저장된 크래시 파일을 확인할 수 있다.
root@7216a6b36080:~/fuzzing_libexif/out/default/crashes# ls
README.txt
id:000000,sig:11,src:000000,time:39195,execs:29490,op:havoc,rep:3
id:000001,sig:11,src:000010,time:109460,execs:88026,op:havoc,rep:12
id:000002,sig:11,src:000404,time:136125,execs:118954,op:havoc,rep:4
id:000003,sig:11,src:000008,time:156917,execs:137193,op:havoc,rep:7
id:000004,sig:11,src:000008,time:159567,execs:138837,op:havoc,rep:6
id:000005,sig:11,src:000008,time:164052,execs:141651,op:havoc,rep:5
id:000006,sig:11,src:000002,time:198239,execs:169669,op:havoc,rep:16
id:000007,sig:11,src:000002,time:202843,execs:172766,op:havoc,rep:12
id:000008,sig:11,src:000085,time:267979,execs:217434,op:havoc,rep:8
id:000009,sig:11,src:000603,time:551635,execs:421559,op:havoc,rep:4
id:000010,sig:11,src:000626,time:575222,execs:439547,op:havoc,rep:8
id:000011,sig:11,src:000593,time:603139,execs:461195,op:havoc,rep:3
id:000012,sig:11,src:000525+000620,time:687152,execs:509351,op:splice,rep:1
id:000013,sig:11,src:000597+000596,time:707472,execs:526863,op:splice,rep:5
id:000014,sig:11,src:000624+000419,time:799686,execs:616505,op:splice,rep:13
id:000015,sig:11,src:000674+000600,time:891599,execs:698948,op:splice,rep:12
id:000016,sig:11,src:000150,time:945295,execs:753099,op:havoc,rep:8
첫 번째 크래시 파일을 대상으로 디버깅을 진행
exif_data_load_data() 함수를 호출할 때 Segmentation fault 에러가 발생하며 종료되는 것을 확인할 수 있다.
Call Stack 과 매핑 영역을 살펴보니 heap을 벗어난 영역에 쓰기를 시도하다 에러가 난 것을 볼 수 있다.
코드 분석
문제가 발생한 exif_data_load_data() 함수를 살펴보자
void
exif_data_load_data (ExifData *data, const unsigned char *d_orig,
unsigned int ds_orig)
{
unsigned int l;
ExifLong offset;
ExifShort n;
const unsigned char *d = d_orig;
unsigned int ds = ds_orig, len;
if (!data || !data->priv || !d || !ds)
return;
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"Parsing %i byte(s) EXIF data...\n", ds);
/*
* It can be that the data starts with the EXIF header. If it does
* not, search the EXIF marker.
*/
if (ds < 6) {
LOG_TOO_SMALL;
return;
}
if (!memcmp (d, ExifHeader, 6)) {
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"Found EXIF header.");
}
...
n = exif_get_short (d + 6 + offset, data->priv->order);
if (offset + 6 + 2 + 12 * n + 4 > ds) {
return;
}
offset = exif_get_long (d + 6 + offset + 2 + 12 * n, data->priv->order);
if (offset) {
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"IFD 1 at %i.", (int) offset);
/* Sanity check. */
if (offset > ds - 6) {
exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA,
"ExifData", "Bogus offset of IFD1.");
} else {
exif_data_load_data_content (data, EXIF_IFD_1, d + 6, ds - 6, offset, 0);
}
}
위 코드는 exif-data.c 파일의 exif_data_load_data() 함수의 일부 코드이다.
exif 파일에 헤더의 정보를 가져오고 헤더에서 가져온 offset 값을 인자로 exif_get_short 함수를 호출한다.
이때 ds 값을 검증하지 않고 넘기게 된다.
ExifShort
exif_get_short (const unsigned char *buf, ExifByteOrder order)
{
return (exif_get_sshort (buf, order) & 0xffff);
}
exif_get_sshort (const unsigned char *buf, ExifByteOrder order)
{
if (!buf) return 0;
switch (order) {
case EXIF_BYTE_ORDER_MOTOROLA:
return ((buf[0] << 8) | buf[1]);
case EXIF_BYTE_ORDER_INTEL:
return ((buf[1] << 8) | buf[0]);
}
/* Won't be reached */
return (0);
}
exif_get_short 함수는 받은 인자를 그대로 넘겨주며 exif_get_sshort를 실행한다.
exif_get_sshort 함수는 buf를 비트 시프트 연산 후 반환하게 된다.
이 과정에서 파일에서 가져오는 ds와 offset 값에 대한 검증이 없기 때문에 버퍼 오버플로우가 발생하게 된다.
6. 패치 파악
- https://github.com/libexif/libexif/commit/8ce72b7f81e61ef69b7ad5bdfeff1516c90fa361
- https://github.com/libexif/libexif/commit/00986f6fa979fe810b46e376a462c581f9746e06
위는 공식 패치 문서이다.
요약 하자면 연산 중간중간 마다 ds 값보다 offset이 더 큰지 검증하는 루틴이 추가 되었고 ds값도 일정이상 클 경우 값을 고정하는 방식으로 버퍼 오버플로우 방지 코드가 추가되었다.
정리
- exif 파일의 헤더에서 가져오는 offset, ds 값에 대한 검증이 없어서 힙 기반 버퍼 오버플로우 발생 (CVE-2009-3895, CVE-2012-2836)
- 이로인해 exif_data_load_data() 함수에 헤더 값을 검증하는 코드가 추가되어 패치되었다.
'Fuzzing' 카테고리의 다른 글
[Fuzzing101] 5. Libxml2 (CVE-2017-9048) (0) | 2024.01.12 |
---|---|
[Fuzzing101] 4. LibTIFF (CVE-2016-9297) (1) | 2024.01.02 |
[Fuzzing101] 3. TCPdump (CVE-2017-13028) (1) | 2023.12.26 |
[Fuzzing101] 1. Xpdf (CVE-2019-13288) (1) | 2023.10.30 |