1. 분석 대상
TCPdump란?
CLI 환경에서 실행하는 패킷 가로채기 프로그램
tcp/ip 뿐만 아니라 컴퓨터에 부착된 네트워크를 통해 송수신되는 기타 패킷을 가로채고 표시하는 기능도 있음
Main Topic
- ASan
- Sanitizers
2. CVE Code
CVE-2017-13028
- TCPdump 4.9.2 이전 버전에서 BOOTP packet parsing 도중 out-of-bounds를 통해 임의 메모리의 값을 읽어오는 취약점
3. CVE 관련 정보
Reference
https://nvd.nist.gov/vuln/detail/CVE-2017-13028
https://techlog.gurucat.net/273
4. 분석 환경
Ubuntu 20.04 환경에서 진행
- TCPdump 4.9.1 Download
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
- libpcap Download
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이란?
- libpcap 라이브러리 에는 pcap 이라는 라이브러리를 포함하고 있다.
- pcap은 유닉스 계열 운영체제에서 네트워크 트래픽 포착을 위한 API를 가지고 있다.
- Windows에서는 Winpcap이라는 libpcap을 사용하며 우리는 유닉스 계열의 Ubuntu를 사용하므로 libpcap을 필요로 한다.
- libpcap directory name change (TCPdump 동작시 libpcap 라이브러리를 찾기위함)
mv libpcap-libpcap-1.8.0/ libpcap-1.8.0
Libpcap Build
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
./configure --enable-shared=no
make
TCPdump Build
cd $HOME/fuzzing_tcpdump/tcpdump-tcpdump-4.9.1/
./configure --prefix="$HOME/fuzzing_tcpdump/install/"
make
make install
설치 확인
root@7216a6b36080:~/fuzzing_tcpdump# ./install/sbin/tcpdump -h
tcpdump version 4.9.1
libpcap version 1.8.0
OpenSSL 1.1.1f 31 Mar 2020
Usage: tcpdump [-aAbdDefhHIJKlLnNOpqStuUvxX#] [ -B size ] [ -c count ]
[ -C file_size ] [ -E algo:secret ] [ -F file ] [ -G seconds ]
[ -i interface ] [ -j tstamptype ] [ -M secret ] [ --number ]
[ -Q in|out|inout ]
[ -r file ] [ -s snaplen ] [ --time-stamp-precision precision ]
[ --immediate-mode ] [ -T type ] [ --version ] [ -V file ]
[ -w file ] [ -W filecount ] [ -y datalinktype ] [ -z postrotate-command ]
[ -Z user ] [ expression ]
root@7216a6b36080:~/fuzzing_tcpdump/tcpdump-tcpdump-4.9.1# $HOME/fuzzing_tcpdump/install/sbin/tcpdump -vvvvXX -ee -nn -r ./tests/geneve.pcap
reading from file ./tests/geneve.pcap, link-type EN10MB (Ethernet)
07:04:33.817203 00:1b:21:3c:ab:64 > 00:1b:21:3c:ac:30, ethertype IPv4 (0x0800), length 156: (tos 0x0, ttl 64, id 57261, offset 0, flags [DF], proto UDP (17), length 142)
20.0.0.1.12618 > 20.0.0.2.6081: [no cksum] Geneve, Flags [C], vni 0xa, proto TEB (0x6558), options [class Standard (0x0) type 0x80(C) len 8 data 0000000c]
b6:9e:d2:49:51:48 > fe:71:d8:83:72:4f, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 48546, offset 0, flags [DF], proto ICMP (1), length 84)
30.0.0.1 > 30.0.0.2: ICMP echo request, id 10578, seq 23, length 64
0x0000: 001b 213c ac30 001b 213c ab64 0800 4500 ..!<.0..!<.d..E.
0x0010: 008e dfad 4000 4011 32af 1400 0001 1400 ....@.@.2.......
0x0020: 0002 314a 17c1 007a 0000 0240 6558 0000 ..1J...z...@eX..
0x0030: 0a00 0000 8001 0000 000c fe71 d883 724f ...........q..rO
0x0040: b69e d249 5148 0800 4500 0054 bda2 4000 ...IQH..E..T..@.
0x0050: 4001 4104 1e00 0001 1e00 0002 0800 2c54 @.A...........,T
0x0060: 2952 0017 f1a2 ce54 0000 0000 1778 0c00 )R.....T.....x..
0x0070: 0000 0000 1011 1213 1415 1617 1819 1a1b ................
0x0080: 1c1d 1e1f 2021 2223 2425 2627 2829 2a2b .....!"#$%&'()*+
0x0090: 2c2d 2e2f 3031 3233 3435 3637 ,-./01234567
07:04:33.817454 00:1b:21:3c:ac:30 > 00:1b:21:3c:ab:64, ethertype IPv4 (0x0800), length 148: (tos 0x0, ttl 64, id 34821, offset 0, flags [DF], proto UDP (17), length 134)
...
What Use Fuzzer? AddressSanitizer (ASan)
- AddressSanitizer(이하 ASan)란 heap, stack, global object 들에 대한 Out-of-Bounds와 Use After Free, Double Free, Memory leak 등에 대한 버그를 찾는 모듈이다.
- 우리는 CVE-2017-13028, out-of-bounds에 관한 crash를 찾기 위해 ASan을 사용한다.
Rebuild with ASan
rm -r $HOME/fuzzing_tcpdump/install
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
make clean
cd $HOME/fuzzing_tcpdump/tcpdump-tcpdump-4.9.1/
make clean
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
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
Fuzzing
afl-fuzz -m none -i $HOME/fuzzing_tcpdump/tcpdump-tcpdump-4.9.1/tests/ -o $HOME/fuzzing_tcpdump/out/ -s 123 -- $HOME/fuzzing_tcpdump/install/sbin/tcpdump -vvvvXX -ee -nn -r @@
- -m
프로세스 메모리 제한 - -i
입력 테스트 케이스를 넣을 디렉터리 - -o
결과를 저장할 디렉터리 - -s
사용할 정적 랜덤 시드 - --
-- 뒤에 실제로 넣을 인자들을 작성 - @@
타겟 프로그램이 파일을 입력으로 받는 경우에 사용한다. @@ 위치에 자동으로 대상 파일의 경로와 이름이 커맨드 라인으로 들어간다. 붙이지 않으면 표준 입력으로 처리하는 것으로 간주한다. - 이외, TCPdump 실행 인자
- 17시간 정도 돌린 결과 337개의 유니크 크래쉬를 찾고 퍼저를 멈췄다.
5. 루트 커즈 분석
~/fuzzing_tcpdump/out 하위 디렉터리에 저장된 크래시 파일을 확인할 수 있다.
root@7216a6b36080:~/fuzzing_tcpdump/out/default/crashes# ls
README.txt id:000168,sig:06,src:009876,time:28825153,execs:5901493,op:havoc,rep:2
id:000000,sig:06,src:001109,time:190324,execs:88660,op:havoc,rep:2 id:000169,sig:06,src:009887,time:28838726,execs:5905727,op:havoc,rep:5
id:000001,sig:06,src:001284,time:447825,execs:196912,op:havoc,rep:1 id:000170,sig:06,src:009871,time:28894781,execs:5919589,op:havoc,rep:4
id:000002,sig:06,src:001284,time:458501,execs:201889,op:havoc,rep:3 id:000171,sig:06,src:009906,time:28900379,execs:5921513,op:havoc,rep:3
id:000003,sig:06,src:001284,time:461688,execs:203372,op:havoc,rep:2 id:000172,sig:06,src:009885+002153,time:28904249,execs:5922840,op:splice,rep:1
id:000004,sig:06,src:001535,time:1435898,execs:428561,op:havoc,rep:2 id:000173,sig:06,src:009856,time:28906071,execs:5923400,op:havoc,rep:7
id:000005,sig:06,src:001539,time:1436787,execs:428957,op:havoc,rep:3 id:000174,sig:06,src:008465,time:28957158,execs:5936482,op:havoc,rep:1
id:000006,sig:06,src:001692+001392,time:1437966,execs:429534,op:splice,rep:2 id:000175,sig:06,src:009880+001711,time:28976672,execs:5943495,op:splice,rep:2
id:000007,sig:06,src:001527+000664,time:2145421,execs:687754,op:splice,rep:4 id:000176,sig:06,src:007622+006928,time:29760970,execs:6157479,op:splice,rep:12
id:000008,sig:06,src:002390,time:2228450,execs:722474,op:havoc,rep:14 id:000177,sig:06,src:008900,time:30190455,execs:6285702,op:havoc,rep:2
id:000009,sig:06,src:002390,time:2240342,execs:726709,op:havoc,rep:10 id:000178,sig:06,src:008900,time:30208613,execs:6292210,op:havoc,rep:2
...
Triger
Bootp parsing 도중 extract_16bits 함수에 heap buffer overflow가 발생한 것을 볼 수 있다.
코드 분석
문제의 print-bootp.c 파일을 보자
void
bootp_print(netdissect_options *ndo,
register const u_char *cp, u_int length)
{
register const struct bootp *bp;
static const u_char vm_cmu[4] = VM_CMU;
static const u_char vm_rfc1048[4] = VM_RFC1048;
bp = (const struct bootp *)cp;
ND_TCHECK(bp->bp_op);
ND_PRINT((ndo, "BOOTP/DHCP, %s",
tok2str(bootp_op_values, "unknown (0x%02x)", bp->bp_op)));
ND_TCHECK(bp->bp_hlen);
if (bp->bp_htype == 1 && bp->bp_hlen == 6 && bp->bp_op == BOOTPREQUEST) {
ND_TCHECK2(bp->bp_chaddr[0], 6);
ND_PRINT((ndo, " from %s", etheraddr_string(ndo, bp->bp_chaddr)));
}
ND_PRINT((ndo, ", length %u", length));
if (!ndo->ndo_vflag)
return;
bootp_print 함수의 시작 부분이다. packet의 주소 파싱과 ndo_vflag가 0이 아닌지 검증하고 넘어간다.
...
/* Only print interesting fields */
if (bp->bp_hops)
ND_PRINT((ndo, ", hops %d", bp->bp_hops));
if (EXTRACT_32BITS(&bp->bp_xid))
ND_PRINT((ndo, ", xid 0x%x", EXTRACT_32BITS(&bp->bp_xid)));
if (EXTRACT_16BITS(&bp->bp_secs))
ND_PRINT((ndo, ", secs %d", EXTRACT_16BITS(&bp->bp_secs)));
ND_PRINT((ndo, ", Flags [%s]",
bittok2str(bootp_flag_values, "none", EXTRACT_16BITS(&bp->bp_flags))));
if (ndo->ndo_vflag > 1)
ND_PRINT((ndo, " (0x%04x)", EXTRACT_16BITS(&bp->bp_flags)));
...
EXTRACT_16BITS 함수의 인자로 bp->bp_secs 와 bp->bp_flags를 입력한다.
이때 EXTRACT_16BITS 함수는 전달받은 bp_secs와 bp_flags의 16bit를 가져오며 ND_PRINT 함수는 출력을 담당하고 있다.
static const struct tok bootp_flag_values[] = {
{ 0x8000, "Broadcast" },
{ 0, NULL}
};
bootp_flag_values 는 bp_flags가 0x8000일 경우 Broadcast 아니면 NULL을 설정한다.
static char *
bittok2str_internal(register const struct tok *lp, register const char *fmt,
register u_int v, const char *sep)
{
static char buf[256]; /* our stringbuffer */
int buflen=0;
register u_int rotbit; /* this is the bit we rotate through all bitpositions */
register u_int tokval;
const char * sepstr = "";
while (lp != NULL && lp->s != NULL) {
tokval=lp->v; /* load our first value */
rotbit=1;
while (rotbit != 0) {
/*
* lets AND the rotating bit with our token value
* and see if we have got a match
*/
if (tokval == (v&rotbit)) {
/* ok we have found something */
buflen+=snprintf(buf+buflen, sizeof(buf)-buflen, "%s%s",
sepstr, lp->s);
sepstr = sep;
break;
}
rotbit=rotbit<<1; /* no match - lets shift and try again */
}
lp++;
}
...
또, bittok2str 함수를 따라가면 다음과 같은 함수가 나오는데, flags가 0x8000 즉, Broadcast로 설정되어 있을 경우 lp 구조체의 string을 계속 가져와 ND_PRINT가 출력하게 된다.
그럼 이제 ndo를 살펴보자
struct netdissect_options {
...
char *ndo_sigsecret; /* Signature verification secret key */
int ndo_packettype; /* as specified by -T */
int ndo_snaplen;
/*global pointers to beginning and end of current packet (during printing) */
const u_char *ndo_packetp;
const u_char *ndo_snapend;
/* pointer to the if_printer function */
if_printer ndo_if_printer;
ndo 구조체는 패킷의 다양한 옵션값을 가지고 있는데, 그중에 ndo_snapend는 스냅샷의 끝 주소를 담고 있다.
gef➤ p *ndo
$1 = {
ndo_bflag = 0x0,
ndo_eflag = 0x2,
ndo_fflag = 0x0,
ndo_Kflag = 0x0,
ndo_nflag = 0x2,
ndo_Nflag = 0x0,
ndo_qflag = 0x0,
ndo_Sflag = 0x0,
ndo_tflag = 0x0,
ndo_uflag = 0x0,
ndo_vflag = 0x4,
ndo_xflag = 0x0,
ndo_Xflag = 0x2,
ndo_Aflag = 0x0,
ndo_Hflag = 0x0,
ndo_packet_number = 0x0,
ndo_suppress_default_print = 0x2,
ndo_tstamp_precision = 0x0,
program_name = 0x7ffff6d15856 "tcpdump",
ndo_espsecret = 0x0,
ndo_sa_list_head = 0x0,
ndo_sa_default = 0x0,
ndo_sigsecret = 0x0,
ndo_packettype = 0x0,
ndo_snaplen = 0x40000,
ndo_packetp = 0x0,
ndo_snapend = 0x606000000055 "",
ndo_if_printer = 0x4ffa20 <ether_if_print>,
ndo_default_print = 0x46f9a0 <ndo_default_print>,
ndo_printf = 0x46f9d0 <ndo_printf>,
ndo_error = 0x46fca0 <ndo_error>,
ndo_warning = 0x4700a0 <ndo_warning>
}
gdb를 통해 ndo 구조체를 확인해 보면, 0x606000000055 주소를 가지고 있는것을 확인할 수 있다.
이 snapend 주소는 다음 코드를 통해 계산된다.
ndo->ndo_snapend = sp + h->caplen;
이 h->caplen은 bootp 파일에서 설정할 수 있는데
53바이트로 설정이 되어 있다. 하지만 이는 bootp 패킷의 실제 데이터 크기보다 작다. 때문에 충분히 할당되지 않은 공간에 계속해서 접근을 시도하다 out of bound read가 발생하는 것이다.
6. 패치 파악
다음은 tcpdump-4.9.2 버전에서 추가된 코드이다.
ND_TCHECK(bp->bp_flags);
ND_PRINT((ndo, ", Flags [%s]",
bittok2str(bootp_flag_values, "none", EXTRACT_16BITS(&bp->bp_flags))));
ND_TCHECK 라는 매크로가 추가된것을 알 수 있는데, ND_TCHECK는 인자의 주소값이 snapend를 넘지 않았는지 확인하는 매크로 이다.
전체 코드는 아래 취약점 패치 공식 문서에서 확인할 수 있다.
정리
- bootp packet 파싱 중 헤더의 snaplen 값 보다 실제 데이터 사이즈가 클 경우 oob-read를 하는 취약점이 존재
- 파싱할 주소가 할당된 주소를 넘지 않았는지 확인하는 매크로를 추가하여 패치되었다.
'Fuzzing' 카테고리의 다른 글
[Fuzzing101] 5. Libxml2 (CVE-2017-9048) (0) | 2024.01.12 |
---|---|
[Fuzzing101] 4. LibTIFF (CVE-2016-9297) (1) | 2024.01.02 |
[Fuzzing101] 2. libexif (CVE-2009-3895) (0) | 2023.11.07 |
[Fuzzing101] 1. Xpdf (CVE-2019-13288) (1) | 2023.10.30 |