peb(process environment block)是一个进程的环境块,其中保存了许多系统级别的信息,如进程的基地址、进程的环境变量、进程的命令行参数等。在windows内核中,peb被实现成了一个结构体,可以在kernel mode中通过undocumented native api(如zwqueryinformationprocess)读取。
在本篇文章中,我们将介绍如何使用golang语言实现一个简单的peb查看器。
读取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_informationvar returnlength uint32ntstatus := 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的详细内容。