CVE-2011-0104 漏洞分析

项目 版本
操作系统 WindowsXP SP3
调试器 windbg 6.12、OD 1.10
漏洞软件 Office Excel 2003 SP3
反编译器 IDA 6.1

注:随书文件中的excel在干净的系统中直接打开会显示“操作系统当前的配置不能运行此程序”,需要先安装一个Office

复现过程

打开Windbg,加载excel,再打开exploit文档

20210425182432

此时Windgb会捕捉到发生在excel.exe中异常,ds:eax指向了一个无效的地址。用kb查看一下栈中的数据,可以看到栈内的数据都被覆盖了。

20210425184839

同时,用ida可以知道异常处所在的函数为sub_300E05AD

用od打开新的excel程序,并在函数入口和异常处下断点,然后加载exploit文件

当程序在函数入口暂停的时候,程序进入了一个新函数,对这个函数的栈顶设置内存写入断点后运行,就可以定位到导致栈溢出的代码

20210425191231

1
2
注:书中写到,exploit是对excel 2007 sp2 写的,因此覆盖到栈顶的是shellcode而不是跳板(所以可以考虑用该版本的excel调试?2333)。嗯 就继续按照书里的来吧

在ida查看此处的汇编代码,

1
2
3
4
5
6
7
8
....

.text:300DE825 lea esi, dword_3088EC40[edx]
.text:300DE82B mov ecx, eax ; ecx=eax=复制的字节数
.text:300DE82D mov edx, ecx
.text:300DE82F shr ecx, 2 ; 以dword位单位进行复制,所以/4
.text:300DE832 mov edi, ebp
.text:300DE834 rep movsd ; 溢出点

接着找到该段代码所属的函数,并用OD在函数起始处下断点,重新运行程序

运行+一点点分析,可以发现这个函数调用了两次(至少),而溢出发生在第二次
20210425220650

总共复制了0x300个字节,0x300即“污点”

通过查看调用栈或ida的交叉引用,定位到调用vulfun的函数

20210425221948

查看vul_func反汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
unsigned int __userpurge VulFun<eax>(void *a1<ebp>, void *a2, signed int a3, unsigned int a4)
{
signed int v4; // ebx@1
int v5; // edx@3
int v6; // eax@3
unsigned int v7; // eax@5
unsigned int result; // eax@8
signed int v9; // eax@12

v4 = a3;
if ( a3 )
{
if ( a3 > a4 )
{
sub_300DD5A6(dword_3088DF34, 6);
goto LABEL_15;
}
v5 = dword_30892C44;
v6 = *(_DWORD *)&NumberOfBytesWritten;
a1 = a2;
do
{
if ( v5 >= v6 )
{
v9 = v4;
if ( v4 > 16384 )
LABEL_15:
v9 = 16384;
sub_3011A989(v9);
v5 = dword_30892C44;
v6 = *(_DWORD *)&NumberOfBytesWritten;
}
v7 = v6 - v5;
if ( v4 < (signed int)v7 )
v7 = v4;
memcpy(a1, &dword_3088EC40[v5], v7);
v4 -= v7;
v5 = v7 + dword_30892C44;
a1 = (char *)a1 + v7;
dword_30892C44 += v7;
if ( !v4 )
break;
v6 = *(_DWORD *)&NumberOfBytesWritten;
}
while ( *(_DWORD *)&NumberOfBytesWritten == 16384 );
result = a1 - a2;
}
else
{
result = 0;
}
return result;
}

可以看到一个memcpy…

再向上看看,发现v7的变化是从这个片段中的子函数来的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
if ( v94 == 167 )                     // 判断recode type是否为0xA7
{
v9 = (int)((char *)v7 + v6);
v10 = dword_30895B44 < 5;
LOBYTE(v10) = dword_30895B44 >= 5;
v98 = (int)((char *)v7 + v6);
LOWORD(v9) = *(_WORD *)((char *)v7 + 1);
v81 = *(_WORD *)((char *)v7 + 1);
v11 = 2 * v10 + 2;
v12 = (int)((char *)v7 + v11 * v81 + 3);
v92 = v9;
i = (unsigned int)((char *)v7 + 3);
v96 = v11;
if ( (signed __int16)v9 > 0 )
{
while ( 1 )
{
--v92;
v13 = i;
while ( 1 )
{
v14 = v93;
if ( dword_30895B44 >= 5 )
{
v97 = *(_DWORD *)v13;
}
else
{
LOWORD(v97) = *(_BYTE *)v13;
HIWORD(v97) = *(_BYTE *)(v13 + 1);
}
if ( (_WORD)v97 )
break;
v13 += v96;
v15 = v92--;
v93 = 1;
i = v13;
if ( !v15 )
{
v14 = v93;
break;
}
}
if ( HIWORD(v97) & 0x12F && v12 >= (unsigned int)v98 )
{
v94 = sub_300DE7C5(); // v93 = 0x3C
if ( v94 != 0x3C )
goto LABEL_187;
v16 = sub_300DE7C5(); // 返回复制字节数0x300
v17 = v96 * v81; // v80 = 0x0C0F,v95 = 0x4
v98 = v16;
v12 = (int)((char *)v95 + v96 * v81 + 3);
v18 = sub_300C3AA4();
VulFun(v5, (void *)v12, v98, -3 - v17 + v18);

sub_300DE7C5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
t __cdecl sub_300DE7C5()
{
int v0; // eax@1
int v1; // ecx@1
int result; // eax@2
int v3; // esi@5
int v4; // ecx@5
int v5; // eax@6

v0 = *(_DWORD *)&NumberOfBytesWritten;
v1 = dword_30892C44;
if ( dword_30892C44 >= *(_DWORD *)&NumberOfBytesWritten - 1 )
{
if ( dword_30892C44 >= *(_DWORD *)&NumberOfBytesWritten )
{
sub_3011A989(1);
v1 = dword_30892C44;
v0 = *(_DWORD *)&NumberOfBytesWritten;
}
v3 = dword_3088EC40[v1];
v4 = v1 + 1;
dword_30892C44 = v4;
if ( v4 >= v0 )
{
sub_3011A989(1);
v4 = dword_30892C44;
}
v5 = 0;
*(_WORD *)((char *)&v5 + 1) = dword_3088EC40[v4];
dword_30892C44 = v4 + 1;
result = v5 | v3;
}
else
{
result = *(_WORD *)&dword_3088EC40[dword_30892C44];
dword_30892C44 += 2;
}
return result;
}

其实是直接复制并使用了样本文件中CONTINUE的Len字段的值,没有进行判断

后记

在网上查了一下污点追踪,大概是一个定位漏洞的方法,感觉这个方法确实挺常用的(吧)。另外,找到了一篇武大的基于污点追踪的嵌入式固件漏洞研究的论文。
感觉这两次复现对于调试能力的提升还是很大的,但总是研究到漏洞的触发就感觉差不多了,至于shellcode和修复就没太想继续研究…

参考:https://blog.csdn.net/bluebloodye/article/details/104911168