恶意程序编写之定义ShellCode
2020-07-15 18:00:47 Author: www.secpulse.com(查看原文) 阅读量:431 收藏

声明

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

前言

    咕咕咕,终于发出来了。

    这次结合知识注入了大量灵魂,应该挺详细的,奥利给 👊🏻 。

环境配置讲解

    当前项目使用的Visual Studio Code 2019。

1、清除多余的函数

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

int main()
{ 
    return 0;
}

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

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

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

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

int MyMain()
{
  return 0;
}

这样看起来是不是就很清爽了呢!

2、禁用安全检查

上面IDA中还有一个多余的函数,这个函数是用于文件安全检查的。我们可以使用这个方式关闭它。

操作流程

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

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

3、设置兼容XP系统

    虽然Windows7 都淘汰了但是还有不少的XP用户,但是v142已经不支持xp编程了,所有我们只能下载v141_xp 来使用。

接着修改运行库

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

4、关闭资源段

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

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

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

5、关闭只读数据段

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

注意以下:

  • vscode 2019关闭了也没办法删除只读数据段(vscode 2018可以删除)

  • 不删除该字段影响不大

6、关闭优化

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

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

代码中的注意事项

1、不要使用双引号

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

禁止使用的格式 

image.png

应该写成的格式

image.png

2、函数动态调用

如果不是用该方法,在我们修改了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 函数的相对地址。

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

接着就是使用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;
}

3、函数动态调用优化

    由上文可以知道我们需要动态调用函数或者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;}

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

  • __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    
   }
}
  • 上面注释中说的入口点,不同程序不同的入口点如下

获取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;}

执行结果

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

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

4、禁止使用全局变量

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

  • static关键字也是一样的,需要避免

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

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

编写第一个ShellCode

image.png

这是普通写法

#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_use***[] = { '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_use***), 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;}

shellcode写法

效果如图

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

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

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

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


函数生成规律

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

先说结论:

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

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

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

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

2、多文件函数生成规律

首先直接来结论:

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

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

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

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

用IDA来查看

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

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

编写Shellcode

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

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

我们只需要把代码放到 z.end.cpp a.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 头文件,这个文件声明了我们所有自定义的函数。

image.png

接着是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函数载入Use***.dll,然后在里面搜寻MessageBoxA函数的地址  char szUse***[] = { '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(szUse***), 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()
{

}

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

最后我们来看最终成果


参考文章

https://bbs.bccn.net/thread-464861-1-1.html

本文作者:Asc0t6e

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/135334.html


文章来源: https://www.secpulse.com/archives/135334.html
如有侵权请联系:admin#unsafe.sh