反内存窃取攻击调研

内存窃取原理

  1. 获取目标进程的句柄:攻击程序首先需要获取到目标进程的句柄,这是通过调用 Windows API 函数 OpenProcess 来实现的。通过进程句柄,攻击程序可以访问目标进程的内存空间。

  2. 扫描内存:一旦获取到目标进程的句柄,攻击程序通过调用 ReadProcessMemory 函数来扫描目标进程的内存。这一步是为了找到与用户输入的数值相匹配的内存地址。用户可以输入一个具体的数值,或者使用模糊搜索(如未知值的增加或减少)来进行内存扫描。

  3. 过滤结果:初次扫描后,攻击程序会返回大量的内存地址。用户可以通过进一步的过滤(如改变游戏中的数值并重新扫描)来减少匹配的内存地址数量。这个过程可能需要重复多次,直到只剩下少量的内存地址。

  4. 锁定内存地址:当用户确定了目标内存地址后,攻击程序可以锁定这些地址,并允许用户修改内存中的数值。这是通过 WriteProcessMemory 函数实现的。用户可以直接修改内存中的数据,从而改变游戏中的数值(如生命值、金钱等)。

  5. 注入代码:攻击程序还可以通过注入自定义的汇编代码来修改目标进程的行为。这通常用于创建复杂的作弊功能,如无敌模式、无限弹药等。代码注入是通过挂钩目标进程的函数或指令来实现的。

ReadProcessMemory

ReadProcessMemory 是一个 Windows API 函数,用于从指定进程的内存中读取数据。这个函数广泛用于调试工具、系统监控工具以及某些恶意软件中。以下是 ReadProcessMemory 的详细信息:

函数原型

1
2
3
4
5
6
7
BOOL ReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesRead
);

参数

  • hProcess:要读取内存的目标进程的句柄。这个句柄需要有 PROCESS_VM_READ 访问权限。
  • lpBaseAddress:要读取的内存的起始地址。
  • lpBuffer:指向接收读取数据的缓冲区的指针。
  • nSize:要读取的字节数。
  • lpNumberOfBytesRead:指向接收实际读取的字节数的变量的指针。如果这个参数为 NULL,则忽略实际读取的字节数。

返回值

如果函数成功,返回值为非零。如果函数失败,返回值为零。可以使用 GetLastError 函数获取扩展的错误信息。

典型用途

  1. 调试工具:调试工具使用 ReadProcessMemory 来读取被调试进程的内存数据,以便检查变量值、栈内容等。
  2. 系统监控工具:这些工具通过读取进程内存来获取性能数据和其他信息。
  3. 游戏修改工具:类似 CheatEngine 的工具使用 ReadProcessMemory 来读取游戏内存数据,以便查找和修改游戏中的数值。

示例代码

以下是一个示例,展示如何使用 ReadProcessMemory 读取目标进程的内存数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <windows.h>
#include <stdio.h>

int main() {
DWORD processId = 1234; // 替换为目标进程的ID
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId);

if (hProcess == NULL) {
printf("OpenProcess 失败,错误码: %d\n", GetLastError());
return 1;
}

// 假设我们要读取某个已知地址的数据
LPCVOID baseAddress = (LPCVOID)0x7FF6A3B00000;
char buffer[128];
SIZE_T bytesRead;

if (ReadProcessMemory(hProcess, baseAddress, buffer, sizeof(buffer), &bytesRead)) {
printf("读取成功,读取的字节数: %zu\n", bytesRead);
printf("数据: %s\n", buffer);
} else {
printf("ReadProcessMemory 失败,错误码: %d\n", GetLastError());
}

CloseHandle(hProcess);
return 0;
}

禁用 ReadProcessMemory 的影响

禁用或限制 ReadProcessMemory 的使用可能会对许多正常的软件和工具产生影响,包括但不限于:

  • 调试工具:无法读取目标进程的内存,调试功能将受限。
  • 系统监控工具:无法获取进程的内存和性能数据。
  • 反病毒软件:某些反病毒软件依赖 ReadProcessMemory 来扫描和分析进程内存,禁用该函数可能会降低其检测能力。

提高程序对 ReadProcessMemory 访问的防护

虽然完全禁止 ReadProcessMemory 并不容易,但可以采取一些措施来提高对这种访问的防护:

  1. 检测和响应:在程序中实现检测机制,监控是否有其他进程尝试读取内存,并作出响应。
  2. 代码混淆和反调试技术:使用代码混淆和反调试技术,使得恶意软件和调试工具更难以分析和读取你的程序内存。
  3. 权限控制:尽量减少程序运行时的权限,避免高权限运行,从而减少被其他高权限进程读取内存的风险。
  4. 使用安全的编程实践:确保程序本身没有容易被利用的漏洞,如缓冲区溢出等。

示例代码:检测和响应

以下是一个简单的示例,展示如何检测是否有其他进程尝试读取内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <windows.h>
#include <stdio.h>

void DetectMemoryRead() {
// 检测方法可以包括监控可疑的进程或句柄活动
// 这里仅为示例,实际实现可能更加复杂
printf("检测到可疑的内存读取活动!\n");
// 采取适当的行动,比如记录日志或退出程序
}

int main() {
// 定期检测内存读取
while (1) {
DetectMemoryRead();
Sleep(5000); // 每5秒检测一次
}

return 0;
}

通过这些措施,可以在一定程度上提高程序的安全性,减少被其他程序使用 ReadProcessMemory 访问的风险。

OpenProcess

OpenProcess 是一个Windows API函数,用于打开一个现有进程,并返回一个进程句柄。这个句柄可以用于读取和写入该进程的内存、挂起和恢复进程、查询进程信息等操作。以下是关于 OpenProcess 的详细信息:

函数原型

1
2
3
4
5
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);

参数

  • dwDesiredAccess:指定所需的访问权限。可以是一个或多个访问权限标志的组合。
  • bInheritHandle:如果此参数为TRUE,进程句柄将被继承。当创建子进程时,句柄将被继承。
  • dwProcessId:要打开的目标进程的标识符(PID)。

常用访问权限标志

  • PROCESS_ALL_ACCESS:请求所有可能的访问权限。
  • PROCESS_CREATE_THREAD:允许创建线程。
  • PROCESS_QUERY_INFORMATION:允许查询进程信息。
  • PROCESS_VM_OPERATION:允许执行虚拟内存操作(如VirtualProtectEx)。
  • PROCESS_VM_READ:允许读取进程内存。
  • PROCESS_VM_WRITE:允许写入进程内存。
  • PROCESS_TERMINATE:允许终止进程。

返回值

如果函数成功,返回目标进程的句柄。如果函数失败,返回NULL。可以调用 GetLastError 函数获取详细的错误信息。

典型用途

  1. 调试工具:调试器使用 OpenProcess 来获取被调试进程的句柄,以便读取和写入其内存,设置断点等。
  2. 系统监控工具:这些工具使用 OpenProcess 来获取系统中运行的进程句柄,从而监控进程的状态和性能。
  3. 反病毒和安全软件:这些软件使用 OpenProcess 来扫描和分析可疑进程的行为。
  4. 游戏修改工具:类似CheatEngine的工具使用 OpenProcess 来获取游戏进程的句柄,从而读取和修改游戏内存数据。

示例代码

以下是一个简单的示例,展示如何使用 OpenProcess 获取目标进程的句柄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <windows.h>
#include <stdio.h>

int main() {
DWORD processId = 1234; // 替换为目标进程的ID
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId);

if (hProcess == NULL) {
printf("OpenProcess 失败,错误码: %d\n", GetLastError());
return 1;
}

// 这里可以对进程执行操作,比如读取内存
// ...

CloseHandle(hProcess);
return 0;
}

限制 OpenProcess 对你的进程的访问

要防止其他程序使用 OpenProcess 访问你的进程,可以采取以下措施:

  1. 提高进程权限:将你的进程设置为系统级或受保护进程,这样只有具有相应权限的进程才能访问你的进程。不过,这通常只适用于系统进程或安全软件。

  2. 检测和响应:在你的程序中实现检测机制,监控是否有其他进程尝试访问你的进程,并作出响应。

  3. 代码混淆和反调试技术:使用代码混淆和反调试技术,使得恶意软件和调试工具更难以分析和访问你的程序。

  4. 使用低权限运行:尽量减少你的程序需要的权限,避免高权限运行,从而减少被其他高权限进程访问的风险。

示例代码:检测调试器

以下是一个简单的示例,展示如何检测是否有调试器附加到你的进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <windows.h>
#include <stdio.h>

void DetectDebugger() {
if (IsDebuggerPresent()) {
printf("检测到调试器!\n");
// 采取适当的行动,比如退出程序
ExitProcess(1);
}
}

int main() {
DetectDebugger();

// 你的程序的其余部分
printf("程序正常运行。\n");

return 0;
}

示例代码:检测并响应可疑访问

可以定期扫描进程句柄并监控是否有可疑的访问行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

void ScanProcesses() {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return;
}

PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);

if (Process32First(hSnapshot, &pe)) {
do {
// 检查每个进程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (hProcess != NULL) {
// 在这里进行检查
// ...
CloseHandle(hProcess);
}
} while (Process32Next(hSnapshot, &pe));
}

CloseHandle(hSnapshot);
}

int main() {
// 定期扫描
while (1) {
ScanProcesses();
Sleep(5000); // 每5秒扫描一次
}

return 0;
}

通过这些措施,可以提高程序的安全性,减少被其他程序使用 OpenProcess 访问的风险。需要注意的是,这些方法并不能完全杜绝所有可能的攻击,安全性需要从多个层面进行综合考虑和防护。

在Windows操作系统中,完全禁止其他程序使用 OpenProcess 访问你的程序并不容易,但可以采取一些措施来提高安全性并限制对你的程序进行恶意操作。以下是一些建议:

1. 使用进程保护技术

利用一些进程保护技术,可以提高进程的安全性。这些技术通常用于保护系统进程和防病毒软件,但也可以用于保护你的应用程序。

  • **Protected Process (受保护进程)**:Windows 提供了一种受保护进程机制,但通常只适用于系统进程和特定的应用程序(如反病毒软件)。普通用户进程无法轻易利用这种保护。

2. 动态检测和响应

在你的应用程序中实现动态检测,监控是否有其他进程尝试使用 OpenProcess 获取你的进程句柄,并作出响应。

  • 实现防护代码
    • 检测被调试状态:通过检查 IsDebuggerPresent 函数或者调试寄存器来判断是否被调试。
    • 定期扫描进程:使用 Windows API 定期扫描所有打开的进程句柄,查看是否有可疑的进程在访问你的应用程序。

3. 代码混淆和反调试技术

使用代码混淆和反调试技术,使得恶意软件和调试工具更难以分析和修改你的应用程序。

  • 代码混淆:通过混淆代码使其更难以被逆向工程。
  • 反调试技术:包括检测调试器、使用异常处理机制、反调试API等。

4. 最小化进程权限

尽量减少你的应用程序需要的权限,并在可能的情况下,使用低权限运行应用程序。

5. 使用 Windows 安全功能

利用 Windows 提供的安全功能,如 User Account Control (UAC)、Windows Defender 和其他安全设置,来增强进程的安全性。

示例代码:检查调试器

以下是一个简单的示例,展示如何检查是否有调试器附加到你的进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <windows.h>
#include <stdio.h>

void DetectDebugger() {
if (IsDebuggerPresent()) {
printf("检测到调试器!\n");
// 采取适当的行动,比如退出程序
ExitProcess(1);
}
}

int main() {
DetectDebugger();

// 你的程序的其余部分
printf("程序正常运行。\n");

return 0;
}

示例代码:定期扫描进程句柄

以下是一个示例,展示如何定期扫描进程句柄(需要管理员权限):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

void ScanProcesses() {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return;
}

PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);

if (Process32First(hSnapshot, &pe)) {
do {
// 检查每个进程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (hProcess != NULL) {
// 在这里进行检查
// ...
CloseHandle(hProcess);
}
} while (Process32Next(hSnapshot, &pe));
}

CloseHandle(hSnapshot);
}

int main() {
// 定期扫描
while (1) {
ScanProcesses();
Sleep(5000); // 每5秒扫描一次
}

return 0;
}

这些方法可以帮助你提高程序的安全性,减少被其他程序使用 OpenProcess 访问的风险。不过,需要注意的是,这些方法并不能完全杜绝所有可能的攻击,安全性需要从多个层面进行综合考虑和防护。


https://chiamzhang.github.io/2024/08/04/反内存窃取攻击调研/
Author
Chiam
Posted on
August 4, 2024
Licensed under