Post

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

libpcapTCPdump와 같은 경로에 존재해야 한다.

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옵션을 사용했다.

0

크래시 분석


바이너리 실행

1

2

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

flagsREPLY 메시지에 대한 타입을 유니캐스트 또는 브로드캐스트 지정하는 필드이다.
또한 1bit만을 사용하며, 나머지 비트는 사용되지 않는다.

3

해당 라인은 가져온 flags0x8000일 경우 Broadcast를 출력하고 0x0일 경우 아무것도 출력하지 않는 루틴인 것 같다.


010 editor를 이용해 bootp의 모든 필드를 1로 설정하고 gdb를 통해 확인해보았다.

4

1
gdb --args /root/install/sbin/tcpdump -vvvvXX -ee -nn -r /root/out/default/crashes/crash-1

5

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 데이터의 사이즈보다 작은 것을 확인할 수 있다.

6

7

해당 멤버는 다음과 같이 계산된다.

1
ndo->ndo_snapend = sp + h->caplen;

8

캡처된 길이인 caplen 멤버를 확인해보면 0x35로 실제 길이보다 작다.

해당 길이는 pcap 헤더에서 설정할 수 있다.

9

따라서 해당 취약점은 데이터가 들어갈 공간이 충분히 할당되지 않았음에도 바운드 체크를 하지 않고, 오프셋 연산으로 데이터를 접근하려고 시도하다가 over-read가 발생하는 취약점이다.


해당 값을 넉넉하게 설정해주면 취약점이 발생하지 않는다.

10

취약점 패치


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))));
This post is licensed under CC BY 4.0 by the author.