简介:RunAsSystem是一款面向64位Windows系统的开源权限提升工具,核心解决普通用户账户在修改系统设置、安装驱动等场景下权限不足的痛点。它将“以系统账户运行”的能力封装进文件右键菜单,用户无需深入研究Windows权限模型,也不必反复输入管理员密码或确认UAC弹窗,右键即可调用底层API以系统账户上下文启动目标程序;源代码开放意味着执行逻辑透明,开发者可审计并改进提权方式。资源包共52个文件,整体为一套Visual Basic解决方案,包含exe可执行程序、pdb调试符号、vb源文件、xml配置、dll动态库、resources资源文件等类型,压缩包仅49KB,结构紧凑,便于直接加载工程学习调试。当前已有155人学习,适合Windows权限机制初学者、自动化运维人员及VB桌面开发者。通过学习该工程,既能理清CreateProcessAsUser/CreateProcessWithToken等API的调用参数与安全上下文切换过程,也能掌握右键菜单扩展、WinForms界面设计、项目资源管理等实践技巧,还可按需修改源码并重新编译出定制版本,是一份体量小但信息密度高的权限提升示例。
1. 开源软件 RunAsSystem:为什么管理员权限还不够,你需要真的 SYSTEM
先抛个反直觉的结论:在 Windows 上调试服务类程序或修复系统组件时,管理员权限和 SYSTEM 权限差着整整一个安全级别。普通管理员能读大部分注册表,但遇到需要访问 SAM、操作服务安全描述符或写入受保护的系统目录时,权限不足的弹窗会一直缠着你。RunAsSystem 这类开源软件解决的正是这个问题:它通过合法的系统机制把任意程序以 SYSTEM 身份拉起。开发者、逆向分析者和运维都会在某个时刻需要它,尤其当你不想为一个临时操作去改服务配置或写注册表时。
RunAsSystem 的典型场景包括:启动一个交互式 shell 查看系统级配置、以 SYSTEM 身份运行某个自写工具的调试版、验证程序的权限行为。它不是一个提权漏洞利用工具,而是通过 Windows 服务控制管理器等公开接口实现的权限切换。安全软件偶尔会误报,这是后话,后面有专门的排查章节。另一个优点是它是完全开源的,代码可控,能自己编译,不必担心二进制来源不明。
2. SYSTEM 权限从哪里来:三种提权路径与编译准备
2.1 拿到 SYSTEM Token 的三种主流方式,为什么选服务启动
要真正理解 RunAsSystem 这类工具,得先明白 Windows 上权限的载体是访问令牌(Access Token)。SYSTEM 账户的 Token 拥有最高的特权集,几乎所有系统对象都能访问。要拿到这个 Token,常见做法有三条路。
第一条是复制现有 Token:先用 SeDebugPrivilege 打开某个 SYSTEM 进程的句柄,再调用 OpenProcessToken 配合 DuplicateTokenEx 复制出主令牌。这个方式的问题在于 SeDebugPrivilege 默认属于管理员组成员,但很多企业环境通过策略移除了该特权,复制操作会直接失败。而且打开系统进程句柄的行为容易被安全软件标记。
第二条是注入到现有服务进程,比如把自己的代码跑在 svchost.exe 里。这个方式对调试不友好,而且注入行为本身就是敏感操作,签名和免杀都是麻烦事。RunAsSystem 项目一般不会走这条路,因为它的目的是启动独立进程,不是做进程注入。
第三条是通过服务控制管理器创建一个临时服务,让 SCM 来启动目标进程。这是 RunAsSystem 最常用的路径:SCM 为服务进程准备 Token 时,天然就是 SYSTEM 身份,不需要额外特权。具体流程大致是:OpenSCManager 拿到服务控制句柄,CreateService 创建一次性服务,再把目标程序路径写到服务的 ImagePath 里,StartService 启动。目标程序继承服务进程的 Token,自然就是 SYSTEM 了。
我平时做权限类工具时,优先选择第三条路,核心原因是稳:不依赖 SeDebugPrivilege 的用户配置,失败路径少,且调用链完全公开,代码审查起来透明。坏处是启动后系统里会短暂出现一个服务登记记录,不过 RunAsSystem 会在退出前清理掉。
2.2 编译 RunAsSystem:从源码到 exe 的关键参数
RunAsSystem 的分发包里通常直接带二进制,但我建议拿到源码自己编译一遍,一方面是确认没有可疑逻辑,另一方面是静态链接能把体积和依赖问题一次解决。项目一般用 C 或 C++ 写,基于 Win32 API,没有额外的高版本运行时依赖。
以 Visual Studio 2022 为例,打开项目文件后,先把配置切换到 Release x64。如果你只需要在 32 位环境下跑,也可以编 x86,但要记住 32 位进程访问 system32 时的重定向问题,后面避坑章会具体讲。编译前的关键开关是运行时库:项目属性 → C/C++ → 代码生成 → 运行库,选多线程(/MT)。静态链接 CRT 之后,exe 丢到干净的 Server Core 或 Windows 10 机器上都能直接跑,不用装 VC 运行库。
一个常见的编译损失是 Unicode:Win32 下建议把字符集设为 Unicode,早期项目有些默认用多字节,路径带中文时会出现服务创建失败。遇到这类问题直接改项目属性里的字符集就行,代码本身不需要改动。
2.3 最小实现骨架:核心调用链长什么样
RunAsSystem 这类工具的核心逻辑不高深,下面是精简后的骨架代码,去掉错误处理和清理细节,保留主干流程,方便你理解调用顺序。
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!hSCM) return -1; char path[MAX_PATH]; GetModuleFileNameA(NULL, path, MAX_PATH); // 服务名与显示名保持唯一,用随机后缀避免冲突 char svcName[64]; sprintf_s(svcName, "RAS_%08X", GetTickCount()); SC_HANDLE hSvc = CreateServiceA( hSCM, svcName, svcName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, "C:\\full\\path\\target.exe args", // ImagePath NULL, NULL, NULL, NULL, NULL); StartServiceA(hSvc, 0, NULL); // 等待服务状态变为运行 SERVICE_STATUS status; QueryServiceStatus(hSvc, &status); DeleteService(hSvc); // 关闭句柄,不必等待进程退出,进程是独立运行的 CloseServiceHandle(hSvc); CloseServiceHandle(hSCM);这段代码的关键是 ImagePath 参数:它直接决定你要启动什么程序、带什么参数。CreateService 的第六个参数 dwStartType 用 SERVICE_DEMAND_START 表示手动启动,避免开机自动拉起。SERVICE_ERROR_IGNORE 意味着即使启动失败,系统也不会弹错误框,便于静默调试。
需要注意的地方是服务以 SYSTEM 身份启动后,目标进程的退出不会影响你当前终端。如果需要把退出码带回来,得用命名管道或事件对象做进程间通信,RunAsSystem 的命令行版会根据编译配置决定是否等待。
3. 命令行实战:参数格式、退出码和交互细节
3.1 常用参数表与典型用法
RunAsSystem 的命令行设计很直白,核心参数不外乎下面几个。不同发行版的参数缩写可能略不同,但语义一致。先看用法串,再解释每个字段。
RunAsSystem.exe -c "cmd.exe /k whoami" RunAsSystem.exe -c "notepad.exe" -i RunAsSystem.exe -c "C:\tools\myfix.exe" -d "C:\tools" -w-c 指定要执行的完整命令行,支持空格和引号,前提是用外层引号包住。不加 -c 时,有些版本会启动默认的 cmd.exe,方便快速验证是否拿到了 SYSTEM 令牌。 -d 是工作目录,很多人忽略它,但目标程序如果依赖相对路径来读配置文件,不设置工作目录会出现诡异的路径错误。-w 表示等待目标进程退出,RunAsSystem 自己保持前台运行。这个选项适合做脚本流程,能把目标程序的退出码通过 RunAsSystem 的退出码返回给上层批处理。
-i 是交互模式标记,用于告诉工具把目标进程会话放到当前交互会话里。注意,SYSTEM Token 默认跑在会话 0,看不到桌面;-i 会把会话信息设法传入创建进程的参数,解决 GUI 程序弹窗不可见的问题。这个参数在不同版本里实现有差异,后面单独讲坑。
3.2 实际验证步骤:从拉起到确认身份
打开一个管理员终端,执行下面两个命令,确认工具确实把进程拉到了 SYSTEM 下。
RunAsSystem.exe -c "cmd.exe /c whoami > C:\\temp\\out.txt" type C:\\temp\\out.txt输出应该显示:NT AUTHORITY\SYSTEM。注意你的终端标题栏和执行环境仍然是管理员身份,只有目标进程是 SYSTEM 身份,不做一次输出重定向检查,很容易被“好像没效果”的直觉误导。
更严谨的确认方式是检查令牌中的完整用户 SID。用 PowerShell 的 Get-Process 读取目标进程的 Path 和 StartTime,再用 Process Explorer 打开该进程的 Security 标签页查看 Token User Name。令牌里的域字段会标 NT AUTHORITY\SYSTEM,同时特权列表里能看到 SeDebugPrivilege、SeImpersonatePrivilege 等管理员没有的特权。
3.3 命令行解析的隐藏逻辑:引号与空格的边界
写参数的时候,最常见的问题是目标命令本身带引号时解析混乱。比如你要启动一个路径里带空格的程序,正确做法是:
RunAsSystem.exe -c "\"C:\\Program Files\\SomeApp\\run.exe\" --flag"给-c 传的值本身要被解析器吃一遍,目标命令的引号需要转义。有些版本对引号的处理是调用 CommandLineToArgvW 后重新拼接,这会导致引号丢失;更稳妥的写法是把外层实际命令放到一个 .cmd 或者 .bat 文件里,再用 -c 指向该文件,规避成套的解析问题。
退出码方面:如果加-w 等待,工具会把目标进程的退出码透传出来;没加-w,工具自身稳定返回 0 或 1,分别表示成功拉起或服务创建失败。批处理脚本里判断结果时,务必确认自己是否用了-w,否则返回值没有参考意义。
4. 进阶场景:服务部署、计划任务与开机自启
4.1 把 RunAsSystem 包装成常驻服务
一次性拉起的场景够大部分情况用了,但某些调试场景要求目标进程随系统启动,并且崩溃后自动拉起。这时直接把 RunAsSystem 注册成服务更干脆。命令行提供 -install 和 -remove 参数,本质是封装了 CreateService 调用。安装命令如下:
RunAsSystem.exe -install -c "C:\\app\\daemon.exe" -n "MyDaemon" sc.exe query MyDaemon-n 指定服务登记名,让后续管理时有明确标识。安装完后服务默认是手动启动或者自动启动,取决于编译版本。管理时直接用 sc 命令改启动类型:
sc.exe config MyDaemon start=auto注意 sc config 里等号后面必须跟一个空格,这是 sc 命令一个著名的历史遗留问题。start=auto 表示开机自启,等号后面没有空格会收到参数无效的报错。
RunAsSystem 的服务模式和单次运行的区别在于,服务模式不会在进程退出后清理服务登记,这样系统重启后由 SCM 再次拉起。目标程序的崩溃恢复策略由服务恢复参数决定,用 sc failure 配置,比如三次失败后重置计数,并指定重启服务。
4.2 在域环境下跨机器运行的注意事项
RunAsSystem 获取的是本机 SYSTEM 令牌,它是本地账户,不是域账户。如果目标程序需要通过网络访问其他机器的共享目录或调用域内接口,SYSTEM 账户在网络身份验证时会以本机机器账户身份出现在对方机器上(即 域名\主机名$ 这样的形式)。因此必须先在目标机器上给该机器账户授权,路径一般是对共享文件夹设置权限时添加“computername$”用户。
如果你希望目标程序以指定域用户的身份访问网络,就不能用 RunAsSystem 了,应该考虑调用 LogonUser 配合 CreateProcessWithLogonW 的工具,它们才是做“以某账户身份启动进程”的正确工具。RunAsSystem 的用户画像一直是“本机最高权限,不跨域”,这是它的边界。
4.3 配合计划任务的稳妥做法
有读者可能会问:既然系统自带 schtasks 也能以 SYSTEM 运行程序,为什么还要用 RunAsSystem?schtasks 的确能在特定时间或系统启动时把任务跑在 SYSTEM 下,但它的灵活性不够:任务计划程序有一套额外的调度策略,误配置会导致任务不执行而且日志难查。RunAsSystem 的优势是即时拉起、即刻清理,不留计划任务痕迹。
不过在某些情况下,它们可以组合:用一个计划任务在每个整点调用 RunAsSystem 拉起某个巡检脚本,脚本自身不需要任何特权判断。任务配置示例:
schtasks /create /tn "RAS_Periodic" /tr "C:\\ras\\RunAsSystem.exe -c C:\\ras\\check.bat" /sc hourly /ru SYSTEM这条命令创建的每个整点任务,用 SYSTEM 账户执行 RunAsSystem,RunAsSystem 再去跑 check.bat。这里多了一层间接引用的好处是:替换 check.bat 时不需要改任务计划,更新代价低。实际项目里我喜欢把 RunAsSystem 当作“权限放大器”,真正干的活在目标脚本里,RunAsSystem 永远保持小体积和简单逻辑,这样出问题时定位链路清晰。
5. 避坑与常见问题排查:稳定运行的五个关键点
5.1 服务创建成功但目标进程没起来
现象:命令返回成功提示,也短暂创建了服务,但目标进程没出现在任务管理器里。
原因一:ImagePath 里的路径带了引号或空格,SCM 解析时把它拆开,实际启动的程序不正确。原因二:目标程序依赖的工作目录没有设置,加载 DLL 失败导致静默退出。
解决:先加 -d 指定工作目录,再用 -w 等待模式跑一遍,观察是否报错。如果加入 -w 后仍然立刻结束,把目标程序替换成 cmd.exe /c pause,确认路径解析正常后,逐步换回原目标。一个固定套路是把目标命令写进 .bat,先跑 .bat 验证逻辑。
5.2 界面程序调起来后没有窗口
现象:以 SYSTEM 启动了 notepad.exe 或自研 GUI 程序,进程在运行,但桌面上看不到窗口。
原因:服务默认运行在会话 0,目标进程继承会话 0 的窗口工作站,你的交互桌面在会话 1 或更高。窗口不是被隐藏,而是渲染到了不可见桌面。
解决:真正的通用方案是设置进程令牌的会话 ID 并把窗口工作站切到交互桌面,这需要调用 SetTokenInformation 配合 CreateProcessAsUser,RunAsSystem 的 -i 参数在不同版本里实现程度不同。如果你的版本不支持有效交互,退而求其次:用计划任务代替服务,计划任务能把进程放到当前交互会话中。例如上面 schtasks /create /it 参数开启交互式任务,能直接看到窗口。
5.3 杀毒软件把工具标记为木马
现象:解压后被实时防护隔离,或编译时告警。
原因:服务创建、进程注入、权限切换这类 API 组合的调用序列非常像恶意软件的提权行为,启发式引擎会挑这类行为。另外,如果下载的二进制是旧版编译产物,时间戳老旧特征也更明显。
解决:首选自己从源码编译,编译器生成的文件在大概率上比网络上转手的二进制更不容易触发特征码。前提是源码干净。另外不要在公共代码托管平台直接下载 release,用源码编译而不是盲目信任任何“绿色版”转载。配置杀软信任目录属于自欺欺人,不做推荐。
5.4 32 位工具调用 64 位目标路径失败
现象:在同一台机器上,RunAsSystem 的 exe 是 x86,-c 里的目标程序明明在 C:\Windows\System32 下,却报告找不到路径。
原因:32 位进程访问 System32 时,Windows 文件系统重定向会把请求转到 SysWOW64 目录。System32 里 64 位的程序在 SysWOW64 里不存在,于是路径无效。
解决:编译 x64 版本一劳永逸。如果必须保留 x86,调用目标时把路径写成 C:\Windows\Sysnative 前缀,这个目录只在 32 位进程视角可见,专门用于绕过重定向。调试时可以用 cmd.exe 内的 echo %PROCESSOR_ARCHITECTURE% 确认自己的进程位数。
5.5 以服务方式安装后系统日志报 7000 错误
现象:服务设置为自动启动,开机后服务没有运行,事件查看器里记录服务-specific 错误 7000。
原因:服务 ImagePath 指向的程序路径不存在,或指向了 RunAsSystem 所在路径但参数里的目标程序路径包含了相对引用,服务启动的工作目录是系统根目录,相对路径全失效。另外,目标程序依赖的密码或凭据没有配置时同样会报这个错。
解决:把目标程序的绝对路径写到 -c 里,确认目标所有依赖的子进程路径也用绝对路径。在命令行窗口手动执行一次 RunAsSystem -install 之后,用 sc start 服务名,观察是否返回“启动成功”。脱离终端调试服务时,用 procmon 过滤服务名或 PID,能看到进程启动后立刻退出的具体原因,是缺 DLL 还是路径错误。
6. 验证一次真的成功:进程树、令牌与网络行为的闭环检查
把工具跑起来只是第一步,真正要确认的是目标进程确实以 SYSTEM 令牌在运行、行为符合预期、系统内没有残留。我一般在做完上述操作后,会用三个维度做收尾验证。
第一个维度是进程树。打开 Process Explorer,找到目标进程,右键选择 Properties → Image 选项卡。真正的 SYSTEM 进程其父进程会是 services.exe,而不是资源管理器或终端。同时 Tokens 选项卡里显示的用户为 NT AUTHORITY\SYSTEM,Privileges 列表里高亮项包含 SeDebugPrivilege、SeAssignPrimaryTokenPrivilege 等管理员终端里看不到的特权。有一点要注意:Process Explorer 默认显示的 Tokens 可能是当前用户视图,你需要在 View 菜单里开启完整令牌信息,显示字段才完整。
第二个维度是行为验证。如果目标是网络服务,用 netstat -ano 找到监听端口和对应 PID,检查该进程是否能绑定低端口和特殊端口。管理员无法监听的受限端口段,SYSTEM 可以。用一个简单的 TCP 测试脚本绑定 1-1024 段内的端口,如果绑定成功,基本可以确认令牌权限到位。
第三个维度是清理痕迹。服务方式的 RunAsSystem 会在退出后删除服务登记,但保险起见操作结束后跑一次查询命令:
sc.exe query type=service state=all | findstr /i ras_ reg query "HKLM\\SYSTEM\\CurrentControlSet\\Services" /s /f "RAS_" /d查出的任何残留都可以手动删除服务名。从那以后,我每次使用这类权限切换工具,都强制走一遍闭环检查:先确认令牌身份,再验证关键行为权限,最后查询登记清理,三个检查全部通过才算这一次操作真正落地。希望这种验证习惯也能帮到你少走弯路。
本文还有配套的精品资源,点击获取