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-free
Use-after-return
Use-after-scope
Double-free
, 유효하지 않은free
Memory 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))));