《网络对抗技术》Exp1 PC平台逆向破解——20184303邢超远
前期准备
一、逆向及Bof基础实践说明
1.1实践目的
本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
1.2实践内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入并运行一段指定的shellcode。
1.3基础知识
- 熟悉Linux基本操作。掌握用户切换命令,知道如何获取root权限;掌握文件下载命令;懂得绝对路径、相对路径的使用方法;能查找指定的文件的路径,并通过路径操作进入到相应的文件夹,运行一个指定的文件;掌握管道“|”的含义和用法;掌握输入、输出重定向的知识;遇到陌生的命令,要掌握通过help、man和info等命令获得相应命令的介绍和帮助的方法;会使用gdb、vi;掌握其他一些必备的命令,包括反汇编等。
- 理解缓冲区溢出(BufferOverflow,Bof)的原理。能看得懂汇编、机器指令、EIP、指令地址、ELF文件格式。
- 掌握机器指令与汇编语言的关系,认识并理解常用的汇编语言,包括MOV、SUB、NOP, JNE, JE, JMP, CMP等。汇编语言和机器码是一一对应的,我们在Intel硬件手册里面可以轻松找到这些对应关系。详见https://wenku.baidu.com/view/eeb46ed184254b35eefd34bf.html。
- 掌握堆栈的含义、概念和用法,知道函数调用和堆栈的关系,看到一段反汇编代码,要能够理解堆栈在其中的运行变化过程。
二、pwn文件
先在主机上下载实验所需的Linux可执行文件,可以通过地址 https://gitee.com/wildlinux/NetSec/attach_files 在码云中下载;也可以如下图所示在云班课作业附件中找到pwn1.zip进行下载,下载完成后将压缩包中的pwn1解压出来。
将pwn1通过共享文件夹导入到kali中,在我的实验中,单独设置了一个文件夹“exp1”作为pwn1的路径,专门用于完成实验一。
实践步骤
一、直接修改程序机器指令,改变程序执行流程
Step1:打开kali,在命令行中进入文件所在目录,输入
objdump -d pwn1 | more
进行反汇编,得到如下图所示结果。
objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息;-d参数表示disassembly——反汇编;pwn1表示要进行反汇编操作的文件名;|表示管道服务,意指将反汇编所得到的内容通过管道输送到一个可执行文件,该文件通过more命令分页显示。
我们通过输入/getShell
可以找到我们需要关注的几个函数,因为它们是挨着的,所以找到getShell即可找到全部三个函数,如下图所示
- 地址为80484b5处,
call 8048491
是汇编指令- 执行一条call指令相当于执行了两条指令:PUSH和JMP,这条指令将调用位于地址
8048491
处的foo函数; - 其对应机器指令为
e8 d7ffffff
,e8
即call指令对应的机器指令; - 本来正常流程,此时此刻EIP的值应该是下条指令的地址,即
80484ba
,但如一解释e8
这条指令呢,CPU就会转而执行EIP + d7ffffff
这个位置的指令。d7ffffff
是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29=8048491
,这正是foo函数的首地址。
- 执行一条call指令相当于执行了两条指令:PUSH和JMP,这条指令将调用位于地址
- main函数调用foo,对应机器指令为
e8 d7ffffff
- 由此可以发现,80484ba加上一个值,所得的就是CPU会跳转到的地址,那我们想让它调用getShell,只要修改
d7ffffff
为getShell-80484ba
对应的补码就行; - 用Windows计算器,直接 47d-4ba就能得到补码,是
c3ffffff
;
- 由此可以发现,80484ba加上一个值,所得的就是CPU会跳转到的地址,那我们想让它调用getShell,只要修改
如上图所示,HEX是16进制表示,我们在这种环境下用getShell的首地址减去执行call指令时EIP的值,即804847d减去80484ba,所得结果如上。在32位环境下,结果是ffffffc3,我们要把ffffffd7改为ffffffc3,即可实现main函数不再调用foo转而调用getShell。改之前我们要明确一点就是,正常来说一个数是高位在前(左)低位在后(右),然而我们在反汇编内容里则是低位在前高位在后,即小端优先。以字节为单位划分,一个16进制数是4bit,一字节就是两个16进制数,所以我们写起来是两个16进制数在一起算作一个字节,那么就要把call指令里面的偏移值改为c3ffffff
。接下来我们研究如何实现上面的更改操作。
上图所示的步骤,选自码云上的讲义,我们按这个步骤做下去,即可完成修改操作。
Step2:输入“ctrl+c”退出当前的反汇编分页显示界面。用cp命令备份一下pwn1文件,然后使用vi命令编辑备份文件。
Step3:由于vi编辑器编辑的对象是文本文件,所以对于一个可执行文件进入编辑模式的话,会出现大片乱码。所以我们要输入 :%!xxd
,以使文件内容以16进制形式显示。效果如下:
Step4:我们要修改的对象是d7 ff ff ff,要将其改成c3 ff ff ff,简单来说就是把d7改成c3。在vi编辑模式下我们可以轻松更改,关键是要找到这个d7在哪里。我们输入 /e8 d7 既可以定位到这里,如果找到多个查询结果,那么就对比着前面的反汇编结果,看看前后一定长度的字节内容是否匹配,一定要确保改的是正确的地方。
Step5:把光标移动到d,按一下 r 键,再输入 c ,然后按一下方向键 → ,再按一下 r 键然后输入 3 。完成修改。
Step6:修改完成后不能立即退出,有一套“退出机制”。首先是把当前的16进制形式转换为原形式:%!xxd -r
;然后是存盘退出 :wq
。如此便完成修改了,main函数可以成功调用到getShell函数。
Step7:验证。我们可以用两种方法来验证是否修改成功了,第一种是反汇编,针对刚刚修改的文件进行反汇编,通过反汇编结果验证。截图如下:
由上图可知,main函数通过call指令调用了首地址位于80847d的getShell函数,证明刚刚的修改是成功有效的。
另一种方法就是直接运行了,刚刚修改的文件名叫pwn2,那么就在文件所在的文件夹下输入./pwn2来执行这个文件。./表示当前目录。运行结果如下图所示:
由上图可知,运行过pwn2以后,即可执行命令行语句了,比如执行ls,就可以显示出当前路径下的所有文件:pwn1(原始文件)和pwn2(攻击过后的文件)。输入exit命令,就退出了shellcode。由此我们看到了攻击效果,程序的功能被改变了。程序本来的功能是什么呢?我们运行一下pwn1就知道了,如上图所示,pwn1运行过后,功能是回显用户输入的字符串,这正是foo函数的效果,也正是程序被攻击前的执行流程。
二、通过构造输入参数,造成BOF攻击,改变程序执行流
Step1:在这里,我们首先要发现程序的漏洞,然后针对漏洞实施攻击。这就要求我们对程序的功能有足够的了解。经过研究可以发现,本程序3个函数中foo函数存在BOF漏洞,可以实施缓冲区溢出攻击。
我们知道foo函数的功能是回显用户输入的字符串,由上图的反汇编结果可知,函数内两条call指令中前面那个读入字符串,后面那个输出输入的字符串。输入字符串按地址递增的顺序自然增长覆盖,系统预留的缓冲区大小只有0x1c,即28字节,地址自然增长填满28字节的区域是正常的,一旦输入字符串过大,超过了28字节,那么接下来4个字节会覆盖到EBP寄存器上,再来4个字节会覆盖到EIP寄存器上,这4个字节,也就是第33、34、35和36个字节是我们关注的重点。因为EIP寄存器存放的是下一条要执行的指令的地址,我们若是能控制这个寄存器的值,那么就可以自由控制程序的走向了。正常来说,foo函数会回显输入的字符串,这其中有输入和输出两步,一旦我们通过缓冲区溢出攻击输入了一个长度较大(不少于36字节)的字符串,那么EIP寄存器的值就有可能被修改,程序的走向就被带偏了。我们可以输入一长段字符串,指定第33、34、35、36个字节的内容就是getShell函数的首地址,那么这段字符串一覆盖到堆栈上,程序的走向就被更改了,foo函数不会回显刚刚输入的字符串,而是会跳转到getShell函数的首地址执行该函数。
另外,除了分析反汇编结果外,我们还可以通过gdb调试工具确定字符串中的哪些字节可以进入到EIP寄存器中。首先我们是为我们的kali下载安装gdb工具,如果没有的话。
上图有两个红框,第一个红框以上的部分是gdb调试pwn1的过程,到第一个红框开始才能输入命令。这里我们输入r,表示运行。下面第二个红框里有两行一模一样的内容,这正是pwn1本来的面目,它会调用foo函数回显输入的字符串。我输入了一条测试字符串,长度为42字节,111111112222222233333333444444441234567890。可以正常回显,所以就有了第二行的输出,但下面紧接着就给出了segmentation fault,即段错误,因为是发生了缓冲区溢出。那么此时各个寄存器状态如何呢?我们通过info r
来查询一下,结果如下图所示:
由上图可以看出,EIP寄存器对应的内容是0x34333231,这正是16进制下的1、2、3和4。这更加说明,我们上面输入的42字节的数据中,1234被写入了EIP寄存器,即第33、34、35、36个字节是会被写入EIP寄存器的。
前面通过反汇编可以知道,getShell函数的首地址是0804847d,根据小端优先的规则,再加上2个16进制数算作1个字节,我们要输入的地址是\x7d\x84\x04\x08。那么攻击字符串就可以构造为11111111222222223333333344444444\x7d\x84\x04\x08\x0a。\x0a表示回车。
Step2:在上述思路的指引下,接下来我们需要确定用什么样的方法注入攻击字符串。我们无法直接通过键盘输入有效的16进制字符,可以通过生成包含这样一个字符串的文件然后经由管道注入到目标文件来实现注入攻击字符串的目的,这需要用到Perl语言。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input-20184303
这样,我们就通过Perl语言把攻击字符串写入到了input-20184303文件中,它在当前文件夹中生成。生成代码及效果截图如下:
Step3:我们将input的输入,通过管道符“|”,作为pwn1的输入。输入命令为
(cat input-20184303; cat) | ./pwn1
效果如下图所示:
从上图可以看出,经过管道注入后,再运行pwn1文件时,它的功能已经不再是回显输入的字符串了,而是调用了getShell函数,可以输入命令行指令。我先后测试了ls和pwd,结果都是符合预期的,前面那个是显示出了当前文件夹里面的全部文件,包括input-20184303、pwn1和pwn2;后面那个显示出了当前所处的路径。都是没问题的。
三、注入Shellcode并执行
Step1:准备一段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\
Step2:做好准备工作。为了让本操作没那么复杂,本操作是有一些限制条件的,本步骤就是要落实这些限制条件。
上图截自码云上的实验讲义。但在实际操作中,由于种种原因还是不会和上图完全一致,这与用户权限有关。
还未安装execstack的话,可以通过apt-get install execstack
命令安装,为避免权限不够,我还在命令前面加上了sudo
。安装过程如下图所示:
sudo apt-get install execstack
安装成功后,接下来我们通过execstack -s pwn4303
设置堆栈可执行,本实验中,我备份pwn1,并重命名为pwn4303,方便之后的运行。通过execstack -q pwn4303
查询文件的堆栈是否可用。如下图所示,出现X的话就表示可用了。
下面要关闭地址随机化。我们通过 more /proc/sys/kernel/randomize_va_space
来查询地址随机化的状态。我们用more来查看的这个文件有3个值,分别是0、1、2,每个值都有不同的含义。当且仅当它的值为2时才表明关闭了地址随机化。下图所示的3段有效指令中,第一段是查询randomize_va_space的值,发现不是0,然后就通过第二段指令将0覆盖进去,最后通过第三段指令重新查看一下该文件的值,验证一下第二段指令的修改是否有效。结果是0,这是正确的、符合预期的!
如下图所示,当我输入第二段指令时,可以看,出连续两次被提示权限不够,即使我已经加了sudo。后来查阅资料才知道,还要加上sh -c才可以。利用 "sh -c" 命令,它可以让 bash 将一个字串作为完整的命令来执行,这样就可以将 sudo 的影响范围扩展到整条命令。详见 https://blog.csdn.net/tangtang_yue/article/details/78030658 。
Step3:使用输出重定向将perl生成的字符串存储到文件input_shellcode-4303中,效果如下图所示:
上面这一大段字符串也是事先规定好的,粘贴过来直接用就可以。
Step4:下面我们通过(cat input_shellcode-4303;cat) | ./pwn4303
注入攻击缓冲区buffer。效果如下图所示:
Step5:使用gdb调试进程。
保持当前的命令行终端不变,另外打开一个命令行,通过ps -ef | grep pwn4303
查询pwn4303的进程号,结果如下图所示:
如上图所示,我们首先输入gdb来开启调试工具。然后等到图中一个红框所示的位置以后可以输入内容,我们就输入attach加上刚刚查询到的进程号,在此我输入了attach 2262
。
然后输入disassemble foo
,针对foo函数进行反汇编,得到如下结果:
从上图可知,ret的地址为0x080484ae
,所以我们就在这个地址设置断点,指令为break *0x080484ae
。效果如下图所示:
此时系统会提示自己说,Breakpoint 1 at 0x80484ae
,这表明断点设置完毕。
现在,我们在第一个命令行终端按下一次回车键,然后回到第二个终端按下c键继续运行。
经历过上面的操作后,我们在第二个终端输入info r esp
查看栈顶指针所在的位置及其存放的数据。
由上图可知,esp寄存器放的是存0xffffd18c,紧接着我们用x/16x 0xffffd18c
查看该地址的及其临近地址所存放内容。我们找到了0x01020304,这也正是返回地址的位置。shellcode就挨着,加上4就是了,所以shellcode的地址是 0xffffd190。
Step6:修改shellcode的值,完成攻击。
如上图所示,我们可以先退出gdb调试工具,然后向刚才生成的input_shellcode-4303文件中覆盖修改后的Perl代码,地址0xffffd319在写入文件时遵循小端优先的原则,按\x19\xd3\xff\xff......的形式写入,每一个16进制数表示一个字节。
我们也可以用xxd命令来验证一下刚刚的文件input_shellcode-4303有没有被正确写入,如下图所示:
Step7:最后,我们针对getShell函数进行调用,通过管道将input_shellcode-4303的输入作为pwn4303的输入,结果如下图所示:
在上图中,输完管道命令后下面第一行的ls是无效的,因为此时程序尚未执行完毕。当出现接下来的代码以后,就可以成功调用命令行了。在此我先后输入了ls、pwd、exit这三个有效命令进行测试,结果都是符合预期的。
实验总结
一、实验收获与感想
本次实验围绕缓冲区溢出攻击进行了三个操作,分别是直接修改程序机器指令、利用foo函数的BOF漏洞和注入shellcode
- 关于实验操作:码云讲义上有着非常详细的实验讲义,视频也针对实验内容进行了逐步的分析,在思考的过程中完成本次实验,使我理解了BOF原理,学会了怎么运行原本不可访问的代码片段,强行修改程序执行流以及注入运行任意代码,了解了如何通过修改栈所存储的指令地址,跳转到想运行的指令。作为网络对抗的第一次实验,接触到了很多之前仅仅只是了解的知识,并在实际的操作中完成这些指令的编译,操作非常简单,但如何去理解这些内容成为了一个难点,只能不断地去回顾视频内容,查阅相关资料。
- 关于实验中出现的问题:实验中出现的问题非常多,花费时间最长的是GDK工具不能使用,execstack的安装,指令权限等,我所使用的KALI是导入相关源汉化后的版本,出现的问题更加多了,这时只有不断的查阅资料,查阅相关博客,解决这些问题,逐步熟悉Linus系统。
二、什么是漏洞?漏洞有什么危害?
-
漏洞:是在硬件、软件、协议的具体实现上存在的缺陷,从而可以使攻击者能够访问或破坏系统。配置不当的问题都可能被攻击者使用,威胁到系统的安全。具体举例来说,比如在 Intel Pentium芯片中存在的逻辑错误,在Sendmail早期版本中的编程错误,在NFS协议中认证方式上的弱点,在Unix系统管理员设置匿名Ftp服务时配置不当的问题都可能被攻击者使用,威胁到系统的安全。因而这些都可以认为是系统中存在的安全漏洞。
-
漏洞的危害:漏洞会影响到的范围很大,包括系统本身及其软件等。在这些不同的软硬件设备中都可能存在不同的安全漏洞问题。这个缺陷或错误可以被不法者或者电脑黑客利用,通过植入木马、病毒等方式来攻击或控制整个电脑,从而窃取电脑中的重要资料和信息,甚至破坏系统。联想电脑管家经常会更新一些漏洞补丁,我们无法在如此繁杂的代码中找到所有的漏洞,只能由用户使用、发现最后回馈给系统,系统再动态的更新代码,弥补漏洞。针对本实验的缓冲区溢出攻击,也是有很多规避方法的,如GCC堆栈保护技术、设置堆栈不可执行、启用地址随机化、加强代码质量检查等,避免缓冲区溢出漏洞。