CVE-2017-13028(tcpdump) 분석
개요
TCPdump : 커맨드라인에서 실행되는 일반적인 패킷 스니퍼
- CVE-2017-13028 : 4.9.2 이전,
print-bootp.c의 bootp_print 함수에서 발생하는buffer over read취약점
타겟 설치
TCPdump설치
1
2
wget https://github.com/the-tcpdump-group/tcpdump/archive/refs/tags/tcpdump-4.9.1.tar.gz
tar -xzvf tcpdump-4.9.1.tar.gz
사용하기 편하도록 경로명을 변경해주었다.
1
mv tcpdump-tcpdump-4.9.1 tcpdump
Fuzzing101에서는 4.9.2 버전을 설치하라고 나오는데, CVE-2017-13028은 4.9.2 버전에서 패치되었다.
그러므로 4.9.2 이전 버전을 설치해주어야 한다.
libpcap설치
TCPdump는 패킷을 캡처하기 위해 libpcap을 사용한다.
1
2
wget https://github.com/the-tcpdump-group/libpcap/archive/refs/tags/libpcap-1.8.0.tar.gz
tar -xzvf libpcap-1.8.0.tar.gz
libpcap은 TCPdump와 같은 경로에 존재해야 한다.
1
mv libpcap-libpcap-1.8.0 libpcap-1.8.0
타겟 빌드
AFL 컴파일러 및 ASAN을 이용하여 빌드하였다.
ASAN은 빠른 메모리 오류 탐지기이며, 컴파일러 계측 모듈과 런타임 라이브러리가 존재한다.
ASAN은 다음과 같은 오류를 탐지할 수 있다.
- 힙, 스택, 전역에 대한
Out-of-bounds접근 Use-after-freeUse-after-returnUse-after-scopeDouble-free, 유효하지 않은freeMemory leaks
ASAN으로 인해 저하되는 속도는 일반적으로 2배이다.
원래는 도커 컨테이너에서 빌드를 시도하였지만, 알 수 없는 충돌로 인해 크래시 파일이 죄다 이상한 파일로 도배되어 있었다.
ASAN은 메모리 할당 실패를 치명적인 오류로 취급하고 그 즉시 프로그램을 종료시킨다고 한다.
AFL에선 이러한 종료를 크래시로 판단하고 쓸모 없는 크래시만 계속 쌓여서 ubuntu 20.04 서버에서 진행하였다.
도커의 메모리 제한을 2GB로 설정해두었는데, 아마 그 이유 때문에 이상한 크래시만 발생한 것 같다.
실제로 ASAN은 기본 실행보다 더 많은 메모리를 사용하며, 최대 3배 많은 스택 메모리를 사용한다.
libpcap빌드
1
2
3
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix=/root/install
AFL_USE_ASAN=1 make
TCPdump빌드
1
2
3
AFL_USE_ASAN=1 CC=afl-clang-lto ./configure --prefix=/root/install
AFL_USE_ASAN=1 make
AFL_USE_ASAN=1 make install
AFL 실행
1
afl-fuzz -m none -i /root/tcpdump/tests/ -o /root/out/ -s 123 -- /root/install/sbin/tcpdump -vvvvXX -ee -nn -r @@
-m: 자식 프로세스의 메모리 제한
ASAN은 많은 가상 메모리를 잡아 먹기 때문에 정확한 크래시를 보기 위해선 제한을 비활성화 해주어야 한다.
TCPdump의 최대한 많은 기능을 출력하기 위해 -vvvvXX -ee -nn -r옵션을 사용했다.
크래시 분석
바이너리 실행
bootp_print 함수에서 호출하는 EXTRACT_16BITS에서 프로그램이 종료되었으며, heap buffer overflow를 감지하였다.
해당 라인의 코드는 다음과 같다.
1
2
ND_PRINT((ndo, ", Flags [%s]",
bittok2str(bootp_flag_values, "none", EXTRACT_16BITS(&bp->bp_flags))));
ND_PRINT는 출력을 담당하는 함수이며, bittok2str는 비트 토큰을 문자열로 변환하는 함수이다.
EXTRACT_16BITS 함수는 이름 그대로 인자의 16비트를 가져오는 함수이다.
bootp_flag_values의 구조체는 다음과 같다.
1
2
3
4
static const struct tok bootp_flag_values[] = {
{ 0x8000, "Broadcast" },
{ 0, NULL}
};
flags는 REPLY 메시지에 대한 타입을 유니캐스트 또는 브로드캐스트 지정하는 필드이다.
또한 1bit만을 사용하며, 나머지 비트는 사용되지 않는다.
해당 라인은 가져온 flags가 0x8000일 경우 Broadcast를 출력하고 0x0일 경우 아무것도 출력하지 않는 루틴인 것 같다.
010 editor를 이용해 bootp의 모든 필드를 1로 설정하고 gdb를 통해 확인해보았다.
1
gdb --args /root/install/sbin/tcpdump -vvvvXX -ee -nn -r /root/out/default/crashes/crash-1
flags 앞의 값들은 정상적으로 설정된 반면에 flags 이후부터는 값이 변하지 않는다.
원활한 분석을 위해 AFL 계측을 제거하고 ASAN 계측만 남겨두게끔 컴파일 하였다.
또한 -g 옵션 및 -O0 옵션을 추가하여 분석에 편하도록 설정하였다.
1
CFLAGS="-fsanitize=address -g -O0" ./configure --prefix=/home/lourcode/Fuzzing101-Practice/Exercise-3/install
TCPdump를 컴파일 할 때 -fsanitize=address 옵션만 켜주면 ASAN이 활성화 된다.
ndo 구조체는 패킷의 옵션 값을 가지고 있다.
ndo 구조체의 ndo_snapend은 스냅샷의 끝 주소를 가지고 있는데 gdb를 통해 확인해보면 bootp 데이터의 사이즈보다 작은 것을 확인할 수 있다.
해당 멤버는 다음과 같이 계산된다.
1
ndo->ndo_snapend = sp + h->caplen;
캡처된 길이인 caplen 멤버를 확인해보면 0x35로 실제 길이보다 작다.
해당 길이는 pcap 헤더에서 설정할 수 있다.
따라서 해당 취약점은 데이터가 들어갈 공간이 충분히 할당되지 않았음에도 바운드 체크를 하지 않고, 오프셋 연산으로 데이터를 접근하려고 시도하다가 over-read가 발생하는 취약점이다.
해당 값을 넉넉하게 설정해주면 취약점이 발생하지 않는다.
취약점 패치
ND_TCHECK 매크로는 매개변수의 주소값이 snapend를 넘지 않았는지 확인하는 매크로이다.
해당 매크로를 추가하여 flags 값이 캡쳐된 값 범위에 있는지 확인한다.
- 매크로
1
2
/* Bail if "var" was not captured */
#define ND_TCHECK(var) ND_TCHECK2(var, sizeof(var))
1
#define ND_TCHECK2(var, l) if (!ND_TTEST2(var, l)) goto trunc
1
2
3
4
#define ND_TTEST2(var, l) \
(IS_NOT_NEGATIVE(l) && \
((uintptr_t)ndo->ndo_snapend - (l) <= (uintptr_t)ndo->ndo_snapend && \
(uintptr_t)&(var) <= (uintptr_t)ndo->ndo_snapend - (l)))
- 패치
1
2
3
ND_TCHECK(bp->bp_flags);
ND_PRINT((ndo, ", Flags [%s]",
bittok2str(bootp_flag_values, "none", EXTRACT_16BITS(&bp->bp_flags))));
-분석.md/0.png)
-분석.md/1.png)
-분석.md/2.png)
-분석.md/3.png)
-분석.md/4.png)
-분석.md/5.png)
-분석.md/6.png)
-분석.md/7.png)
-분석.md/8.png)
-분석.md/9.png)
-분석.md/10.png)