B站pwn教程笔记-5
复习和回顾
首先复习一下ELF文件在内存和磁盘中的不同。内存只关注读写这权限,会合并一些代码段。
动态链接库只在内存中单独装在一份
因为很多软件都要用动态链接库了,不可能一个个单独复制一份。但是在有的调试环境下会单独显示出来各一份。
ld.so是装载器。
为什么用栈来储存,因为函数都是后调用先返回,刚好符合栈的后进先出的特点。
ebp指向先前EBP的字节的开头部分(默认小端序)返回地址上面紧跟的就是子函数参数,
为了绕过一些保护手段,攻击手段不断升级了
ret2libc
最后EIP地址改为libc库里面的位置
pwntools获取某函数在got表项地址。
左图为64位地址空间,右边是32位。(没有开启pie)
1.右边长度长,高地址6字节。
2.地址起始位置不一样,具体看图,64是400000,32是8048000。下面前半部分是关闭金丝雀,后面是关闭PIE
nop滑梯
nop的机器码是0x90。见下图,假如没有开启站保护,但是这虚拟内存有时候不断和内核有随机偏移量,因此我们无法得知shelllcode的具体地址,就可以溢出大量的nop,然后让eip指在大致的地方,不断执行nop最终到shellcode。
下图是 ret2shellcode和ret2libc的对比。
栈这样构造的原理
此时栈的返回地址已经被覆盖为system。这时候system函数就来找参数。但是需要注意他的长参数在他上方两个字长。exit函数同理。但其实exit并不是必须有,有的题只要拿到system就够了。
为啥会这样呢?正常情况而言应该如下图,调用函数的时候父函数保存的参数下方有ret addr和prev ebp,应该隔了俩东西,实际上上图他们的参数仅仅相隔一个东西。
一般子函数父函数分界线就是下图所示。
解释:大家看system函数的汇编部分。其实基本上一个子函数都是以push ebp开头的,也就是说call指令是提供了ret addr这部分,实际上的prev ebp是子函数自己汇编第一句话压入栈之中。我们溢出的时候是控制在一个字节,实际上执行到system栈自己就搞了一个ebp,并不矛盾。关键是不能错误地理解为call system执行的时候就已经会自动生成prev ebp了。
这里要特别注意,还是要从底层汇编代码入手开始分析。这里的两个字长是因为函数本身有push ebp这个指令,但不是所有的情况都是如此,还是要具体情况具体分析。比如调用多个函数,有时候还需要结合pop_ret等指令。如下图(通用结构)上面的是2个函数或以下的简化版本。
动态讲述栈执行过程
下图就是我们构造出来的栈帧了。
首先eip指在system,执行system,函数自己压入栈帧一个rev ebp,栈结构变为下图。
不要忘记调用约定(32位)参数从右往左入栈,反之,函数找自己参数也是遵循这个规则。exit函数同理,不再赘述了。
如何得知system、exit函数地址
就算手上有原来的libc文件,乍一看也肯定不知道。因为有ASLR地址随机化。
现在复习一下昨天的内容,也就是动态链接过程。最后都会指向libc的system。
也就是说,不管程序在什么地方引用过库函数,肯定在plt表就有对应的表项。如果没有,也有攻击手段,会通过泄露大量信息之类的拿到地址。,以后应该会学习到。
所以不难理解,直接让代码跳到system@plt,就等同于执行system,这也就解答了如何得知他的地址的疑问,我们只要让这个函数运行起来就可以了。
实战
漏洞还是很明显
一般情况下先要给“bin/sh”给read进内存空间中🤔
用这行代码直接找到system的plt地址
这行代码可以快速列出字符串,配合grep可以过滤
之前介绍过,可以找字符串的地址。next是迭代器。
PAYLOAD