Win32程序运行原理

Win32程序运行原理

CPU的保护模式和Windows系统

80386处理器有三种工作模式:实模式、保护模式和虚拟86模式

实模式和虚拟模式是为了和8086处理器兼容而设置的

Windows操作系统运行在保护模式中

Windows的多任务实现

多任务隔离技术:可以使每个任务都有独立的地址空间,就像每个任务独享一个CPU一样

在Windows中任务被进程取代

进程就是正在运行的应用程序的实例(执行它的是线程,进程实则就是一块应用程序的空间)

Windows是多任务操作系统 每个进程内的线程只能访问自己 线程的内存,不能访问其他进程的内存

例如 有进程A,B

A进程中的线程只能访问自己进程的内存,不能访问B进程中的地址

虚拟内存

Windows为每个进程分配4GB的地址空间主要依靠CPU支持

CPU在保护模式下支持虚拟存储

虚拟内存:将磁盘空间当做内存空间来使用

页文件:包含了对所有进程都有效的虚拟内存

4GB虚拟地址的前半部分留给系统,后半部分留给用户

1621362607000

系统空间:内核代码、设备驱动代码等等。部分空间是共享的

内核模式和用户模式:

80386处理器共定义了4中(0~3)特权级别 称为

1621363114545

0是特权级(最高级)

3是用户级

Windows有两种模式:

内核模式是0级:系统程序(驱动等等)

用户模式是3级

当应用程序调用系统函数时,会从用户模式切换到内核模式去执行

内核对象

内核对象:系统提供用户模式下代码与内核模式下代码进行交互的基本接口。

1621364048617

对象句柄:

调用函数创建一个内核对象时会返回一个此对象的句柄

很多API函数都需要使用此句柄来辨别处理哪个内核对象,该句柄仅对创建该内核对象的进程有效

也可以多个进程共享一个内核对象,调用DuplicateHandle复制一个进程句柄传给其他进程

使用计数

系统为进程分配内核对象资源时,会将内核对象使用计数属性初始化为1

以后每次打开这个内核对象,系统就会将使用计数加1,关闭则减1

使用计数为0时,说明这个内核对象所有引用都已经关闭,系统会释放该内核对象资源

进程的创建

进程和线程:

进程:

磁盘将可执行文件载入内存之后就变成了进程

进程是一个正在运行的程序

拥有自己的虚拟空间地址、代码、数据、其他系统资源

有一个或多个线程

一个进程要完成任何事情,必须拥有一个在它地址空间中运行的线程,此线程负责执行该进程地址空间的代码

线程:

进程内执行代码的独立实体

系统创建一个进程后,会创建一个线程来执行进程内的代码,这个线程称为主线程

主线程运行过程中可以创建其他线程,一般主线程创建的线程称为辅助线程或子线程

组成Win32进程的两个部分

1.进程内核对象:操作系统使用此内核对象进行管理该进程

2.私有的虚拟地址空间:包含了所有可执行的或是DLL模块的代码和数据、程序动态申请内存的地方

应用程序的启动过程

控制台应用程序的启动过程

1.操作系统会调用C/C运行期启动函数(会初始化C/C运行期库)

2.C/C++运行期启动函数调用入口main函数

Win32应用程序的启动过程

1.操作系统会调用CreateProcess函数来创建一个新的进程

当一个线程调用CreateProcess函数的时候,系统会创建一个进程内核对象,初始化使用计数为1

该进程内核对象是一个系统用来管理这个进程的数据结构

2.为新的进程创建一个虚拟空间,加载应用程序运行时所需要的代码和数据

3.为新的进程创建一个主线程

4.主线程会执行C/C++运行期启动代码

5.C/C++运行期启动代码会调用main函数

如果系统成功创建一个进程和一个主线程,CreateProcess会返回TRUE,否者返回FALSE

创建进程称为父进程,被创建进程称为子进程

系统在创建新进程的时候会传递一个STARTUPINFO类型的变量,这个结构体包含了父进程传递给子进程的一些信息

STARTUPINFO结构体定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _STARTUPINFO 
{
DWORD cb; //包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,它可用作版本控制手段.应用程序必须将cb初始化为sizeof(STARTUPINFO)
PSTR lpReserved; //保留。必须初始化为NULL
PSTR lpDesktop; //用于标识启动应用程序所在的桌面的名字。如果该桌面存在,新进程便与指定的桌面相关联。如果桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。如果lpDesktop是NULL(这是最常见的情况 ),那么该进程将与当前桌面相关联
PSTR lpTitle; //用于设定控制台窗口的名称。如果lpTitle是NULL,则可执行文件的名字将用作窗口名.This parameter must be NULL for GUI or console processes that do not create a new console window.
DWORD dwX; //用于设定应用程序窗口相对屏幕左上角位置的x 坐标(以像素为单位)。
DWORD dwY; //对于GUI processes用CW_USEDEFAULT作为CreateWindow的x、y参数,创建它的第一个重叠窗口。若是创建控制台窗口的应用程序,这些成员用于指明相对控制台窗口的左上角的位置
DWORD dwXSize; //用于设定应用程序窗口的宽度(以像素为单位)
DWORD dwYSize; //子进程将CW_USEDEFAULT 用作CreateWindow 的nWidth、nHeight参数来创建它的第一个重叠窗口。若是创建控制台窗口的应用程序,这些成员将用于指明控制台窗口的宽度
DWORD dwXCountChars; //用于设定子应用程序的控制台窗口的宽度(屏幕显示的字节列)和高度(字节行)(以字符为单位)
DWORD dwYCountChars;
DWORD dwFillAttribute; //用于设定子应用程序的控制台窗口使用的文本和背景颜色
DWORD dwFlags; //请参见下一段和表4-7 的说明
WORD wShowWindow; //用于设定如果子应用程序初次调用的ShowWindow 将SW_*作为nCmdShow 参数传递时,该应用程序的第一个重叠窗口应该如何出现。本成员可以是通常用于ShowWindow 函数的任何一个SW_*标识符,除了SW_SHOWDEFAULT.
WORD cbReserved2; //保留。必须被初始化为0
PBYTE lpReserved2; //保留。必须被初始化为NULL
HANDLE hStdInput; //用于设定供控制台输入和输出用的缓存的句柄。按照默认设置,hStdInput 用于标识键盘缓存,hStdOutput 和hStdError用于标识控制台窗口的缓存
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

dwFlags 使用标志及含义

1
2
3
4
5
6
7
STARTF_USESIZE // 使用dwXSize和dwYSize成员
STARTF_USESHOWWINDOW //使用wShowWindow成员
STARTF_USEPOSITION //使用dwX和dwY成员
STARTF_USECOUNTCHARS //使用dwXCountChars和dwYCountChars成员
STARTF_USEFILLATTRIBUTE //使用dwFillAttribute成员
STARTF_USESTDHANDLES //使用hStdInput、hStdOutput和hStdError成员
STARTF_RUN_FULLSCREEN //强制在x 8 6 计算机上运行的控制台应用程序以全屏幕方式启动运行

GetStartupInfo函数

获取父进程创建自己时使用的STARTUPINFO结构

1
2
3
4
5
VOID GetStartupInfo(

LPSTARTUPINFO lpStartupInfo // STARTUPINFO指针

);

定义一个STARTUPINFO结构体变量后要初始化cb成员

1
2
STARTUPINFO si = {sizeof(STARTUPINFO)};
GetStartupInfo(&si);

CreateProcess函数

用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件

1
2
3
4
5
6
7
8
9
10
11
12
 BOOL CreateProcess(  
 LPCTSTR lpApplicationName, // 应用程序名称
 LPTSTR lpCommandLine, // 命令行字符串
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性 NULL默认安全属性
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性 NULL默认安全属性
 BOOL bInheritHandles, // 是否继承父进程的属性
 DWORD dwCreationFlags, // 创建标志
 LPVOID lpEnvironment, // 指向新的环境块的指针
 LPCTSTR lpCurrentDirectory, // 指向当前目录名的指针
 LPSTARTUPINFO lpStartupInfo, // 传递给新进程的信息
 LPPROCESS_INFORMATION lpProcessInformation // 新进程返回的信息
);

lp开头代表是说明变量类型为指针变量

LPCSTR 是 const char *

WINDEF.h头文件包含了变量类型对应的宏名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef unsigned long       DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef float FLOAT;
typedef FLOAT *PFLOAT;
typedef BOOL near *PBOOL;
typedef BOOL far *LPBOOL;
typedef BYTE near *PBYTE;
typedef BYTE far *LPBYTE;
typedef int near *PINT;
typedef int far *LPINT;
typedef WORD near *PWORD;
typedef WORD far *LPWORD;
typedef long far *LPLONG;
typedef DWORD near *PDWORD;
typedef DWORD far *LPDWORD;
typedef void far *LPVOID;
typedef CONST void far *LPCVOID;
typedef int INT;
typedef unsigned int UINT;
typedef unsigned int *PUINT;

创建一个新的进程打开记事本

1621369626292

PROCESS_INFORMATION结构体

1621369983635

创建了一个进程之后,如果不使用hProcess或hThread时就应该释放它

父进程必须要有一个CloseHandle函数来关闭CreateProcess函数返回的两个内核对象句柄,否者基本子进程已经终止了,该进程的内核对象和主线程的内核对象仍然没有释放

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
///////////////////////////////////////////////////////////////
// 02CreateProcess.cpp文件
#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
char szCommandLine[] = "cmd";
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;

si.dwFlags = STARTF_USESHOWWINDOW; // 指定wShowWindow成员有效
si.wShowWindow = TRUE; // 此成员设为TRUE的话则显示新建进程的主窗口,
// 为FALSE的话则不显示
BOOL bRet = ::CreateProcess (
NULL, // 不在此指定可执行文件的文件名
szCommandLine, // 命令行参数
NULL, // 默认进程安全性
NULL, // 默认线程安全性
FALSE, // 指定当前进程内的句柄不可以被子进程继承
CREATE_NEW_CONSOLE, // 为新进程创建一个新的控制台窗口
NULL, // 使用本进程的环境变量
NULL, // 使用本进程的驱动器和目录
&si,
&pi);

if(bRet)
{
// 既然我们不使用两个句柄,最好是立刻将它们关闭
::CloseHandle (pi.hThread);
::CloseHandle (pi.hProcess);

printf(" 新进程的进程ID号:%d \n", pi.dwProcessId);
printf(" 新进程的主线程ID号:%d \n", pi.dwThreadId);
}
return 0;
}

windows是通过dwFlags来查看STARTUPINFO变量中的哪一个成员有效,再去取那个成员的值

使用wShowWindow成员用STARTF_USESHOWWINDOW

使用dwXSize和dwXSize用STARTF_USESIZE

使用dwX,dwY成员用STARTF_USEPOSITION

dwFlags = STARTF_USESHOWWINDOW | STARTF_USESIZE | STARTF_USEPOSITION

则wShowWindow,dwXSize,dwXSize,dwX,dwY成员都有效

CREATE_NEW_CONSOLE 表示创建一个新的控制台

CloseHandle函数

关闭一个内核对象

1
2
3
4
5
BOOL CloseHandle(

HANDLE hObject //代表一个已打开对象handle。

);

TRUE:执行成功;

FALSE:执行失败,可以调用GetLastError()获知失败原因。

进程控制

获取系统进程


PROCESSENTRY32 结构

1
2
3
4
5
6
7
8
9
10
11
12
typedef   struct  {    
DWORD dwSize; // 结构大小;
DWORD cntUsage; // 此进程的引用计数;
DWORD th32ProcessID; // 进程ID;
DWORD th32DefaultHeapID; // 进程默认堆ID;
DWORD th32ModuleID; // 进程模块ID;
DWORD cntThreads; // 此进程开启的线程计数;
DWORD th32ParentProcessID;// 父进程ID;
LONG pcPriClassBase; // 线程优先权;
DWORD dwFlags; // 保留;
char szExeFile[MAX_PATH]; // 进程全名;
} PROCESSENTRY32;

CreateToolhelp32Snapshot

可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。

头文件:tlhelp32.h

返回值: 调用成功,返回快照的句柄,调用失败,返回INVALID_HANDLE_VALUE**

1
2
3
4
5
6
7
**HANDLE WINAPI CreateToolhelp32Snapshot(**

*DWORD dwFlags,* //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等

*DWORD th32ProcessID* //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表 //获取 当前进程快照时可以设为0

);

指定快照中包含的系统内容,dwFlags这个参数能够使用下列数值(常量)中的一个或多个。
TH32CS_INHERIT       声明快照句柄是可继承的。
TH32CS_SNAPALL        在快照中包含系统中所有的进程和线程。
TH32CS_SNAPHEAPLIST    在快照中包含在th32ProcessID中指定的进程的所有的堆。
TH32CS_SNAPMODULE    在快照中包含在th32ProcessID中指定的进程的所有的模块。
TH32CS_SNAPPROCESS    在快照中包含系统中所有的进程。
TH32CS_SNAPTHREAD     在快照中包含系统中所有的线程。
H32CS_SNAPALL = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE)


process32First

是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后,我们可以利用process32First函数来获得第一个进程的句柄。

1
2
3
4
BOOL WINAPI Process32First(
HANDLE hSnapshot, //快照句柄
LPPROCESSENTRY32 lppe //PROCESSENTRY32指针
);

Process32Next

是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后,我们可以利用Process32Next函数来获得下一个进程的句柄。

1
2
3
4
BOOL WINAPI Process32Next(
HANDLE hSnapshot, //快照句柄
LPPROCESSENTRY32 lppe //PROCESSENTRY32指针
);


获取系统进程的例子

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
///////////////////////////////////////////////////////////////
// 02ProcessList.cpp文件


#include "stdafx.h"
#include <windows.h>
#include <tlhelp32.h> // 声明快照函数的头文件

int main(int argc, char* argv[])
{
PROCESSENTRY32 pe32;
// 在使用这个结构之前,先设置它的大小
pe32.dwSize = sizeof(pe32);

// 给系统内的所有进程拍一个快照
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hProcessSnap == INVALID_HANDLE_VALUE)
{
printf(" CreateToolhelp32Snapshot调用失败! \n");
return -1;
}

// 遍历进程快照,轮流显示每个进程的信息
BOOL bMore = ::Process32First(hProcessSnap, &pe32);
while(bMore)
{
printf(" 进程名称:%s \n", pe32.szExeFile);
printf(" 进程ID号:%u \n\n", pe32.th32ProcessID);

bMore = ::Process32Next(hProcessSnap, &pe32);
}

// 不要忘记清除掉snapshot对象
::CloseHandle(hProcessSnap);
return 0;
}


终止当前进程

终止进程也就是结束程序的执行,让它从内存中卸载。进程终止的原因可能有4种:

(1)主线程的入口函数返回。

(2)进程中一个线程调用了ExitProcess 函数。

(3)此进程中的所有线程都结束了。

(4)其他进程中的一一个线程调 用了TerminateProcess 函数。

Exitprocess结束当前进程函数

1
void Exitprocess(UINT uExitCode);//uExitCode为退出代码

TerminateProcess终止其他进程函数

1
2
3
4
BOOL TerminateProcess(
HANDLE hprocess, //要结束的进程句柄
UINT uExitCode //指定目标进程的退出代码
);

OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。

1
2
3
4
5
HANDLE OpenProcess(
DWORD dwDesiredAccess, //渴望得到的访问权限(标志)
BOOL bInheritHandle, // 是否继承句柄
DWORD dwProcessId// 进程id
);

**dwDesiredAccess :**获取的权限,可分为以下几种

PROCESS_ALL_ACCESS:获取所有权限

PROCESS_CREATE_PROCESS:创建进程

PROCESS_CREATE_THREAD:创建线程

PROCESS_DUP_HANDLE:使用DuplicateHandle()函数复制一个新句柄

PROCESS_QUERY_INFORMATION:获取进程的令牌、退出码和优先级等信息

PROCESS_QUERY_LIMITED_INFORMATION:获取进程特定的某个信息

PROCESS_SET_INFORMATION:设置进程的某种信息

PROCESS_SET_QUOTA:使用SetProcessWorkingSetSize函数设置内存限制

PROCESS_SUSPEND_RESUME:暂停或者恢复一个进程

PROCESS_TERMINATE:使用Terminate函数终止进程

PROCESS_VM_OPERATION:在进程的地址空间执行操作

PROCESS_VM_READ:使用ReadProcessMemory函数在进程中读取内存

PROCESS_VM_WRITE:使用WriteProcessMemory函数在进程中写入内存

SYNCHRONIZE:使用wait函数等待进程终止

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
///////////////////////////////////////////////////////////////
// 02TerminateProcess.cpp文件


#include "stdafx.h"
#include <windows.h>

BOOL TerminateProcessFromId(DWORD dwId)
{
BOOL bRet = FALSE;
// 打开目标进程,取得进程句柄
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
if(hProcess != NULL)
{
// 终止进程
bRet = ::TerminateProcess(hProcess, 0);
}
CloseHandle(hProcess);
return bRet;
}

int main(int argc, char* argv[])
{
DWORD dwId;
printf(" 请输入您要终止的进程的ID号: \n");
scanf("%u", &dwId);
if(TerminateProcessFromId(dwId))
{
printf(" 终止进程成功! \n");
}
else
{
printf(" 终止进程失败! \n");
}

return 0;
}

GetLastError获取调用线程的最后出错代码

1
DWORD GetLastError(VOID);

GetExitCodeProcess 获取一个已中断进程的退出代码

1
2
3
4
BOOL GetExitCodeProcess(
HANDLE hProcess, //想获取退出代码的一个进程的句柄
LPDWORD lpExitCode //用于装载进程退出代码的一个长整数变量。如进程尚未中止,则设为常数STILL_ACTIVE
);

一旦进程终止, 就会有下列事件发生:

(1)所有被这个进程创建或打开的对象句柄就会关闭。

(2)此进程内的所有线程将终止执行。

(3)进程内核对象变成受信状态,所有等待在此对象上的线程开始运行,即WaitForSingleObject函数返回。

(4)系统将进程对象中退出代码的值由STILL_ ACTIVE改为指定的退出码。


ReadProcessMemory是一个内存操作函数, 其作用为根据进程句柄读入该进程的某个内存空间

1
2
3
4
5
6
7
BOOL ReadProcessMemory(
HANDLE hProcess, //待读进程的句柄
PVOID pvAddressRemote, //目标进程中待读内容的起始位置
PVOID pvBufferLocal, //用来接受读取数据的缓冲区
DWORD dwSize, //要读取的字节数
PDWORD pdwNumBytesRead //用来供函数返回实际读取的字节数
);

WriteProcessMemory是计算机语言中的一种函数。此函数能写入某一进程的内存区域(直接写入会出Access Violation错误),故需此函数入口区必须可以访问,否则操作将失败。

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess, //由OpenProcess返回的进程句柄。
LPVOID lpBaseAddress, //要写的内存首地址
LPVOID lpBuffer, //指向要写的数据的指针。
DWORD nSize, //要写入的字节数。
LPDWORD lpNumberOfBytesWritten //用来供函数返回实际写入的字节数
);

应该在目标进程的整个用户地址空间进行搜索。在进程的整个4GB地址中,Windows 98系列的操作系统为应用程序预留的是4MB到2GB部分,Windows2000系列的操作系统预留的是64KB到2GB部分,所以在搜索前还要先判断操作系统的类型,以决定搜索的范围。

OSVERSIONINFO 操作系统的信息版本结构

1621378080153

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _OSVERSIONINFO {

DWORD dwOSVersionInfoSize; // 本结构的大小,必须在调用之前设置

DWORD dwMajorVersion;//操作系统的主版本号

DWORD dwMinorVersion;//操作系统的次版本号

DWORD dwBuildNumber;//操作系统的编译版本号

DWORD dwPlatformld;//操作系统平台。可以是VER_PLATFORM_WIN32_NT (2000系列)等

TCHAR szCSDVersion[128];//指定安装在系统上的最新服务包,例如“Service Pack3"等

} OSVERSIONINFO;

GetVersionEX返回当前操作系统的版本号(在64位系统上是32位字节长度)。

1
2
3
BOOL GetVersionEx(
LPOSVERSIONINFO lpVersionInformation // 指向版本信息结构体的指针
);

游戏修改器例子

Windows采用了分页机制来管理内存,每页的大小是4KB (在x86处理器上)。也就是说Windows是以4KB为单位来为应用程序分配内存的。所以可以按页来搜索目标内存,以提高搜索效率。下面的CompareAPage函数的功能就是比较目标进程内存中1页大小的内存。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
///////////////////////////////////////////////////////////////
// 02MemRepair.cpp文件
#define _CRT_SECURE_NO_WARNINGS
#include "windows.h"
#include "stdio.h"
#include <iostream>


BOOL FindFirst(DWORD dwValue); // 在目标进程空间进行第一次查找
BOOL FindNext(DWORD dwValue); // 在目标进程地址空间进行第2、3、4……次查找

DWORD g_arList[1024]; // 地址列表
int g_nListCnt; // 有效地址的个数
HANDLE g_hProcess; // 目标进程句柄


//////////////////////

BOOL WriteMemory(DWORD dwAddr, DWORD dwValue);
void ShowList();


int main(int argc, char* argv[])
{
// 启动02testor进程
char szFileName[] = "ConsoleApplication1.exe";
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
::CreateProcess(NULL, szFileName, NULL, NULL, FALSE,
CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
// 关闭线程句柄,既然我们不使用它
::CloseHandle(pi.hThread);
g_hProcess = pi.hProcess;

// 输入要修改的值
int iVal;
printf(" Input val = ");
scanf("%d", &iVal);

// 进行第一次查找
FindFirst(iVal);

// 打印出搜索的结果
ShowList();


while(g_nListCnt > 1)
{
printf(" Input val = ");
scanf("%d", &iVal);

// 进行下次搜索
FindNext(iVal);

// 显示搜索结果
ShowList();
}


// 取得新值
printf(" New value = ");
scanf("%d", &iVal);

// 写入新值
if(WriteMemory(g_arList[0], iVal))
printf(" Write data success \n");


::CloseHandle(g_hProcess);
return 0;
}

BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
{
// 读取1页内存
BYTE arBytes[4096];
if(!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL))
return FALSE; // 此页不可读

// 在这1页内存中查找
DWORD* pdw;
for(int i=0; i<(int)4*1024-3; i++)
{
pdw = (DWORD*)&arBytes[i];
if(pdw[0] == dwValue) // 等于要查找的值?
{
if(g_nListCnt >= 1024)
return FALSE;
// 添加到全局变量中
g_arList[g_nListCnt++] = dwBaseAddr + i;
}
}

return TRUE;
}

BOOL FindFirst(DWORD dwValue)
{
const DWORD dwOneGB = 1024*1024*1024; // 1GB
const DWORD dwOnePage = 4*1024; // 4KB

if(g_hProcess == NULL)
return FALSE;

// 查看操作系统类型,以决定开始地址
DWORD dwBase;
OSVERSIONINFO vi = { sizeof(vi) };
::GetVersionEx(&vi);
if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
dwBase = 4*1024*1024; // Windows 98系列,4MB
else
dwBase = 64*1024; // Windows NT系列,64KB

// 在开始地址到2GB的地址空间进行查找
for(; dwBase <2*dwOneGB; dwBase += dwOnePage)
{
// 比较1页大小的内存
CompareAPage(dwBase, dwValue);
}

return TRUE;
}

BOOL FindNext(DWORD dwValue)
{
// 保存m_arList数组中有效地址的个数,初始化新的m_nListCnt值
int nOrgCnt = g_nListCnt;
g_nListCnt = 0;

// 在m_arList数组记录的地址处查找
BOOL bRet = FALSE; // 假设失败
DWORD dwReadValue;
for(int i=0; i<nOrgCnt; i++)
{
if(::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL))
{
if(dwReadValue == dwValue)
{
g_arList[g_nListCnt++] = g_arList[i];
bRet = TRUE;
}
}
}

return bRet;
}

// 打印出搜索到的地址
void ShowList()
{
for(int i=0; i< g_nListCnt; i++)
{
printf("%08lX \n", g_arList[i]);
}
}

BOOL WriteMemory(DWORD dwAddr, DWORD dwValue)
{
return ::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
///////////////////////////////////////////////////////////////
// 02Testor.cpp文件



#include <stdio.h>

// 全局变量测试
int g_nNum;
int main(int argc, char* argv[])
{
int i = 198; // 局部变量测试
g_nNum = 1003;

while(1)
{
// 输出个变量的值和地址
printf(" i = %d, addr = %08lX; g_nNum = %d, addr = %08lX \n",
++i, &i, --g_nNum, &g_nNum);
getchar();
}

return 0;
}