项目 |
版本 |
操作系统 |
WindowsXP SP3 |
调试器 |
windbg 6.12、OD 1.10 |
漏洞软件 |
Office Excel 2003 SP3 |
反编译器 |
IDA 6.1 |
注:随书文件中的excel在干净的系统中直接打开会显示“操作系统当前的配置不能运行此程序”,需要先安装一个Office
复现过程
打开Windbg,加载excel,再打开exploit文档
此时Windgb会捕捉到发生在excel.exe中异常,ds:eax指向了一个无效的地址。用kb查看一下栈中的数据,可以看到栈内的数据都被覆盖了。
同时,用ida可以知道异常处所在的函数为sub_300E05AD
用od打开新的excel程序,并在函数入口和异常处下断点,然后加载exploit文件
当程序在函数入口暂停的时候,程序进入了一个新函数,对这个函数的栈顶设置内存写入断点后运行,就可以定位到导致栈溢出的代码
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在函数起始处下断点,重新运行程序
运行+一点点分析,可以发现这个函数调用了两次(至少),而溢出发生在第二次
总共复制了0x300个字节,0x300即“污点”
通过查看调用栈或ida的交叉引用,定位到调用vulfun的函数
查看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; int v5; int v6; unsigned int v7; unsigned int result; signed int v9;
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 ) { 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(); if ( v94 != 0x3C ) goto LABEL_187; v16 = sub_300DE7C5(); v17 = v96 * v81; 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; int v1; int result; int v3; int v4; int v5;
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