SystemInformer DLL注入功能定位:从菜单到内核的完整链路
目标进程找到了,想注入一个 DLL,可进程列表右键菜单里没有你以为的"注入"——SystemInformer 的 DLL注入入口其实藏在进程属性的"模块"页里,菜单名叫"Load module..."。下面顺着这个菜单项,把调用链和实操方式一次拉直。
一图看懂注入调用链路
当前版本的 DLL注入入口在"模块"页右键菜单,一次点击按"界面 → 封装 → 注入实现 → 访问级别检查"单向流过四个文件,内核驱动只负责授予访问级别,不直接执行注入。
图从上到下传递的是进程句柄和 DLL 路径,回流的是一个 NTSTATUS。注意真正执行注入的是 D 节点,E 和 F 是"权限闸门"——先确认驱动给客户端的级别够不够,再谈后面的一切。
三层架构自顶向下拆解
三层的分工一句话:界面层找目标,通信层执行注入,驱动层决定你能走多远。
界面层
界面层只负责两件事:定位目标进程、选定要注入的 DLL 文件。
关键文件:
- SystemInformer/prpgmod.c:进程属性"模块"页的模块列表上注册"Load module..."右键菜单项,点击后经确认提示分发到封装函数;
- SystemInformer/actions.c:弹出文件选择框、打开进程句柄、调用 phlib 完成实际动作。
对你意味着什么:操作入口永远是"属性 → 模块 → 右键",而不是主窗口进程列表。找不到菜单项时先确认你打开的是属性对话框。
status = PhOpenProcess(
&processHandle,
PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SET_LIMITED_INFORMATION |
PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
PROCESS_VM_READ | PROCESS_VM_WRITE | SYNCHRONIZE,
Process->ProcessId
);
这组访问权决定了后续每一步能不能做:没有 PROCESS_CREATE_THREAD 就建不了远程线程,没有 PROCESS_VM_WRITE 就写不进 DLL 路径。
通信层
通信层负责注入本身,入口是 phlib/nativethread.c 里的两个函数。
关键文件:
- phlib/nativethread.c:
PhLoadDllProcess(远程线程版,当前菜单唯一调用的路径)与PhLoadDllProcessApcThread(APC 版,仅作为 API 暴露); - phlib/kph.c:与驱动的通信封装及访问级别查询;
- kphlib/:内核/用户共享的消息结构定义与版本校验。
if (KphProcessLevel(ProcessHandle) > KphLevelMed)
{
return STATUS_ACCESS_DENIED;
}
这一行是注入的守门员:KphProcessLevel 查询驱动为当前客户端在该进程上授予的访问级别,目标比"中等"更敏感而客户端没有相应权限时直接拒绝——很多"管理员却注入失败"的情况就停在这里。
驱动层
驱动层自己不加载任何模块,它负责给用户态客户端授予常规 API 拿不到的访问级别。
关键文件:
- KSystemInformer/:内核驱动全部源码;
- KSystemInformer/comms_handlers.c:按目标进程的保护状态计算授予客户端的访问级别;
- KSystemInformer/ksidll.def:驱动扩展库的导出接口。
EXPORTS
KsiInitialize
KsiInsertQueueApc
KsiQueueWorkItem
KsiInitializeDpc
KsiInitializeSystemProcess
这 5 个导出全部是供第三方内核扩展组件做 APC、工作项、DPC 管理用的,DLL注入不走它们——早期版本曾基于 CreateRemoteThread 实现,现已无痕迹,别被"主注入路径是内核服务"的说法带偏。
| 注入路径 | 适用场景 | 权限要求 | 实现文件 |
|---|---|---|---|
| 远程线程(默认) | 普通用户进程,当前菜单唯一路径 | 进程句柄需含 CREATE_THREAD、VM_READ、VM_WRITE 等;客户端访问级别至少"中等" | phlib/nativethread.c |
| APC 队列(API 暴露) | 需要目标线程以更少干预完成加载的场景;当前主程序菜单未调用它(以当前版本源码为准) | 同上,且需目标线程能处理用户 APC | phlib/nativethread.c |
端到端实操:完成一次 DLL注入
按下面 6 步就能完成一次真实注入,唯一前置条件是内核驱动已正常加载。
- 以管理员身份运行主程序,确认内核驱动已加载(否则访问级别降级,注入大概率失败)。→ 源码:
KSystemInformer/main.c - 选中目标进程,打开"属性",切到"模块"页,在模块列表上右键选择"Load module..."⚠️ 若开启了警告提示,会先弹出一个确认框,提醒加载模块可能触发目标程序的访问限制或封号。→ 源码:
SystemInformer/prpgmod.c - 在文件对话框中选中要注入的 DLL,默认过滤器就是
*.dll。→ 源码:SystemInformer/actions.c - 程序用包含
PROCESS_CREATE_THREAD、PROCESS_VM_WRITE的一组访问权重新打开目标进程,随后调用PhLoadDllProcess。→ 源码:SystemInformer/actions.c PhLoadDllProcess先检查访问级别,再把 DLL 完整路径以 Unicode 写入目标进程内存,创建以LoadLibraryW为起点的远程线程,等待 5000 毫秒超时。→ 源码:phlib/nativethread.c- 判定结果:成功则无任何弹窗,同一模块列表里能看到新模块出现;失败则弹出"load the DLL into <进程名> failed"错误框,状态码就是底层系统调用返回的 NTSTATUS。→ 源码:
SystemInformer/actions.c
高频踩坑与快速排查
九成失败集中在权限、驱动、保护、版本四类,看错误框里的状态码基本能一眼定位。
- 权限不足:现象是错误框返回
STATUS_ACCESS_DENIED→ 原因是客户端对该目标的访问级别低于"中等",或进程句柄没拿到对应访问权 → 解法是整程序以管理员运行,并确认驱动正常加载。 - 驱动未加载:现象是功能整体降级或进程状态不完整 ⚠️ → 原因是内核组件被杀毒软件拦截或签名验证未通过 → 解法是按 HACKING.md 处理签名与加载问题后重启主程序。
- 目标进程受保护:现象是管理员权限下句柄打不开或注入仍失败 → 原因是目标带反作弊、反调试保护,驱动授予的级别不够 → 解法是先换普通目标验证流程,受保护进程当前版本不保证注入成功(以当前版本源码为准)。
- 版本不匹配:现象是驱动起不来或通信被直接拒绝 → 原因是主程序与驱动版本不一致,消息头版本校验
KphMsgValidate不通过 → 解法是主程序与内核驱动换用同一发布版本。
当前架构下 DLL注入由用户态系统调用完成,驱动只管理访问级别。
- phlib/nativethread.c:两条注入实现(远程线程与 APC 队列)的源码
- SystemInformer/actions.c:界面封装、进程句柄打开与错误上报逻辑
- KSystemInformer/comms_handlers.c:驱动侧访问级别计算逻辑
- HACKING.md:项目构建与驱动签名说明
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



