前言

在一次汉化游戏的过程中,遇见了游戏的CRC校验。笔者想过掉该CRC检测,而由于游戏的代码是通过自映射后的内存区域,无法利用VirtualProtect函数进行修改。所以有了本文记录~

0x1 如何判断代码用了自映射

判断的方法有以下几点要素:

  1. 游戏没有驱动级保护【事无绝对,可能有也会用到此技术】
  2. 内存属性无法修改
  3. 代码的内存类型为MEM_MAPPED类型

0x2 代码自映射原理

程序节区重新映射时设置Section属性为SEC_NO_CHANGE,就可以达到防止代码被inline hook和设置断点调试。

相关代码和文章:

  1. Jevon101/Self-Remapping-Code: 代码重映射:此程序重新映射它的加载映像(image),避免映像中页面保护属性通过NtProtectVirtualMemory函数被修改。 (github.com)
  2. 代码自映射Self-Remapping Code - 思泉 Jev0n
  3. 【笔者自己fork的一份代码】204065248/Self-Remapping-Code: This program remaps its image to prevent the page protection of pages contained in the image from being modified via NtProtectVirtualMemory. (github.com)

0x2 如何修改自映射代码内存属性

当我们懂得了原理后,那么修改属性页就会变得很方便,主要分为以下几个步骤。

  1. 使用VirtualAlloc申请一片空间,用来本分原节数据
  2. 使用ReadProcessMemory将原数据备份
  3. 使用NtCreateSection函数创建一个新的节点,大小与原节点相同,并且为可读可写可执行。
  4. 使用NtUnmapViewOfSection解除原地址映射
  5. 使用NtMapViewOfSection将我们创建的节映射到原地址位置
  6. 使用WriteProcessMemory函数将备份的数据写入我们的节中
  7. 最后使用VirtualFree函数清理掉我们申请的备份空间

这样我们就完成了代码自映射后修改他的内存属性页的问题了。

注意:本段代码执行过程中需要将目标进程暂停,防止解除原地址映射时导致程序执行的代码被清空而引发的崩溃问题。如果为dll劫持或注入方式则需要释放一个进程来完成暂停这件事。

0x3 代码示例

RemapViewOfSection.h

#pragma once
#include <Windows.h>

namespace RemapViewOfSection
{

    typedef struct _UNICODE_STRING {
        USHORT Length;
        USHORT MaximumLength;
        PWSTR  Buffer;
    } UNICODE_STRING, * PUNICODE_STRING;


    typedef struct _OBJECT_ATTRIBUTES {
        ULONG Length;
        HANDLE RootDirectory;
        PUNICODE_STRING ObjectName;
        ULONG Attributes;
        PVOID SecurityDescriptor;
        PVOID SecurityQualityOfService;
    } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;


    typedef NTSTATUS(NTAPI* NtCreateSection_t)(
        PHANDLE SectionHandle,
        ACCESS_MASK DesiredAccess,
        POBJECT_ATTRIBUTES ObjectAttributes,
        PLARGE_INTEGER MaximumSize,
        ULONG SectionPageProtection,
        ULONG AllocationAttributes,
        HANDLE FileHandle
        );

    typedef NTSTATUS(NTAPI* NtUnmapViewOfSection_t)(
        HANDLE ProcessHandle,
        PVOID BaseAddress
        );


    typedef enum _SECTION_INHERIT
    {
        ViewShare = 1,
        ViewUnmap = 2
    } SECTION_INHERIT;

    typedef NTSTATUS(NTAPI* NtMapViewOfSection_t)(
        HANDLE SectionHandle,
        HANDLE ProcessHandle,
        LPVOID BaseAddress,
        ULONG ZeroBits,
        SIZE_T CommitSize,
        PLARGE_INTEGER SectionOffset,
        PSIZE_T ViewSize,
        SECTION_INHERIT InheritDisposition,
        ULONG AllocationType,
        ULONG Win32Protect
        );

    typedef NTSTATUS(NTAPI* NtSuspendProcess_t)(
        HANDLE ProcessHandle
        );

    typedef NTSTATUS(NTAPI* NtResumeProcess_t)(
        HANDLE ProcessHandle
        );

    extern NtCreateSection_t NtCreateSection;
    extern NtUnmapViewOfSection_t NtUnmapViewOfSection;
    extern NtMapViewOfSection_t NtMapViewOfSection;
    extern NtSuspendProcess_t NtSuspendProcess;
    extern NtResumeProcess_t NtResumeProcess;


    bool Init();
    bool RemapViewOfSection(HANDLE processHandle, void* baseAddress, SIZE_T regionSize, DWORD newProtection);
    MEMORY_BASIC_INFORMATION GetSectionInfo(HANDLE processHandle, uintptr_t baseAddress);

} // namespace RemapViewOfSection

RemapViewOfSection.cpp

#include "RemapViewOfSection.h"

#include "xorstr.hpp"

namespace RemapViewOfSection
{

    NtCreateSection_t NtCreateSection;
    NtUnmapViewOfSection_t NtUnmapViewOfSection;
    NtMapViewOfSection_t NtMapViewOfSection;
    NtSuspendProcess_t NtSuspendProcess;
    NtResumeProcess_t NtResumeProcess;

    bool Init()
    {
        const HMODULE hNtDll = LoadLibrary(xorstr_(TEXT("ntdll.dll")));
        if (hNtDll == nullptr) {
            return false;
        }

        NtCreateSection = reinterpret_cast<NtCreateSection_t>(GetProcAddress(hNtDll, xorstr_("NtCreateSection")));
        if (NtCreateSection == nullptr) {
            return false;
        }

        NtUnmapViewOfSection = reinterpret_cast<NtUnmapViewOfSection_t>(GetProcAddress(hNtDll, xorstr_("NtUnmapViewOfSection")));
        if (NtUnmapViewOfSection == nullptr) {
            return false;
        }

        NtMapViewOfSection = reinterpret_cast<NtMapViewOfSection_t>(GetProcAddress(hNtDll, xorstr_("NtMapViewOfSection")));
        if (NtMapViewOfSection == nullptr) {
            return false;
        }

        NtSuspendProcess = reinterpret_cast<NtSuspendProcess_t>(GetProcAddress(hNtDll, xorstr_("NtSuspendProcess")));
        if (NtSuspendProcess == nullptr) {
            return false;
        }

        NtResumeProcess = reinterpret_cast<NtResumeProcess_t>(GetProcAddress(hNtDll, xorstr_("NtResumeProcess")));
        if (NtResumeProcess == nullptr) {
            return false;
        }
        return true;
    }


    bool _RemapViewOfSection(HANDLE processHandle, void* baseAddress, SIZE_T regionSize, DWORD newProtection, void* copyBuffer) {
        // 拷贝当前节点
        SIZE_T numberOfBytesRead;
        if (!ReadProcessMemory(processHandle, baseAddress, copyBuffer, regionSize, &numberOfBytesRead)) {
            return false;
        }

        HANDLE hSection;
        LARGE_INTEGER sectionMaxSize;
        sectionMaxSize.QuadPart = regionSize;

        // 创建一个新的节点,大小与原节点相同,并且可读可写可执行
        NTSTATUS status = NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, &sectionMaxSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
        if (FAILED(status)) {
            return false;
        }

        // 解除原baseAddress的映射
        status = NtUnmapViewOfSection(processHandle, baseAddress);
        if (FAILED(status)) {
            return false;
        }

        // 映射新的节点到原baseAddress位置
        auto viewBase = reinterpret_cast<uintptr_t>(baseAddress);
        LARGE_INTEGER sectionOffset{};
        SIZE_T viewSize = 0;
        status = NtMapViewOfSection(hSection, processHandle,
            &viewBase, 0, regionSize, &sectionOffset, &viewSize, ViewUnmap, 0, newProtection);
        if (FAILED(status)) {
            return false;
        }

        // 拷贝原节点数据到新节点
        SIZE_T numberOfBytesWritten;
        if (!WriteProcessMemory(processHandle, reinterpret_cast<void*>(viewBase), copyBuffer, viewSize, &numberOfBytesWritten)) {
            return false;
        }

        return true;
    }

    bool RemapViewOfSection(HANDLE processHandle, void* baseAddress, SIZE_T regionSize, DWORD newProtection) {
        // 申请一块内存,用于拷贝原节点数据
        void* copyBuffer = VirtualAlloc(nullptr, regionSize, MEM_COMMIT  MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (!copyBuffer) {
            return false;
        }
        // 将该节点进行重新映射并修改权限
        const bool result = _RemapViewOfSection(processHandle, baseAddress, regionSize, newProtection, copyBuffer);
        // 释放拷贝内存
        VirtualFree(copyBuffer, 0, MEM_RELEASE);
        return result;
    }

    MEMORY_BASIC_INFORMATION GetSectionInfo(HANDLE processHandle, uintptr_t baseAddress) {
        MEMORY_BASIC_INFORMATION mbi{};
        uintptr_t address = 0;
        while (VirtualQueryEx(processHandle, reinterpret_cast<LPVOID>(address), &mbi, sizeof(mbi))) {
            address = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;

            if (mbi.RegionSize > 100000 && reinterpret_cast<uintptr_t>(mbi.BaseAddress) <= baseAddress && baseAddress < address &&
                (mbi.State & MEM_COMMIT) && (mbi.Type & (MEM_MAPPED  MEM_PRIVATE  MEM_IMAGE)))
            {
                return mbi;
            }

            if (address == NULL  address > 0x7FFFFFFF0000) {
                break;
            }
        }
        return {};
    }
} // namespace RemapViewOfSection

使用示例

#include <iostream>
#include "RemapViewOfSection.h"
#include "defer.h"
#include <fstream>
#include <string>
#include "xorstr.hpp"

std::string GetProgramPath()
{
    char buffer[MAX_PATH]{};
    const DWORD dwSize = GetModuleFileNameA(nullptr, buffer, MAX_PATH);
    if (dwSize == 0) {
        return "";
    }
    std::string strFullPath(buffer);
    const auto pos = strFullPath.find_last_of("\\/");
    if (pos != std::wstring::npos) {
        strFullPath = strFullPath.substr(0, pos);
    }
    return strFullPath;
}

bool DeleteSelf(size_t index)
{
    const std::string strProgramPath = GetProgramPath();
    const std::string strBatPath = strProgramPath + xorstr_("\\delete.bat");
    const std::string strProgramFilePath = strProgramPath + xorstr_("\\tmp") + std::to_string(index) + xorstr_(".exe");
    std::ofstream outfile(strBatPath);
    if (outfile)
    {
        outfile << xorstr_(":try") << std::endl; //定义标记
        outfile << xorstr_("choice /t 1 /d y >nul") << std::endl; //暂停3秒
        outfile << xorstr_("del \"") + strProgramFilePath + xorstr_("\"") << std::endl; //删除原程序文件
        outfile << xorstr_("if exist \"") + strProgramFilePath + xorstr_("\" goto :try") << std::endl; //如果删除失败,运行到标记try处,循环以上步骤
        outfile << xorstr_("del \"") + strBatPath + xorstr_("\""); //删除批处理文件
    }
    outfile.close();

    STARTUPINFOA si{};
    PROCESS_INFORMATION pi{};
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE; //以隐藏状态运行
    ZeroMemory(&pi, sizeof(pi));
    if (!CreateProcessA(NULL, (LPSTR)strBatPath.c_str(), NULL, NULL, FALSE, IDLE_PRIORITY_CLASS, NULL, NULL, &si, &pi))
    {
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return false;
    }
    return true;
}

int main(int argc, char* argv[])
{
    if (!RemapViewOfSection::Init())
    {
        return -1;
    }

    try
    {
        if (argc != 6)
        {
            return -2;
        }

        const SIZE_T index = std::strtoul(argv[1], nullptr, 0);
        const DWORD pid = std::strtoul(argv[2], nullptr, 0);
        const uintptr_t baseAddress = std::strtoull(argv[3], nullptr, 0);
        const SIZE_T regionSize = std::strtoull(argv[4], nullptr, 0);
        const DWORD dwProtect = std::strtoul(argv[5], nullptr, 0);
        defer({
            DeleteSelf(index);
            });

        if (pid)
        {
            const HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
            if (!hProcess)
            {
                return -3;
            }
            defer({
                    CloseHandle(hProcess);
                });
            RemapViewOfSection::NtSuspendProcess(hProcess);
            RemapViewOfSection::RemapViewOfSection(hProcess, reinterpret_cast<void*>(baseAddress), regionSize, dwProtect);
            RemapViewOfSection::NtResumeProcess(hProcess);
        }
    }
    catch (...)
    {
        return -4;
    }
    return 0;
}