一 前言
EMM的Exploit方法很巧妙,尤其是对于我这种菜鸟来说,更是耳目一新。尽管MS08-067漏洞已经过去N久了,但貌似还没有分析Exploit的文章出现,看来大牛们都懒得写,所以我贴篇自己的调试总结出来,与各位小菜们共勉。如有雷同,实属巧合。
关于漏洞本身的分析文章已经铺天盖地,所以此处不再重复,有不清楚的请先复习下。注意测试之前要关闭DEP保护。
二 调试XPSP2_2976版本
我测试的netapi32.dll版本是2976,在XPSP2平台上成功,这部分的调试将以此版本为主;
根本上来说,Exploit的难点就是如何在栈的低址构造出L'\\'字符,即0x005c,所以EMM的思路就是在调用NetpwPathCanonicalize前,先调用一个其它函数,帮助在栈的低址构造出L'\\'字符。这个函数就是NetpwNameCompare,调用代码:
// srvsvc_NetPRNameCompare -> NetpwNameCompare func23(L"ph4nt0m",(wchar_t *)"\x53\x00\x56\x89\x56\x89\x56\x89\x56\x89",(wchar_t *)"\x4D\x00\x56\x89\x56\x89",4,0);
NetpwNameCompare
|__CompareOemNames
|__RtlUpcaseUnicodeToOemN
最后调用的是RtlUpcaseUnicodeToOemN,它的主要功能就是把一个Unicode字符串转换为大写的OEM字符串,函数原型如下:
NTSTATUS RtlUpcaseUnicodeToOemN(
OUT PCHAR OemString,
IN ULONG MaxBytesInOemString,
OUT PULONG BytesInOemString OPTIONAL,
IN PWSTR UnicodeString,
IN ULONG BytesInUnicodeString
);
自己可以写个测试代码来看看,输入的这两个字符串经过RtlUpcaseUnicodeToOemN调用后,会变成什么样子捏?
typedef LONG NTSTATUS; typedef NTSTATUS (WINAPI *pRtlUpcaseUnicodeToOemN)( OUT PCHAR OemString, IN ULONG MaxBytesInOemString, OUT PULONG BytesInOemString OPTIONAL, IN PWSTR UnicodeString, IN ULONG BytesInUnicodeString ); NTSTATUS status; pRtlUpcaseUnicodeToOemN RtlUpcaseUnicodeToOemN; CHAR OemString1[100] = {0}; CHAR OemString2[100] = {0}; ULONG MaxBytesInOemString1 = sizeof(OemString1); ULONG MaxBytesInOemString2 = sizeof(OemString2); ULONG BytesInOemString = 0; CHAR UnicodeString1[] = "\x53\x00\x56\x89\x56\x89\x56\x89\x56\x89"; CHAR UnicodeString2[] = "\x4D\x00\x56\x89\x56\x89"; ULONG BytesInUnicodeString1 = sizeof(UnicodeString1)-sizeof(UnicodeString1[0]); ULONG BytesInUnicodeString2 = sizeof(UnicodeString2)-sizeof(UnicodeString2[0]); hLib = LoadLibrary("ntdll.dll"); RtlUpcaseUnicodeToOemN = (pRtlUpcaseUnicodeToOemN)GetProcAddress(hLib, "RtlUpcaseUnicodeToOemN"); status = RtlUpcaseUnicodeToOemN(OemString1, MaxBytesInOemString1, &BytesInOemString, (PWSTR)UnicodeString1, BytesInUnicodeString1); status = RtlUpcaseUnicodeToOemN(OemString2, MaxBytesInOemString2, &BytesInOemString, (PWSTR)UnicodeString2, BytesInUnicodeString2); FreeLibrary(hLib);
用VC6编译就可以了,调试运行它可以看到:
输入的第一个字符串: \x53\x00\x56\x89\x56\x89\x56\x89\x56\x89
转换后会变成: 53 D2 5C D2 5C D2 5C D2 5C 00
第二个字符串: \x4D\x00\x56\x89\x56\x89
转换后会变成: 4D D2 5C D2 5C 00
可见,经过RtlUpcaseUnicodeToOemN转换后,栈缓冲区中就会存放着L'\\'(0x005C)字符,虽然CompareOemNames函数返回后栈就无效了,但0x005C字符还在;紧接着在后面调用NetpwPathCanonicalize,由于0x005C正好位于栈的低地址,因此能被成功搜索到,之后调用wcscpy,由于源缓冲区是可控制的输入参数,因此只要精确覆盖wcscpy的返回地址,就能得到CPU的控制权了;
另外,由于是在MS08-067函数返回前执行的wcscpy,因此即使MS08-067函数有GS保护也没用。
三 调试XPSP2_2180版本
在测试2180版本的netapi32.dll时,发现总是不能成功,所以又调试了一遍,意外发现了2180版本的另外一个BUG,即CanonicalizePathName的栈缓冲区在某些情况下会导致没有初始化就使用了,所以导致Exploit不能成功。它的输入参数如下:

用IDA分析它的流程可知,如果Prefix的长度不为0,那么它执行如下操作:
wcscpy(Buffer, Prefix); wcscat(Buffer, L'\\'); wcscat(Buffer, PathName);
wcscat(Buffer, PathName);
从这里就可以看出问题来了,如果我们输入的Prefix长度是0,那么Buffer缓冲区还没有被初始化就使用了。下图显示的就是执行wcscat前的情况,

可以看到这个缓冲区的起始数据正好不是0,开始两个字节是0003,所以wcscat的目标缓冲区将是从0003之后地址开始,下图显示的是执行wcscat之后,

现在找到Exploit对于XPSP2_2180版本无效的原因了。由于这个缓冲区的开头夹杂了随机数据,在进入到带漏洞的MS08-067函数后,无法通过验证,早早就退出了,所以Exploit无法成功。不过,这个BUG在2180版本之后已被改正,例如前面讲到的2976版本,在wcscat之前对Buffer缓冲区的开头数据赋了初值0。
四 问题
自己把Shellcode替换为创建calc.exe进程,测试成功,如下图所示:

但是由于calc.exe进程是在svchost.exe中创建的,所以能够看到创建成功,却无法显示出calc的GUI界面,不知道这种情况下,有什么办法可以让calc弹出GUI来?