golang怎么实现peb

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的详细内容,更多请关注https://www.sxiaw.com/其它相关文章!