实验目的
- 本次实践的对象是一个名为pwn1的linux可执行文件。
- 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
- 该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
实验内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
实验要求
- 熟悉Linux基本操作
- 理解Bof的原理
- 会使用gdb,vi
实验步骤
(一)直接修改程序机器指令,改变程序执行流程
- 知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具
- 学习目标:理解可执行文件与机器指令
- 进阶:掌握ELF文件格式,掌握动态技术
思路
- 一个程序的可执行文件可通过
objdump
命令对其进行反汇编,通过对汇编语言的学习可以看出程序读取数据的过程。
- 从反汇编的文件来看,main函数中调用foo函数,使用call指令。通过分析发现foo函数与getShell函数的首地址之间
存在偏移,两者之间相差了14。系统调用foo函数对应机器指令为e8 d7ffffff
,则如果想调用getShell函数,就要对e8 d7ffffff
进行修改。 - 已经知道电脑是小端模式,即数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。因此,如果要修改,就要将"d7"改为"c3"。
- 具体操作过程:
- 将pwn1进行备份,修改备份文件
- 用vi打开备份文件,用
:%!xxd
将显示模式切换为16进制模式 - 用
/d7
查找要修改的内容,定位后将d7改为c3 - 用
:%!xxd -r
将转换16进制为原格式,保存退出。
- 再次反汇编之后,确定已经将程序修改为调用getShell函数,接下来就是运行程序了。
- 运行时发现无法找到目录或文件,按照老师提供的教程进行操作就可以了。
运行截图
思考
运行成功之后就完成了实验的第一个部分,按照老师的指导一步步操作还算顺利。遇到的问题是,第一次在16进制模式修改完数据后出现操作失误,先保存后转换,在运行的时候导致文件损坏不可用。这是因为一开始保存的时候就改变了文件的格式,使得它不再是一个可执行文件的格式。查找资料后没有找到能将其再次转换的方法,只好重新进行实验。
参考资料
(二)通过构造输入参数,造成BOF攻击,改变程序执行流
- 知识要求:堆栈结构,返回地址
- 学习目标:理解攻击缓冲区的结果,掌握返回地址的获取
- 进阶:掌握ELF文件格式,掌握动态技术
思路
- 利用缓存区溢出进行攻击,是利用输入的数据将返回值进行覆盖。如果用getShell函数的地址进行覆盖,就会使程序调用getShell函数实现BOF攻击。
- 调用函数的时候,就会将函数的地址作为返回值压栈,即放入栈顶。字符串字节数足够大的时候就能覆盖返回值。
- 理清思路,使用gdb进行调试,弄清楚是哪几个字节会覆盖返回地址。
- 将“1234”替换为getShell函数的地址,即
0x0804847d
。已经知道电脑是小端模式,输入的时候要注意使用正确的字节序。 - 同时,输入的时候要注意,我们是没办法直接输入ASCII码值为“0804847d"的字符,需要使用
perl
命令先生成包括这样字符串的一个文件。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
- 用
xxd input
查看input文件的内容 - 后将input的输入,通过管道符“|”,作为输入。运行之后就进入了getShell函数,实验的第二部分就完成了。
运行截图
思考
- 已经确定了使用getShell的地址进行覆盖,要注意的是在输入地址的时候,只输入
0804847d
不会被计算机识别为16进制值,要使用/x08/x04/x84/x7d
- 关于Perl:Perl有很多命令行参数,通过它可以让你的程序更简练,并且可以写出很多只有一行命令的perl。
-
-e
:用于在命令行而不是在脚本中执行 Perl 命令。 - perl遵循正则表达式的标准。
-
- 尝试过用vim编辑器输入16进制,将其变为16进制编辑状态编辑后总会有错误,是因为无法同时改变ASCII码值与其对应的符号。
- 关于“|”管道:将第一条命令的结果作为第二条命令的参数来使用。用“;”区分两条命令,如果直接在命令行输入cat而不输入其余的任何东西,这时候的cat会等待标准输入。
(cat input; cat) | ./pwn1
先运行程序,将input的内容作为输入显示在程序中,第二个cat就会等待标准输入,此时就可以输入shell命令了。
参考资料
(三)注入Shellcode并执行
- 准备一段Shellcode
- shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
所以这段机器指令被称为shellcode。 - 在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
思路
- 使用shellcode进行攻击,经过验证结构为:
anything+retaddr+nops+shellcode
.nop一为是了填充,二是作为“着陆区/滑行区”,猜测返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。 - 一开始没有关闭地址随机化,每一次操作esp的地址都会变化,因此实验的关键就是要关闭地址随机化。
- 之后使用gdb调试程序,使用
ps -ef | grep 文件名
获取程序运行的PID,在gdb中对其进行追踪。查询esp寄存器的内容,并用x/16x
查看其中的内容,看到01020304
- shellcode在retaddr之后,因此我们要插入的地址是
01020304
地址加上0x04
运行截图
思考
一开始做实验的时候,没有认真思考shellcode的结构问题,再次打开虚拟机之后也没有关闭地址随机化,导致实验遇到了问题。重新开始之后,跟着老师的教程走来完成实验。其实确立了思路之后,重要的就是查命令来完成实验,对shellcode也不是很了解,还要继续学习。