The ZwUnmapViewOfSection routine unmaps a view of a section from the virtual address space of a subject process.
NTSTATUS ZwUnmapViewOfSection(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress );
这个函数在 wdm.h 里声明,它的功能是卸载进程的内存镜像(Image Buffer),内存镜像是指进程4GB虚拟地址空间中从 ImageBase 开始,长度为 SizeOfImage 的内存。
卸载内存镜像之后,就得到了一个“干净”的4GB空间,然后我们可以用 VirtualAllocEx 往里面填数据,比如换成其他程序的内存镜像。
这样做的意义就是我们可以从内存中启动一个程序,而不用涉及磁盘读写,因为常规的 CreateProcess 必须指定程序路径,而利用这种技术可以避免这一点。
下面给出一个简单的程序,演示如何创建一个挂起的“傀儡进程”,卸载其内存镜像,并替换为另一个程序的内存镜像,然后恢复运行。
该程序在32位XP可以正常运行,但是在64位WIN10 VirtualAllocEx 会返回0x1E7
main.cpp
// 程序功能:创建一个自己的傀儡进程并卸载内存镜像,用另一个程序的imagebuffer替换
// 32位 多字节字符集
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <WINDOWS.H>
#include <STRING.h>
#include <MALLOC.H>
#include "PE.hpp"
BOOL EnableDebugPrivilege();
int main(int argc, char *argv[])
{
// 提权
EnableDebugPrivilege();
// 读取源文件
LPVOID pSrcFileBuffer = NULL;
DWORD dwSrcFileSize = FileToMemory("D:\\Program32\\littlegame.exe", &pSrcFileBuffer);
if (dwSrcFileSize == 0)
{
printf("读取文件失败\n");
return -1;
}
// 拉伸成内存镜像
LPVOID pSrcImageBuffer = NULL;
DWORD dwSrcImageBufferSize = FileBufferToImageBuffer(pSrcFileBuffer, &pSrcImageBuffer);
// 获取当前进程主模块路径
char szCurrentPaths[MAX_PATH] = {
0 };
GetModuleFileName(NULL, szCurrentPaths, MAX_PATH);
// 以挂起方式创建一个当前进程的傀儡进程,我们只需要它的4GB空间
STARTUPINFO si = {
0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
CreateProcess(NULL, szCurrentPaths, NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
// 获取新进程主线程上下文
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &context);
// 获取 ZwUnmapViewOfSection 函数指针
HMODULE hModuleNt = LoadLibrary("ntdll.dll");
if (hModuleNt == NULL)
{
printf("获取ntdll句柄失败\n");
TerminateThread(pi.hThread, 0);
return -1;
}
typedef DWORD(WINAPI *_TZwUnmapViewOfSection)(HANDLE, PVOID);
_TZwUnmapViewOfSection pZwUnmapViewOfSection = (_TZwUnmapViewOfSection)GetProcAddress(hModuleNt, "ZwUnmapViewOfSection");
if (pZwUnmapViewOfSection == NULL)
{
printf("获取 ZwUnmapViewOfSection 函数指针失败\n");
TerminateThread(pi.hThread, 0);
return -1;
}
// 调用 ZwUnmapViewOfSection 卸载新进程内存镜像
pZwUnmapViewOfSection(pi.hProcess, GetModuleHandle(NULL));
// 获取源程序的ImageBase
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pSrcImageBuffer;
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
DWORD dwSrcImageBase = pOptionHeader->ImageBase;
// 在傀儡进程的源程序的ImageBase处申请SizeOfImage大小的内存
LPVOID pImageBase = VirtualAllocEx(
pi.hProcess, (LPVOID)dwSrcImageBase, dwSrcImageBufferSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if ((DWORD)pImageBase != dwSrcImageBase)
{
printf("VirtualAllocEx 错误码: 0x%X\n", GetLastError()); // 0x1e7 试图访问无效地址
printf("申请到的指针: 0x%X, 期望的地址: 0x%X\n", (DWORD)pImageBase, dwSrcImageBase);
TerminateThread(pi.hThread, 0);
return -1;
}
// 将源程序内存镜像复制到傀儡进程4GB中
if (0 == WriteProcessMemory(
pi.hProcess, (LPVOID)dwSrcImageBase, pSrcImageBuffer, dwSrcImageBufferSize, NULL))
{
printf("写入源程序内存镜像失败\n");
TerminateThread(pi.hThread, 0);
return -1;
}
// 修正入口点
context.Eax = pOptionHeader->AddressOfEntryPoint + dwSrcImageBase;
// 修正 ImageBase
WriteProcessMemory(pi.hProcess, (LPVOID)(context.Ebx + 8), &dwSrcImageBase, 4, NULL);
context.ContextFlags = CONTEXT_FULL;
SetThreadContext(pi.hThread, &context);
// 恢复线程
ResumeThread(pi.hThread);
// 脱壳成功
printf("脱壳成功,源程序正在运行,敲任意字符退出\n");
free(pSrcFileBuffer);
free(pSrcImageBuffer);
system("pause");
return 0;
}
// 提权函数:提升为DEBUG权限
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk = FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (