"""打印某个 PID 的父链,并标注**每一级是否在作业对象里**。 用法: python proc_chain.py [depth] # depth 默认 6 为什么需要它 —— 这是本技能的核心判据: · 链条顶部(`svchost.exe` / `services.exe` / `wininit.exe` 那一层)出现 `in_job=False` ⇒ 该进程**不属于任何会话上下文** ⇒ 它会活过会话收尾(= 真常驻)。 · 若整条链 `in_job=True`、且父级是宿主/沙箱进程(如 `sandbox-cli.exe`) ⇒ 它随那个上下文一起死。 ⚠️ 反直觉但很重要:**服务自身 `in_job=True` 并不代表失败**。当服务由计划任务托管时, `服务 → powershell.exe` 两级都会是 `in_job=True`,但那是**计划任务自己的作业对象**, 只要再往上的 `svchost.exe`(计划任务服务)是 `in_job=False`,就已经脱离宿主了。 ⛔ 只读:只用 `PROCESS_QUERY_LIMITED_INFORMATION` 打开进程,不做任何修改。 """ from __future__ import annotations import ctypes import ctypes.wintypes as w import os import sys _k = ctypes.WinDLL("kernel32", use_last_error=True) _n = ctypes.WinDLL("ntdll") PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 class _PROCESS_BASIC_INFORMATION(ctypes.Structure): _fields_ = [ ("Reserved1", ctypes.c_void_p), ("PebBaseAddress", ctypes.c_void_p), ("Reserved2", ctypes.c_void_p * 2), ("UniqueProcessId", ctypes.c_void_p), ("InheritedFromUniqueProcessId", ctypes.c_void_p), ] def inspect(pid: int) -> tuple[int, str, bool] | None: """返回 (父 PID, 自身镜像路径, 是否在作业对象内);打不开则 None。""" h = _k.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) if not h: return None try: injob = w.BOOL(0) _k.IsProcessInJob(h, None, ctypes.byref(injob)) pbi = _PROCESS_BASIC_INFORMATION() size = w.DWORD(0) _n.NtQueryInformationProcess(h, 0, ctypes.byref(pbi), ctypes.sizeof(pbi), ctypes.byref(size)) ppid = int(pbi.InheritedFromUniqueProcessId or 0) buf = ctypes.create_unicode_buffer(1024) ln = w.DWORD(len(buf)) name = buf.value if _k.QueryFullProcessImageNameW(h, 0, buf, ctypes.byref(ln)) else "" return ppid, name, bool(injob.value) finally: _k.CloseHandle(h) def main() -> int: if len(sys.argv) < 2 or not sys.argv[1].strip().isdigit(): print(__doc__) return 2 pid = int(sys.argv[1]) depth = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2].isdigit() else 6 print(f"起始 PID = {pid}") for level in range(depth): info = inspect(pid) if info is None: print(f" {' ' * level}PID {pid} —— 打不开(进程已退出,或权限不足)") return 0 ppid, name, injob = info mark = "在作业对象内" if injob else "★ 不在任何作业对象内" print(f" {' ' * level}PID {pid:<7} in_job={injob} {mark}") print(f" {' ' * level} └─ 镜像: {name}") if not ppid: break pid = ppid print() print("判据:链条顶部出现「不在任何作业对象内」= 已脱离会话上下文(真常驻);") print(" 反之为「在作业对象内」时,看它的父级 —— 若父级是宿主/沙箱进程(sandbox-cli.exe 等),") print(" 则该进程会随那个上下文一起被杀。") return 0 if __name__ == "__main__": sys.exit(main())