Win32程序运行原理
CPU的保护模式和Windows系统
80386处理器有三种工作模式:实模式、保护模式和虚拟86模式
实模式和虚拟模式是为了和8086处理器兼容而设置的
Windows操作系统运行在保护模式中
Windows的多任务实现
多任务隔离技术:可以使每个任务都有独立的地址空间,就像每个任务独享一个CPU一样
在Windows中任务被进程取代
进程就是正在运行的应用程序的实例(执行它的是线程,进程实则就是一块应用程序的空间)
Windows是多任务操作系统 每个进程内的线程只能访问自己 线程的内存,不能访问其他进程的内存
例如 有进程A,B
A进程中的线程只能访问自己进程的内存,不能访问B进程中的地址
虚拟内存
Windows为每个进程分配4GB的地址空间主要依靠CPU支持
CPU在保护模式下支持虚拟存储
虚拟内存:将磁盘空间当做内存空间来使用
页文件:包含了对所有进程都有效的虚拟内存
4GB虚拟地址的前半部分留给系统,后半部分留给用户
系统空间:内核代码、设备驱动代码等等。部分空间是共享的
内核模式和用户模式:
80386处理器共定义了4中(0~3)特权级别 称为环
0是特权级(最高级)
3是用户级
Windows有两种模式:
内核模式是0级:系统程序(驱动等等)
用户模式是3级
当应用程序调用系统函数时,会从用户模式切换到内核模式去执行
内核对象
内核对象:系统提供用户模式下代码与内核模式下代码进行交互的基本接口。
对象句柄:
调用函数创建一个内核对象时会返回一个此对象的句柄
很多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; PSTR lpReserved; PSTR lpDesktop; PSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; PBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;
dwFlags 使用标志及含义
1 2 3 4 5 6 7 STARTF_USESIZE STARTF_USESHOWWINDOW STARTF_USEPOSITION STARTF_USECOUNTCHARS STARTF_USEFILLATTRIBUTE STARTF_USESTDHANDLES STARTF_RUN_FULLSCREEN
GetStartupInfo函数
获取父进程创建自己时使用的STARTUPINFO结构
1 2 3 4 5 VOID GetStartupInfo ( LPSTARTUPINFO lpStartupInfo ) ;
定义一个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, LPSECURITY_ATTRIBUTES lpThreadAttributes, 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;
创建一个新的进程打开记事本
创建了一个进程之后,如果不使用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 #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; si.wShowWindow = TRUE; 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; DWORD th32DefaultHeapID; DWORD th32ModuleID; DWORD cntThreads; DWORD th32ParentProcessID; LONG pcPriClassBase; DWORD dwFlags; char szExeFile[MAX_PATH]; } PROCESSENTRY32;
可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。
头文件:tlhelp32.h
返回值: 调用成功,返回快照的句柄,调用失败,返回INVALID_HANDLE_VALUE**
1 2 3 4 5 6 7 **HANDLE WINAPI CreateToolhelp32Snapshot (** *DWORD dwFlags,* *DWORD th32ProcessID* ) ;
指定快照中包含的系统内容,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 ) ;
Process32Next
是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后,我们可以利用Process32Next函数来获得下一个进程的句柄。
1 2 3 4 BOOL WINAPI Process32Next ( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ) ;
获取系统进程的例子
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 #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); } ::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 #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 操作系统的信息版本结构
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 #define _CRT_SECURE_NO_WARNINGS #include "windows.h" #include "stdio.h" #include <iostream> BOOL FindFirst (DWORD dwValue) ; BOOL FindNext (DWORD dwValue) ; DWORD g_arList[1024 ]; int g_nListCnt; HANDLE g_hProcess; BOOL WriteMemory (DWORD dwAddr, DWORD dwValue) ;void ShowList () ;int main (int argc, char * argv[]) { 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) { BYTE arBytes[4096 ]; if (!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096 , NULL )) return FALSE; 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 ; const DWORD dwOnePage = 4 *1024 ; 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 ; else dwBase = 64 *1024 ; for (; dwBase <2 *dwOneGB; dwBase += dwOnePage) { CompareAPage(dwBase, dwValue); } return TRUE; } BOOL FindNext (DWORD dwValue) { int nOrgCnt = g_nListCnt; g_nListCnt = 0 ; 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 #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 ; }