Windows下Shellcode开发

平台

vc6.0 vs2005 vs2008 vs2010 vs2012 vs2013 vs2015 vs2017

创建

Win32程序控制台

一、shellcode编写原则

1.修改程序入口

编译时编译器会自动生成的代码,对编写shellcode产生干扰,所以需要清除

  • 1. 修改程序入口点(VS位例子)程序员源代码如下:
    #include <windows.h>
    #pragma comment(linker, "/ENTRY:EntryMain")
    int EntryMain()
    {
        return 0;
    }
    

Release模式下

  • 工程属性(右键项目)->配置属性->链接器->高级->入口点 处设置入口函数名称
  • 添加如下代码
    #pragma comment(linker, "/ENTRY:EntryName")
    

    Debug模式下几乎不可能改变,因为MSVCRT.lib中某些对象文件的唯一链接器引用。链接器定义的实际入口点名称不是main,而是mainCRTStartup。不过方法如下,缺点就是要保留main函数,这样就无法达到自定义程序入口的目的

  • 工程属性(右键项目)->配置属性->链接器->高级->入口点 处设置入口函数名称,然后在 工程属性(右键项目)->配置属性->链接器->输入->强制符号引用 将值设置为:_mainCRTStartup(x86)或 mainCRTStartup(x64)
  • 也可以添加如下代码
    #pragma comment(linker, "/ENTRY:wmainCRTStartup ") // wmain will be called
    #pragma comment(linker, "/ENTRY:mainCRTStartup  ") // main will be called
    

    但是这样只能调用wmainmain

    这样ida反汇编:

  • 2.关闭缓冲区安全检查(GS检查)依旧是在release下进行工程属性(右键项目) ->c/c++->代码生成->安全检查,设置为禁用安全检查

    这个时候就只有一个函数了

这样将shellcode写入到函数中就不会因为其他函数造成干扰

2.设置工程兼容WindowsXP

我也很想设置好这个但是:配置完了过后,再切换到原来的工具集将丢失头文件的路径,要重新导入,修复的话很麻烦,尽量不要选择这个

  • 在visual studio installer 里面添加对 c++的WindowsXP支持
  • 工程属性(右键项目) ->常规->平台工具集->设置为含有当前vs年份+WindowsXP,如:
  • 工程属性(右键项目) ->c/c++->代码生成->运行库:多线程调试MTD(Debug) 或 MT(Release)这样就能保证程序能在windowsxp下运行

3.关闭生成清单

程序使用PEid之类的工具的话会发现EP段有三个段

理想情况下应该只保留代码段,这样便于直接提取代码段得到shellcode,其中.rsrc就是vs默认的生成清单段

清楚过程如下:

工程属性(右键项目) ->链接器->清单文件->生成清单:否

4.函数动态调用

这里以弹出MessageBox位例子

#pragma comment(linker, "/ENTRY:EntryName")//手动设置了入口点就不需要加这句 
#include <windows.h>

int EntryName()
{
    MessageBox(NULL, NULL, NULL, NULL);
    return 0;
}

编译前执行操作 工程属性(右键项目) ->C/C++->语言->符合模式:否

对CTF中二进制的朋友应该明白:类似在Linux上的pltgot的转换,在windows下,函数调用是通过user32.dll或者kernel32.dll来实现的,中间存在一个寻找地址的操作,而这个操作又是通过编译器实现的,这样程序员只需要记住名字就可以调用库中的函数了。

在ida中通过汇编就可以说明这一点:

但是shellcode的编写选用调用函数的话,就必须知道相对偏移才能正确获得函数的内存地址,所以shellcode要杜绝绝对地址的直接调用,如将上面的程序变为shellcode时,在汇编中直接call call dword ptr ds:[0x00E02000](x32dbg调试中的语句)是要避免的,所以函数要先获得的动态地址,然后再调用。

GetProcAddress函数

官方文档

作用:在指定动态连接库中获得指定的要导出函数地址

实例:

#pragma comment(linker, "/ENTRY:EntryName") 
#include <windows.h>

int EntryName()
{
    //MessageBox(NULL, NULL, NULL, NULL);
    GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
    return 0;
}

之前的程序经过调试,确定MessageBox是在user32.dll中,所以在第一个参数加载user32.dll,第二个参数填写函数名称,但是MessageBox有两种重载MessageBoxA(Ascii)和MessageBoxW(Wchar?),这里选择Ascii的版本(MessageBoxA)

dll导出表也可以使用PEid查看

子系统->输出表

那么可以通过内嵌汇编来调用函数

#pragma comment(linker, "/ENTRY:EntryName") 
#include <windows.h>

int EntryName()
{
    //MessageBox(NULL, NULL, NULL, NULL);
    LPVOID lp = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
    char *ptrData = "Hello Shellcode";
    __asm
    {
        push 0
        push 0
        mov ebx,ptrData
        push ebx
        push 0
        mov eax,lp
        call eax
    }
    return 0;
}

这样提取出来的shellcode就不含编译器参杂的动态调用偏移

现在规范化

可以将鼠标移到函数上,ctrl+鼠标左键进入函数定义,然后自定义一个函数指针,格式如下:

int EntryName()
{    
    typedef HANDLE (WINAPI *FN_CreateFileA)
        (
            __in     LPCSTR lpFileName,
            __in     DWORD dwDesiredAccess,
            __in     DWORD dwShareMode,
            __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
            __in     DWORD dwCreationDisposition,
            __in     DWORD dwFlagsAndAttributes,
            __in_opt HANDLE hTemplateFile
        );
    FN_CreateFileA fn_CreateFileA;
    fn_CreateFileA = (FN_CreateFileA)GetProcAddress(LoadLibraryA("kernel32.dll"), "CreateFileA");
    fn_CreateFileA("Shellcode.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

    return 0;
}

同理也可以这样设置printf

    typedef  int (__CRTDECL *FN_printf)
        (char const* const _Format, ...);
    FN_printf fn_printf;
    fn_printf = (FN_printf)GetProcAddress(LoadLibraryA("msvcrt.dll"), "printf");
    fn_printf("%s\n", "hello shellcode");

我们在编写shellcode使用GetProcAddressLoadLibraryA两个函数时,怎么找到这两个函数的地址呢?

5.获得GetProcAddress地址和LoadLibraryA("kerner32.dll")结果

获得LoadLibraryA("kerner32.dll")结果

PEB

进程环境信息块,全称:Process Envirorment Block Structure。MSDN:https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb,包含了一写进程的信息。

typedef struct _PEB {
  BYTE                          Reserved1[2];    /*0x00*/
  BYTE                          BeingDebugged;    /*0x02*/
  BYTE                          Reserved2[1];    /*0x03*/
  PVOID                         Reserved3[2];    /*0x04*/
  PPEB_LDR_DATA                 Ldr;            /*0x0c*/
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

fs寄存器

在80386及之后的处理器 又增加了两个寄存器 FS 寄存器和 GS寄存器

其中FS寄存器的作用是:

偏移 说明
000 指向SEH链指针
004 线程堆栈顶部
008 线程堆栈底部
00C SubSystemTib
010 FiberData
014 ArbitraryUserPointer
018 FS段寄存器在内存中的镜像地址
020 进程PID
024 线程ID
02C 指向线程局部存储指针
030 PEB结构地址(进程结构)
034 上个错误号

所以获得fs:[0x30]就可以获得PEB的信息

得到PEB信息后,在使用PEB->Ldr来获取其他信息

PEB->Ldr

msdn:https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];    /*0x00*/
  PVOID      Reserved2[3];    /*0x08*/
  LIST_ENTRY InMemoryOrderModuleList;    /*0x14*/
} PEB_LDR_DATA, *PPEB_LDR_DATA;

注意InMemoryOrderModuleList

The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks.

双向链接列表的头部,该列表包含该进程已加载的模块。列表中的每个项目都是指向LDR_DATA_TABLE_ENTRY结构的指针。有关更多信息,请参见备注。

备注

/*LIST_ENTRY*/
typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

/*LDR_DATA_TABLE_ENTRY*/
typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];                /*0x00*/
    LIST_ENTRY InMemoryOrderLinks;    /*0x08*/
    PVOID Reserved2[2];                /*0x10*/
    PVOID DllBase;                    /*0x14*/
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

_LDR_DATA_TABLE_ENTRY中我们就可以得到DLL文件的基址(DllBase),从而得到偏移。

那么以上代码可为

xor eax,eax            ;清空eax
mov eax,fs:[0x30]    ;eax = PEB
mov eax,[eax+0xc]    ;eax = PEB->Ldr
;一个BYTE:1字节,一个PVOID:4字节
;所以Ldr的偏移位=2*1+1+1+2*4=12=0xc
mov eax,[eax+0x14]    ;eax = PEB->Ldr.InMemoryOrderModuleList
mov eax,[eax]        ;·struct _LIST_ENTRY *Flink;·访问的
;将eax=下一个模块的地址,从而切换模块
;1. .exe程序 -> 2.ntdll.dlls
mov eax,[eax]        ;2.ntdll.dll->3.kernel32.dll
mov eax,[eax+0x10]    ;kernel32.dll->DllBase
ret                    ;返回eax寄存器

到这里我们就可以成功获得DLL文件的基址,也就是实现了获得LoadLibraryA("kerner32.dll")结果

获得GetProcAddress地址

预备知识

这里简单说下PE文件头,msdn:https://docs.microsoft.com/en-us/windows/win32/debug/pe-format


typedef struct IMAGE_DOS_HEADER{
      WORD e_magic;            //DOS头的标识,为4Dh和5Ah。分别为字母MZ
      WORD e_cblp;
      WORD e_cp;
      WORD e_crlc;
      WORD e_cparhdr;
      WORD e_minalloc;
      WORD e_maxalloc;
      WORD e_ss;
      WORD e_sp;
      WORD e_csum;
      WORD e_ip;
      WORD e_cs;
      WORD e_lfarlc;
      WORD e_ovno;
      WORD e_res[4];
      WORD e_oemid;
      WORD e_oeminfo;
      WORD e_res2[10];
      DWORD e_lfanew;             //指向IMAGE_NT_HEADERS的所在
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

其中e_lfanew指向IMAGE_NT_HEADERS的所在

IMAGE_NT_HEADERS

分为32位和64位两个版本,这里讲32位,https://docs.microsoft.com/zh-cn/windows/win32/api/winnt/ns-winnt-image_nt_headers32

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
  • Signature四字节大小的签名去定义PE文件,标志为:”PE\x00\x00”
  • FileHeaderIMAGE_FILE_HEADER结构体来说e明文件头
  • OptionalHeader文件的可选头

这里用的到的是OptionalHeader,因为它定义了很多程序的基础数据

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

其中用得到的是:DataDirectory

DataDirectory

A pointer to the first IMAGE_DATA_DIRECTORY structure in the data directory.

The index number of the desired directory entry. This parameter can be one of the following values.

通过这个成员我们可以查看一些结构体的偏移和大小,其中IMAGE_DATA_DIRECTORY如下

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

如:IMAGE_DIRECTORY_ENTRY_EXPORT,这是一个PE文件的导出表,里面记录了加载函数的信息,内容大致如下

之后找到这个:_IMAGE_EXPORT_DIRECTORY

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

就可以用AddressOfFunctions AddressOfNames AddressOfNameOrdinals来找到函数了

通过基址找到GetProcAddress

FARPROC _GetProcAddress(HMODULE hMouduleBase)
{
    //由之前找到的DllBase来得到DOS头的地址
    PIMAGE_DOS_HEADER lpDosHeader = 
        (PIMAGE_DOS_HEADER)hMouduleBase;

    //找到 IMAGE_NT_HEADERS 的所在
    PIMAGE_NT_HEADERS32 lpNtHeader = 
        (PIMAGE_NT_HEADERS)((DWORD)hMouduleBase + lpDosHeader->e_lfanew);

    if (!lpNtHeader->OptionalHeader//检查可选文件头的导出表大小是否 不为空
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
    {
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader//检查可选文件头的导出表的偏移是否 不为空
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
    {
        return NULL;
    }


    PIMAGE_EXPORT_DIRECTORY lpExport = //获得_IMAGE_EXPORT_DIRECTORY对象
        (PIMAGE_EXPORT_DIRECTORY)((DWORD)hMouduleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    //下面变量均是RVA,要加上hModuleBase这个基址
    PDWORD lpdwFunName =
        (PDWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfNames);
    PWORD lpword =
        (PWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr =
        (PDWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfFunctions);
    //DWORD   AddressOfFunctions;      指向输出函数地址的RVA
    //DWORD   AddressOfNames;          指向输出函数名字的RVA
    //DWORD   AddressOfNameOrdinals;   指向输出函数序号的RVA

    DWORD dwLoop = 0;//遍历查找函数
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExport->NumberOfNames-1;dwLoop++)
    {
        char *pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hMouduleBase);//char *pFunName = lpwdFunName[0] = "func1";
        if (pFunName[0] == 'G'&&
            pFunName[1] == 'e'&&
            pFunName[2] == 't'&&
            pFunName[3] == 'P'&&
            pFunName[4] == 'r'&&
            pFunName[5] == 'o'&&
            pFunName[6] == 'c'&&
            pFunName[7] == 'A'&&
            pFunName[8] == 'd'&&
            pFunName[9] == 'd'&&
            pFunName[10] == 'r'&&
            pFunName[11] == 'e'&&
            pFunName[12] == 's'&&
            pFunName[13] == 's')
            //if(strcmp(pFunName,"GetProcAddress"))
        {
            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hMouduleBase);
            break;
        }
    }
    return pRet;
}
;这里原作者是寻找SwapMouseButton函数
;将最后一段汇编参数修改为MessageBoxA的16位小端序
;即可找到MessageBoxA函数的地址
xor ecx, ecx
mov eax, fs:[ecx + 0x30] ; EAX = PEB
mov eax, [eax + 0xc]     ; EAX = PEB->Ldr
mov esi, [eax + 0x14]    ; ESI = PEB->Ldr.InMemOrder
lodsd                    ; EAX = Second module
xchg eax, esi            ; EAX = ESI, ESI = EAX
lodsd                    ; EAX = Third(kernel32)
mov ebx, [eax + 0x10]    ; EBX = Base address

mov edx, [ebx + 0x3c]    ; EDX = DOS->e_lfanew
add edx, ebx             ; EDX = PE Header
mov edx, [edx + 0x78]    ; EDX = Offset export table
add edx, ebx             ; EDX = Export table
mov esi, [edx + 0x20]    ; ESI = Offset namestable
add esi, ebx             ; ESI = Names table
xor ecx, ecx             ; EXC = 0

Get_Function:

inc ecx                              ; Increment the ordinal
lodsd                                ; Get name offset
add eax, ebx                         ; Get function name
cmp dword ptr[eax], 0x50746547       ; GetP
jnz Get_Function
cmp dword ptr[eax + 0x4], 0x41636f72 ; rocA
jnz Get_Function
cmp dword ptr[eax + 0x8], 0x65726464 ; ddre
jnz Get_Function
mov esi, [edx + 0x24]                ; ESI = Offset ordinals
add esi, ebx                         ; ESI = Ordinals table
mov cx, [esi + ecx * 2]              ; Number of function
dec ecx
mov esi, [edx + 0x1c]                ; Offset address table
add esi, ebx                         ; ESI = Address table
mov edx, [esi + ecx * 4]             ; EDX = Pointer(offset)
add edx, ebx                         ; EDX = GetProcAddress

xor ecx, ecx    ; ECX = 0
push ebx        ; Kernel32 base address
push edx        ; GetProcAddress
push ecx        ; 0
push 0x41797261 ; aryA
push 0x7262694c ; Libr
push 0x64616f4c ; Load
push esp        ; "LoadLibrary"
push ebx        ; Kernel32 base address
call edx        ; GetProcAddress(LL)

add esp, 0xc    ; pop "LoadLibrary"
pop ecx         ; ECX = 0
push eax        ; EAX = LoadLibrary
push ecx
mov cx, 0x6c6c  ; ll
push ecx
push 0x642e3233 ; 32.d
push 0x72657375 ; user
push esp        ; "user32.dll"
call eax        ; LoadLibrary("user32.dll")

add esp, 0x10                  ; Clean stack
mov edx, [esp + 0x4]           ; EDX = GetProcAddress
xor ecx, ecx                   ; ECX = 0
push ecx
mov ecx, 0x616E6F74            ; tona
push ecx
sub dword ptr[esp + 0x3], 0x61 ; Remove "a"
push 0x74754265                ; eBut
push 0x73756F4D                ; Mous
push 0x70617753                ; Swap
push esp                       ; "SwapMouseButton"
push eax                       ; user32.dll address
call edx                       ; GetProc(SwapMouseButton)

6.小细节

  • 避免全局变量(包括static之类的)的使用这违反了避免对地址直接调用的原则
  • 确保API的DLL被加载(显式加载)这个可以在一般情况下写好程序,使用PEid查看输入表,就可以知道在那个DLL调用了那个函数。也可以使用vs的跳转到定义或msdn查询

 

二、整合:shellcode开发框架

0.创建程序

新建项目->控制台应用->能同时选择控制台应用和空项目最好;不能的话选择控制台应用

编译器选择release版本

关闭生成清单:工程属性(右键项目) ->链接器->清单文件->生成清单:否

关闭缓冲区检查:工程属性(右键项目) ->c/c++->代码生成->安全检查,设置为禁用安全检查

关闭调试信息:工程属性(右键项目) ->链接器->调试->生成调试信息:否

设置函数入口:#pragma comment(linker, "/ENTRY:EntryName")

1.静态注入框架

1.编写代码

正常的功能

#include <windows.h>
int main()
{
    CreateFileA("shellcode.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    MessageBoxA(NULL, "Hello shellcode!", "shell", MB_OK);
    return 0;
}

实现:

前面讲过了,shellcode要避免对地址的直接调用,所以我们需要使用GetProcAddressLoadLibraryA,所以将之前的getKernel32和getProcAddress导入到程序中

DWORD getKernel32();
FARPROC getProcAddress(HMODULE hMouduleBase);

2.实现CreateFileA

CreateFileA实现动态调用,先创建函数指针,然后声明一个对象

fn_CreateFileA = (FN_CreateFileA)GetProcAddress(LoadLibraryA("kernel32.dll"), "CreateFileA");

声明对象时:1.要调用GetProcAddress,2.第一个参数:LoadLibraryA(“kernel32.dll”),3.第二个参数:”CreateFileA”字符串。

1。 使用动态调用GetProcAddress

按照之前的方法,代码如下:

    typedef FARPROC (WINAPI *FN_GetProcAddress)
        (
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
        );
    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());

动态调用的是自己的函数getProcAddress(getProcAddress又是通过getkernel32和PE文件头找到的),这样在CreateFileA的动态调用里面的参数就可以填fn_GetProcAddress

2。第一个参数:LoadLibraryA(“kernel32.dll”)

直接使用getkernel32汇编代码

3。第二个参数:”CreateFileA”字符串。

因为直接填写字符串会被编译器认为是静态变量,而我们要避免静态变量,所以要新建变量

char szFuncName[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };

所以,最后我们的代码是这样的:

    typedef FARPROC (WINAPI *FN_GetProcAddress)
        (
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
        );
    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());

    typedef HANDLE(WINAPI *FN_CreateFileA)
        (
            __in     LPCSTR lpFileName,
            __in     DWORD dwDesiredAccess,
            __in     DWORD dwShareMode,
            __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
            __in     DWORD dwCreationDisposition,
            __in     DWORD dwFlagsAndAttributes,
            __in_opt HANDLE hTemplateFile
            );

    char szFuncName[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
    char szNewFile[] = { 'S','h','e','l','l','c','o','d','e','.','t','x','t',0};
    FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress((HMODULE)getKernel32(), szFuncName);
    fn_CreateFileA(szNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

3.实现MessageBoxA()

和上面CreateFileA实现不同的是,MessageBoxA是位于User32.dll中的,所以要动态加载LoadLibraryA

typedef HMODULE(WINAPI *FN_LoadLibraryA)
        (
            _In_ LPCSTR lpLibFileName
        );
    char szLoadLibrary[]= { 'L','o','a','d','L','i','b','r','a','r','y','A' ,0};
    FN_LoadLibraryA fn_LoadLibraryA=(FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(),szLoadLibrary);

这样LoadLibraryA被替换为了fn_LoadLibraryA

然后再载入DLL为文件

    char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l' };
    char szMsgBox[] = { 'M','e','s','s','a','g','e','B','o','x','A' };
    FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress((HMODULE)fn_LoadLibraryA(szUser32),szMsgBox);

最终的代码如下:

    //动态加载LoadLibraryA函数
    typedef HMODULE(WINAPI *FN_LoadLibraryA)
        (
            _In_ LPCSTR lpLibFileName
        );
    char szLoadLibrary[]= { 'L','o','a','d','L','i','b','r','a','r','y','A' ,0};
    FN_LoadLibraryA fn_LoadLibraryA=(FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(),szLoadLibrary);
    //动态加载MessageBoxA函数
    typedef int (WINAPI *FN_MessageBoxA)
        (
            _In_opt_ HWND hWnd,
            _In_opt_ LPCSTR lpText,
            _In_opt_ LPCSTR lpCaption,
            _In_ UINT uType
        );
    char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l' };
    char szMsgBox[] = { 'M','e','s','s','a','g','e','B','o','x','A' };
    //载入DLL文件
    FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress((HMODULE)fn_LoadLibraryA(szUser32),szMsgBox);
    //调用函数
    char szMsgBoxContent[] = { 'H','e','l','l','o',' ','s','h','e','l','l','c','o','d','e','!' ,0 };
    char szMsgBoxTitle[] = { 's','h','e','l','l',0 };
    fn_MessageBoxA(NULL,szMsgBoxContent,szMsgBoxTitle, 0);

4.最终的源代码

#pragma comment(linker, "/ENTRY:MainEntry")
#include <windows.h>

DWORD getKernel32();
FARPROC getProcAddress(HMODULE hMouduleBase);

int MainEntry()
{
    //CreateFileA("shellcode.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    typedef FARPROC (WINAPI *FN_GetProcAddress)
        (
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
        );
    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());

    typedef HANDLE(WINAPI *FN_CreateFileA)
        (
            __in     LPCSTR lpFileName,
            __in     DWORD dwDesiredAccess,
            __in     DWORD dwShareMode,
            __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
            __in     DWORD dwCreationDisposition,
            __in     DWORD dwFlagsAndAttributes,
            __in_opt HANDLE hTemplateFile
            );

    char szCreateFileA[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
    char szNewFile[] = { 'S','h','e','l','l','c','o','d','e','.','t','x','t',0};
    FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress((HMODULE)getKernel32(), szCreateFileA);
    fn_CreateFileA(szNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

    typedef HMODULE(WINAPI *FN_LoadLibraryA)
        (
            _In_ LPCSTR lpLibFileName
        );
    char szLoadLibrary[]= { 'L','o','a','d','L','i','b','r','a','r','y','A' ,0};
    FN_LoadLibraryA fn_LoadLibraryA=(FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(),szLoadLibrary);

    typedef int (WINAPI *FN_MessageBoxA)
        (
            _In_opt_ HWND hWnd,
            _In_opt_ LPCSTR lpText,
            _In_opt_ LPCSTR lpCaption,
            _In_ UINT uType
        );
    char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l' };
    char szMsgBox[] = { 'M','e','s','s','a','g','e','B','o','x','A' };
    FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress((HMODULE)fn_LoadLibraryA(szUser32),szMsgBox);

    char szMsgBoxContent[] = { 'H','e','l','l','o',' ','s','h','e','l','l','c','o','d','e','!' ,0 };
    char szMsgBoxTitle[] = { 's','h','e','l','l',0 };
    fn_MessageBoxA(NULL,szMsgBoxContent,szMsgBoxTitle, 0);
    //MessageBoxA(NULL, "Hello shellcode!", "shell", MB_OK);
    return 0;
}

__declspec(naked) DWORD getKernel32()
{
    __asm
    {
        mov eax, fs:[0x30]
        mov eax, [eax + 0xc]
        mov eax, [eax + 0x14]
        mov eax, [eax]
        mov eax, [eax]
        mov eax, [eax + 0x10]
        ret
    }
}

FARPROC getProcAddress(HMODULE hMouduleBase)
{
    //由之前找到的DllBase来得到DOS头的地址
    PIMAGE_DOS_HEADER lpDosHeader =
        (PIMAGE_DOS_HEADER)hMouduleBase;

    //找到 IMAGE_NT_HEADERS 的所在
    PIMAGE_NT_HEADERS32 lpNtHeader =
        (PIMAGE_NT_HEADERS)((DWORD)hMouduleBase + lpDosHeader->e_lfanew);

    if (!lpNtHeader->OptionalHeader//检查可选文件头的导出表大小是否 不为空
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
    {
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader//检查可选文件头的导出表的偏移是否 不为空
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
    {
        return NULL;
    }


    PIMAGE_EXPORT_DIRECTORY lpExport = //获得_IMAGE_EXPORT_DIRECTORY对象
        (PIMAGE_EXPORT_DIRECTORY)((DWORD)hMouduleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    //下面变量均是RVA,要加上hModuleBase
    PDWORD lpdwFunName =
        (PDWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfNames);
    PWORD lpword =
        (PWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr =
        (PDWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfFunctions);
    //DWORD   AddressOfFunctions;      指向输出函数地址的RVA
    //DWORD   AddressOfNames;          指向输出函数名字的RVA
    //DWORD   AddressOfNameOrdinals;   指向输出函数序号的RVA

    DWORD dwLoop = 0;//遍历查找函数
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExport->NumberOfNames - 1; dwLoop++)
    {
        char *pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hMouduleBase);//char *pFunName = lpwdFunName[0] = "func1";
        if (pFunName[0] == 'G'&&
            pFunName[1] == 'e'&&
            pFunName[2] == 't'&&
            pFunName[3] == 'P'&&
            pFunName[4] == 'r'&&
            pFunName[5] == 'o'&&
            pFunName[6] == 'c'&&
            pFunName[7] == 'A'&&
            pFunName[8] == 'd'&&
            pFunName[9] == 'd'&&
            pFunName[10] == 'r'&&
            pFunName[11] == 'e'&&
            pFunName[12] == 's'&&
            pFunName[13] == 's')
            //if(strcmp(pFunName,"GetProcAddress"))
        {
            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hMouduleBase);
            break;
        }
    }
    return pRet;
}

5.提取shellcode并静态植入(生成框架)

使用PEid来获得程序偏移量,从而得到程序加载到的地方

然后使用十六进制编辑器打开编写的程序,这里我用的是HxD,跳转到程序入口,也就是上面的偏移量

这里长度不能太短了,能把要执行的代码包裹完就行,这里选择到0x660的位置。

这样我们就得到了他的二进制代码,即shellcode

然后我们实现静态插入,这里我用PEView来测试

也是使用PEid来获得程序偏移量(0x400),然后在十六进制编辑器中转到,覆盖为我们上面shellcode

保存后运行:

这里成功创建了Shellcode.txt文件,然后成功弹出了MessageBox,但是字节填入过多,导致错误的参数被填入,我们这里是对PE文件进行直接覆盖,导致文件偏移计算有问题,最后乱码。

2.利用函数地址差提取shellcode

1.预备知识

单文件中函数的位置

这里要明白两种概念,函数定义、函数声明、函数编译的顺序

#include <iostream>
int Plus(int , int );//函数声明
int main()
{
    std::cout << "> "<<Plus(1,2)<<std::endl;
}

int Plus(int a, int b)//函数定义
{
    return a + b;
}

函数声明:把函数的名字、函数类型以及形参类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致)。

函数定义:函数功能的确立,包括指定函数名,函数值类型、形参类型、函数体等,它是一个完整的、独立的函数单位。

函数编译的顺序

这个在vs里面关掉优化,代码是如下

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

int Plus(int , int );
int Div(int, int);

int main()
{
    Plus(2, 3);
    Div(2, 3);
    return 0;
}

int Div(int a, int b)
{
    puts("Divds");
    return a - b;
}

int Plus(int a, int b)
{
    puts("Plus");
    return a + b;
}

在IDA中观察,发现函数生成的顺序和声明的顺序不一样,起决定作用的是定义顺序。

利用编译顺序,将一直两端函数的地址做差,就能得到两函数之间的代码段的相对位置和程序代码段的大小

多文件函数生成位置的关系

项目文件如下

//A.cpp
#include "A.h"
#include <stdio.h>
void FuncA()
{
    puts("This Is FuncA");
}
//B.cpp
#include "B.h"
#include <stdio.h>
void FuncB()
{
    puts("This Is FuncB");
}
//main.cpp
#include <iostream>
#include "A.h"
#include "B.h"
int main()
{
    FuncA();
    FuncB();
}

在IDA中

发现顺序是FuncA FuncB main,交换调用顺序和include的顺序,发现生成顺序依然没有改变。

其实编译顺序是由编译器的配置文件决定的,文件后缀名为:.vcxproj

修改上面cpp的顺序就修改函数生成顺序了

2.编写代码

还是按照创建程序的步骤建立一个项目,但是不要关闭调试信息

在项目里面添加一个 header.h 0.entry.cpp a_start.cpp z_end.cpp,这样文件排序可以很直观的找到代码而且默认的编译顺序是0-9,a-Z

要实现的功能:0.entry.cpp提取shellcode,a_start.cpp z_end.cpp生成shellcode

header.h

#pragma once
#ifndef HEAD_H
#define HEAD_H

#include <windows.h>

void ShellcodeStart();
void ShellcodeEntry();
void ShellcodeEnd();
DWORD getKernel32();
FARPROC getProcAddress(HMODULE hMouduleBase);

#endif // !HEAD_D

0.entry.cpp

IO交互部分,不参与shellcode的部分

#pragma comment(linker, "/ENTRY:MainEntry")
#include <stdio.h>
#include <Windows.h>
#include "header.h"

void CreateShellcode()//创建文件并写入
{
    typedef  int (__CRTDECL *FN_printf)
        (char const* const _Format, ...);
    FN_printf fn_printf;
    fn_printf = (FN_printf)GetProcAddress(LoadLibraryA("msvcrt.dll"), "printf");

    HANDLE hBin = CreateFileA("sh.bin", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, NULL);
    if (hBin == INVALID_HANDLE_VALUE)
    {
        fn_printf("Wrong in Generic\n");
        return;
    }
    DWORD dwLen = (DWORD)ShellcodeEnd - (DWORD)ShellcodeStart;
    DWORD dwWriter;
    WriteFile(hBin, ShellcodeStart, dwLen, &dwWriter, NULL);
    CloseHandle(hBin);

}

int MainEntry()
{
    CreateShellcode();
    return 0;
}

a_start.cpp

利用两函数做差就可以得到ShellcodeEnrtry的代码

(ShellcodeStart – ShellcodeEnd = getKernel32+getProcAddress+ShellcodeEntry)

,最后通过0.entry.cpp写入到bin文件

#include <windows.h>
#include "header.h"
__declspec(naked) void ShellcodeStart()
{
    __asm
    {
        jmp ShellcodeEntry
    }
}

__declspec(naked) DWORD getKernel32()
{
    __asm
    {
        mov eax, fs:[0x30]
        mov eax, [eax + 0xc]
        mov eax, [eax + 0x14]
        mov eax, [eax]
        mov eax, [eax]
        mov eax, [eax + 0x10]
        ret
    }
}

FARPROC getProcAddress(HMODULE hMouduleBase)
{
    //由之前找到的DllBase来得到DOS头的地址
    PIMAGE_DOS_HEADER lpDosHeader =
        (PIMAGE_DOS_HEADER)hMouduleBase;

    //找到 IMAGE_NT_HEADERS 的所在
    PIMAGE_NT_HEADERS32 lpNtHeader =
        (PIMAGE_NT_HEADERS)((DWORD)hMouduleBase + lpDosHeader->e_lfanew);

    if (!lpNtHeader->OptionalHeader//检查可选文件头的导出表大小是否 不为空
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
    {
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader//检查可选文件头的导出表的偏移是否 不为空
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
    {
        return NULL;
    }


    PIMAGE_EXPORT_DIRECTORY lpExport = //获得_IMAGE_EXPORT_DIRECTORY对象
        (PIMAGE_EXPORT_DIRECTORY)((DWORD)hMouduleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    //下面变量均是RVA,要加上hModuleBase
    PDWORD lpdwFunName =
        (PDWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfNames);
    PWORD lpword =
        (PWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr =
        (PDWORD)((DWORD)hMouduleBase + (DWORD)lpExport->AddressOfFunctions);
    //DWORD   AddressOfFunctions;      指向输出函数地址的RVA
    //DWORD   AddressOfNames;          指向输出函数名字的RVA
    //DWORD   AddressOfNameOrdinals;   指向输出函数序号的RVA

    DWORD dwLoop = 0;//遍历查找函数
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExport->NumberOfNames - 1; dwLoop++)
    {
        char *pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hMouduleBase);//char *pFunName = lpwdFunName[0] = "func1";
        if (pFunName[0] == 'G'&&
            pFunName[1] == 'e'&&
            pFunName[2] == 't'&&
            pFunName[3] == 'P'&&
            pFunName[4] == 'r'&&
            pFunName[5] == 'o'&&
            pFunName[6] == 'c'&&
            pFunName[7] == 'A'&&
            pFunName[8] == 'd'&&
            pFunName[9] == 'd'&&
            pFunName[10] == 'r'&&
            pFunName[11] == 'e'&&
            pFunName[12] == 's'&&
            pFunName[13] == 's')
            //if(strcmp(pFunName,"GetProcAddress"))
        {
            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hMouduleBase);
            break;
        }
    }
    return pRet;
}

void ShellcodeEntry()
{
    typedef FARPROC(WINAPI *FN_GetProcAddress)
        (
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
            );
    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());

    typedef HMODULE(WINAPI *FN_LoadLibraryA)
        (
            _In_ LPCSTR lpLibFileName
            );
    char szLoadLibrary[] = { 'L','o','a','d','L','i','b','r','a','r','y','A' ,0 };
    FN_LoadLibraryA fn_LoadLibraryA = (FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(), szLoadLibrary);

    typedef int (WINAPI *FN_MessageBoxA)
        (
            _In_opt_ HWND hWnd,
            _In_opt_ LPCSTR lpText,
            _In_opt_ LPCSTR lpCaption,
            _In_ UINT uType
            );
    char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l',0 };
    char szMsgBox[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
    FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress((HMODULE)fn_LoadLibraryA(szUser32), szMsgBox);

    char szMsgBoxContent[] = { 'H','e','l','l','o',0 };
    char szMsgBoxTitle[] = { 't','i','t','l','e',0 };
    fn_MessageBoxA(NULL, szMsgBoxContent, szMsgBoxTitle, 0);
    //MessageBoxA(NULL, "Hello", "title", MB_OK);
}

z_end.cpp

标志shellcode的结束

#include <windows.h>
#include "header.h"
void ShellcodeEnd(){}

3.效果

最后生成的bin文件是一串二进制代码,需要shellcode加载器才能运行,接下来就编写shellcode加载器

3.加载器

我们编写的shellcode实际上只是一串二进制代码,必须包含在一个程序中才能运行起来,应为加载器只需要讲二进制文件跑起来就行了,所以不需要再遵守shellcode编写原则

#include <stdio.h>
#include <windows.h>
int main(int argc, char *argv[])
{
    //1-代开文件并读取
    HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Open file wrong\n");
        return -1;
    }
    DWORD dwSize;
    dwSize = GetFileSize(hFile, 0);
    //2-将文件内容加载到一个内存中
    LPVOID lpAddress = VirtualAlloc(NULL,dwSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    if (lpAddress == NULL)
    {
        printf("VirtualAlloc error : %d", GetLastError());
        CloseHandle(hFile);
        return -1;
    }
    DWORD dwRead;
    ReadFile(hFile, lpAddress, dwSize,&dwRead,0);
    //3-使用汇编转到shellcode
    __asm
    {
        call lpAddress
    }
    _flushall();
    system("pause");
}

其实shellcode就是从汇编提取出来的机器码,当把shellcode加载到内存中,我们也可以使用函数的方式调用,

将汇编改为((void(*)(void))lpAddress)();,这样也能成功执行shellcode

4.对框架进行优化

目前我们只实现了一个函数,但是要实现更加复杂的功能(如反弹一个远程shell)的话就必须,因此我们需要加以改进

1.创建一个头文件,将shellcode的函数(Start和End之间)原型放到这里面

#pragma once
#include <windows.h>
typedef FARPROC(WINAPI *FN_GetProcAddress)
(
    _In_ HMODULE hModule,
    _In_ LPCSTR lpProcName
    );

typedef HMODULE(WINAPI *FN_LoadLibraryA)
(
    _In_ LPCSTR lpLibFileName
    );

typedef int (WINAPI *FN_MessageBoxA)
(
    _In_opt_ HWND hWnd,
    _In_opt_ LPCSTR lpText,
    _In_opt_ LPCSTR lpCaption,
    _In_ UINT uType
    );

之后定义一个结构体并声明

typedef struct _FUNCIONS
{
    FN_GetProcAddress fn_GetProcAddress;
    FN_LoadLibraryA fn_LoadLibraryA;
    FN_MessageBoxA fn_MessageBoxA;
}FUNCIONS, *PFUNCIONS;

这样就能在ShellcodeEntry中调用函数了

2.寻找函数地址

由于函数的声明在api.h文件中了,所以要重新寻址

那么我们在a_start上定义如下函数

void InitFunctions(PFUNCIONS pFn)
{
    pFn->fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
    char szLoadLibrary[] = { 'L','o','a','d','L','i','b','r','a','r','y','A' ,0 };
    pFn->fn_LoadLibraryA = (FN_LoadLibraryA)pFn->fn_GetProcAddress((HMODULE)getKernel32(), szLoadLibrary);

    //MessageBoxA
    char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l', 0 };
    char szMsgBox[] = { 'M','e','s','s','a','g','e','B','o','x','A' ,0 };
    pFn->fn_MessageBoxA = (FN_MessageBoxA)pFn->fn_GetProcAddress((HMODULE)pFn->fn_LoadLibraryA(szUser32), szMsgBox);
}

修改后的ShellcodeEntry函数

void ShellcodeEntry()
{
    char szMsgBoxContent[] = { 'H','e','l','l','o',0 };
    char szMsgBoxTitle[] = { 't','o','p',0 };
    FUNCIONS fn;
    InitFunctions(&fn);
    fn.fn_MessageBoxA(NULL, szMsgBoxContent, szMsgBoxTitle, MB_OK);
}

//记得添加相应的头文件

之后要添加函数的话:

1.将函数原型和声明添加到api.h;2.在初始化函数部分设置寻址;3.在ShellcodeEntry中调用

3.将所有的函数功能实现放到另一个文件中

在header.h中添加void CreateConfig(PFUNCIONS pFn)函数定义

创建一个b_work.cpp,在文件中可以实现MessageBoxA的功能

void MessageboxA(PFUNCIONS pFn)
{
    char szMsgBoxContent[] = { 'H','e','l','l','o',0 };
    char szMsgBoxTitle[] = { 't','o','p',0 };
    pFn->fn_MessageBoxA(NULL, szMsgBoxContent, szMsgBoxTitle, MB_OK);
}

最后在a_start的ShellcodeEntry中调用

void ShellcodeEntry()
{
    FUNCIONS fn;
    InitFunctions(&fn);
    MessageboxA(&fn);
}

 

相关知识

  • PE文件结构
  • exe程序入口
  • 函数指针
  • c++函数调用
  • c++联合编译

 

参考文章

(完)