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位進程時才會發生這種情況,反之亦然。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM