1.1 实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
(1)手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
(2)利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
(3)注入一个自己制作的shellcode并运行这段shellcode。
1.2需掌握知识
1.2.1掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
NOP(机器码:90):NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。
JNE(机器码:75):条件转移指令,如果不相等则跳转。
JE(机器码:74):条件转移指令,如果相等则跳转。
JMP:无条件转移指令。段内直接短转Jmp
short(机器码:EB)段内直接近转移Jmp
near(机器码:E9)段内间接转移Jmp
word(机器码:FF)段间直接(远)转移Jmp far(机器码:EA)
CMP:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
1.2.2能看懂常用指令,如管道(|),输入、输出重定向(>)等。
管道操作:
1、就是将一端命令的输出交给另一端的命令处理
格式:命令1 | 命令2
2、改变执行命令时的默认输入与输出,输入输出重定向
重定向输入 <
重定向输出 > >>
1.2.3理解Bof的原理。
缓冲区溢出的概念:
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上。理想的情况是:程序会检查数据长度,而且并不允许输入超过缓冲区长度的字符。但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患。操作系统所使用的缓冲区,又被称为"堆栈",在各个操作进程之间,指令会被临时储存在"堆栈"当中,"堆栈"也会出现缓冲区溢出
1.2.4掌握反汇编与十六进制编程器
反汇编指令:objdump -d 文件名
将显示模式切换为16进制模式:%!xxd
转换16进制为原格式:%!xxd -r
1.2.5会使用gdb,vi
2.实验过程
2.1实践一、手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数
首先我们看一下所要操作的文件pwn1是一个怎样功能的文件
将pwn1改名为我的学号
pwn1是一个能返回输入内容的程序。
2.1.1用反汇编指令查看机器指令
我们使用了objdump -d 20164304 | more
审查代码之后不难发现
main函数调用位于地址8048491处的foo函数
为什么是8048491呢
是因为80484b5的机器指令是 e8 d7 ff ff ff
E8的作用是跳转地址,d7 ff ff ff是需要跳转的偏移量,为补码,逐位取反再加1,转换为十进制为-41,再转换为十六进制为-29,则80484b5通过与-29相加得到下一条地址8048491
所以我们利用这一功能,
通过修改e8 d7 ff ff ff的值,使其跳转至getshell函数的初地址804847d
经计算:804847d与80484ba的差值转换成补码为c3 ff ff ff,所以下一步操作也很明了,尝试将d7 ff ff ff修改成c3 ff ff ff
2.1.2修改机器指令代码
我们通过vi编辑文件内容,将d7 ff ff ff地址进行修改
输入 vi 20164304
得到的是乱码,因此使用:%!xxd来显示十六进制字符。
直接输入/e8 d7查找e8 d7 ff ff ff,(由于其中不止e8一条指令,同时也存在e8d7,所以一定注意搜索时e8和d7之间的空格)
通过回车,选中需要修改部分按R,或直接用i键进入编辑模式。
此时我遇见了我第一个问题,由于对vi编辑器使用不熟练,所以在修改完之后,不知道该怎么退出vi编辑器,准确来说是不知道怎么退出插入模式,无法输入退出指令。经过上网搜索补习之后,知道了vi编辑器一共有三种模式,初始为命令模式,进入编辑模式之后需要用esc来退出编辑,才可以使用指令。
修改完毕需要记得保存修改部分
一定记得用:wq来保存!这是我的第二个问题,之前直接用了q!直接退出,发现改过部分并没有保存。
重新用反汇编,查看新文件指令是否经过修改
如图所示,文件名称发生了很多改变
这是我遇见的第三个问题:
就是之前由于无法退出编辑插入模式,最后我只能用非正常退出方式关闭kali,之后再用编辑器打开,就会出现文件打开错误的提示。我采取的方法是更换文件的文件名,勉强能让我把实验继续下去。在这一步时文件名变更为pwn2。
在这里我们能看到已经修改成功为e8 c3 ff ff ff
运行文件
实践一成功!
2.2实践2(通过构造输入参数,造成BOF攻击,改变程序执行流):
2.2.1操作原理
由于之前文件已经被修改,所以这时我们抓来一只新鲜的pwn1文件
我们会发现当输入字段过长时会出现错误
通过反汇编之后,可以看到foo函数中,804849d和80484a8之间只留了28个字节的缓冲区,因此foo函数有Buffer Overflow漏洞,当输入达到28B时产生溢出。所以借助这个漏洞,我们同样可以让溢出的字节覆盖返回地址,从而触发getshell函数
2.2.2确认输入字符串哪几个字符会覆盖到返回地址
执行这一环节所用到的工具是gdb语句
gdb:
GDB的全称是GNU project debugger,是类Unix系统上一个十分强大的调试器。这里通过一个简单的例子(插入算法)来介绍如何使用gdb进行调试,特别是如何通过中断来高效地找出死循环;我们还可以看到,在修正了程序错误并重新编译后,我们仍然可以通过原先的GDB session进行调试(而不需要重开一个GDB),这避免了一些重复的设置工作;同时,在某些受限环境中(比如某些实时或嵌入式系统),往往只有一个Linux字符界面可供调试。这种情况下,可以使用job在代码编辑器、编译器(编译环境)、调试器之间做到无缝切换。这也是高效调试的一个方法。
输入字符串1111111122222222333333334444444455555555555
并使用info r 来查看当前寄存器的状态
我们可以看到eip被0x34343434所覆盖,0x34表示数字4,即表示当前返回地址为4444,进而我们输入 1111111122222222333333334444444412345678来确定是哪几位数字会覆盖地址。
根据eip,可以看到是1234,所以只要把这四个字符替换为 getShell 的内存地址,输给pwn1,pwn1就会运行getShell
而getshell的初地址为804847d,所以转换成代码为\x7d\x84\x04\08,为了输入\x7d\x84\x04\08,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
使用xxd查看文件内容
在这里出现了第四个问题:由于代码输入错误,少输入了\x08,导致第一次失败
将input的输入,
通过管道符“|”,作为pwn1的输入 (cat input; cat) | ./pwn1 ,最后,获得shell
实践2成功
2.3实践3(注入Shellcode并执行)
2.3.1准备工作
shellcode就是一段机器指令(code)
通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
所以这段机器指令被称为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语句设置堆栈可执行,所以先安装execstack工具(校园网速真实)
安装完毕,拿出我们的第三个pwn1文件命名为pwn3
通过使用execstack指令,设置堆栈可执行,查询文件的堆栈是否可执行,随后关闭了地址随机功能,保证在shellcode植入的时候不会受到随机地址的影响。
2.3.2构造要注入的payload。
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr。
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
我们这个buf够放这个shellcode了
结构为:nops+shellcode+retaddr。
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
nop一为是了填充,二是作为“着陆区/滑行区”。
我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
注入shellcode,根据老师在课堂上演示的,所以使用新结构:anything+retaddr+nops+shellcode。即代码为:
perl -e 'print "A" x 32;print "\x20\xd3\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
要通过填充32个A,将缓冲区填满,使其溢出至eip,
接下来我们来确定\x4\x3\x2\x1到底该填什么。打开一个终端注入这段攻击buf:
(这一部分的代码超长,第一次过于自信选择了手动输入出了错,最后还是低头复制了,还是那句话,记得用xxd检查一下)
这时候,我们应该打开第二个终端,这里我一开始也被困住了,因为不知道该怎么开第二个终端:
开启第二个终端方法:
1.用正常的方式打开一个终端。
2.使用组合键 ctrl+shift+t , 这时就在同一个窗口中打开了另一个终端,当然再按一次ctrl+shift+t,会再生成一个,需要多少了大家可以自行决定。
3.按组合键Alt+1,就会切换到第一个终端,按Alt+2,就会切换到第2个终端,Alt+n,对应的就会切换到第n个终端。
下图为出现的另一个错误:
由于在这一步手贱按了回车,导致在查询进程就找不到,用gdb无法连接到进程
正确如下:
如图所示,使用ps -ef | grep pwn3指令得到进程号为1157
随后使用gdb操作,进行调试,在0x080484ae处设置了断点,这时,要在另一个终端上,按下回车,一开始我忘记了要按回车,gdb进程一直在coutinuing
在断点处,查看注入buf的内存地址
得到0xffffd37c,所以在将shellcode的前四位x4\x3\x2\x1,改为\xff\xff\xd3\x80,最后成功!
实践3成功
3.1实验心得
其实这不是我们第一次接触缓冲区溢出攻击了,在大二下的时候信息安全技术概论的课堂上,就有学习简单的缓冲区溢出的基本原理,但是一直没有机会去进行实践。本次实验给了我一次对缓冲区溢出攻击从简到难的递进式认识,也深入了解了,缓冲区溢出从实现上的原理和具体核心操作是地址转移操作。本次实验大体上,没有比较严重的问题和错误,主要的错误分为两类,一部分是我对于汇编语言和linux的操作还是不够熟练,另一部分就是我的粗心问题了,但让我认识到了,通过缓冲区溢出攻击,可以在机器指令层次上,通过偷偷改变程序的返回地址,转接至自己设置的其他程序指令,可以进行安装后门,复制扩散等操作,一旦成功效果比较可怕,而且具有一定的隐蔽性,不易查出问题,但是会依赖漏洞和固定地址。
3.2什么是漏洞?漏洞有什么危害?
漏洞是指一个系统或软件在设计的过程中,具备了正常的可行性之后,暴露出来的安全性问题,包括应用软件或操作系统设计时的缺陷或编码时产生的错误,也可能来自业务在交互处理过程中的设计缺陷或逻辑流程上的不合理之处。通常对用户信息隐私和系统稳定性有很大的威胁,黑客通过某些漏洞,在漏洞被修复之前,可以借此获取权限,或者绕过某些限制,来获取自己的目标信息,或者破坏目的。漏洞是无法避免的,但是可以尽可能去预防的。
漏洞的危害:漏洞的影响巨大,对于一般用户来说,自身的信息隐私的安全性将由于漏洞的存在大大降低,对于普通用户来说漏洞不影响正常的使用,但是对于有不法想法的高等技术型人才来说,是一种掠夺的武器,且在漏洞被修复之前,普通用户几乎毫无还手之力,对商业,国家和个人的财产和安全都有着巨大的威胁,造成的损失也是无比巨大的。