win32获取进程树,以及命令行参数

1.先上代码

package main

import (
    "bytes"
    "errors"
    "flag"
    "fmt"
    "io"
    "os"
    "sort"
    "syscall"
    "unsafe"
)

func main() {
    out := flag.String("o", "-", "write result to file, - eq stdout")
    flag.Parse()

    procMap := make(map[uint32]*Process, 32)
    err := ForEachProcessEntry(func(entry *syscall.ProcessEntry32) error {
        cmdline, err := GetCmdline(entry.ProcessID)
        if err != nil {
            return err
        }
        procMap[entry.ProcessID] = &Process{
            Pid:      entry.ProcessID,
            Ppid:     entry.ParentProcessID,
            Name:     syscall.UTF16ToString(entry.ExeFile[:]),
            Cmdline:  cmdline,
            Children: make(map[uint32]*Process),
        }
        return nil
    })
    if err != nil {
        fmt.Println("ForEachProcessEntry:", err)
        return
    }
    proc := make(ProcessSlice, 0, 32)
    for _, v := range procMap {
        if v.Pid == 0 {
            proc = append(proc, v)
            continue // 系统进程
        }
        tmp, ok := procMap[v.Ppid]
        if ok {
            tmp.Children[v.Pid] = v
        } else {
            proc = append(proc, v)
        }
    }
    sort.Sort(proc)

    var fmtOut *os.File
    if *out == "-" {
        fmtOut = os.Stdout
    } else {
        fmtOut, err = os.Create(*out)
        if err != nil {
            fmt.Println("os.Create:", *out, ",error:", err)
            return
        }
        defer fmtOut.Close()
    }

    str := bytes.NewBufferString("%10d,%10d:")
    for _, v := range proc {
        fmt.Fprintf(fmtOut, "%10d,%10d: name:[%s], cmdline:[%s]\n", v.Pid, v.Ppid, v.Name, v.Cmdline)
        WriteChildren(fmtOut, v.Children, str, 1)
    }
}

func WriteChildren(w io.Writer, children map[uint32]*Process, strFmt *bytes.Buffer, layer int) {
    if len(children) == 0 {
        return
    }
    strFmt.Truncate(10 /* len("%10d,%10d:") */)
    for i := 0; i < layer*4; i++ {
        if i > 0 && i%4 == 0 {
            strFmt.WriteByte('|')
        }
        strFmt.WriteByte(' ')
    }
    strFmt.WriteString("\\_ name:[%s], cmdline:[%s]\n")
    fmtStr := strFmt.String()
    layer++
    for _, v := range children {
        fmt.Fprintf(w, fmtStr, v.Pid, v.Ppid, v.Name, v.Cmdline)
        WriteChildren(w, v.Children, strFmt, layer) // 递归打印子进程
    }
}

type (
    Process struct {
        Pid, Ppid uint32
        Name      string
        Cmdline   string
        Children  map[uint32]*Process
    }
    ProcessSlice []*Process
)

func (d ProcessSlice) Less(i, j int) bool {
    return d[i].Pid < d[j].Pid
}

func (d ProcessSlice) Swap(i, j int) {
    d[i], d[j] = d[j], d[i]
}

func (d ProcessSlice) Len() int {
    return len(d)
}

var (
    ntQueryInformationProcess = syscall.MustLoadDLL("ntdll.dll").MustFindProc("NtQueryInformationProcess")
    readProcessMemory         = syscall.MustLoadDLL("kernel32.dll").MustFindProc("ReadProcessMemory")
)

func GetCmdline(pid uint32) (string, error) {
    /* 翻译这个C++代码: https://stackoverflow.com/a/42341811/11844632 */
    if pid == 0 { // 系统进程,无法读取
        return "", nil
    }
    const (
        PROCESS_QUERY_INFORMATION = 0x0400 // 定义在winnt.h
        PROCESS_VM_READ           = 0x0010 // 定义在winnt.h
        ProcessParameters         = 32     // ntddk.h的头文件分析的指针偏移
        CommandLine               = 112    // winternl.h的头文件分析的指针偏移
    )
    h, err := syscall.OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, false, pid)
    if err != nil {
        sysErr, ok := err.(syscall.Errno)
        if ok && sysErr == syscall.ERROR_ACCESS_DENIED {
            return "", nil // 没权限,忽略这个进程
        }
        return "", err
    }
    defer syscall.CloseHandle(h)

    var pbi struct {
        ExitStatus                   int
        PebBaseAddress               int64
        AffinityMask                 int64
        BasePriority                 int
        UniqueProcessId              int64
        InheritedFromUniqueProcessId int64
    }
    r0, _, _ := ntQueryInformationProcess.Call(uintptr(h), 0,
        uintptr(unsafe.Pointer(&pbi)), unsafe.Sizeof(pbi), 0)
    if r0 != 0 {
        return "", errors.New("ntQueryInformationProcess")
    }

    var rtlUserProcParamsAddress int64
    r0, _, _ = readProcessMemory.Call(uintptr(h),
        uintptr(pbi.PebBaseAddress+ProcessParameters),
        uintptr(unsafe.Pointer(&rtlUserProcParamsAddress)),
        unsafe.Sizeof(rtlUserProcParamsAddress), 0, 0)
    if r0 == 0 {
        return "", errors.New("readProcessMemory rtlUserProcParamsAddress")
    }

    var commandLine struct {
        Length        uint16
        MaximumLength uint16
        Buffer        int64
    }
    r0, _, _ = readProcessMemory.Call(uintptr(h),
        uintptr(rtlUserProcParamsAddress+CommandLine),
        uintptr(unsafe.Pointer(&commandLine)),
        unsafe.Sizeof(commandLine), 0, 0)
    if r0 == 0 {
        return "", errors.New("readProcessMemory commandLine")
    }

    commandLineContents := make([]uint16, commandLine.Length/2)
    r0, _, _ = readProcessMemory.Call(uintptr(h),
        uintptr(commandLine.Buffer),
        uintptr(unsafe.Pointer(&commandLineContents[0])),
        uintptr(commandLine.Length), 0, 0)
    if r0 == 0 {
        return "", errors.New("readProcessMemory commandLineContents")
    }
    return syscall.UTF16ToString(commandLineContents), nil
}

func ForEachProcessEntry(f func(*syscall.ProcessEntry32) error) error {
    snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
    if err != nil {
        return err
    }
    defer syscall.CloseHandle(snapshot)
    var procEntry syscall.ProcessEntry32
    procEntry.Size = uint32(unsafe.Sizeof(procEntry))
    if err = syscall.Process32First(snapshot, &procEntry); err != nil {
        return err
    }
    for {
        if err = f(&procEntry); err != nil {
            return err
        }
        if syscall.Process32Next(snapshot, &procEntry) != nil {
            return nil
        }
    }
}

2.再上结果

1.下面是输出的部分结果,更下面的进程树有点私密,就不放了
         0,         0: name:[[System Process]], cmdline:[]
         4,         0:    \_ name:[System], cmdline:[]
        88,         4:    |    \_ name:[Registry], cmdline:[]
       412,         4:    |    \_ name:[smss.exe], cmdline:[]
       624,       616: name:[csrss.exe], cmdline:[]
       716,       708: name:[csrss.exe], cmdline:[]
       736,       616: name:[wininit.exe], cmdline:[]
       864,       736:    \_ name:[services.exe], cmdline:[]
      3232,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1328,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3308,       864:    |    \_ name:[AutoUpdate.exe], cmdline:[]
      4184,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1128,       864:    |    \_ name:[svchost.exe], cmdline:[]
      7660,       864:    |    \_ name:[svchost.exe], cmdline:[C:\WINDOWS\system32\svchost.exe -k UnistackSvcGroup]
      2952,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2368,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2172,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2652,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2920,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2700,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1796,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2236,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2328,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3180,       864:    |    \_ name:[svchost.exe], cmdline:[]
      7500,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1924,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3152,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1624,       864:    |    \_ name:[nvvsvc.exe], cmdline:[]
      8228,       864:    |    \_ name:[svchost.exe], cmdline:[]
     13480,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1028,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2052,       864:    |    \_ name:[svchost.exe], cmdline:[C:\WINDOWS\system32\svchost.exe -k UnistackSvcGroup -s WpnUserService]
      3128,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1468,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1680,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1044,      1680:    |    |    \_ name:[sihost.exe], cmdline:[sihost.exe]
      3856,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2960,       864:    |    \_ name:[svchost.exe], cmdline:[]
      8136,       864:    |    \_ name:[SecurityHealthService.exe], cmdline:[]
      5668,       864:    |    \_ name:[PresentationFontCache.exe], cmdline:[]
      3008,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3028,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3108,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3400,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1424,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1808,      1424:    |    |    \_ name:[ctfmon.exe], cmdline:[]
      6100,       864:    |    \_ name:[svchost.exe], cmdline:[]
      5688,       864:    |    \_ name:[svchost.exe], cmdline:[]
      5372,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3144,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1444,       864:    |    \_ name:[svchost.exe], cmdline:[]
      4036,       864:    |    \_ name:[svchost.exe], cmdline:[]
      7880,       864:    |    \_ name:[SgrmBroker.exe], cmdline:[]
      1528,       864:    |    \_ name:[svchost.exe], cmdline:[]
      4296,       864:    |    \_ name:[svchost.exe], cmdline:[C:\WINDOWS\system32\svchost.exe -k UnistackSvcGroup -s CDPUserSvc]
      3352,       864:    |    \_ name:[SUService.exe], cmdline:[]
      2624,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2308,       864:    |    \_ name:[svchost.exe], cmdline:[]
      5728,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2672,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1672,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2376,       864:    |    \_ name:[svchost.exe], cmdline:[]
      8488,       864:    |    \_ name:[SunloginClient.exe], cmdline:[]
      2224,      8488:    |    |    \_ name:[SunloginClient.exe], cmdline:[]
      2228,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2416,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1412,       864:    |    \_ name:[svchost.exe], cmdline:[]
       948,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2804,       864:    |    \_ name:[svchost.exe], cmdline:[]
      9128,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1916,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1484,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1192,      1484:    |    |    \_ name:[taskhostw.exe], cmdline:[taskhostw.exe {222A245B-E637-4AE9-A93F-A59CA119A75E}]
      7476,      1484:    |    |    \_ name:[taskhostw.exe], cmdline:[]
       968,       864:    |    \_ name:[svchost.exe], cmdline:[]
      6600,       864:    |    \_ name:[svchost.exe], cmdline:[C:\WINDOWS\system32\svchost.exe -k ClipboardSvcGroup -p -s cbdhsvc]
      1648,       864:    |    \_ name:[nvSCPAPISvr.exe], cmdline:[]
      1404,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1724,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1228,       864:    |    \_ name:[svchost.exe], cmdline:[]
       988,       864:    |    \_ name:[svchost.exe], cmdline:[]
      5056,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3640,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2096,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2536,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2332,       864:    |    \_ name:[igfxCUIService.exe], cmdline:[]
      4740,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3880,       864:    |    \_ name:[svchost.exe], cmdline:[]
      3136,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1584,       864:    |    \_ name:[svchost.exe], cmdline:[]
      2692,       864:    |    \_ name:[svchost.exe], cmdline:[]
      1008,       864:    |    \_ name:[svchost.exe], cmdline:[]

2.为了验证结果正确性,检查了结果进程数量,完全正确,tasklist会把自己算进去,所以会多一个
# tasklist /nh | find /v /c ""
187
# .\proc /nh | find /v /c ""
186

3.做个总结

偶尔看到一个帖子,有人问这个,就研究了一下下。发现其实也不难,不过方法确实是百度搜不到的。但还是被我搜到了。所有就做了个例子供大家参考。
也想过做个Linux的,但是那太简单了,还是不要献丑了。

captcha