1. 환경설정 및 설치
대상(tcpdump) 다운로드
# 디렉토리 생성
cd $HOME
mkdir fuzzing_tcpdump && cd fuzzing_tcpdump/
# 다운도르 및 압축해제
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
tcpdump를 사용하기 위한 libpcap 라이브러리 다운로드
# 다운로드 및 압축해제
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
# tcpdump가 로컬 경로를 원활하게 찾기 위해 libpcap 이름 변경
mv libpcap-libpcap-1.8.0/ libpcap-1.8.0
libpcap, tcpdump 빌드 및 설치 (+ASAN)
# tcpdump는 libpcap을 사용해 네트워크 패킷을 분석하므로 libpcap이 먼저 빌드된다.
# libpacp 빌드 및 설치 (+ASAN)
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_tcpdump/install/"
AFL_USE_ASAN=1 make
# tcpdump 빌드 및 설치 (+ASAN)
cd $HOME/fuzzing_tcpdump/tcpdump-tcpdump-4.9.1/
AFL_USE_ASAN=1 CC=afl-clang-lto ./configure --prefix="$HOME/fuzzing_tcpdump/install/"
AFL_USE_ASAN=1 make
AFL_USE_ASAN=1 make install
- Address Sanitizer(ASAN)은 메모리 오류를 감지하는 오픈소스 도구로써, version 3.1부터 LLVM 컴파일러에 통합되어 사용할 수 있다.
- AFL_USE_ASAN=1을 앞에 붙임으로써 ASAN을 사용해 AFL++을 빌드할 수 있다.
2. Fuzzing 단계

leginwos@leginwos:~/fuzzing_tcpdump/tcpdump-tcpdump-4.9.2$ afl-fuzz -m none -i $HOME/fuzzing_tcpdump/tcpdump-tcpdump-4.9.2/tests/ -o $HOME/fuzzing_tcpdump/out/ -s 123 -- $HOME/fuzzing_tcpdump/install/sbin/tcpdump -vvvvXX -ee -nn -r @@
3. 결과 분석

- bootp_print에서 호출하는 EXTRACT_32BITS에서 heap buffer overflow가 발생했다.

# bootp_print.c
ND_PRINT((ndo, ", Flags [%s]",
bittok2str(bootp_flag_values, "none", EXTRACT_16BITS(&bp->bp_flags))));
# bootp_flag_values
static const struct tok bootp_flag_values[] = {
{ 0x8000, "Broadcast" },
{ 0, NULL}
};
- ASAN으로 확인한 bootp_print.c를 자세히 보면, bootp_flag_values함수와 EXTRACT_16BITS 함수가 보인다.
- 여기서 EXTRACT_16BITS 함수는 이름 그대로 16비트만큼 추출하는 함수로 보인다.
- ND_PRINT는 출력하는 함수이고, bittok2str은 bit tocken을 문자로 바꾸는 함수이다.
- bootp_flag_values 함수는 flag값이 0x8000이면 Broadcast를, 0이면 아무것도 출력하지 않는다.

- ndo 구조체는 tcpdump의 packet에 있는 여러가지 값들을 가진다.
- 여기서 ndo_snapend는 이름 그대로 ndo 구조체의 snapshot의 끝주소를 가지고, 이 ndo_snapend는 아래와 같이 계산된다.


# print.c
ndo->ndo_snapend = sp + h->caplen;
- gdb을 사용해서 caplen을 확인하면 0x35이고, wireshark에서는 0x53으로 gdb에서의 caplen이 더 작은 것을 확인할 수 있다.
- 그렇기 때문에, 데이터의 길이를 확인하지 않으면, out-of-bounds가 일어나 heap buffer overflow가 발생할 수 있게 된다.
4. 취약점 패치

ND_TCHECK :
/* Bail if "var" was not captured */
#define ND_TCHECK(var) ND_TCHECK2(var, sizeof(var))
ND_TCHECK2 :
/* Bail if "l" bytes of "var" were not captured */
#define ND_TCHECK2(var, l) if (!ND_TTEST2(var, l)) goto trunc
trunc:
ND_PRINT((ndo, "%s", tstr));
return -1;
ND_TTEST2 :
#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)))
- ND_TCHECK 매크로를 사용해서 변수가 캡쳐되었는지 확인하고, 만약 아니라면 ND_TCHECK2 매크로를 호출한다.
- ND_TCHECK2 매크로는 변수의 1byte가 캡쳐되었는지 확인하고, 만약 아니라면 goto trunc로 오류 메세지를 호출하고 -1을 반환해 프로그램을 종료한다.
- ND_TTEST2 매크로는 데이터의 캡쳐 여부를 ndo_snapend로 확인해 해당 데이터의 주소가 유효한 범위 안에 있는지 확인한다.