Windows API 程式相關筆記

進擊的汪sir發表於2021-08-13

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

相關文章