2017-2018-2 20155228 《网络对抗技术》 实验一:PC平台逆向破解
2017-2018-2 20155228 《网络对抗技术》 实验一:PC平台逆向破解
实验内容
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
这几种思路,基本代表现实情况中的攻击目标:
运行原本不可访问的代码片段
强行修改程序执行流
以及注入运行任意代码。
实验要求
评分标准
截图要求
-
所有操作截图主机名为本人姓名拼音
-
所编辑的文件名包含自己的学号
如未按如上格式要求,则相应部分报告内容不记成绩。
报告内容
-
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码(0.5分)
-
掌握反汇编与十六进制编程器 (0.5分)
-
能正确修改机器指令改变程序执行流程(0.5分)
-
能正确构造payload进行bof攻击(0.5分)
报告整体观感
-
报告格式范围,版面整洁 加0.5。
-
报告排版混乱,加0分。
文字表述
-
报告文字内容非常全面,表述清晰准确 加1分。
-
报告逻辑清楚,比较简要地介绍了自己的操作目标与过程 加0.5分。
-
报告逻辑混乱表述不清或文字有明显抄袭可能 加0分。
实践指导
实验步骤
1.关于在64位kali上运行32位pwn1的问题
直接照着以下教程一步一步走就可以了
问题解决方案教程
值得一提的是教程中源不一定可以用,至少对我的kali来说不能用,导致后面下载安装软件的时候失败,所以后来我又重新找了一些其他的源。
#auto
deb http://http.kali.org/kali kali-rolling main non-free contrib
#auto
deb http://http.kali.org/kali kali-rolling main non-free contrib
#中科大的源
deb http://mirrors.ustc.edu.cn/kali kali-rolling main contrib non-free
deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main contrib non-free
deb http://mirrors.ustc.edu.cn/kali-security kali-current/updates main contrib non-free
deb-src http://mirrors.ustc.edu.cn/kali-security kali-current/updates main contrib non-free
deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
#阿里云源
deb http://mirrors.aliyun.com/kali sana main non-free contrib
deb http://mirrors.aliyun.com/kali-security/ sana/updates main contrib non-free
deb-src http://mirrors.aliyun.com/kali-security/ sana/updates main contrib non-free
deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib
deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib
#浙大
deb http://mirrors.zju.edu.cn/kali kali-rolling main contrib non-free
deb-src http://mirrors.zju.edu.cn/kali kali-rolling main contrib non-free
#东软大学
deb http://mirrors.neusoft.edu.cn/kali kali-rolling/main non-free contrib
deb-src http://mirrors.neusoft.edu.cn/kali kali-rolling/main non-free contrib
#重庆大学
deb http://http.kali.org/kali kali-rolling main non-free contrib
deb-src http://http.kali.org/kali kali-rolling main non-free contrib
# deb cdrom:[Debian GNU/Linux 2018.1 _Kali-rolling_ - Official Snapshot amd64 LIVE/INSTALL Binary 20180126-21:23]/ kali-last-snapshot contrib main non-free
# deb cdrom:[Debian GNU/Linux 2018.1 _Kali-rolling_ - Official Snapshot amd64 LIVE/INSTALL Binary 20180126-21:23]/ kali-last-snapshot contrib main non-free
2.关于在Kali上安装VirtualBox增强功能的问题
以前在信息安全系统设计基础课用ubuntu时有安装增强功能以实现共享剪贴板和文件夹的功能,宿主机和虚拟机用邮箱来传递数据还是挺麻烦的。
百度上很容易可以找到教程,都是大同小异的,一般就是以下的流程
安装过程中很容易出现失败的情况如下:
Building the main Guest Additions module [FAILED]
可以通过输入以下命令查看记录详细错误信息的日志:
cat /var/log/vboxadd-install.log
错误信息为:
unable to find the sources of your current Linux kernel. Specify KERN_DIR-<directory> and run Make again
解决办法在百度上五花八门,总的来说是需要提前安装一些依赖性软件,比如说gcc
、make
、kernel-header
等等,大概有这几种解决办法:
apt-get install dkms
apt-get install -y linux-headers-$(uname -r)
apt-get install gcc make kernel
等到要安装软件的时候就能感受到一个好的源的重要性了,有的源根本连不上,有的源卡得心态爆炸,源不好使做什么都难受。
把这些必要的软件准备好了就可以顺利安装增强功能了吗?
可能还会出现相同的错误导致安装失败。
这时候真的想把电脑给砸了
后来找了很久找到一个教程,解决这个问题的办法比较少见:在线安装VirtualBox增强功能
kali 安装 VirtualBox 增强功能 完整解决办法
apt-get install -y virtualbox-guest-x11
安装完成过后打开共享文件夹就可以在桌面看到共享文件夹了
3.关于输入法的问题
我在查怎么安装kali增强功能的时候才发现kali居然没有中文拼音输入法,这就很尴尬了
百度找到一个教程
主要是这两个命令
apt-get install fcitx
apt-get install fcitx-googlepinyin
跟着教程走,安装完成后配置输入法
呃...配置输入法?为什么界面不一样,我到哪里去配置输入法?!
百度一下,我还是不知道,很绝望啊
一般来说输入法也是一个软件,是可以找到图标点进去配置输入法的,还是自己再找找吧
找了很久终于找到了配置输入法
我把Google输入法设置为默认输入法,然后注意到是左shift激活输入法,ctrl+space切换输入法
好了就此就可以开始做实验了
4.直接修改程序机器指令,改变程序执行流程
手工改可执行文件有时候可以用来跳过验证过程,因为这是直接修改机器指令,所以无需利用漏洞
实验要用到pwn1
是一个可执行程序,其代码已经被翻译成机器语言了,所以使用
objdump -d pwn1 | more
其中
objdump
:对象导出-d
:反汇编|
:管道符
其实后面那个| more
加不加都无所谓,加了是为了方便阅读
反汇编得到三列结果
-
第一列是内存地址,也就是运行起来是的地址,当然打开文件时是看不到地址的
-
第二列是机器指令,注意不同的cpu有不同的指令集
-
第三列是汇编指令,一般汇编指令与机器指令是一一对应的
看一下主函数main
注意到有一行代码是调用foo
函数,其实main
调用foo
是通过访问foo
所在内存地址来实现的,只要修改代码就可以使程序跳到其他函数上面去
现在想要通过修改调用地址实现对getshell
的调用,getshell
的内存地址是0x0804847d,foo
的内存地址是0x08048491,
分析至此,接下来该做什么就很明确了:
- 做差求相对位移:
getshell
的内存地址-foo
的内存地址,地址之差0x14 - 做加法或者做减法修改代码
- 反汇编查看修改结果是否正确
- 运行程序
机器指令e8
对应汇编指令call
,考虑到linux是小端,低地址位为d7
,高地址位为ff
,所以要修改的数据就是d7
由于无法确定是是d7+14
还是d7-14
,所以两个都试一下
经过尝试可以确定是做减法
所以是应该将d7
修改为c3
具体的操作细节在实验指导里有介绍
root@KaliYL:~# cp pwn1 pwn2
root@KaliYL:~# vi pwn2
以下操作是在vi内
- 按ESC键
- 输入如下,将显示模式切换为16进制模式
:%!xxd - 查找要修改的内容
/e8d7 - 找到后前后的内容和反汇编的对比下,确认是地方是正确的
- 修改d7为c3
- 转换16进制为原格式
:%!xxd -r - 存盘退出vi
:wq
其中:%!xxd
是把代码转成16进制数,不转根本就是一堆乱码,当然最后要用:%!xxd -r
改回来
修改并保存之后再次反汇编查看结果
运行程序pwn1
和pwn2
5.通过构造输入参数,造成BOF攻击,改变程序执行流
主函数mian
调用子函数foo
的时候会开辟一块内存空间作为堆栈,用来放子函数的各项参数还有返回地址。
pwn1
的正常功能是输入什么打印什么,如果输入的数据超过堆栈中用来存放子函数参数的内存单元时就会发生缓冲区溢出,溢出的数据就会填入到存放返回地址ret
的内存单元中,从堆栈的结构来看,形成缓冲区溢出覆盖返回地址的是输入数据的后半部分。关键的问题在于准确计算出溢出到ret
所处内存单元需要多长的数据,而且也要知道是数据的哪一部分会写入ret
内存单元中
首先确定哪几位数据会覆盖返回地址
使用gdb
对pwn1
进行调试,主要是可以随时看各个寄存器的值,主要是看eip
寄存器的值,因为eip
寄存器总是放着cpu会执行的下一条指令所处的内存地址。
输入1111 1111 2222 2222 3333 3333 4444 4444 5555 5555
就可以看出这时候已经发生缓冲区溢出了
使用info -r
命令查看各个寄存器的值,发现eip
寄存器的值是0x35353535,这是5的ascii码的16进制表示形式
但是之前输了8个5进去,还得想办法确定到底是哪四个5进入了eip
所以再运行程序一次,这次输入把那8个5换成不一样的值
1111 1111 2222 2222 3333 3333 4444 4444 1234 5678
现在可以确定是1234
会进入eip
了
然后确定getshell
的内存地址
使用反汇编命令可以确定getshell
的内存地址为0x0804847d
最后想办法把getshell
地址作为foo
函数输入的一部分溢出到eip
中
有一点值得注意,linux是小端操作系统,所以说输入数据时会比较别扭,应该是输0x7d840408
还有一个问题是,foo
函数的输入会被转换为十六进制的ascii码,直接输入getshell
的十六进制地址肯定有问题
解决的办法就是使用perl
语言提前把foo
函数的输入写进文件,再把文件作为foo
函数的输入
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
其中
print
:执行print
函数打印字符串\x0a
:表示回车,没有这个foo
函数就不知道什么时候输入结束
然后用xxd input
查看input
文件内容是否是0x7d840408
确认无误就可以把input
作为foo
函数的输入
(cat input; cat) | ./pwn1
这里的管道符意为将打印值(其实就是input
的值)作为./input
的输入值
6.注入Shellcode并执行
之前都是基于原来程序就有的函数getshell
,现在希望执行自己输入的代码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\
getshell
的内存地址可以通过反汇编查到,但是shellcode
的内存地址怎么确定呢?
答案是在运行程序是查看堆栈的所处的地址,堆栈中buffer
也就是存放foo
函数参数的内存地址就是shellcode
的内存地址
shellcode
的在运行时所处的内存地址是动态分配的,每次运行程序都在变,所以说每次找到的shellcode
的地址只能在这次程序运行时使用,下次就又得重新找
为了图方便,所以先把shellcode
或者说是foo
的参数在内存的地址中固定下来
另外,程序为了防止此类攻击,一般都设置堆栈不可执行,所以攻击之前要设置堆栈可执行
execstack -s pwn1 //设置堆栈可执行
execstack -q pwn1 //查询文件的堆栈是否可执行
more /proc/sys/kernel/randomize_va_space
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
more /proc/sys/kernel/randomize_va_space
堆栈中存储返回地址的内存单元的相对位置是固定的,shellcode
可以放在ret
之前也可以放在ret
之后,这里把shellcode
放在ret
的后面
foo
函数输入中填写ret
的位置是相对固定的,但是ret
的值是什么需要确定
在堆栈中foo
函数输入参数的内存空间大小和shellcode
的长度往往是不一致的,所以foo
函数的输入除了shellcode
和ret
还要有nop
作为填充
现在的问题就落在寻找shellcode
的内存地址或者说堆栈中foo
函数输入参数的内存地址
通过构建一个完整的但是ret
的值是随便写的foo
函数输入来确定ret
的值
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
注意这里的代码是把shellcode
放在ret
前面的,但是不影响找ret
的内存地址,也就是说只需要找到1234的位置,根据之前把shellcode
放在ret
之后的结构,ret
地址+0x4就是shellcode
首地址
这个截图的红框框画的不对,因为教程把我坑了一把,shellcode
首地址是0xffffd210
现在可以构建一个有正确ret
的foo
函数输入input_shellcode
并进行注入了
PSP时间统计
步骤 | 耗时 | 百分比 |
---|---|---|
需求分析 | 20min | 8% |
设计 | 40min | 16% |
代码实现 | 120min | 50% |
测试 | 20min | 8% |
总结分析 | 40min | 16% |
实验收获和感想
虽然上课没有跟着老师做实验但是做了笔记,其中包括很多细节的处理和理解,这是实验知道里面没有的,课后再动手做实验时候结合着笔记再看看实验指导,可以说每一步都是很清楚,所以说不求在课上就把实验结果做出来,只求在课上把实验思路和细节搞懂。
什么是漏洞和漏洞的危害
个人认为漏洞是程序设计时无意留下的,容易被攻击者利用的程序设计缺陷,危害就有很多了,包括程序、设备、数据的非法访问、窃取、篡改等等