Analyzing CVE-2022-23093 (FreeBSD Ping Buffer Overflow)

About The Project

In November of 2022 the FreeBSD project announced CVE-2022-23093, a buffer overflow vulnerability in the ping utility. This blog post will analyze the vulnerability as well as document the steps to setup said environment to analyze the root cause of the issue with gdb. cve_logo.png

Illuminating the Security Advisory

The FreeBSD advisory gave the following description to the vulnerability:

ping reads raw IP packets from the network to process responses in the pr_pack() function. As part of processing a response ping has to reconstruct the IP header, the ICMP header and if present a “quoted packet,” which represents the packet that generated an ICMP error. The quoted packet again has an IP header and an ICMP header.

The pr_pack() copies received IP and ICMP headers into stack buffers for further processing. In so doing, it fails to take into account the possible presence of IP option headers following the IP header in either the response or the quoted packet. When IP options are present, pr_pack() overflows the destination buffer by up to 40 bytes.

As stated, the “quoted packet” is the original ICMP message that is embedded within an ICMP error packet. To visualize this, I’ll ping a host on my network that does not exist, and we’ll see the orignal packet (an ICMP echo request) embedded in an ICMP destination host unreachable packet.

First lets take a look at a normal ICMP echo request. Below we see a ICMP Echo Request from FreeBSD client 192.168.122.241 to a non-existant destination host of 10.10.0.22.

icmp_echo_request.png

Referencing the CVE description,

As part of processing a response ping has to reconstruct the IP header, the ICMP header and if present a “quoted packet,” which represents the packet that generated an ICMP error. The quoted packet again has an IP header and an ICMP header.

Below we see an ICMP Host Unreachable packet in blue and the original echo request embeded as a “quoted response” highlighted in red.

icmp_host_unreachable.png

Now, onto the vulnerable function pr_pack. Referencing the CVE description again,

it fails to take into account the possible presence of IP option headers following the IP header in either the response or the quoted packet. When IP options are present, pr_pack() overflows the destination buffer by up to 40 bytes.

Highlighting the quoted portion of the destination unreachable packet below in green, the header length is only 20 bytes with the abscence of the IP options field. The size of the IP struct for FreeBSD is only 20 bytes (demonstrated later in the blog), therefore our addition of an IP options field would create a larger packet, thus overflowing the destination structure.

icmp_host_unreachable_options.png

Patch Analysis

Referencing the patch file in the code snippet below, the memcpy function is removed which performs a copy based on the header length (hlen) into the IP struct (&ip). Effectively, the header length can be user controlled via packet crafting software and thus overflow the IP struct. There are other places the patch file also replaced some functionality, but for the purpose fo this demo we will focus on this memcpy function at line 1149 in ping.c.

.........................truncated.........................................
 	memcpy(&l, buf, sizeof(l));
 	hlen = (l & 0x0f) << 2;
-	memcpy(&ip, buf, hlen); // <--- right here
 
-	/* Check the IP header */
+	/* Reject IP packets with a short header */
+	if (hlen < sizeof(struct ip)) {
.........................truncated.........................................

Instrumenting Analysis with GDB

During the installation of FreeBSD, you can choose to include the source code of the core utilities. You’ll find the ping source code within /usr/src/sbin/ping. Here, you can build a debug version of ping to resolve symbols with gdb. Next, identifying our function of interest from the patch file and the CVE description, pr_pack.

Now to instrument our debugging, create a file called gdbscript(this could be called anything) with the following contents in the script block below. With gdb an end user can invoke debugging the ping utility, and feed arguments into the gdb console via the -x cli argument. From the first line down, this sets the user line argument to send 1 ICMP echo request to a destination host of 10.10.32.32 which does not exist on my network. This is intentional as, an ICMP destination host unreachable response with the quoted packet is expected as a response. This enable fiddling with the embedded (“quoted”) options field of the IP header to make the IP header size larger than expected for the IP struct. Next, b ping.c:1149specifies to break at a specific offset within the ping.c file. Finally, the r command runs the binary. Upon hitting the breakpoint within the pr_pack function, 10 lines of code are listed via l (provided via the deubgging symbols), in addition to printing the contents of the hlen and buf variables.

set args -c 1 10.10.32.32
b ping.c:1149
r
l
p hlen
p buf

This handy script allows us to semi-automate the process of putting our target binary ping in the desired state to observe the contents of the buffer and the size of the header. I’ve also added some deubgging statements for screenshot purposes of this write up. After the FreeBSD ping echo request is sent, a scapy script is triggered to send a forged packet. When the ping utility recieves this packet, the breakpoint it hit, and the hlenand buf variable are printed. The image below shows gdb stopping and printing the appropriate variables along with printing the size of the appropriate structures.

gdb_stack_trace.png

Printing out hex bytes of the bufvariable and comparing it to the wireshark packet capture, we can identify exactly where in the header the bytes are being controlled, thus influence what over writes the ip struct.

wireshark_bytes.png

Getting Lazy with Proof-of-Concepts

When fiddling with controling content to the buf variable, I was running into an issue where a NULL byte would truncrate my buf array, thus not overwriting the ip struct with the entire contents of the buf variable. I later was able to overcome this, but it got me fiddling around with other gdb commands such as modifying the variables value directly. Given we’re in gdb, one could also modify the content of the user variables here to demonstrate the buffer overlow rather than fighting with their proof-of-concept script.

Its important to note the difference between “you are trying to save time” on a weekend project for your silly blog (like this one) or you cannot actually exploit said vulnerability “as is”. Knowning the difference is key in demonstrating whether or not a vulnerability can be exploited in the “wild” or if its just “debugger magic” making the modifications. For demonstration purposes, if we were to modify the gdb script to set the variables at the same breakpoint, a scapy script would not be necessary as we can just instrument the desired variable values.

set args -c 1 10.10.32.32
b ping.c:1149
r
l
set hlen=60
set buf="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
p hlen
p buf

Highlighted in purple are 0x41 ASCII “A"s showing that the values within buf were indeed copied over.

gdb_stack_trace_2.png

Capsicum & Ping

FreeBSD has a sandbox technology called “capsicum”. Capsicum limits the capabilities of a given process, similar to other Linux sandbox technologies. By default, Capsicum is enabled in some versions of FreeBSD (I believe 13 and on but could not find hard documentation here) for ping and other utilities. While ping is a setuid binary owned by root (due to its requirement of creating raw sockets), the sandbox technology makes this less of a concern. Running ping with truss, we can see capsicum at work via cap_enter and then the appropriate capabilities are being set on the ping process to limit the processes interaction with the system.

cap_enter.png

Beyond the Blog

All supported versions of FreeBSD are affected and should be updated as soon as possible. The adivsory provides the following commands for individuals runnign FreeBSD 13 and later.

$> freebsd-update fetch
$> freebsd-update install

I was finishing up my write-up when I saw Live Overflow had published a video on the same CVE. Live Overflow is a master at the craft, definitely give it a watch!

References