1 实验要求
1.1 实验目标
本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
- 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- 掌握反汇编与十六进制编程器
- 正确修改机器指令改变程序执行流程
- 正确构造payload进行bof攻击
1.2 实验内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
2 实验步骤
2.1 直接修改程序机器指令,改变程序执行流程
先从码云上把需求的文件pwn1下载下来,我存放在exp文件夹中
首先输入命令objdump -d pwn1 | more汇编一下pwn1文件,输入/getShell 直接找到getShell函数以及后面的foo和main函数
我们可以看到在main函数中,进行的其实就是foo函数的调用。
主函数中的call 8048491 <foo>语句所对应的机器指令是e8 d7 ff ff ff,其中e8代表call,那么后面的d7 ff ff ff就是对应的foo地址8048491。
现在我们要使call的不是foo,而是getShell,那么应将8048491改为804847d。
通过学习,已知d7 ff ff ff 是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491。
那么要修改8048491,只要修改d7 ff ff ff为804847d-80484ba对应的补码就行。
我们用计算器计算一下804847d-80484ba=c3 ff ff ff
计算完成之后,接下来就需要开始修改代码了。
为避免产生不可逆转的错误,且不影响原文件,又保证之后实验的顺利进行,所以先用cp命令将pwn1拷贝成pwn2,在pwn2上进行操作。
打开pwn2文件,输入:%!xxd,将显示模式切换为16进制模式。/e8 d7查找要修改的内容(此处中间一定要加空格!由于空格问题一直卡在这)
进入编辑模式 ,将e8 d7改为e8 c3,再输入:%!xxd -r切换回原格式,并:wq保存并退出。
修改完成以后,再次反汇编一下pwn2文件,确认是否修改成功。
可以发现d7已经被改成了c3.
最后我们分别运行未被修改的pwn1和已被修改的pwn2进行对比,
发现pwn1中会显示用户所输入的字符串,而pwn2出现了getShell的提示符$。
2.2 通过构造输入参数,造成BOF攻击,改变程序执行流
打开pwn1文件,首先进行反汇编objdump -d pwn1 | more,并找到getShell函数以及后面的foo和main函数
我们观察可以发现该可执行文件正常运行是在主函数中call调用foo函数,同时在堆栈上压上返回地址值:80804ba(即call指令的下一条指令地址)
但是这个函数有Buffer overflow漏洞。
在foo函数中读入字符串时,系统只预留了0x1c字节(即28字节)的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址。
接下来需要确认超过28字节的字符串中到底哪几位决定了eip寄存器的值。
我们用gdb来调试一下,如果正常输入小于等于28字节(即小于等于预留空间)的话,程序会正常运行并退出。
现在输入1111111111111111111111111111234567892345,超出了12个字节,可以发现跳到eip寄存器中的值正是5678(即第33-36字节)
同时还能发现,输入的字节序应该是小端优先,
也就是说,我们要让getShell的地址去替换“5678”的话,由于getShell的起始地址是0804847d,那么我们应该输入的是1111111111111111111111111111234x7dx84x04x08
但是,由于键盘没法输入x7dx84x04x08这样的16进制值,所以必须用perl语言先生成包括这样字符串的一个文件,输入以下代码:
whh20174320@kali:~/exp$ perl -e 'print "11111111111111111111111111112345x7dx84x04x08x0a"' > input
x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
关于Perl: Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。 使用输出重定向“>”将perl生成的字符串存储到文件input中。
我们使用16进制查看指令xxd查看input文件的内容确实生成了我们想要的x7dx84x04x08
然后将input的输入,通过管道符“|”,作为pwn1的输入,使用命令:(cat input; cat) | ./pwn1
这一来我们就成功调用了getShell函数。(这里我用ls命令和pwd命令分别查看了一下)
耶~成功!
2.3 注入Shellcode并执行
首先我们需要准备一段Shellcode,如下:
x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80
另外,我们要先修改一些设置:(执行之前需要先安装一下perlink,apt-get install prelink)
- 设置堆栈可执行 : execstack -s pwn1
- 关闭地址空间随机化: echo "0" > /proc/sys/kernel/randomize_va_space (只能超级用户来进行操作,否则权限不够)
Linux下有两种基本构造攻击buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr
retaddr在缓冲区的位置是固定的,缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边。
我们这个buf够放这个shellcode了,结构为:nops+shellcode+retaddr。nop一为是了填充,二是作为“着陆区/滑行区”。
返回地址只要落在任何一个nop上,自然会滑到shellcode。
一样的,我们先用perl来准备我们要输入到pwn1里的十六进制数:
perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode
但是由于我们不知道shellcode的起始地址,所以我们暂时用x4x3x2x1来代替(不能要行尾的回车号!运行的时候也不能打回车!教训惨痛)
接下来,首先我们来确定x4x3x2x1到底该填什么。
打开一个终端注入这段攻击buf:
再开另外一个终端,用gdb来调试pwn1这个进程:
1.找到pwn1的进程号:
进程号为:5250。
2.启动gdb调试这个进程:
3.通过设置断点,来查看注入buf的内存地址:
可以观察发现已经看到了0x01020304,说明之前设置的返回地址就在这:0xffffd32c。
那么就意味着shellcode是紧挨着它的,因此我们可以推出来shellcode的地址就是:0xffffd32c+4=0xffffd330。
已经成功找到了shellcode的起始地址,那么就可以把原来文件中的数据进行修改了。
修改之后我们再把它输入到pwn1中去,我们就可以看到成功注入了shellcode。
3 实验感想与思考
3.1 实验过程中遇到的困难
在这次实验中,我发现细节尤为重要。在第一部分中由于在查找时e8d7中间少了个空格,就一直卡在那儿,并且检查不出错误;第二部分在确定getShell地址时,输入数据的个数一直有问题,简简单单的28位数和32位数数不清楚!哈哈;第三部分在编辑好预设数据后多打一个回车(还是在老师千叮万嘱不能打回车的前提下),一遍遍重做,最后才注意到这个细节。
3.2 实验心得
本次实验是网络对抗的第一次实验,已经能够体会到网络对抗这门课的难度了。也不是说这实验技术上要求多高,有多难,而是一个细节都不能马虎,一步错步步错。就是因为这一个一个的小细节导致整体难度就大大增加,耗费了我很长的时间才把整个实验做完。在实验过程中,主要是两方面的感想,一是发现了网络攻防技术的巧妙,一小段代码,几字节字符就能轻轻松松改变整个程序,篡改它的执行顺序,甚至是瘫痪,着实奇妙有趣;二是细节决定成败,在计算机技术上,不仅仅是网络对抗,每一个字符都需要严格谨慎,需要步步为营,否则本来是一小步问题将会酿成严重的后果。
3.3 思考
什么是漏洞?漏洞有什么危害?
我认为漏洞就是程序或者软件在运行过程中出现的无法避免的错误或者缺陷,而且这样的错误、缺陷能够被黑客利用,来攻击电脑。它的危害就是容易被有心者利用来窃取信息、攻击主机导致信息泄露,电脑瘫痪,甚至被利用去做一些违法的行为。同时,漏洞也容易造成病毒轻而易举的入侵电脑,存在极大的风险和安全隐患。