PEB (Process Environment Block) is the environment block of a process, which stores a lot of system-level information, such as the base address of the process, the environment variables of the process, the command line parameters of the process, etc. In the Windows kernel, PEB is implemented as a structure, which can be read through Undocumented Native API (such as ZwQueryInformationProcess) in Kernel Mode.
In this article, we will introduce how to use golang language to implement a simple PEB viewer.
Get the handle of the current process.
In golang, we can use the GetCurrentProcess function in the syscall package to get the handle of the current process.
handle, err := syscall.GetCurrentProcess() if err != nil { fmt.Println("获取当前进程句柄失败:", err) return } defer syscall.CloseHandle(handle)
Query the information of the current process, including the address of the PEB.
In Windows, we can use ZwQueryInformationProcess or NtQueryInformationProcess to read process information. However, these APIs are not directly exposed in golang, so we need to use the unsafe package to call system functions.
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 }
In the above code, we define a PROCESS_BASIC_INFORMATION structure to save the process information returned by the NtQueryInformationProcess function. We tell the system the information we need to read by specifying the PROCESS_BASIC_INFORMATION_CLASS enumeration value. What we need here is the PEB information. In addition, we also need to provide a buffer to save the returned information and the size of this buffer.
For specific implementation, please refer to this project [https://github.com/processhacker/phnt](https://github.com/processhacker/phnt), which implements some system APIs and provides Some data structures, such as PROCESS_BASIC_INFORMATION.
Read the information in the PEB structure.
PEB is a very important structure that stores information about many processes. The following is the definition of 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;
We can use golang's unsafe package to read these data. For example, we can use the following code to read the ImageBaseAddress of PEB:
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())
In the above code, we first define a PEB structure and specify types for the fields in the structure. Next, we implemented a GetImageBaseAddress function to return the ImageBaseAddress field in the PEB. Finally, we read the information in the PEB by converting the base address of the PEB to the *PEB type.
Read the module information of the process.
After obtaining the ImageBaseAddress in PEB, we can traverse the InMemoryOrderModuleList in PEB_LDR_DATA to obtain all module information loaded in the process.
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;
We can use the following code to traverse module information:
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) }
In the above code, we first define an LDR_DATA_TABLE_ENTRY structure to save module information. Then we define a PEB_LDR_DATA structure and convert the peb.Ldr pointer to this structure pointer. Finally, we traverse the InMemoryOrderModuleList list and read each module.
After obtaining the base address of the module, we can use the ReadProcessMemory function to read the data in the module. For specific implementation, please refer to this project [https://github.com/AllenDang/w32/blob/master/process_windows.go](https://github.com/AllenDang/w32/blob/master/process_windows.go), It implements functions for reading data from the process.
However, it should be noted that if the process we want to obtain is another process, then we need to specify the access permissions of the process when reading the process data. In golang, we can use the CreateToolhelp32Snapshot function to obtain a list of all processes and specify specific access permissions when obtaining the process handle.
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 } } } }
This article introduces how to use golang language to implement a simple PEB viewer. PEB is a process environment block, which is implemented as a structure in the Windows kernel, which stores a lot of system-level information. By using golang's unsafe package, we can read the PEB information and module information of the process. However, it should be noted that when reading the PEB information and module information of another process, you need to specify access permissions.
The above is the detailed content of How to implement peb in golang. For more information, please follow other related articles on the PHP Chinese website!