|
一个编写传奇封外挂(反外挂)系统的完成过程 - 内存监测篇
本节涉及到PE文件结构方面的知识,需要用到PE文件结构知识这里只是简单罗列和讲解一下,更详细的内容请自行学习了解。
一个常规的PE文件包括:Dos头 + Nt头 + 数据目录 + 节表和节内容组成,有些PE文件在节内容最后还包含附加数据,但是附加数据是不会载入内存的、可以作为特殊数据存贮之用。
PE文件结构体的定义在winnt.h中。
DOS头:
- typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
- WORD e_magic; // Magic number
- WORD e_cblp; // Bytes on last page of file
- WORD e_cp; // Pages in file
- WORD e_crlc; // Relocations
- WORD e_cparhdr; // Size of header in paragraphs
- WORD e_minalloc; // Minimum extra paragraphs needed
- WORD e_maxalloc; // Maximum extra paragraphs needed
- WORD e_ss; // Initial (relative) SS value
- WORD e_sp; // Initial SP value
- WORD e_csum; // Checksum
- WORD e_ip; // Initial IP value
- WORD e_cs; // Initial (relative) CS value
- WORD e_lfarlc; // File address of relocation table
- WORD e_ovno; // Overlay number
- WORD e_res[4]; // Reserved words
- WORD e_oemid; // OEM identifier (for e_oeminfo)
- WORD e_oeminfo; // OEM information; e_oemid specific
- WORD e_res2[10]; // Reserved words
- LONG e_lfanew; // File address of new exe header
- } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
复制代码 NT头:
- typedef struct _IMAGE_NT_HEADERS {
- DWORD Signature;
- IMAGE_FILE_HEADER FileHeader;
- IMAGE_OPTIONAL_HEADER32 OptionalHeader;
- } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
-
- typedef struct _IMAGE_FILE_HEADER {
- WORD Machine;
- WORD NumberOfSections;
- DWORD TimeDateStamp;
- DWORD PointerToSymbolTable;
- DWORD NumberOfSymbols;
- WORD SizeOfOptionalHeader;
- WORD Characteristics;
- } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
-
-
- typedef struct _IMAGE_OPTIONAL_HEADER {
- //
- // Standard fields.
- //
-
- WORD Magic;
- BYTE MajorLinkerVersion;
- BYTE MinorLinkerVersion;
- DWORD SizeOfCode;
- DWORD SizeOfInitializedData;
- DWORD SizeOfUninitializedData;
- DWORD AddressOfEntryPoint;
- DWORD BaseOfCode;
- DWORD BaseOfData;
-
- //
- // NT additional fields.
- //
-
- DWORD ImageBase;
- DWORD SectionAlignment;
- DWORD FileAlignment;
- WORD MajorOperatingSystemVersion;
- WORD MinorOperatingSystemVersion;
- WORD MajorImageVersion;
- WORD MinorImageVersion;
- WORD MajorSubsystemVersion;
- WORD MinorSubsystemVersion;
- DWORD Win32VersionValue;
- DWORD SizeOfImage;
- DWORD SizeOfHeaders;
- DWORD CheckSum;
- WORD Subsystem;
- WORD DllCharacteristics;
- DWORD SizeOfStackReserve;
- DWORD SizeOfStackCommit;
- DWORD SizeOfHeapReserve;
- DWORD SizeOfHeapCommit;
- DWORD LoaderFlags;
- DWORD NumberOfRvaAndSizes;
- IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
- } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
-
- typedef struct _IMAGE_DATA_DIRECTORY {
- DWORD VirtualAddress;
- DWORD Size;
- } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
复制代码 节表:
- typedef struct _IMAGE_SECTION_HEADER {
- BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
- union {
- DWORD PhysicalAddress;
- DWORD VirtualSize;
- } Misc;
- DWORD VirtualAddress;
- DWORD SizeOfRawData;
- DWORD PointerToRawData;
- DWORD PointerToRelocations;
- DWORD PointerToLinenumbers;
- WORD NumberOfRelocations;
- WORD NumberOfLinenumbers;
- DWORD Characteristics;
- } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
复制代码 通过内存监测判断是否存在非法外挂是外挂监测的最重要最有效的方法。只要有非法外挂一定会存在内存操作(非注入式全局加速、以及驱动层拦截篡改游戏封包挂除外,后面会有章节介绍)。我们需要检测游戏进程本身的代码节内存和允许加载的合法DLL的代码内存。需要重点检测PE文件的代码节和节间空间内存,检测内存是否被篡改。
外挂程序一般会修改我们的游戏程序以达到相关功能目的,所以需要监控游戏程序的代码节,确定没有被修改。外挂程序还可能注入一小段程序用来间接调用我们的函数。
内存检测的处理方法是校验在游戏程序正常运行过程中确定不会被更改的数据的校验和判断的。正常方式编写的程序,代码节与数据节是分开的,我们可以认为代码节数据以及PE头数据以及节间空白区域的内存是不会变动的,通过定时计算他们的校验合来检测非法外挂。
野内存:与我们的“线程监测篇”相似、非预料空间外的线程叫“野线程”,那么非预料空间的内存我们就叫‘野内存’。通过注入法远程调用函数一定存在野内存。我们通过不断扫描内存页面可以发现野内存的存在,需要用到VirtualQuery函数,请自行查阅函数相关文档。 从函数基地址逐一扫描内存页,遇到拥有可执行的属性的野内存就可判定使用了外挂程序。需要注意的是:有些外挂作者会很聪明地将自己的代码写入到PE节间的空白处理,或者程序的其它空白处,所以校验的时候要一并纳入监测。
- WINBASEAPI
- SIZE_T
- WINAPI
- VirtualQuery(
- _In_opt_ LPCVOID lpAddress,
- _Out_writes_bytes_to_(dwLength,return) PMEMORY_BASIC_INFORMATION lpBuffer,
- _In_ SIZE_T dwLength
- );
复制代码 还有一个方法就是,取消我们认为正常情况下不可能修改的内存区域的写属性。但凡发现该区域属性变化,也可能是外挂程序所位。但是外挂程序也可能快速地赋予可写属性之后再修改回去。还是得配合内存校验法才行、可能用到的相关函数如下:
- BOOL
- WINAPI
- VirtualProtect(
- _In_ LPVOID lpAddress,
- _In_ SIZE_T dwSize,
- _In_ DWORD flNewProtect,
- _Out_ PDWORD lpflOldProtect
- );
-
- BOOL
- WINAPI
- VirtualProtectEx(
- _In_ HANDLE hProcess,
- _In_ LPVOID lpAddress,
- _In_ SIZE_T dwSize,
- _In_ DWORD flNewProtect,
- _Out_ PDWORD lpflOldProtect
- );
-
- BOOL
- WINAPI
- ReadProcessMemory(
- _In_ HANDLE hProcess,
- _In_ LPCVOID lpBaseAddress,
- _Out_writes_bytes_to_(nSize,*lpNumberOfBytesRead) LPVOID lpBuffer,
- _In_ SIZE_T nSize,
- _Out_opt_ SIZE_T* lpNumberOfBytesRead
- );
-
- BOOL
- WINAPI
- WriteProcessMemory(
- _In_ HANDLE hProcess,
- _In_ LPVOID lpBaseAddress,
- _In_reads_bytes_(nSize) LPCVOID lpBuffer,
- _In_ SIZE_T nSize,
- _Out_op
复制代码 本篇的检测还应该包含监测注入的DLL程序, 外挂作者可能直接将代码写程序DLL注入到进程正常空间中,而不使用野内存,这种方式检测较为简单直接,可使用特征库来直接判断。
|
|