B站pwn教程笔记-8
接着上次的习题刷,然后补充新的知识。这开始就接触花式栈溢出了
pwn3(ret2libc较难)
上次已经知道大致思路,现在看看怎么实现。
使用命令 ldd 可看出连接的LIBC是哪个,如下图所示。(第一行)
这里只是一个软连接,保险起见,我们可以file这个地方,看真实地址。
这句话意思是相同目录(i386-linux-gnu)下夫人libc-2-28才是真的libc本体。
其实,libc如果不是大版本更新,一般来说一些函数如write偏移啥的基本应该不会有大变化。但是后面要学Heap有个结构是高版本的才有,和远程libc不一样不一定可以打通。
有时候链接还不能维持太长时间,服务器看消耗带宽太多了就关了。
payload构造的时候需要注意:因为不知道system地址,我们可以泄露got表后,再次执行一次脆弱函数,再次实现栈溢出进行后续操作。这时候我们的返回地址就覆盖为后门函数地址。这里要注意用symbols表示函数名字哦,和libc一样。
后面write的参数跟这个就可以了。write是三个参数,根据参数从右往左压栈,1反而是最后压栈,也就是最接近ebp,所以我们这样写。以后记住,因为写脚本是低地址往高地址写,从左往右写入参数即可。(32位)4是因为32位是4字节的地址。
其实泄露出的地址存在不可见字符,如下所示,咱们怎么转化呢?
哈哈,早就听闻但是没有实践过的u32出马了。这就可以搞定。然后继续溢出就行
同时注意recv可以跟长度,远程的时候我们可以根据这来接受泄露的got表地址。
说一个知识点,pwntools有个debug模式。
出来的内容如下所示
会把每一次IO都输出出来,方便哪里卡住了我们排查。
栈初始结构&&金丝雀保护
首先先来看看main函数前面的区域,正常来说start后面是main,这时候main的栈帧被创建,那他前面的栈空间仍然有东西存放,这部分存放了什么呢?
start纯汇编写的,压入了很多东西。在start函数下断点,这时候还没有往里面压东西,但是我们查看栈空间却发现里面有一大堆字符串
这都是shell的环境变量。还有个动调技巧,可以直接输入return将一个函数跳过去了。有的程序main函数无法被gdb识别,可以打开IDA复制其相关的地址即可。有的程序去除符号表,我们不知道函数名,但是有时候IDA会自动识别标注main的。识别不了的就标注为sub_xxx了。
执行完start函数后多了很多东西
具有金丝雀保护的程序,在发生栈溢出不会提示段错误,会提示下图。
一般来说,金丝雀在ebp的下方(具体实现还得看ida),栈帧被销毁,也就是函数返回了,要使用ebp和addr的值,会先检查金丝雀值有没有被篡改。如果篡改,调用下面的这个函数,然后退出程序。
一般程序有下面这个语言,基本就是在读取canary。fs是一个寄存器。这样,v4保存的就是canary,可以在栈空间看v4在栈帧的位置。
同时,可以查看相应的汇编代码,更进一步看看v4是怎么进入栈的(视频的程序是通过mov和rax寄存器)
下面代码就是检查。
smashes-canary习题练习
这一题是PCTF的真题,代码乱tm跳转,看着和基础训练题真是不一样。难看死了。而且貌似其所用的libc版本很老,新虚拟机环境的libc已经把他这道题考点的漏洞给修复了,咱们本地gdb调试不了了。
这道题很独特,写了这样一句话:意思为服务器会在相同地方存放一个FLAG
但是很遗憾的是,我们执行程序如果输入了数据,会覆写掉这里的flag。看代码,
如果啥都不输入,也会拿不到flag,因为我们发现了会把它清零的语句
现在按我自己的想法,发散思维,要么想办法绕过这个程序的限制,要么老老实实找漏洞栈溢出拿shell。看老师接下来怎么讲述。好吧,现在老师说由于金丝雀,无法栈溢出。估计按我现在的水平,还不考虑说攻克他。
知识点:ELF文件比较小的时候,可以通过编译时某些方法把一些段和节在虚拟内存映射出2份。
在gdb,可以看内存空间,可以看到这里就有另外一份PCTF。暂时不知道讲师是怎么知道这里有的,后面慢慢听吧。
在
pwngdb
(一款增强GDB
功能、用于漏洞利用分析的插件 )里,x/20x
是GDB
内存查看命令x
的一种用法:
- 第一个
x
:是GDB
中用于查看内存内容的命令。/
:是分隔符,用于分隔命令和参数。20
:表示要显示的内存单元数量 。这里即指定显示 20 个。- 第二个
x
:是格式说明符,表示以十六进制格式显示内存单元。不同格式说明符含义不同,比如d
表示十进制、o
表示八进制 、c
表示以字符形式显示、s是字符串。
老师说先放一下,失误了,那我也先暂时不写这一块。
这道题用的漏洞太老了,实际遇到的这种情况也不多。
用的是金丝雀检查函数的漏洞(已经被修复了)。在检验不通过的时候,正常程序会报错,报错信息有一行是程序名。这一行咋来的呢?
可以看上图,0x0的位置是漏洞已经被修复了,实际情况来说这里写的是代表程序名字的字符串的地址,我们溢出只要把这里的地址改为程序上面写的“这里是flag”的地址就可以了。就算检验不通过,我们目的就是利用检验函数的漏洞实现攻击的。
栈迁移
期待这里好久了。终于开始讲述了。
先给一页PPT。
解释一下Application那部分内容。1.栈溢出长度不够:很显然,有可能到prev_ebp就无法覆盖了,或覆盖不完全ROP(有很多pop|ret这种)栈溢出长度就够了。
首先介绍最简单的情况:只可以覆盖ebp的值。这时候我们还能劫持执行流吗?可以。
函数有俩收尾汇编,leave和ret。leave就是相当于mov esp,ebp和pop ebp,这时候esp指向返回地址(我们没法控制这个返回地址)ebp已经pop到另一个地方了,下一次push ebp的时候,ebp已经跑路了。
这个对环境要求比较高,看样子是先暂时不讲述。
现在来看看 更通用的
此时我们的栈布局如图所示。上面的箭头是esp指向ret_addr,ebp通过覆盖先前ebp的值已经迁移过了,我们现在就是通过布置gadget让esp也迁移过去,这样就欺骗程序有一个新的栈帧了。gadget可以是mov esp,ebp之类,只要能实现栈迁移即可。
其实栈迁移较为灵活,也没有固定的套路。但是核心思想是一样的,由于都是在栈顶进行栈的数据进出操作,只要我们控制住esp就可以了(ebp在实际中也只是辅佐左右,辅佐esp)
下面继续开始刷题,上面只是稍微讲解一下思想,具体的还得靠自己做题领悟了。
PWN3_X64(ret2libc)
思路其实和pwn3差不多,只不过变成64位。
这里要注意我们只能控制write前俩参数,运气好,题目的rcx寄存器直接就是大于8的数字,这也是看运气的一个体现了。左边是第一次的ROP链,右边是第二次。
当然,也可以利用ret2csu,可能就不需要靠运气了。
格式化字符串漏洞引入
先看x86平台
格式化字符串就是那%xx。当然格式化字符这个概念就不过多介绍了。
就算不给后面的参数,也会读出来数据。这是为啥呢?
gdb调试看看。其中gcc -g可以带着源码调试
这里注意一下,如果read里面输%x,printf会认为是格式化字符的,见下图
这时候它打印的却是栈上内容!
其实是因为,printf找参数,往上数2个字长。(函数调用约定,有push addr和ebp俩阻隔的,平时rop也是这样,比如system_addr+++next_addr+++参数,实际上参数就在addr的后面的后面,下图明显%x上面是print的地址。因为先scanf输入这四个。这四个入栈了,再print。
我们这四个方框正常就是%x后面跟的参数,但是这里并没有给参数,printf还会以为这是参数地址,就把这些内容打印出来了。
下次继续深入学习格式化字符串漏洞。这次先到这里。