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的,但是那太简单了,还是不要献丑了。