PEB(Process Environment Block)是一個進程的環境區塊,其中保存了許多系統層級的信息,如進程的基底位址、進程的環境變數、進程的命令列參數等。在Windows核心中,PEB被實作成了一個結構體,可以在Kernel Mode中透過Undocumented Native API(如ZwQueryInformationProcess)讀取。
在本篇文章中,我們將介紹如何使用golang語言實作一個簡單的PEB檢視器。
取得目前程序的句柄。
在golang中,我們可以使用syscall套件中的GetCurrentProcess函數來取得目前程序的句柄。
handle, err := syscall.GetCurrentProcess() if err != nil { fmt.Println("获取当前进程句柄失败:", err) return } defer syscall.CloseHandle(handle)
查詢目前進程的信息,包括PEB的位址。
在Windows中,我們可以使用ZwQueryInformationProcess或NtQueryInformationProcess來讀取進程資訊。不過,在golang中這些API並沒有直接暴露出來,因此我們需要使用unsafe套件來呼叫系統函數。
var pbi PROCESS_BASIC_INFORMATION var returnLength uint32 ntStatus := NtQueryInformationProcess( handle, PROCESS_BASIC_INFORMATION_CLASS, uintptr(unsafe.Pointer(&pbi)), uint32(unsafe.Sizeof(pbi)), uintptr(unsafe.Pointer(&returnLength)), ) if ntStatus != STATUS_SUCCESS { fmt.Println("获取进程PEB信息失败:", ntStatus) return }
在上面的程式碼中,我們定義了一個PROCESS_BASIC_INFORMATION結構體,用來保存NtQueryInformationProcess函數傳回的進程資訊。我們透過指定PROCESS_BASIC_INFORMATION_CLASS枚舉值來告訴系統我們需要讀取的信息,這裡我們需要的是PEB資訊。另外,我們還需要提供一個緩衝區來保存傳回的信息,和這個緩衝區的大小。
具體的實作可以參考這個專案[https://github.com/processhacker/phnt](https://github.com/processhacker/phnt),它實作了一些系統API,並且提供了一些資料結構,例如PROCESS_BASIC_INFORMATION。
讀取PEB結構體中的資訊。
PEB是一個非常重要的結構體,其中保存了許多進程的資訊。下面是PEB的定義:
typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN SpareBool; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PRTL_CRITICAL_SECTION FastPebLock; PVOID AtlThunkSListPtr; PVOID IFEOKey; PVOID CrossProcessFlags; PVOID UserSharedInfoPtr; ULONG SystemReserved[1]; ULONG AtlThunkSListPtr32; PVOID ApiSetMap; } PEB, *PPEB;
我們可以使用golang的unsafe套件來讀取這些資料。例如,我們可以使用下面的程式碼來讀取PEB的ImageBaseAddress:
type PEB struct { InheritedAddressSpace bool ReadImageFileExecOptions bool BeingDebugged bool SpareBool bool Mutant syscall.Handle ImageBaseAddress uintptr Ldr *PEB_LDR_DATA ProcessParameters *RTL_USER_PROCESS_PARAMETERS SubSystemData uintptr ProcessHeap uintptr FastPebLock *RTL_CRITICAL_SECTION AtlThunkSListPtr uintptr IFEOKey uintptr CrossProcessFlags uintptr UserSharedInfoPtr uintptr SystemReserved [1]uint32 AtlThunkSListPtr32 uintptr ApiSetMap uintptr } func (p *PEB) GetImageBaseAddress() uintptr { return p.ImageBaseAddress } peb := (*PEB)(unsafe.Pointer(pbi.PebBaseAddress)) fmt.Printf("ImageBaseAddress: 0x%x\n", peb.GetImageBaseAddress())
在上面的程式碼中,我們先定義了一個PEB結構體,並且給結構體中的欄位都指定了型別。接著,我們實作了一個GetImageBaseAddress函數,用來傳回PEB中的ImageBaseAddress欄位。最後,我們透過將PEB的基底位址轉換為*PEB類型,來讀取PEB中的資訊。
讀取進程的模組資訊。
在取得了PEB中的ImageBaseAddress後,我們可以遍歷PEB_LDR_DATA中的InMemoryOrderModuleList來取得進程中載入的所有模組資訊。
typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; struct { PVOID LoadedImports; PVOID EntryPointActivationContext; }; }; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; HANDLE SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; BOOLEAN ShutdownInProgress; HANDLE ShutdownThreadId; } PEB_LDR_DATA, *PPEB_LDR_DATA;
我們可以使用如下的程式碼來遍歷模組資訊:
type LDR_DATA_TABLE_ENTRY struct { InLoadOrderLinks LIST_ENTRY InMemoryOrderLinks LIST_ENTRY InInitializationOrderLinks LIST_ENTRY DllBase uintptr EntryPoint uintptr SizeOfImage uint32 FullDllName UNICODE_STRING BaseDllName UNICODE_STRING Flags uint32 LoadCount uint16 TlsIndex uint16 HashLinks LIST_ENTRY TimeDateStamp uint32 } type PEB_LDR_DATA struct { Length uint32 Initialized bool SsHandle syscall.Handle InLoadOrderModuleList LIST_ENTRY InMemoryOrderModuleList LIST_ENTRY InInitializationOrderModuleList LIST_ENTRY } pebLdrData := (*PEB_LDR_DATA)(unsafe.Pointer(peb.Ldr)) moduleList := (*LIST_ENTRY)(unsafe.Pointer(&pebLdrData.InMemoryOrderModuleList)) for moduleList.Flink != uintptr(unsafe.Pointer(&pebLdrData.InMemoryOrderModuleList)) { ldrDataTableEntry := (*LDR_DATA_TABLE_ENTRY)(unsafe.Pointer(moduleList.Flink)) moduleName := WcharPtrToString(ldrDataTableEntry.BaseDllName.Buffer, uint32(ldrDataTableEntry.BaseDllName.Length/2)) moduleBase := ldrDataTableEntry.DllBase moduleSize := ldrDataTableEntry.SizeOfImage moduleEntry := ldrDataTableEntry.EntryPoint moduleList = (*LIST_ENTRY)(unsafe.Pointer(moduleList.Flink)) fmt.Printf("模块名称:%s,基地址:%x,大小:%x,入口点:%x\n", moduleName, moduleBase, moduleSize, moduleEntry) }
在上面的程式碼中,我們先定義了一個LDR_DATA_TABLE_ENTRY結構體,用來保存模組的資訊。然後我們定義了一個PEB_LDR_DATA結構體,並且將peb.Ldr指標轉換為這個結構體指標。最後,我們遍歷InMemoryOrderModuleList鍊錶,對每個模組進行讀取操作。
在取得到模組的基底位址後,我們可以用ReadProcessMemory函數來讀取模組中的資料。具體的實作可以參考這個專案[https://github.com/AllenDang/w32/blob/master/process_windows.go](https://github.com/AllenDang/w32/blob/master/process_windows.go),它實現了從進程中讀取資料的函數。
不過需要注意的是,如果我們要取得的進程是另一個進程,那麼在讀取進程資料的時候需要指定進程的存取權限。在golang中,我們可以使用CreateToolhelp32Snapshot函數來取得所有進程列表,並且在取得進程句柄時指定特定的存取權限。
const ( PROCESS_QUERY_INFORMATION = 0x0400 PROCESS_VM_READ = 0x0010 PROCESS_VM_WRITE = 0x0020 PROCESS_VM_OPERATION = 0x0008 PROCESS_CREATE_THREAD = 0x0002 PROCESS_CREATE_PROCESS = 0x0080 PROCESS_TERMINATE = 0x0001 PROCESS_ALL_ACCESS = 0x1F0FFF TH32CS_SNAPPROCESS = 0x00000002 ) func openProcess(pid uint32) (handle syscall.Handle, err error) { handle, err = syscall.OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION|PROCESS_VM_WRITE, false, pid) return } func main() { snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) defer syscall.CloseHandle(snapshot) var procEntry PROCESSENTRY32 procEntry.Size = uint32(unsafe.Sizeof(procEntry)) var ( handle syscall.Handle err error ) if Process32First(snapshot, &procEntry) { for { if strings.EqualFold(strings.ToLower(WcharPtrToString(procEntry.ExeFile[:])), "notepad.exe") { fmt.Printf("找到 notepad 进程,pid:%d\n", procEntry.ProcessID) handle, err = openProcess(procEntry.ProcessID) if err != nil { fmt.Println("打开进程失败:", err) } } if !Process32Next(snapshot, &procEntry) { break } } } }
本文介紹如何使用golang語言實作一個簡單的PEB檢視器。 PEB是進程環境區塊,在Windows核心中實現了一個結構體,其中保存了許多系統層級的資訊。透過使用golang的unsafe包,我們可以讀取進程的PEB資訊和模組資訊。不過要注意的是,在讀取另一個進程的PEB資訊和模組資訊時,需要指定存取權限。
以上是golang怎麼實作peb的詳細內容。更多資訊請關注PHP中文網其他相關文章!