반응형
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

ZZoMb1E_PWN

[Fuzzing 101] TCPdump (CVE-2017-13028) 본문

STUDY/CVE && Fuzzing

[Fuzzing 101] TCPdump (CVE-2017-13028)

ZZoMb1E 2024. 1. 19. 18:46
728x90
반응형

 자료들을 참고하여 분석을 진행하였기에 잘못된 부분이 있을지도 모릅니다. 

※ 보완 혹은 수정해야 되는 부분이 있다면 알려주시면 확인 후 조치하도록 하겠습니다.
 

1. 분석대상


TCPdump란?

  네트워크에서 주고 받는 패킷을 캡처하여 확인 및 분석할 때 사용하는 프로그램이다.

 

 

2. CVE Code


CVE-2017-13028

print-bootp.c의 bootp_print()에서 발생하는 Buffer Over-read 취약점이다. 공격자가 악성 패킷을 보내 tcpdump를 실행하는 시스템에서 임의의 코드를 실행 혹은 서비스 거부를 유발할 수 있다.

CWE-125

Out-of-bounds Read

민감한 정보를 읽거나 충돌을 유발시키는 것으로 문자열 등의 데이터를 읽는 과정에서 NULL과 같이 문자열의 끝을 가리키는 값이 존재한다는 가정하에 발생한다. 문자열 범위 밖에 해당 값이 존재할 경우 메모리 유출 및 BOF로 이어질 수 있다.
 

3. CVE 관련 정보



https://nvd.nist.gov/vuln/detail/CVE-2017-13028 

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-13028

 

4. 분석  환경 및 구현


분석 환경 : Ubuntu 20.04
 
 

실습을 위한 설치 과정


 
TCPdump 파일 실습할 디렉터리 경로 생성

cd $HOME
mkdir fuzzing_tcpdump && cd fuzzing_tcpdump/

  
취약한 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

 

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를 사용한다. 

 

일반적인 build

mv libpcap-libpcap-1.8.0/ libpcap-1.8.0
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
./configure --enable-shared=no
make

cd $HOME/fuzzing_tcpdump/tcpdump-tcpdump-4.9.1/
./configure --prefix="$HOME/fuzzing_tcpdump/install/"
make
make install

 


 

실행 확인

$HOME/fuzzing_tcpdump/install/bin/tcpdump -h

 


  
TCPdump / libpcap를 AFL++ 이용하여 빌드 - 계측코드 삽입
 
recompile

sudo rm -r $HOME/fuzzing_tcpdump/install
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
sudo make clean

cd $HOME/fuzzing_tcpdump/tcpdump-tcpdump-4.9.1/
sudo make clean

cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-fast ./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-fast ./configure --prefix="$HOME/fuzzing_tcpdump/install/"
AFL_USE_ASAN=1 make
AFL_USE_ASAN=1 make install

 
※ 제 환경에서 AFL에 문제가 있는지 afl-clang-lto에서 오류가 발생하여 afl-clang-fast로 진행했습니다. 
실습하실 때 afl-clang-lto로 하시면 됩니다.

※ 지금은 해당 문제를 해결했습니다.


fuzzer 수행
 

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 @@

-i : 입력에 사용할 예제의 디렉터리 경로 설정 옵션
-o : AFL++의 결과를 저장할 디렉터리 경로 설정 옵션
-s : 무작위 정적 시드 사용
@@ : 입력 파일 이름으로 대체할 대상 표시

-m : AFL의 메모리 제한을 해제하도록 설정

 

TCPdump의 옵션

-nn : 호스트, 포트, 프로토콜 이름 등을 숫자로 출력

-ee : 링크 레벨 헤더 출력

-vvvXX : 더 자세한 설명 출력

-r : 입력 파일 읽기
 

 
 

5. 루크 커즈 분석


ASAN으로 진행을 했기 때문에 GDB가 아닌 파일의 파라미터로 crash 파일을 전달해주면 분석을 할 수 있다.

$HOME/fuzzing_tcpdump/install/sbin/tcpdump -vvvXX -ee -nn -r'./crash.pcap'

 


 

분석



BackTrace 부분을 살펴보면  _start()  __libc_start_main()  main()  pcap_loop()  print_packet() → pretty_print_packet() → ether_print() → ethertype_print() → ip_print()  ip_print_demux() → udp_print() → bootp_print() EXTRACT_16BITS  순으로 호출되는 것을 확인할 수 있다.

 

 

코드 분석


main()에서 패킷을 분석 및 출력하기 위해 pcap_loop()를 호출한다.


tcpdump.c:main()

#ifdef HAVE_CAPSICUM
	cansandbox = (ndo->ndo_nflag && VFileName == NULL && zflag == NULL);
	if (cansandbox && cap_enter() < 0 && errno != ENOSYS)
		error("unable to enter the capability mode");
#endif	/* HAVE_CAPSICUM */

	do {
		status = pcap_loop(pd, cnt, callback, pcap_userdata);
		if (WFileName == NULL) {
			/*
			 * We're printing packets.  Flush the printed output,
			 * so it doesn't get intermingled with error output.
			 */

pcap_loop()를 호출할 때 패킷에 대해 호출될 콜백 함수를 지정해주고 있다.

 

pcap.c:pcap_loop()

int
pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
{
	register int n;

	for (;;) {
		if (p->rfile != NULL) {
			/*
			 * 0 means EOF, so don't loop if we get 0.
			 */
			n = pcap_offline_read(p, cnt, callback, user);
		} else {
			/*
			 * XXX keep reading until we get something
			 * (or an error occurs)
			 */
			do {
				n = p->read_op(p, cnt, callback, user);
			} while (n == 0);
		}
		if (n <= 0)
			return (n);
		if (!PACKET_COUNT_IS_UNLIMITED(cnt)) {
			cnt -= n;
			if (cnt <= 0)
				return (0);
		}
	}
}

무한 반복하여 패킷을 읽다가 패킷 캡처가 실패하였을 때 종료된다.

 

n = pcap_offline_read(p, cnt, callback, user);

이 코드에 의해서 savefile.c의 pcap_offline_read()가 호출된다.

 

savefile.c:pcap_offline_read()

int
pcap_offline_read(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
{
	struct bpf_insn *fcode;
	int status = 0;
	int n = 0;
	u_char *data;

	while (status == 0) {
		struct pcap_pkthdr h;

		/*
		 * Has "pcap_breakloop()" been called?
		 * If so, return immediately - if we haven't read any
		 * packets, clear the flag and return -2 to indicate
		 * that we were told to break out of the loop, otherwise
		 * leave the flag set, so that the *next* call will break
		 * out of the loop without having read any packets, and
		 * return the number of packets we've processed so far.
		 */
		if (p->break_loop) {
			if (n == 0) {
				p->break_loop = 0;
				return (-2);
			} else
				return (n);
		}

		status = p->next_packet_op(p, &h, &data);
		if (status) {
			if (status == 1)
				return (0);
			return (status);
		}

		if ((fcode = p->fcode.bf_insns) == NULL ||
		    bpf_filter(fcode, data, h.len, h.caplen)) {
			(*callback)(user, &h, data);
			if (++n >= cnt && cnt > 0)
				break;
		}
	}
	/*XXX this breaks semantics tcpslice expects */
	return (n);
}

p->next_packet_op()에 의해 읽어진 패킷이 정상 패킷이면서 필터를 통과하는지 확인 검증하는 함수이다.

callback으로 print_packet()을 전달받아서 실행하게 된다.

 

 

tcpdump.c:print_packet()

static void
print_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
{
	++packets_captured;

	++infodelay;

	pretty_print_packet((netdissect_options *)user, h, sp, packets_captured);

	--infodelay;
	if (infoprint)
		info(0);
}

패킷을 캡처할 때마다 호출되는 callback 함수로, 캡처된 패킷 정보를 출력 및 패킷 수와 지연 상태에 대한 정보를 업데이트 하는 함수이다.

 

print.c:pretty_print_packet()

void
pretty_print_packet(netdissect_options *ndo, const struct pcap_pkthdr *h,
    const u_char *sp, u_int packets_captured)
{
	u_int hdrlen;

	if(ndo->ndo_packet_number)
		ND_PRINT((ndo, "%5u  ", packets_captured));

	ts_print(ndo, &h->ts);

	/*
	 * Some printers want to check that they're not walking off the
	 * end of the packet.
	 * Rather than pass it all the way down, we set this member
	 * of the netdissect_options structure.
	 */
	ndo->ndo_snapend = sp + h->caplen;

        hdrlen = (ndo->ndo_if_printer)(ndo, h, sp);

	/*
	 * Restore the original snapend, as a printer might have
	 * changed it.
	 */
	ndo->ndo_snapend = sp + h->caplen;
	if (ndo->ndo_Xflag) {
		/*
		 * Print the raw packet data in hex and ASCII.
		 */
		if (ndo->ndo_Xflag > 1) {
			/*
			 * Include the link-layer header.
			 */
			hex_and_ascii_print(ndo, "\n\t", sp, h->caplen);
		} else {
			/*
			 * Don't include the link-layer header - and if
			 * we have nothing past the link-layer header,
			 * print nothing.
			 */
			if (h->caplen > hdrlen)
				hex_and_ascii_print(ndo, "\n\t", sp + hdrlen,
				    h->caplen - hdrlen);
		}
	}

패킷 정보를 출력하는 함수이다.

hdrlen = (ndo->ndo_if_printer)(ndo, h, sp); 코드의 흐름을 따라가보겠다.

 

grep 명령어를 사용하여 구조체가 정의된 부분을 찾아가 보았다.

ndo->ndo_if_printer이 get_if_printer()을 가리키는 것을 확인할 수 있다.

 

그리고 해당 함수는 print.c에서 정의하고 있다.

 

 

print.c:get_if_printer

if_printer
get_if_printer(netdissect_options *ndo, int type)
{
	const char *dltname;
	if_printer printer;

	printer = lookup_printer(type);
	if (printer == NULL) {
		dltname = pcap_datalink_val_to_name(type);
		if (dltname != NULL)
			(*ndo->ndo_error)(ndo,
					  "packet printing is not supported for link type %s: use -w",
					  dltname);
		else
			(*ndo->ndo_error)(ndo,
					  "packet printing is not supported for link type %d: use -w", type);
	}
	return printer;
}

주어진 타입에 맞는 패킷 출력 함수를 찾는 함수이다.

 

 

if_printer
lookup_printer(int type)
{
	const struct printer *p;

	for (p = printers; p->f; ++p)
		if (type == p->type)
			return p->f;

#if defined(DLT_USER2) && defined(DLT_PKTAP)
	/*
	 * Apple incorrectly chose to use DLT_USER2 for their PKTAP
	 * header.
	 *
	 * We map DLT_PKTAP, whether it's DLT_USER2 as it is on Darwin-
	 * based OSes or the same value as LINKTYPE_PKTAP as it is on
	 * other OSes, to LINKTYPE_PKTAP, so files written with
	 * this version of libpcap for a DLT_PKTAP capture have a link-
	 * layer header type of LINKTYPE_PKTAP.
	 *
	 * However, files written on OS X Mavericks for a DLT_PKTAP
	 * capture have a link-layer header type of LINKTYPE_USER2.
	 * If we don't have a printer for DLT_USER2, and type is
	 * DLT_USER2, we look up the printer for DLT_PKTAP and use
	 * that.
	 */
	if (type == DLT_USER2) {
		for (p = printers; p->f; ++p)
			if (DLT_PKTAP == p->type)
				return p->f;
	}
#endif

	return NULL;
	/* NOTREACHED */
}

printers 구조체에서 맞는 타입을 검색한다.

 

static const struct printer printers[] = {
	{ ether_if_print,	DLT_EN10MB },
#ifdef DLT_IPNET
	{ ipnet_if_print,	DLT_IPNET },
#endif
#ifdef DLT_IEEE802_15_4
	{ ieee802_15_4_if_print, DLT_IEEE802_15_4 },
#endif
#ifdef DLT_IEEE802_15_4_NOFCS
	{ ieee802_15_4_if_print, DLT_IEEE802_15_4_NOFCS },
#endif
#ifdef DLT_PPI
	{ ppi_if_print,		DLT_PPI },
#endif
#ifdef DLT_NETANALYZER
	{ netanalyzer_if_print, DLT_NETANALYZER },
......

#ifdef DLT_JUNIPER_ETHER
	{ juniper_ether_print,	DLT_JUNIPER_ETHER },
#endif

......

분석하고자 하는 CVE에 맞는 타입은 DLT_JUNIPER_ETHER이다.

 

print-jumiper.c:juniper_ether_print()

#ifdef DLT_JUNIPER_ETHER
u_int
juniper_ether_print(netdissect_options *ndo,
                    const struct pcap_pkthdr *h, register const u_char *p)
{
        struct juniper_l2info_t l2info;

        l2info.pictype = DLT_JUNIPER_ETHER;
        if (juniper_parse_header(ndo, p, h, &l2info) == 0)
            return l2info.header_len;

        p+=l2info.header_len;
        /* this DLT contains nothing but raw Ethernet frames */
        ether_print(ndo, p, l2info.length, l2info.caplen, NULL, NULL);
        return l2info.header_len;
}

ehter_print()를 실행하고 있다.

 

 

print-ether.c:ether_print()

else if (length_type == ETHERTYPE_JUMBO) {
		/*
		 * Alteon jumbo frames.
		 * See
		 *
		 *	http://tools.ietf.org/html/draft-ietf-isis-ext-eth-01
		 *
		 * which indicates that, following the type field,
		 * there's an LLC header and payload.
		 */
		/* Try to print the LLC-layer header & higher layers */
		llc_hdrlen = llc_print(ndo, p, length, caplen, &src, &dst);
		if (llc_hdrlen < 0) {
			/* packet type not known, print raw packet */
			if (!ndo->ndo_suppress_default_print)
				ND_DEFAULTPRINT(p, caplen);
			llc_hdrlen = -llc_hdrlen;
		}
		hdrlen += llc_hdrlen;
	} else {
		if (ethertype_print(ndo, length_type, p, length, caplen, &src, &dst) == 0) {
			/* type not known, print raw packet */
			if (!ndo->ndo_eflag) {
				if (print_encap_header != NULL)
					(*print_encap_header)(ndo, encap_header_arg);
				ether_hdr_print(ndo, (const u_char *)ep, orig_length);
			}

			if (!ndo->ndo_suppress_default_print)
				ND_DEFAULTPRINT(p, caplen);
		}
	}
	return (hdrlen);
}

패킷의 내용을 분석하여 출력하는 함수이다.

패킷의 길이가 너무 긴 경우에는 출력 가능한 만큼만 출력하고 길이를 반환한다.

 

이더넷 페이로드가 어떤 경우에도 포함되지 않는 경우라면 ethertype_print()를 실행한다.

 

print-ether.c:ethertype_print()

int
ethertype_print(netdissect_options *ndo,
                u_short ether_type, const u_char *p,
                u_int length, u_int caplen,
                const struct lladdr_info *src, const struct lladdr_info *dst)
{
	switch (ether_type) {

	case ETHERTYPE_IP:
	        ip_print(ndo, p, length);
		return (1);
......

이더넷 패킷의 필드 유형에 따라 패킷을 부분석하고 출력하는 함수이다.

해당 CVE에서는 ETHERTYPE_IP이기 때문에 IP 패킷에 대한 분석 함수를 수행한다.

 

print-ip.c:ip_print()

if ((ipds->off & 0x1fff) == 0) {
		ipds->cp = (const u_char *)ipds->ip + hlen;
		ipds->nh = ipds->ip->ip_p;

		if (ipds->nh != IPPROTO_TCP && ipds->nh != IPPROTO_UDP &&
		    ipds->nh != IPPROTO_SCTP && ipds->nh != IPPROTO_DCCP) {
			ND_PRINT((ndo, "%s > %s: ",
				     ipaddr_string(ndo, &ipds->ip->ip_src),
				     ipaddr_string(ndo, &ipds->ip->ip_dst)));
		}
		ip_print_demux(ndo, ipds);
	} else {
		/*
		 * Ultra quiet now means that all this stuff should be
		 * suppressed.
		 */
		if (ndo->ndo_qflag > 1)
			return;

		/*
		 * This isn't the first frag, so we're missing the
		 * next level protocol header.  print the ip addr
		 * and the protocol.
		 */
		ND_PRINT((ndo, "%s > %s:", ipaddr_string(ndo, &ipds->ip->ip_src),
		          ipaddr_string(ndo, &ipds->ip->ip_dst)));
		if (!ndo->ndo_nflag && (proto = getprotobynumber(ipds->ip->ip_p)) != NULL)
			ND_PRINT((ndo, " %s", proto->p_name));
		else
			ND_PRINT((ndo, " ip-proto-%d", ipds->ip->ip_p));
	}
	return;

trunc:
	ND_PRINT((ndo, "%s", tstr));
	return;
}

IP 버젼, 헤더의 길이, 패킷의 총 길이를 확인하고 오류 패킷인지를 구분한다.

오류 패킷이 아니라면 헤더 필드를 출력하고, 첫 프래그먼트 처리 후 다음 레벨의 프로토콜을 처리한다.

이때 다음 레벨을 처리하기 위해서 ip_print_demux()를 호출한다.

 

print-ip.c:ip_print_demux()

static void
ip_print_demux(netdissect_options *ndo,
	       struct ip_print_demux_state *ipds)
{
	struct protoent *proto;

again:
	switch (ipds->nh) {

	case IPPROTO_AH:
		if (!ND_TTEST(*ipds->cp)) {
			ND_PRINT((ndo, "[|AH]"));
			break;
		}
		ipds->nh = *ipds->cp;
		ipds->advance = ah_print(ndo, ipds->cp);
		if (ipds->advance <= 0)
			break;
		ipds->cp += ipds->advance;
		ipds->len -= ipds->advance;
		goto again;

......

case IPPROTO_UDP:
		/* pass on the MF bit plus the offset to detect fragments */
		udp_print(ndo, ipds->cp, ipds->len, (const u_char *)ipds->ip,
			  ipds->off & (IP_MF|IP_OFFMASK));
		break;

......

IPPROTO_AH, IPPROTO_ESP 등 다양한 프로토콜에 대한 처리를 수행한다.

crash 파일을 살펴보면 udp인 것을 확인할 수 있다.

 

때문에 IPPROTO_UDP에 해당하는 부분의 코드가 수행된다.

udp_print()함수가 호출되게 된다.

 

print-udp.c:udp_print()

if (!ndo->ndo_qflag) {
		if (IS_SRC_OR_DST_PORT(NAMESERVER_PORT))
			ns_print(ndo, (const u_char *)(up + 1), length, 0);
		else if (IS_SRC_OR_DST_PORT(MULTICASTDNS_PORT))
			ns_print(ndo, (const u_char *)(up + 1), length, 1);
		else if (IS_SRC_OR_DST_PORT(TIMED_PORT))
			timed_print(ndo, (const u_char *)(up + 1));
		else if (IS_SRC_OR_DST_PORT(TFTP_PORT))
			tftp_print(ndo, (const u_char *)(up + 1), length);
		else if (IS_SRC_OR_DST_PORT(BOOTPC_PORT) || IS_SRC_OR_DST_PORT(BOOTPS_PORT))
			bootp_print(ndo, (const u_char *)(up + 1), length);
		else if (IS_SRC_OR_DST_PORT(RIP_PORT))
			rip_print(ndo, (const u_char *)(up + 1), length);
		else if (IS_SRC_OR_DST_PORT(AODV_PORT))
			aodv_print(ndo, (const u_char *)(up + 1), length,
			    ip6 != NULL);
	        else if (IS_SRC_OR_DST_PORT(ISAKMP_PORT))
			 isakmp_print(ndo, (const u_char *)(up + 1), length, bp2);
	        else if (IS_SRC_OR_DST_PORT(ISAKMP_PORT_NATT))
			 isakmp_rfc3948_print(ndo, (const u_char *)(up + 1), length, bp2);

UDP 패킷을 분석 및 추력하는 함수이다.

헤더 추출 및 출발지/목적지 포트를 분석하여 패킷 유형을 판단 후 트래픽인지 확인 및 무결성 검사를 수행한다.

추가로 패킷 유형이 Bootstrap Protocol에 속하는지 확인한다.

 

print-bootp.c:bootp_print()

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;

	ND_TCHECK(bp->bp_secs);

	/* The usual hardware address type is 1 (10Mb Ethernet) */
	if (bp->bp_htype != 1)
		ND_PRINT((ndo, ", htype %d", bp->bp_htype));

	/* The usual length for 10Mb Ethernet address is 6 bytes */
	if (bp->bp_htype != 1 || bp->bp_hlen != 6)
		ND_PRINT((ndo, ", hlen %d", bp->bp_hlen));

패킷 분석 및 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()를 이용하여 16비트를 가져와서 출력한다.

 

bootp_flag_values관련하여 print-bootp.c에 구현되어 있는 것을 확인할 수 있다.

bootp_print()코드를 살펴보면 bittok2str()를 호출한다.

 

 

util-print.c:bittok2str()

char *
bittok2str(register const struct tok *lp, register const char *fmt, register u_int v)
{
    return (bittok2str_internal(lp, fmt, v, ", "));
}

bittok2str_internal()를 호출하고 있다.

 

 

util-print.c:bittok2str_internal()

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

        if (buflen == 0)
            /* bummer - lets print the "unknown" message as advised in the fmt string if we got one */
            (void)snprintf(buf, sizeof(buf), fmt == NULL ? "#%08x" : fmt, v);
        return (buf);
}

Broadcast로 설정되어 있는 경우 lp구조체의 문자열을 가져와서 출력한다.

 

bootp_flag_values 값 설정

static const struct tok bootp_flag_values[] = {
	{ 0x8000,	"Broadcast" },
	{ 0, NULL}
};

0x8000이면 Broadcast 설정, 0이면 NULL로 설정한다.

 

 

 

netdissect_options 구조체

typedef struct netdissect_options netdissect_options;

struct netdissect_options {
  int ndo_bflag;		/* print 4 byte ASes in ASDOT notation */
  int ndo_eflag;		/* print ethernet header */
  int ndo_fflag;		/* don't translate "foreign" IP address */
  int ndo_Kflag;		/* don't check TCP checksums */
  int ndo_nflag;		/* leave addresses as numbers */
  int ndo_Nflag;		/* remove domains from printed host names */
  int ndo_qflag;		/* quick (shorter) output */
  int ndo_Sflag;		/* print raw TCP sequence numbers */
  int ndo_tflag;		/* print packet arrival time */
  int ndo_uflag;		/* Print undecoded NFS handles */
  int ndo_vflag;		/* verbosity level */
  int ndo_xflag;		/* print packet in hex */
  int ndo_Xflag;		/* print packet in hex/ascii */
  int ndo_Aflag;		/* print packet only in ascii observing TAB,
				 * LF, CR and SPACE as graphical chars
				 */
  int ndo_Hflag;		/* dissect 802.11s draft mesh standard */
  int ndo_packet_number;	/* print a packet number in the beginning of line */
  int ndo_suppress_default_print; /* don't use default_print() for unknown packet types */
  int ndo_tstamp_precision;	/* requested time stamp precision */
  const char *program_name;	/* Name of the program using the library */

	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;

스냅샷의 끝 주소를 가리키는 ndo_snapend가 있는 것을 확인할 수 있다.

연산 과정의 코드는 다음과 같다.

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

gdb에서 구조체를 바로 확인해볼려고 했으나 오류로 인해 확인 불가 상황이라 직접 들어가보았다.

 

ndo_snapend의 값을 확인할 수 있다.

 

crash파일에는 어떻게 정의되어 있는지 비교해보겠다.

실제 파일의 크기보다 더 큰 값을 읽게 되면서 취약점이 터지게 된다.

 

 

 

6. 패치 파악


패치된 버젼과의 비교를 수행했다.

        ND_TCHECK(bp->bp_flags);
	   	ND_PRINT((ndo, ", Flags [%s]", 
bittok2str(bootp_flag_values, "none", EXTRACT_16BITS(&bp->bp_flags))));

ND_TCHECK이라는 매크로를 추가하여 bp->bp_flags가 snapend를 넘지 않았는지 확인하는 코드를 추가하였다.

 

/* Bail if "var" was not captured */
#define ND_TCHECK(var) ND_TCHECK2(var, sizeof(var))


#define ND_TCHECK2(var, l) if (!ND_TTEST2(var, l)) goto trunc


#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)))

 

728x90
반응형