首頁  >  文章  >  後端開發  >  golang怎麼實作peb

golang怎麼實作peb

PHPz
PHPz原創
2023-04-14 11:21:29834瀏覽

PEB(Process Environment Block)是一個進程的環境區塊,其中保存了許多系統層級的信息,如進程的基底位址、進程的環境變數、進程的命令列參數等。在Windows核心中,PEB被實作成了一個結構體,可以在Kernel Mode中透過Undocumented Native API(如ZwQueryInformationProcess)讀取。

在本篇文章中,我們將介紹如何使用golang語言實作一個簡單的PEB檢視器。

讀取PEB的步驟

  1. 取得目前程序的句柄。

    在golang中,我們可以使用syscall套件中的GetCurrentProcess函數來取得目前程序的句柄。

    handle, err := syscall.GetCurrentProcess()
    if err != nil {
        fmt.Println("获取当前进程句柄失败:", err)
        return
    }
    defer syscall.CloseHandle(handle)
  2. 查詢目前進程的信息,包括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。

  3. 讀取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中的資訊。

  4. 讀取進程的模組資訊。

    在取得了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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
上一篇:golang 怎麼學下一篇:golang 怎麼學