郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

终于写完了,应该挺详细的,奥利给

img

环境配置讲解

当前项目使用的vs2019,其他vs编译器都可以

清除多余的函数

首先创建一个项目,然后什么配置都不修改使用release生成如下代码

int main()
{
return 0;
}

然后我们把生成的exe文件放到ida里面去可以看到下图,明明我们什么函数都没有加为什么会多出这么多函数呢?

image-20200418151303938

其实这些函数都是vs编译器自动帮我加进去的,我们的代码段加上这些函数就组成了一个PE文件

位置:项目->配置属性->高级->入口点中添加MyMian字段,这个字段可以随意修改

接着我们把原先的代码替换为如下代码,然后重新生成

int MyMain()
{

return 0;
}

image-20200418152511957

可以看到多余的函数都不见了

禁用安全检查

上面是不是还有一个多余函数,该函数是用于安全检查用的,我们一样可以关闭它。

位置:项目->配置属性->C/C++->代码生成->安全检查,点击禁用安全检查

然后重新生成后就能看到只剩下一个字段了

image-20200418153257417

设置兼容XP系统

虽然win7都淘汰了但是还是有不少哔哔哔哔(自带消音)还用着windwos XP,但是v142已经不支持xp编程了,所以我们只能下载v141_xp来使用

位置:项目->配置属性->常规->平台工作集,选用Visual Studio 2017 - Windows XP (v141_xp)(需要自己下载该项目包

image-20200418153951398

接着修改运行库

位置:项目->配置属性->C/C++->代码生成->运行库,改成MT格式

image-20200418154222811

关闭资源段

当我们把以上调试好的exe文件放入到PEiD中的时候可以看到还多出了资源段和只读数据段

image-20200418154752381

位置:项目->配置属性->链接器->清单文件->生成清单,改成

重新生成后的文件放入PEiD中可以看到资源段已经消失了

image-20200418155111496

关闭只读数据段

位置:项目->配置属性->链接器->调试->生成调试信息,改为

注意:

  • vs2019关闭了也没办法删除只读数据段
  • vs2008可以删除
  • 不删除该字段影响不大

关闭优化

vs编译器会自动把没用用到的参数优化了,所以我们要关闭优化,会更直观。

位置:项目->配置属性->C/C++->优化,修改为已禁用

总结

  • 项目->配置属性->链接器->高级->入口点中添加MyMian字段
  • 项目->配置属性->C/C++->代码生成->安全检查,点击禁用安全检查
  • 项目->配置属性->常规->平台工作集,选用Visual Studio 2017 - Windows XP (v141_xp)
  • 项目->配置属性->链接器->清单文件->生成清单,改成
  • 项目->配置属性->链接器->调试->生成调试信息,改为
  • 项目->配置属性->C/C++->优化,修改为已禁用
  • 项目->配置属性->C/C++->代码生成->运行库,改成MT格式
  • 项目->配置属性->C/C++->语言->符合模式,改成格式

代码中的注意事项

杜绝双引号

vs开放平台中,双引号字符串会被编译到只读数据段,以引用绝对地址的方式使用,shellcode是为了便于所有机器上使用的,所有要避免一切绝对地址的使用,下面的函数调用会报错,之所以这样写是为了直观理解,实际需要用动态调用。

  • 禁止使用的格式

    //1.字符串和指针形式
    char szBuff[]="XXXXXXXXXX";
    char *pzBuff="XXXXXXXXXX";
    //2.函数中的引用
    char szDate[10];
    char *pzBuff="XXXXX";
    sprintf(szDate,"%s",pzBuff);
    memcpy(szDate,"xxxx");
  • 应该写成的格式

    //1.字符串的形式
    char szBuff[]={'x','x','x','\0'};
    //2.汇编的形式
    _asm
    {
    _EMIT 'X'
    _EMIT 'X'
    _EMIT 'X'
    _EMIT 'X'
    _EMIT 0
    }
    //3.函数的形式
    char szDate[10];
    char szBuff[]={'1','2','3','4','5'};
    char szOut[]={'%','s','\0'};
    char *pzBuff="XXXXX";
    memcpy(szDate,szBuff);
    sprintf(szDate,szOut,pzBuff);

函数动态调用

如果不是用该方法,在我们修改了main函数的入口后,调用include中的函数就好花式报错,但是你自己写的函数就不会

先贴一个调用Windows APICreateFileA函数的例子,后面再详细讲解

#include<windows.h>
#pragma comment(linker,"/entry:MyMain")
int MyMain()
{
typedef HANDLE(WINAPI* FN_CreateFileA)(//在windows.h源文件中找到CreateFileA的定义然后拷贝过来,接着用typedef定义一个高仿的
__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("ascotbe.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);//接着就用直接使用它了

return 0;
}

首先如果使用自定义的函数入口点需要添加#pragma comment(linker,"/entry:MyMain")MyMain的值为你的入口函数名

接着我们需要获取DLL中函数的地址,使用GetProcAddress(LoadLibraryA("kernel32.dll"), "CreateFileA");这段代码的意思就是获取kernel32.dll动态链接库中的CreateFileA函数的相对地址。

image-20200418204345026

然后找到CreateFileA函数的声明,然后复制过来

image-20200418204703230

接着就是使用typedef定义复制过来的函数,以及声明调用等

如果使用printf函数也是一样的,代码如下

#include<stdio.h>
#pragma comment(linker,"/entry:MyMain")
int MyMain()
{
typedef int(__CRTDECL* FN_printf)(_In_z_ _Printf_format_string_ char const* const _Format, ...);
FN_printf Fn_printf;
Fn_printf = (FN_printf)GetProcAddress(LoadLibraryA("msvcrt.dll"), "printf");
Fn_printf("asoctbe is a vegetable~");
return 0;
}

函数动态调用优化

由上文可以知道我们需要动态调用函数或者API,但是有两个地方违背了我们核心的原理。

  • 动态调用的前提我们需要获取kernel32.dll的基址,这个地方还是需要用到API中的LoadLibraryA函数!
  • 即使获取到kernel32.dll的基址我们还是需要用GetProcAddress函数来提取里面的函数!

首先我们解决第一个痛点:

获取kernel32.dll基址

先贴代码

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

__declspec(naked)DWORD getKernel32()
{
__asm {
XCHG EAX, EBX
MOV EBX, FS:[30H] //从TIB(Thread Information Block)中取得PEB(Process Environment Block)的地址
MOV EBX, [EBX + 0CH] //从PEB中取得LDR类的基址
MOV EBX, [EBX + 14H] //从LDR中获取InMemoryOrderModuleList.Flink的地址(入口1,属于主进程)
MOV EBX, [EBX] //从InMemoryOrderModuleList.Flink取得InMemoryOrderModuleList.Flink.Flink的地址(入口2,属于ntdll.dll)
MOV EBX, [EBX] //同上,取得InMemoryOrderModuleList.Flink.Flink.Flink的地址(入口3,属于kernel32.dll)
MOV EBX, [EBX + 10H] //通过InMemoryOrderModuleList.Flink.Flink.Flink提供的_LDR_DATA_TABLE_ENTRY,再从中最终获取入口3的基址,也就是kernel32.dll的基址了
XCHG EAX, EBX// 此过程的返回kernel32.dll的基址,存于EAX寄存器中
RETN
}
}

int MyMain()
{
HMODULE hLoadLibraryA = (HMODULE)getKernel32();//将获取到的基址传给hLoadLibraryA
//由于没办法直接使用printf函数,这边还是用上面的方法来动态调
typedef int(__CRTDECL* FN_printf)(
_In_z_ _Printf_format_string_ char const* const _Format,
...);
FN_printf fn_printf;
fn_printf = (FN_printf)GetProcAddress(LoadLibraryA("msvcrt.dll"), "printf");
//对比两个函数的地址
fn_printf("0x%08X\n", hLoadLibraryA);
fn_printf("0x%08X\n", LoadLibraryA("kernel32.dll"));
return 0;
}

可以看到返回的结果是一模一样的

image-20200419155111509

备注:

  • __declspec(naked)是用来告诉编译器函数代码的汇编语言为自己的所写,不需要编译器添加任何汇编代码

  • 上面的汇编使用的是MASM版本,NASM版本代码如下,根据系统使用不同的代码

    __declspec(naked)DWORD getKernel32()
    {
    __asm {
    XCHG EAX, EBX
    MOV EBX, FS: [30H] //从TIB(Thread Information Block)中取得PEB(Process Environment Block)的地址
    MOV EBX, [EBX + 0CH] //从PEB中取得LDR类的基址
    MOV EBX, [EBX + 14H] //从LDR中获取InMemoryOrderModuleList.Flink的地址(入口1,属于主进程)
    MOV EBX, [EBX] //从InMemoryOrderModuleList.Flink取得InMemoryOrderModuleList.Flink.Flink的地址(入口2,属于ntdll.dll)
    MOV EBX, [EBX] //同上,取得InMemoryOrderModuleList.Flink.Flink.Flink的地址(入口3,属于kernel32.dll)
    MOV EBX, [EBX + 10H] //通过InMemoryOrderModuleList.Flink.Flink.Flink提供的_LDR_DATA_TABLE_ENTRY,再从中最终获取入口3的基址,也就是kernel32.dll的基址了
    XCHG EAX, EBX //此过程的返回kernel32.dll的基址,存于EAX寄存器中
    RETN
    }
    }
  • 上面注释中说的入口点,不同程序不同的入口点如下

    image-20200419161011535

获取kernel32.dll中的函数

有了kernel32.dll基址后,我们可以通过基址来获取到里面需要用到API函数

依旧直接贴代码

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

__declspec(naked)DWORD getKernel32()
{
__asm {
XCHG EAX, EBX
MOV EBX, FS:[30H] //从TIB(Thread Information Block)中取得PEB(Process Environment Block)的地址
MOV EBX, [EBX + 0CH] //从PEB中取得LDR类的基址
MOV EBX, [EBX + 14H] //从LDR中获取InMemoryOrderModuleList.Flink的地址(入口1,属于主进程)
MOV EBX, [EBX] //从InMemoryOrderModuleList.Flink取得InMemoryOrderModuleList.Flink.Flink的地址(入口2,属于ntdll.dll)
MOV EBX, [EBX] //同上,取得InMemoryOrderModuleList.Flink.Flink.Flink的地址(入口3,属于kernel32.dll)
MOV EBX, [EBX + 10H] //通过InMemoryOrderModuleList.Flink.Flink.Flink提供的_LDR_DATA_TABLE_ENTRY,再从中最终获取入口3的基址,也就是kernel32.dll的基址了
XCHG EAX, EBX// 此过程的返回kernel32.dll的基址,存于EAX寄存器中
RETN
}
}


FARPROC _GetProcAddress(HMODULE hModuleBase/*句柄模块*/)
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + 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 lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);

DWORD dwLoop = 0;
FARPROC pRet = NULL;
for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);

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')
{
pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
break;
}
}
return pRet;
}
int MyMain()
{
HMODULE hLoadLibraryA = (HMODULE)getKernel32();

typedef int(__CRTDECL* FN_printf)(
_In_z_ _Printf_format_string_ char const* const _Format,
...);
FN_printf fn_printf;
fn_printf = (FN_printf)GetProcAddress(LoadLibraryA("msvcrt.dll"), "printf");

//声明定义,先转到到原函数定义,然后重新定义
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);

FN_GetProcAddress fn_GetProcAddress;
fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress(hLoadLibraryA);

fn_printf("0x%xn\n", fn_GetProcAddress);
fn_printf("0x%xn\n", GetProcAddress);
return 0;
}

看下图的结果,说明代码没错

image-20200419165857971

上面代码的流程,首先获取kernel32.dll的基址,然后利用typedef来声明动态调用,接着调用写好的_GetProcAddress函数来把地址负责给声明好的fn_GetProcAddress中,接着我们直接使用fn_GetProcAddress函数就和调用windows api一样的。

备注:

  • _GetProcAddress函数的写法涉及到PE结构编写,后面有时间会单独写一篇相关的文章

禁止使用全局变量

因为全局变量在使用vs平台进行编译的时候会加载到PE结构中的特定区段中,类似于使用了绝对地址,这样是我们写shellcode的时候需要避免的。

备注:static关键字也是一样的,需要避免

确保加载所需要的动态链接库

在上面的printf函数中我们也是使用动态调用的方式来实现的,所以在使用非自己写的函数的时候都必须确保加载了所需要的动态链接库!!!!!

编写我们第一个shellcode

我们普通的代码写法

#include <windows.h>
#include <stdio.h>
#include <windows.h>
int main()
{
CreateFileA("1.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
MessageBoxA(NULL, "hello world", tip, MB_OK);
return 0;
}

shellcode写法

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

FARPROC _GetProcAddress(HMODULE hModuleBase);
DWORD getKernel32();

int MyMain()
{
//声明定义GetProcAddress
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);

//获取GetProcAddress真实地址
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress((HMODULE)getKernel32());


//声明定义CreateFileA
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)GetProcAddress(LoadLibrary("kernel32.dll"), "CreateFileA");
//带引号的字符串打散处理
char xyCreateFile[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
//动态获取CreateFile的地址
FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress((HMODULE)getKernel32(), xyCreateFile);
char xyNewFile[] = { 'a','s','c','o','t','b','e','.','t','x','t','\0' };
fn_CreateFileA(xyNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);


//定义LoadLibraryA
typedef HMODULE(WINAPI* FN_LoadLibraryA)(
__in LPCSTR lpLibFileName
);
char xyLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
//动态获取LoadLibraryA的地址
FN_LoadLibraryA fn_LoadLibraryA = (FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryA);


//定义MessageBoxA
typedef int (WINAPI* FN_MessageBoxA)(
__in_opt HWND hWnd,
__in_opt LPCSTR lpText,
__in_opt LPCSTR lpCaption,
__in UINT uType);

//原来的:MessageBoxA(NULL, "Hello world", "tip", MB_OK);
char xy_user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 };
char xy_MessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress(fn_LoadLibraryA(xy_user32), xy_MessageBoxA);
char xy_Hello[] = { 'H','e','l','l','o',' ','w','o','r','l','d',0 };
char xy_tip[] = { 't','i','p' };
fn_MessageBoxA(NULL, xy_Hello, xy_tip, MB_OK);
return 0;
}


__declspec(naked)DWORD getKernel32()
{
__asm {
XCHG EAX, EBX
MOV EBX, FS: [30H] //从TIB(Thread Information Block)中取得PEB(Process Environment Block)的地址
MOV EBX, [EBX + 0CH] //从PEB中取得LDR类的基址
MOV EBX, [EBX + 14H] //从LDR中获取InMemoryOrderModuleList.Flink的地址(入口1,属于主进程)
MOV EBX, [EBX] //从InMemoryOrderModuleList.Flink取得InMemoryOrderModuleList.Flink.Flink的地址(入口2,属于ntdll.dll)
MOV EBX, [EBX] //同上,取得InMemoryOrderModuleList.Flink.Flink.Flink的地址(入口3,属于kernel32.dll)
MOV EBX, [EBX + 10H] //通过InMemoryOrderModuleList.Flink.Flink.Flink提供的_LDR_DATA_TABLE_ENTRY,再从中最终获取入口3的基址,也就是kernel32.dll的基址了
XCHG EAX, EBX// 此过程的返回kernel32.dll的基址,存于EAX寄存器中
RETN
}
}


FARPROC _GetProcAddress(HMODULE hModuleBase)
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + 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 lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);

DWORD dwLoop = 0;
FARPROC pRet = NULL;
for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);

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')
{
pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
break;
}
}
return pRet;
}

效果如下图

image-20200419203622478

接下来我们来提取进程中的shellcode段,首先用PEID来看看文件的偏移点

image-20200419204935411

我们用16进制进制来看看shellcode代码段,如图所示从200-5D7都是我们shellcode

image-20200419221130197

我们把这段代码复制下来另存为一个文件

然后我们用shellcode加载器加载他,看下是否成功

1

函数生成规律

单文件函数生成规律

先说结论:

  • 在单个文件中,函数的生成顺序和函数书写的顺序相关,和函数声明的位置不想关

首先验证我们结论,我们看下面两张图

image-20200420215258991

第二张图我们把b函数放到MyMain函数上面,其他的不变

image-20200420220008795

可以看到我们的结论是完全正确的!!

多文件函数生成规律

首先直接来结论:

  • 文件生成顺序和头文件引用顺序无关,只和工程项目vcxproj结尾的文件相关

  • 如果有多个文件,并且多个文件里面有多个函数,那么函数生成的顺序第一优先级为vcxproj书写的顺序相关,第二优先级只和cpp文件中书写函数的顺序相关,举个例子vcxproj文件中顺序为a.cpp dome.cpp b.cpp,并且a.cpp文件中有两个函数acb.cpp文件只有b函数,dome.cpp文件只有MyMain函数,那么函数排列的顺序就是这样的

    a函数->c函数->MyMain函数->b函数

首先我们来验证第一条结论

image-20200420223622886

用IDA来查看

image-20200420224120045

接着我们来修改vcxproj文件中文件中的位置,可以发现论证成立

image-20200420224649670

接着我们来验证第二个结论,可见结论完全正确

image-20200420230609726

编写Shellcode

首先vs文件创建排序是按数字其次才是字母排序,编译顺序也是如此(0-9-a-z)

首先我们创建一侧项目,然后按上面的要求来配置我们的文件,最后我们可以得到一个入下图所示的解决方案

image-20200426224531306

我们只需要把代码放到z.end.cppa.start.cpp之间即可

首先我们贴0.main.cpp的代码,由于该函数不需要加载在shellcode中,所以我们可以按照正常的写法来写,然后获取ShellcodeEnd函数还有ShellcodeStart函数使他们相减即可

#include"statement.h"
#pragma comment(linker,"/entry:MyMain")
void MyMain()
{
CreateShellcode();

}
void CreateShellcode()
{

HMODULE hMsvcrt = LoadLibraryA("msvcrt.dll");
HANDLE hBin = CreateFileA("sh.bin", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, NULL);

if (hBin == INVALID_HANDLE_VALUE)
{
return;
}
DWORD dwSize = (DWORD)ShellcodeEnd - (DWORD)ShellcodeStart;
DWORD dwWrite;
WriteFile(hBin, ShellcodeStart, dwSize, &dwWrite, NULL);
CloseHandle(hBin);

}

接着我们来定义api.h头文件,这个文件都存放着我们所有的动态调用的api类,定义好动态调用的类后我们把它用typedef来集合起来,看不懂的可以看我原来搬运的一篇文章

#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 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
);

typedef struct _FUNCTIONS
{
FN_GetProcAddress fn_GetProcAddress;
FN_LoadLibraryA fn_LoadLibraryA;
FN_MessageBoxA fn_MessageBoxA;
FN_CreateFileA fn_CreateFileA;

}FUNCTIONS, * PFUNCTIONS;

接着是statement.h头文件,这个文件声明了我们所有自定义的函数

#pragma once
#include<windows.h>
#include"api.h"
void ShellcodeStart();
void ShellcodeEntry();
void ShellcodeEnd();
void CreateShellcode();
void InitFunctions(PFUNCTIONS pFn);
void CreateConfigFile(PFUNCTIONS pFn);

接着是a.start.cpp文件,首先需要定一个ShellcodeStart函数,这个函数是我们所有代码的入口,一个jmp的汇编命令,该命令跳转到的我们真正的起始函数中,接下来就是获取基址,以及一些动态调用的赋值

#include"statement.h"
#include"api.h"

__declspec(naked) void ShellcodeStart()
{
__asm
{
jmp ShellcodeEntry
}

}

//获取系统中kernel32.dll基址的方法
__declspec(naked) DWORD getKernel32()
{
__asm
{
mov eax, fs: [030h] ;
test eax, eax;
js finished;
mov eax, [eax + 0ch];
mov eax, [eax + 14h];
mov eax, [eax];
mov eax, [eax]
mov eax, [eax + 10h]
finished:
ret
}
}

//获取GetProcAddress函数地址
FARPROC getProcAddress(HMODULE hModuleBase)
{
FARPROC pRet = NULL;
PIMAGE_DOS_HEADER lpDosHeader;
PIMAGE_NT_HEADERS32 lpNtHeaders;
PIMAGE_EXPORT_DIRECTORY lpExports;
PWORD lpwOrd;
PDWORD lpdwFunName;
PDWORD lpdwFunAddr;
DWORD dwLoop;

lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
lpNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
if (!lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
{
return pRet;
}
if (!lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
return pRet;
}
lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
if (!lpExports->NumberOfNames)
{
return pRet;
}
lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
lpwOrd = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);
for (dwLoop = 0; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
{
char* pszFunction = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);
if (pszFunction[0] == 'G'
&& pszFunction[1] == 'e'
&& pszFunction[2] == 't'
&& pszFunction[3] == 'P'
&& pszFunction[4] == 'r'
&& pszFunction[5] == 'o'
&& pszFunction[6] == 'c'
&& pszFunction[7] == 'A'
&& pszFunction[8] == 'd'
&& pszFunction[9] == 'd'
&& pszFunction[10] == 'r'
&& pszFunction[11] == 'e'
&& pszFunction[12] == 's'
&& pszFunction[13] == 's')
{
pRet = (FARPROC)(lpdwFunAddr[lpwOrd[dwLoop]] + (DWORD)hModuleBase);
break;
}
}
return pRet;
}

void InitFunctions(PFUNCTIONS pFn)
{
//获取LoadLibraryA函数的地址
pFn->fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
char szLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
pFn->fn_LoadLibraryA = (FN_LoadLibraryA)pFn->fn_GetProcAddress((HMODULE)getKernel32(), szLoadLibraryA);

//使用LoadLibrary函数载入User32.dll,然后在里面搜寻MessageBoxA函数的地址
char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l',0 };
char szMessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
pFn->fn_MessageBoxA = (FN_MessageBoxA)pFn->fn_GetProcAddress(pFn->fn_LoadLibraryA(szUser32), szMessageBoxA);

//在Kernel32.dll中找到CreateFileA函数的地址
char szCreateFileA[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
pFn->fn_CreateFileA = (FN_CreateFileA)pFn->fn_GetProcAddress((HMODULE)getKernel32(), szCreateFileA);

}

void ShellcodeEntry()
{

FUNCTIONS fn;
InitFunctions(&fn);
CreateConfigFile(&fn);

//举例子想再写一个获得系统信息的函数则可以这样写
//GetSystemInfos(&fn);//然后去b.work定义函数,在header.h中声明函数,
}

b.code.cpp这个文件是我们所有的函数使用,如果还想加函数使用之类的都可以写在这边,然后在a.start.cpp中的 ShellcodeEntry函数调用即可

#include"statement.h"
#include"api.h"
void CreateConfigFile(PFUNCTIONS pFn)
{
//MessageBoxA(NULL, "Hello world", "tip", MB_OK);
char szHello[] = { 'H','e','l','l','o',',','a','s','c','o','t','b','e','!',0 };
char szTip[] = { '啊','巴','啊','巴',0 };
pFn->fn_MessageBoxA(NULL, szHello, szTip, MB_OK);
//CreateFileA("1.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
char szName[] = { '1','.','t','x','t',0 };
pFn->fn_CreateFileA(szName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

}

最后是z.end.cpp这里面只要写一个结束的函数就行

#include "api.h"
void ShellcodeEnd()
{

}

生成代码,可以发现顺序完全一致

image-20200426230753970

最后我们来看最终成果

image-20200426230753970

参考文章

<Windows平台高效Shellcode编程技术实战>
https://bbs.bccn.net/thread-464861-1-1.html