MS06-040 浅入浅出

dm

起初以为这个只是2个简单的栈溢出, 一个是在 NetpwNameCompare 里面.

.text:7CDD649D mov esi, ds:__imp_wcslen
.text:7CDD64A3 lea eax, [ebp+var_408] ; 目标缓冲区
.text:7CDD64A9 push eax ; wchar_t *
.text:7CDD64AA call esi ; __imp_wcslen ; 计算长度
.text:7CDD64AC mov edi, eax
.text:7CDD64AE pop ecx
.text:7CDD64AF lea eax, [ebp+var_204] ; 目标缓冲区
.text:7CDD64B5 push eax ; wchar_t *
.text:7CDD64B6 call esi ; __imp_wcslen ; 计算长度
.text:7CDD64B8 pop ecx
.text:7CDD64B9 mov ecx, 100h ; 比较是否 > 0x100
.text:7CDD64BE cmp edi, ecx
.text:7CDD64C0 jg loc_7CDCD761
.text:7CDD64C6 cmp eax, ecx
.text:7CDD64C8 jg loc_7CDCD761
.text:7CDD64CE push [ebp+arg_0] ; wchar_t *
.text:7CDD64D1 mov esi, ds:__imp_wcscpy
.text:7CDD64D7 lea eax, [ebp+var_408]
.text:7CDD64DD push eax ; wchar_t *
.text:7CDD64DE call esi ; __imp_wcscpy ; 拷贝, 溢出!
.text:7CDD64E0 pop ecx
.text:7CDD64E1 lea eax, [ebp+var_204]
.text:7CDD64E7 pop ecx
.text:7CDD64E8 push [ebp+arg_4] ; wchar_t *
.text:7CDD64EB push eax ; wchar_t *
.text:7CDD64EC call esi ; __imp_wcscpy ; 拷贝, 溢出!

可以看到这里微软犯了一个很傻的错误, 前面2个wcslen居然判断目标缓冲区的长度! 紧接着的长度判断根本就无效了, wcscpy拷贝后就是一个栈式的缓冲区溢出.
但是在XP下面微软已经修正了这个问题, 所以这个漏洞只影响2000.
然后另外一个漏洞存在于CanonicalizePathName函数中.
2000 下的反汇编:

.text:7CDCAB37 push edi
.text:7CDCAB38 cmp [ebp+arg_0], esi ; 判断传入参数1是否为NULL
.text:7CDCAB3B mov edi, ds:__imp_wcslen
.text:7CDCAB41 mov ebx, 411h ; 长度
.text:7CDCAB46 jnz loc_7CDD6323 ; 如果不为NULL就跳转

.text:7CDD6323 loc_7CDD6323: ; CODE XREF: sub_7CDCAB28+1Ej
.text:7CDD6323 push [ebp+arg_0]
.text:7CDD6326 call edi ; call wcslen
.text:7CDD6328 mov esi, eax
.text:7CDD632A pop ecx
.text:7CDD632B test esi, esi ; 判断长度是否为0
.text:7CDD632D jz loc_7CDCAB53
.text:7CDD6333 cmp esi, ebx ; 判断长度是否 > 0x411
.text:7CDD6335 ja loc_7CDCABCF
.text:7CDD633B push [ebp+arg_0] ; wchar_t *
.text:7CDD633E lea eax, [ebp+var_414]
.text:7CDD6344 push eax ; wchar_t *
.text:7CDD6345 call ds:__imp_wcscpy ; 拷贝, 溢出!

虽然这里已经检测了传入的长度是否 > 0x411, 而本身缓冲区分配的长度为0x414, 但是wcslen函数是计算Unicode字符串长度的, 应该把计算后的结果乘以2判断.
显然这里没有, 所以如果你传入的数据只要不超过0x411 * 2, 是可以绕过这个长度检测的, 所以后面拷贝导致了栈式溢出.
反观Windows XP的代码:
.text:5B878808 loc_5B878808: ; CODE XREF: CanonicalizePathName(x,x,x,x,x)+32j
.text:5B878808 push esi ; wchar_t *
.text:5B878809 call ds:__imp__wcslen
.text:5B87880F mov ebx, eax
.text:5B878811 test ebx, ebx
.text:5B878813 pop ecx
.text:5B878814 jz loc_5B86A31F
.text:5B87881A cmp ebx, 208h ; 判断长度是否 > 0x208
.text:5B878820 ja loc_5B86A3C4
.text:5B878826 lea eax, [ebp+var_418]
.text:5B87882C push esi ; wchar_t *
.text:5B87882D push eax ; wchar_t *
.text:5B87882E call ds:__imp__wcscpy

可见已经做了相应的长度判断, 即使能够传递的大小也只有0x410, 而栈里面分配的长度式0x420, 不足以溢出. 所以一开始以为XP已经补了这个漏洞, 当微软出XP补丁
还以为多此一举.
后来发现metasploit公布的ms06-040攻击代码里面支持XP的target, 起初还抱着怀疑的态度. 后来再仔细的分析一下, 居然发现自己疏忽了这么大一个细节, 看来还是
自己audit代码的功底不够, 同时也发现了ms06-040中真正的'秘密'.
0x1f 调用的函数类型:
long function_1f (
[in] [unique] [string] wchar_t * arg_00,
[in] [string] wchar_t * arg_01,
[out] [size_is(arg_03)] char * arg_02,
[in] [range(0, 64000)] long arg_03,
[in] [string] wchar_t * arg_04,
[in,out] long * arg_05,
[in] long arg_06
);

.text:5B86A2F4 mov eax, [ebp+arg_8]
.text:5B86A2F7 push esi
.text:5B86A2F8 mov esi, [ebp+arg_0] ; arg_04 传入参数
.text:5B86A2FB mov [ebp+var_41C], eax
.text:5B86A301 mov eax, [ebp+arg_10]
.text:5B86A304 xor ebx, ebx
.text:5B86A306 cmp esi, ebx
.text:5B86A308 push edi
.text:5B86A309 mov edi, [ebp+arg_4] ; arg_01 传入参数
.text:5B86A30C mov [ebp+var_420], eax
.text:5B86A312 jnz loc_5B878808 ; 如果arg_04不为NULL的话跳转

.text:5B878808 loc_5B878808: ; CODE XREF: CanonicalizePathName(x,x,x,x,x)+32j
.text:5B878808 push esi ; wchar_t *
.text:5B878809 call ds:__imp__wcslen ; 计算arg_04的长度
.text:5B87880F mov ebx, eax
.text:5B878811 test ebx, ebx
.text:5B878813 pop ecx
.text:5B878814 jz loc_5B86A31F ; 如果为0则跳转

注意这里是很重要的一个判断, 如果arg_04长度不为0的话, 就会进入上面那个判断是否>0x208的流程, 这样就无法到达可以利用的那部分代码了.
所以我们这里要使的arg_04不为NULL, 而且长度为0, 进入这个跳转流程.

.text:5B86A31F ; CanonicalizePathName(x,x,x,x,x)+E58Cj ...
.text:5B86A31F mov esi, ds:__imp__wcslen
.text:5B86A325 push edi ; wchar_t *
.text:5B86A326 call esi ; __imp__wcslen ; 计算arg_01的长度
.text:5B86A328 add eax, ebx
.text:5B86A32A cmp eax, 207h ; 是否大于 0x207
.text:5B86A32F pop ecx
.text:5B86A330 ja loc_5B86A3C4 ; 大于则跳转
.text:5B86A336 lea eax, [ebp+var_418]
.text:5B86A33C push edi ; wchar_t *
.text:5B86A33D push eax ; wchar_t *
.text:5B86A33E call ds:__imp__wcscat ; wcscat拷贝数据.

上面似乎也有长度检测, 栈里面分配的空间是0x420, 貌似这部分代码是没有问题, 但是值得注意的是这里用的是wcscat函数进行拷贝.
man手册中对于wcscat的描述:


#include

wchar_t *wcscat(wchar_t *ws1, const wchar_t *ws2);

DESCRIPTION

The wcscat() function appends a copy of the wide-character string pointed to by ws2 (including the terminating null wide-character code)
to the end of the wide-character string pointed to by ws1. The initial wide-character code of ws2 overwrites the null wide-character code at the end of ws1.
If copying takes place between objects that overlap, the behaviour is undefined.

可见其功能是和C中的strcat功能是差不多的, 只是一个针对unicode. 一个是ansi的. 综合对上面的汇编理解, 我们可以写一个简单的符合上述流程的函数.

void func(char *string)
{
char buff[256];

if (strlen(string) > 0 && strlen(string) < 256) // 长度检测
{
strcat(buff, string); // strcat 长度拷贝
}
}

乍看是很安全的操作, 检测了长度, 应该不会导致缓冲区溢出. 但是设想一下如果该函数被调用2次会出现什么情况.
int main(int argc, char *argv[])
{
func(argv[1]);

func(argv[1]);
}
由于func中使用了strcat, 而且函数中对buff并没有初始化, 所以第二次strcat的时候拷贝的数据其实是从第一次拷贝结束的地方开始. 数据叠加, 导致溢出发生.
所以如果我们可以多次调用0x1f 这个调用, 并且每次传递的参数只要不超过0x207 * 2长度, 大约调用2次后即可以触发这个wcscat的栈式缓冲区溢出.
了解了这个真正'秘密'后, 来看看微软的补丁是怎么做的:
.text:5B878864 loc_5B878864: ; CODE XREF: CanonicalizePathName(x,x,x,x,x)+32j
.text:5B878864 cmp [esi], di ; 判断arg_04中的前2个直接是否为00 00.
.text:5B878867 jz loc_5B86A1B8 ; 如果是跳转
.text:5B87886D push esi ; wchar_t *
.text:5B87886E call ds:__imp__wcslen
.text:5B878874 mov edi, eax
.text:5B878876 test edi, edi
.text:5B878878 pop ecx
.text:5B878879 jz loc_5B86A1BF

.text:5B86A1B8 loc_5B86A1B8: ; CODE XREF: CanonicalizePathName(x,x,x,x,x)+E6E7j
.text:5B86A1B8 mov [ebp+var_418], di ; 前空缓冲区最开始的两个字节为00 00
.text:5B86A1BF
.text:5B86A1BF loc_5B86A1BF: ; CODE XREF: CanonicalizePathName(x,x,x,x,x)+E6F9j
.text:5B86A1BF ; CanonicalizePathName(x,x,x,x,x)+E751j ...
.text:5B86A1BF mov esi, ds:__imp__wcslen
.text:5B86A1C5 push ebx ; wchar_t *
.text:5B86A1C6 call esi ; __imp__wcslen
.text:5B86A1C8 add edi, eax
.text:5B86A1CA cmp edi, eax
.text:5B86A1CC pop ecx
.text:5B86A1CD jnb loc_5B8788DE
.text:5B86A1D3

.text:5B86A1D8 loc_5B86A1D8: ; CODE XREF: CanonicalizePathName(x,x,x,x,x)+E764j
.text:5B86A1D8 lea eax, [ebp+var_418]
.text:5B86A1DE push ebx ; wchar_t *
.text:5B86A1DF push eax ; wchar_t *
.text:5B86A1E0 call ds:__imp__wcscat

.text:5B8788DE loc_5B8788DE: ; CODE XREF: CanonicalizePathName(x,x,x,x,x)+4Dj
.text:5B8788DE cmp edi, 207h
.text:5B8788E4 jbe loc_5B86A1D8
.text:5B8788EA jmp loc_5B86A1D3

可以看到微软的补丁代码, 也不是增加了太多指令, 只是多一个判断和一个清空, 也就是说如果发现arg_04不为NULL, 而且长度为0的情况, 就简单的初始化
缓冲区最开始的2个字节为0, 这样就算你多次调用0x1f调用, 每次wcscat的时候都是从缓冲区的首字节开始, 这样来避免缓冲区溢出的发生.

上一篇: bug exp search已经基本完成
下一篇: Bug Exp Search

访客评论
#1
回复 路客 2006-08-14, 09:44:36
写得太好了
#2
回复 amxku 2006-08-14, 15:08:36
哈哈,是写的蛮不错的
#3
回复 kiki 2006-08-16, 07:48:15
国外人写的一个ms06040扫描器
http://www.nosec.org/web/files/ms06040scanner.exe
#4
回复 kiki 2006-08-16, 07:50:23
ms06040 exploit

http://www.nosec.org/web/index.php?q=node/103
#5
回复 hehe 2006-08-16, 10:00:12
国外人写的一个ms06040扫描器 .........
这是一个病毒网址!!3楼提供的。
#6
回复 hehe 2006-08-16, 10:01:09
写得好呀!!
发表评论

评论内容 (必填):