一、序言

结合之前所讲的内容,我们将如下代码的功能转写为shellcode的实现方式:

#include <windows.h>

int main()
{
    CreateFileA("D:\\1.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    MessageBox(NULL, L"Hello World", L"imbyter.com", MB_OK);

    return 0;
}

上述代码执行的功能为:

  1. 创建文件D:\1.txt;
  2. 弹框提示;

我们将如上功能转用shellcode的方式来实现,大概可以分为以下几部:

二、编译链接属性设置

为达到最好shellcode执行效果,需要修改主函数入口点、关闭缓冲区安全检查、设置系统兼容性、修改运行库、关闭生成清单,以及关闭调试信息。详见Shellcode项目属性规范

三、获取kernel32基址

按照则 原Kernel32基址的获取 的方式,实现对kernel32基址的获取。

四、函数地址动态获取

按照原则 函数地址动态获取 的方式,实现用GetProcAddress动态获取函数地址。

五、调整源码中函数位置

将CreateFileA于MessageBox以动态调用的方式实现,并且要注意,修改函数名后的入口函数EntryMain,要放在整个cpp文件中的最头部。

六、最终实现代码

#include <windows.h>
#include <Winternl.h>

#pragma optimize("", off ) 
#pragma comment(linker,"/entry:EntryMain")

HMODULE GetKernel32BaseAddress();
FARPROC _GetPorcAddress();

int EntryMain()
{
    // 获取GetPorcAddress函数地址
    typedef FARPROC(WINAPI* FN_GetProcAddress)(__in HMODULE hModule, __in LPCSTR lpProcName);
    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)_GetPorcAddress();
    if (fn_GetProcAddress)
    {
        // 获取LoadLibraryA函数地址
        char szLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
        typedef HMODULE(WINAPI* FN_LoadLibraryA)(__in LPCSTR lpLibFileName);
        FN_LoadLibraryA fn_LoadLibraryA = (FN_LoadLibraryA)fn_GetProcAddress(GetKernel32BaseAddress(), szLoadLibraryA);
        if (fn_LoadLibraryA)
        {
            // 获取CreateFileA函数地址
            char szCreateFileA[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
            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_GetProcAddress(GetKernel32BaseAddress(), szCreateFileA);
            // 执行CreateFileA
            char szFilePath[] = { 'D',':','\\','1','.','t','x','t',0 };
            fn_CreateFileA(szFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

            // 获取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 };
            typedef int (WINAPI* FN_MessageBoxA)(__in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType);
            FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress(fn_LoadLibraryA(szUser32), szMessageBoxA);
            // 执行MessageBoxA
            char szCaption[] = { 'i','m','b','y','t','e','r','.','c','o','m',0 };
            char szText[] = { 'H','e','l','l','o',' ','W','o','r','l','d', 0 };
            fn_MessageBoxA(0, szText, szCaption, MB_OK | MB_ICONINFORMATION);
        }
    }

    return 0;
}

// 获取kernel32基址
HMODULE GetKernel32BaseAddress()
{
    HMODULE hKernel32 = NULL;

    // 用户保存模块名
    WCHAR wszModuleName[MAX_PATH];

#ifdef _WIN64    // 64位PEB偏移为0x60
    PPEB lpPeb = (PPEB)__readgsqword(0x60);
#else            // 32位PEB偏移为0x30
    PPEB lpPeb = (PPEB)__readfsdword(0x30);
#endif

    PLIST_ENTRY pListHead = &lpPeb->Ldr->InMemoryOrderModuleList;
    PLIST_ENTRY pListData = pListHead->Flink;

    // 遍历所有模块
    while (pListData != pListHead)
    {
        PLDR_DATA_TABLE_ENTRY pLDRData = CONTAINING_RECORD(pListData, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

        DWORD dwLen = pLDRData->FullDllName.Length / 2;
        if (dwLen > 12)    // 12 是"kernel32.dll"的长度,获取到的完整路径肯定要比模块名长
        {
            // 从获取到的模块完整路径中提取模块名
            for (size_t i = 0; i < 12; i++)
            {
                wszModuleName[11 - i] = pLDRData->FullDllName.Buffer[dwLen - 1 - i];
            }

            // 最终要获取的目标模块名("kernel32.dll"),逐个字节比较,包含大小写。
            if ((wszModuleName[0] == 'k' || wszModuleName[0] == 'K') &&
                (wszModuleName[1] == 'e' || wszModuleName[1] == 'E') &&
                (wszModuleName[2] == 'r' || wszModuleName[2] == 'R') &&
                (wszModuleName[3] == 'n' || wszModuleName[3] == 'N') &&
                (wszModuleName[4] == 'e' || wszModuleName[4] == 'E') &&
                (wszModuleName[5] == 'l' || wszModuleName[5] == 'L') &&
                (wszModuleName[6] == '3') &&
                (wszModuleName[7] == '2') &&
                (wszModuleName[8] == '.') &&
                (wszModuleName[9] == 'd' || wszModuleName[9] == 'D') &&
                (wszModuleName[10] == 'l' || wszModuleName[10] == 'L') &&
                (wszModuleName[11] == 'l' || wszModuleName[11] == 'L'))
            {
                hKernel32 = (HMODULE)pLDRData->DllBase;
                break;
            }
        }
        pListData = pListData->Flink;
    }
    return hKernel32;
}

// 获取GetPorcAddress函数地址
FARPROC _GetPorcAddress()
{
    // 保存最终结果
    FARPROC pGetPorcAddress = NULL;

    // kernel32基址
    HMODULE hKernel32 = GetKernel32BaseAddress();
    if (!hKernel32)
    {
        return NULL;
    }

    PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hKernel32;
    PIMAGE_NT_HEADERS lpNTHeader = (PIMAGE_NT_HEADERS)((unsigned char*)hKernel32 + 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;
    }

    // 通过导出表中的导出函数名,定位"GetProcAddress"的位置
    PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((unsigned char*)hKernel32 + lpNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD lpdwFunName = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNames);
    PWORD lpdwOrd = (PWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfFunctions);

    for (DWORD dwLoop = 0; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
    {
        char* pFunName = (char*)(lpdwFunName[dwLoop] + (unsigned char*)hKernel32);
        // 比较函数名
        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'
            )
        {
            pGetPorcAddress = (FARPROC)(lpdwFunAddr[lpdwOrd[dwLoop]] + (unsigned char*)hKernel32);
            break;
        }
    }
    return pGetPorcAddress;
}

注意:在我们这一节所讲的shellcode编写方法中,入口函数EntryMain一定要放到所有函数的最前面。

在VS平台对代码进行编译的时,会按照一个cpp文件从上到下的顺序对各函数进行编译,生成的exe文件中函数所在的顺序和cpp文件函数的对应顺序基本一致。

我们DIE(或其他二进制编辑器)打开上述代码生成的exe文件,点击“入口点”处“>”:

第09节 第一种shellcode编写实战-LMLPHP

第09节 第一种shellcode编写实战-LMLPHP

然后在新弹框显示的界面左边,发现地址为0200,表示代码入口点的文件偏移为x0200。重新使用二进制编辑器(WinHex)打开exe,从0x200处开始复制,一直到大量连续为0的开始处:

第09节 第一种shellcode编写实战-LMLPHP

然后右键选择“编辑”->“复制选快”->“至新文件”,保存为新文件,这个新文件就是我们最终的shellcode文件。

七、测试shellcode

shellcode都是运行在其他进程的内存空间中,我们测试上面得到的shellcode功能是否正常,可以尝试将上述shellcode运行在我们自己的测试程序中。

  1. 确认上述shellcode最终实现代码的编译版本,要明确是x86版本,还是x64版本。此处我的编译方式为x86。

  2. 重新编写一个和上述shellcode编译类型相同的正常程序。比如一个如下代码的x86方式编译的exe程序:

    #include <iostream>
    
    int main()
    {
        std::cout << "Hello imbyter.com!\n";
    }
  3. 用调试工具(x64dbg)打开第2步生成的exe文件,然后选择菜单中“调试”->“运行到用户代码”,让程序运行到自己的代码空间:

    第09节 第一种shellcode编写实战-LMLPHP
  4. 打开我们前面得到的shellcode文件,全选内容并以“十六进制数值”的方式进行复制:

    第09节 第一种shellcode编写实战-LMLPHP
  5. 将上述复制的数据粘贴到x64dbg下的测试程序。如图在当前执行的语句上右键“二进制”->“粘贴时忽略大小”:

    第09节 第一种shellcode编写实战-LMLPHP
  6. 此时会发现当前代码已经全部粘贴为shellcode的二进制数据,然后点击菜单“调试”->“运行”(或者左上角运行的快捷图标):

    第09节 第一种shellcode编写实战-LMLPHP
  7. 如果发现有上述弹框提示,并且生成了D:\1.txt,表示shellcode执行成功。如果未有结果,需要检查是否是由于shellcode的版本和测试exe程序的版本不一致或其他什么问题,可在下方留言。


如果有任何问题,可以在我们的知识社群中提问和沟通交流:

第09节 第一种shellcode编写实战-LMLPHP​​

一个人走得再快,不如一群人走得更远!🤜🤛


05-10 22:26