Windows API 進程相關筆記
- 2021 年 8 月 13 日
- 筆記
- WIN32, 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//要打開的本地進程的標識符。
);
第一個參數具體有哪些可以參考
//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位進程時才會發生這種情況,反之亦然。