伪造令牌提权Windows ring3 最高权限
起因是我想在搞一些操作windows进程的事情时,老是需要右键以管理员身份运行,感觉很麻烦。就研究了一下怎么提权,顺手瞄了一眼Windows下用户态权限分配,然后也是感谢《深入解析Windows操作系统》这本书给我偷令牌的灵感吧,写了两三天,成功获取了用户态下所有特权+用户组。
一、windows的权限
在 Windows 操作系统中,“你是谁”以及“你能做什么”完全由一个核心内核对象决定。无论是用户登录、服务启动,还是进程创建,Windows 安全引用监视器(SRM)都会为每个进程分配一个令牌。Windows 的权限机制并不是一个单点功能,而是一整套围绕“对象”“身份”“能力”展开的安全体系。它的核心目标只有一个:在多用户、多进程、多权限级别共存的环境中,让系统既能安全运行,又能灵活扩展。
这套机制的关键在于三个概念:安全主体、访问令牌、受保护对象。Windows 不关心你写了什么代码,只关心你的线程当前挂着哪一张令牌,以及你试图触碰的对象设置了怎样的门禁。
在 Windows 中,真正参与权限判断的不是“用户”这个抽象概念,而是 Security Principal。它可以是本地用户、域用户、系统账户,甚至是服务账户。每一个 Security Principal 在系统中都有一个唯一的 SID,这个 SID 就像身份证号码一样,贯穿其一生。
当你登录系统时,无论是交互式登录、服务启动,还是计划任务触发,LSASS 都会负责认证你的身份,并为你生成一个访问令牌。这个令牌不是简单的“你是谁”,而是一个完整的能力集合快照。
这个快照在登录完成后通常是静态的,除非显式进行特权调整或令牌复制。也正因为如此,Windows 的提权行为,本质上不是“获得新能力”,而是“获得另一张令牌”。
1.令牌的结构
令牌中包含用户 SID 和所属的所有组 SID。这些组并不只是显示在“用户属于 Administrators”那么简单,还包括诸如 INTERACTIVE、SERVICE、AUTHENTICATED USERS 等隐式组。每一个组都可以被启用、禁用,或者标记为仅用于拒绝访问。
令牌中还包含一组特权,也就是 Privileges,例如 SeDebugPrivilege、SeLoadDriverPrivilege、SeImpersonatePrivilege 等。这些特权并不是默认全部启用的,大多数处于 Disabled 状态,需要显式调整。
此外,令牌还携带完整性级别。自 Windows Vista 起,引入了强制完整性控制机制,低完整性进程即便拥有访问控制列表允许的权限,也可能被完整性策略直接拦截。
最后,令牌还记录了是否为主令牌还是模拟令牌,这个细节直接决定了它能否被用于创建新进程。
二、申请UAC和调试权限
1.强制管理员运行
函数 IsRunAsAdmin() 通过检查当前令牌是否含有 DOMAIN_ALIAS_RID_ADMINS(管理员组 SID)来判断权限。 如果不是管理员,SelfElevate() 函数利用了一个经典技巧:ShellExecuteExW 配合 runas 动词。
原理:runas 会触发 Windows 的用户账户控制(UAC)机制。如果当前用户在管理员组但以中等完整性级别(Medium Integrity Level)运行,这会弹窗请求提升。这是本程序中唯一一处需要用户给权的代码。(uac似乎也能绕过,更改环境变量+劫持dll)
[code]BOOL IsRunAsAdmin() { BOOL fIsRunAsAdmin = FALSE; PSID pAdminSID = NULL; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; if (AllocateAndInitializeSid( &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSID)) { if (!CheckTokenMembership(NULL, pAdminSID, &fIsRunAsAdmin)) { fIsRunAsAdmin = FALSE; } FreeSid(pAdminSID); } return fIsRunAsAdmin;}void SelfElevate() { WCHAR szPath[MAX_PATH]; if (GetModuleFileNameW( NULL, szPath, ARRAYSIZE(szPath))) { SHELLEXECUTEINFOW sei = {sizeof(sei)}; sei.cbSize = sizeof(sei); sei.lpVerb = L"runas"; sei.lpFile = szPath; sei.hwnd = NULL; sei.nShow = SW_NORMAL; if (!ShellExecuteExW(&sei)) { std::wcout |