The return-to-csu (ret2csu) technique provides a universal gadget for whenever the program image itself does not contain many useful gadgets. See ret2csu for details on how it works in practice.
Requirements
- Program runs on Linux.
- Program is linked against libc.
For addresses mentioned in this guide, refer to the Ghidra disassembly of __libc_csu_init borrowed from the GitHub Gist, reproduced below.
We can extract two useful gadgets from the __libc_csu_init
:
- Caller gadget at
c0
(004033c0
) setsrdx
(r13) andrsi
(r12). In addition, it setsedi
atc6
, but this doesn’t help us set the fullrdi
, so we need to use apop rdi
gadget later. - Popper gadget at
d9
setsrbx
,rbp
,r12
,r13
,r14
, andr15
.
To build a ret2csu payload that calls a function with three arguments (stored in rdi
, rsi
, and rdx
):
- Return to the popper gadget to set registers. Initialize them properly so that the caller gadget does not misbehave.
- Return to the caller gadget to set
rdx
andrsi
.- Bypass call instruction. The
call [R15 + RBX*0x8]
atc8
is problematic, because we don’t want to call anything before havingrdi
ready (which will have to happen after the whole gadget). Therefore, we need to find an address of a pointer to_init
or_fini
usingx/50x &_DYNAMIC
in gdb so that the call doesn’t segfault or modify registers. - Beware of
rbx
andr14
comparison atd0
. Initialize them such that thejnz
instruction doesn’t branch. - Make sure there’s a garbage value on stack since
rsp
is incremented by the gadget atd5
. - The caller gadget doesn’t have a return address, so we continue onto the popper gadget again. Just put some garbage on the stack.
- Bypass call instruction. The
- Return to a
pop rdi; ret
gadget to setrdi
. - Return to the target function. Voila!
**************************************************************
* FUNCTION *
**************************************************************
undefined __libc_csu_init()
undefined AL:1 <RETURN>
__libc_csu_init XREF[4]: Entry Point(*),
_start:00401dfa(*),
_start:00401dfa(*), 004ab2f0(*)
00403350 f3 0f 1e fa ENDBR64
** some instructions **
LAB_0040339d
0040339d 4c 8d 3d LEA R15,[__frame_dummy_init_array_entry]
5c 4c 0b 00
004033a4 4c 8d 35 LEA R14,[__do_global_dtors_aux_fini_array_entry]
65 4c 0b 00
004033ab e8 50 dc CALL _init
ff ff
004033b0 4d 29 fe SUB R14,R15
004033b3 49 c1 fe 03 SAR R14,0x3
004033b7 74 1c JZ LAB_004033d5
004033b9 31 db XOR EBX,EBX
004033bb 0f 1f 44 NOP dword ptr [RAX + RAX*0x1]
00 00
LAB_004033c0
004033c0 4c 89 ea MOV RDX,R13
004033c3 4c 89 e6 MOV RSI,R12
004033c6 89 ef MOV EDI,EBP
004033c8 41 ff 14 df CALL qword ptr [R15 + RBX*0x8] <-- 1
004033cc 48 83 c3 01 ADD RBX,0x1
004033d0 49 39 de CMP R14,RBX <-- 2
004033d3 75 eb JNZ LAB_004033c0 <-- 3
LAB_004033d5
004033d5 48 83 c4 08 ADD RSP,0x8 <-- 4
004033d9 5b POP RBX
004033da 5d POP RBP
004033db 41 5c POP R12
004033dd 41 5d POP R13
004033df 41 5e POP R14
004033e1 41 5f POP R15
004033e3 c3 RET <-- 5