Windows API 进程相关笔记
0. 前言
最近做了一个进程信息相关的项目,整理了一下自己做项目时的笔记,分享给大家
1. 相关概念
1.1 HANDLE
概念
HANDLE(句柄)是Windows操作系统中的一个概念。 在Windows程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标示这些资源的标示号,即句柄。 句柄指的是一个核心对象在某一个进程中的唯一索引,而不是指针。
Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。
系统对内核对象以链表的形式进行管理,载入到内存中的每一个内
核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的handle。
1.2 DLL和进程的地址空间
一旦系统将一个DLL的文件映像映射到调用进程的地址空间后,进程中的所有线程都可以调用该DLL中的函数了。此时,该DLL中的代码和数据全部存放在进程的地址空间中,且DLL中的函数创建的任何对象都为调用线程或调用进程所拥有-DLL绝对不会拥有任何对象。
举个例子,如果DLL中的一个函数调用了VirtualAlloc,系统就会从调用进程的地址空间中预定地址空间区域(即申请内存)。如果稍后从进程的地址空间中撤销对DLL的映射,那么这块地址区域仍然被保持为预定状态(DLL被从调用进程中取消映射, 并不会主动释放动态分配的内存)。被预定的空间区域的拥有者是进程,只有当线程调用了VirtualFree函数或者当进程终止时,该区域才会被释放。
也就是说,当一个DLL被加载到调用进程的地址空间内,DLL内所有申请的内存空间都是属于调用进程的,静态变量和全局变量都会是一份全新的实例。对于DLL中申请的内存要记得释放掉,并且严格遵循“谁申请谁释放”的原则,尽量不要dll中申请的内存,然后在调用线程中直接通过操作符或者API函数进行删除(当dll和调用进程使用的运行库编译选项不是同时为MD时,会导致程序崩溃)
1.3 DLL
DLL:Dynamic Link Library,即动态链接库,这种库包含了可由多个程序同时使用的代码和数据
DLL最初用于节约应用程序所需要的磁盘和内存空间。早前,在传统的非共享库中,一部分代码简单地附加到调用的程序中。如果两个程序同时调用同一个子程序,就会出现两份那段代码。相反,许多应用共享的代码能够切分到一个DLL中,在硬盘上存为一个文档,在内存中只需使用一个实例
DLL的缺点
设想这样一个场景:程序A会使用1.0版本的动态链接库X,则在程序A安装到系统时,会同时安装该1.0版本的动态链接库X。假设另一个程序B也会使用到动态链接库X,那么程序B直接复制到硬盘中即可正常运行,因为动态链接库已经存在于系统中。然而有一天,另一程序C也要使用动态链接库X,但是由于程序C开发的时间较晚,其需要较新版本---2.0版本的动态链接库X。则在程序C被安装到系统时,2.0版本的动态链接库X 也必须随之安装到系统中,此时系统中1.0版本的动态链接库将被2.0版本所取代(替换)。
情况1:新版本的动态链接库不兼容旧版本。如,A何B需要X所提供的功能,在升级到2.0后,新版本的X竟然把此功能取消了(很难想象吧,呵呵但有时候就是如此....)。则此时虽然C能正常运行,但A和B均无法工作了。
情况2:新版本的动态链接库兼容旧版本,但是存在一个bug。
1.4 Windows下堆结构
Windows下的堆主要有两种,进程的默认堆和自己创建的私有堆。在程序启动时,系统在刚刚创建的进程虚拟地址空间中创建一个进程的默认堆,而且程序也可以通过 HeapCreate 函数来调用 ntdll 中的RtlCreateHeap 来创建自己的私有堆,所以一个进程中可以存在多个堆。
虽说这两种堆名称不同,但是其本质是相同的,区别的只是返回的句柄不同,私有堆虽然名字是私有,但并不是只能在创建它的线程中使用,如果得到它的句柄,在其他线程中也可使用。
2. Windows API函数
CreateToolhelp32Snapshot
CreateToolhelp32Snapshot可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。说到底,可以获取系统中正在运行的进程信息,线程信息,等。
HANDLE WINAPI CreateToolhelp32Snapshot(
_In_ DWORD dwFlags, //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等
_In_ DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0
);
进程快照
快照就是snapshot, 类似于screen-shot(屏幕快照,当你按prtscr键时抓的当前windows全屏)进程快照就是当前系统中正在运行的所有进程列表,一般用CreateToolhelp32Snapshot得到.
Windows是多线程的.所以有以下3点注意事项导致需要使用"快照"这个概念.
1 当你第一次调用某个函数枚举进程的时候,你得到了当前系统进程信息,而你第二次试图得到这个信息的时候,这个信息可能已经发生了变化.所以这个信息是一个"照片",是过去某个时刻的情况.
2 进程的创建是一个"漫长"的过程,在枚举进程函数被调用过程中,进程可能发生了变化,所以得到的仍然是某个时刻的"照片
Module32First function (tlhelp32.h)
检索与进程关联的第一个模块的信息。
BOOL Module32First(
HANDLE hSnapshot,//从先前调用CreateToolhelp32Snapshot函数返回的快照句柄。
LPMODULEENTRY32 lpme //一个指向MODULEENTRY32结构的指针
);
MODULEENTRY32结构体
描述属于指定进程的模块列表中的条目 module 模块 entry入口
typedef struct tagMODULEENTRY32 {
DWORD dwSize;// 结构体大小
DWORD th32ModuleID;// 该成员不再使用,并且始终设置为1
DWORD th32ProcessID; // 要检查其模块的进程的标识符
DWORD GlblcntUsage;
DWORD ProccntUsage;
BYTE *modBaseAddr; // 模块在所属进程上下文中的基地址
DWORD modBaseSize; // 模块的大小,以字节为单位
HMODULE hModule;
char szModule[MAX_MODULE_NAME32 + 1]; // 模块名
char szExePath[MAX_PATH];// 模块路径
} MODULEENTRY32;
PROCESSENTRY32 structure (tlhelp32.h)
typedef struct tagPROCESSENTRY32 {
DWORD dwSize;//将该成员设置为sizeof(PROCESSENTRY32)。如果不初始化dwSize, Process32First会失败
DWORD cntUsage;
DWORD th32ProcessID; // 进程ID
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads; // 进程启动的执行线程数
DWORD th32ParentProcessID; // 创建该进程的进程的标识符(它的父进程)
LONG pcPriClassBase; // 由该进程创建的任何线程的基本优先级
DWORD dwFlags;
CHAR szExeFile[MAX_PATH];// 进程的可执行文件的名称。要检索可执行文件的完整路径,调用Module32First函数并检查返回的MODULEENTRY32结构的szExePath成员。但是,如果调用进程是32位进程,则必须调用QueryFullProcessImageName函数来检索64位进程的可执行文件的完整路径。
} PROCESSENTRY32;
描述快照时驻留在系统地址空间中的进程列表中的一个条目。
NtQueryInformationProcess function (winternl.h)
__kernel_entry NTSTATUS NtQueryInformationProcess(
HANDLE ProcessHandle,//要检索其信息的进程的句柄
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,//一个指向由调用应用程序提供的缓冲区的指针,函数将请求的信息写入其中。所写信息的大小取决于ProcessInformationClass参数的数据类型
ULONG ProcessInformationLength,//ProcessInformation参数所指向的缓冲区大小,以字节为单位。
PULONG ReturnLength// 指向变量的指针,函数在该变量中返回所请求信息的大小。如果函数成功,这是ProcessInformation参数所指向的写入缓冲区的信息的大小,但如果缓冲区太小,这是成功接收信息所需的缓冲区的最小大小。
);
ProcessInformationClass 参数
ProcessInformationClass
要检索的流程信息的类型。该参数可以是PROCESSINFOCLASS枚举中的下列值之一。
Value | Meaning |
---|---|
ProcessBasicInformation 0 | Retrieves a pointer to a PEB structure that can be used to determine whether the specified process is being debugged, and a unique value used by the system to identify the specified process.Use the CheckRemoteDebuggerPresent and GetProcessId functions to obtain this information. |
**ProcessDebugPort ** 7 | Retrieves a DWORD_PTR value that is the port number of the debugger for the process. A nonzero value indicates that the process is being run under the control of a ring 3 debugger.Use the CheckRemoteDebuggerPresent or IsDebuggerPresent function. |
**ProcessWow64Information ** 26 | Determines whether the process is running in the WOW64 environment (WOW64 is the x86 emulator that allows Win32-based applications to run on 64-bit Windows).Use the IsWow64Process2 function to obtain this information. |
ProcessImageFileName 27 | Retrieves a UNICODE_STRING value containing the name of the image file for the process.Use the QueryFullProcessImageName or GetProcessImageFileName function to obtain this information. |
**ProcessBreakOnTermination ** 29 | Retrieves a ULONG value indicating whether the process is considered critical.Note This value can be used starting in Windows XP with SP3. Starting in Windows 8.1, IsProcessCritical should be used instead. |
**ProcessSubsystemInformation ** 75 | Retrieves a SUBSYSTEM_INFORMATION_TYPE value indicating the subsystem type of the process. The buffer pointed to by the ProcessInformation parameter should be large enough to hold a single SUBSYSTEM_INFORMATION_TYPE enumeration. |
OpenProcess function (processthreadsapi.h)
HANDLE OpenProcess(
DWORD dwDesiredAccess,//对进程对象的访问。此访问权限将根据进程的安全描述符进行检查。该参数可以是进程访问权限的一个或多个。如果调用者已经启用了SeDebugPrivilege特权,则不管安全描述符的内容如何,请求的访问都会被授予。
BOOL bInheritHandle,//如果该值为TRUE,则该进程创建的进程将继承该句柄。否则,进程不会继承此句柄。
DWORD dwProcessId//要打开的本地进程的标识符。
);
第一个参数具体有哪些可以参考
https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
我用到的
Value | Meaning |
---|---|
PROCESS_ALL_ACCESS | All possible access rights for a process object.Windows Server 2003 and Windows XP: The size of the PROCESS_ALL_ACCESS flag increased on Windows Server 2008 and Windows Vista. If an application compiled for Windows Server 2008 and Windows Vista is run on Windows Server 2003 or Windows XP, the PROCESS_ALL_ACCESS flag is too large and the function specifying this flag fails with ERROR_ACCESS_DENIED. To avoid this problem, specify the minimum set of access rights required for the operation. If PROCESS_ALL_ACCESS must be used, set _WIN32_WINNT to the minimum operating system targeted by your application (for example, #define _WIN32_WINNT _WIN32_WINNT_WINXP ). For more information, see Using the Windows Headers. |
PROCESS_QUERY_INFORMATION (0x0400) | 检索某个进程的特定信息所必需的,比如它的令牌、退出代码和优先级类(请参阅OpenProcessToken)。 |
PROCESS_VM_READ (0x0010) | 需要使用ReadProcessMemory读取进程中的内存 |
GetProcessHeap function (heapapi.h)
HANDLE GetProcessHeap();
如果函数成功,返回值是调用进程堆的句柄。
如果函数失败,返回值为NULL。
PROCESS_INFORMATION structure (processthreadsapi.h)
包含关于新创建的进程及其主线程的信息。它与CreateProcess、CreateProcessAsUser、CreateProcessWithLogonW或CreateProcessWithTokenW函数一起使用。
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;//新创建的进程的句柄。句柄用于指定在进程对象上执行操作的所有函数中的进程。
HANDLE hThread;//新创建的进程的主线程的句柄。句柄用于指定在线程对象上执行操作的所有函数中的线程。
DWORD dwProcessId;//可用于标识进程的值。该值从创建进程开始有效,直到进程的所有句柄被关闭并释放进程对象为止;此时,标识符可能被重用。
DWORD dwThreadId;//一个可用于标识线程的值。该值从线程创建时开始有效,直到线程的所有句柄被关闭和线程对象被释放;此时,标识符可能被重用。
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
PROCESS_BASIC_INFORMATION
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
ReadProcessMemory function (memoryapi.h)
读取进程内存地址的信息
BOOL ReadProcessMemory(
HANDLE hProcess,// 被读取内存的进程的句柄。句柄必须具有对进程的PROCESS_VM_READ访问权限。
LPCVOID lpBaseAddress,// 指向要从其中读取的指定进程的基址的指针。在任何数据传输发生之前,系统验证所有的数据在基地址和内存中指定的大小是可读访问,如果它不能访问,功能失败。
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesRead
);
3. Bug
每当我尝试从调试器中调用debuggee上的CreateToolhelpSnapshot时,我得到一个299错误。MSDN表示,只有从32位查询64位进程时才会发生这种情况,反之亦然。