0x00
在内网渗透中令牌窃取通常用于从Administrator权限提升到System权限或者用于获取trustedinstaller等权限,令牌窃取通过Metasploit框架提供的载荷很容易实现,在学习了Windows访问控制的内容之后,可以自己实现一个令牌窃取的工具,比如 JCTokenUtil、SharpToken、incognito,这篇文章是笔者学习令牌窃取的原理和实现的产出。
0x01 令牌列举
令牌列举就是列举本地计算机上所有的访问令牌,NtQuerySystemInformationAPI是用来检索指定的系统信息的,NtQuerySystemInformationAPI的原型如下
__kernel_entry NTSTATUS NtQuerySystemInformation(
[in] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[in, out] PVOID SystemInformation,
[in] ULONG SystemInformationLength,
[out, optional] PULONG ReturnLength
);
SystemInformationClass参数标识要检索的系统信息,它是SYSTEM_INFORMATION_CLASS中枚举的值之一。SYSTEM_INFORMATION_CLASS的原型如下
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemPathInformation,
SystemProcessInformation,
SystemCallCountInformation,
SystemDeviceInformation,
SystemProcessorPerformanceInformation,
SystemFlagsInformation,
SystemCallTimeInformation,
SystemModuleInformation,
SystemLocksInformation,
SystemStackTraceInformation,
SystemPagedPoolInformation,
SystemNonPagedPoolInformation,
SystemHandleInformation,
SystemObjectInformation,
SystemPageFileInformation,
SystemVdmInstemulInformation,
SystemVdmBopInformation,
SystemFileCacheInformation,
SystemPoolTagInformation,
SystemInterruptInformation,
SystemDpcBehaviorInformation,
SystemFullMemoryInformation,
SystemLoadGdiDriverInformation,
SystemUnloadGdiDriverInformation,
SystemTimeAdjustmentInformation,
SystemSummaryMemoryInformation,
SystemNextEventIdInformation,
SystemEventIdsInformation,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemExtendServiceTableInformation,
SystemPrioritySeperation,
SystemPlugPlayBusInformation,
SystemDockInformation,
SystemPowerInformation,
SystemProcessorSpeedInformation,
SystemCurrentTimeZoneInformation,
SystemLookasideInformation
} SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS;
当枚举值为SystemProcessInformation时标识要检索计算机上的所有进程信息,这时SystemInformation参数指向检索结果的第一个进程的SYSTEM_PROCESS_INFORMATION结构,SYSTEM_PROCESS_INFORMATION的原型如下
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
PVOID Reserved2;
ULONG HandleCount;
ULONG SessionId;
PVOID Reserved3;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG Reserved4;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
PVOID Reserved5;
SIZE_T QuotaPagedPoolUsage;
PVOID Reserved6;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;
NumberOfThreads成员指示进程拥有的线程数,UniqueProcessId成员指示进程的唯一进程ID,HandleCount成员指示进程拥有的句柄数,NextEntryOffset指示下一个SYSTEM_PROCESS_INFORMATION结构距离当前结构的偏移,最后一个SYSTEM_PROCESS_INFORMATION结构的NextEntryOffset成员值为0。
SYSTEM_PROCESS_INFORMATION结构后紧跟若干个SYSTEM_THREAD_INFORMATION结构表示此进程的若干个线程。
通过NtQuerySystemInformationAPI获取到计算机上所有的进程后,可以通过DuplicateHandle函数获取每个进程拥有的所有句柄,然后用NtQueryObject函数来检索句柄对应的对象类型。
NtQueryObject函数原型如下
__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryObject(
[in, optional] HANDLE Handle, //要检索的句柄
[in] OBJECT_INFORMATION_CLASS ObjectInformationClass,
[out, optional] PVOID ObjectInformation,
[in] ULONG ObjectInformationLength,
[out, optional] PULONG ReturnLength
);
OBJECT_INFORMATION_CLASS是一个枚举类型,原型如下
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectTypeInformation
} OBJECT_INFORMATION_CLASS;
当指定ObjectInformationClass成员为ObjectTypeInformation枚举值时,ObjectInformation参数返回PUBLIC_OBJECT_TYPE_INFORMATION结构,PUBLIC_OBJECT_TYPE_INFORMATION结构原型如下
typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG Reserved [22]; // reserved for internal use
} PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION;
TypeName成员表示对象的类型字符串,当对象是访问令牌时该值为Token,通过控制该值为Token就可以过滤出所有访问令牌的句柄。
注意的是,工具获取的是访问令牌模拟级别大于SecurityImpersonation模拟级别的访问令牌,通过GetTokenInformation函数获取访问令牌的TokenImpersonationLevel成员来确定访问令牌的模拟级别。
通过上面的步骤就可以获取到每个进程拥有的访问令牌句柄,但是上面检索的访问令牌不包括每个进程本身的访问令牌,所以需要再通过OpenProcessToken函数来获取进程本身的令牌句柄。
0x02 窃取令牌来执行命令
比如我们想窃取NT AUTHORITY\SYSTEM账户的令牌来执行命令,我们需要先找到NT AUTHORITY\SYSTEM账户的令牌。这个很简单,通过GetTokenInformation函数获取到令牌的用户SID,然后用LookupAccountSidA函数将SID转换为对应的账户名即可。
找到NT AUTHORITY\SYSTEM账户的令牌后,需要把令牌的TokenSessionId改为当前进程令牌的TokenSessionId,调用SetTokenInformation函数来设置即可。注意更改令牌的TokenSessionId需要SeTcbPrivilege特权。
最后调用CreateProcessAsUserA函数来执行命令,CreateProcessAsUserA函数的原型如下
BOOL CreateProcessAsUserA(
[in, optional] HANDLE hToken,
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
lpCommandLine参数传递要执行的命令,hToken参数传递NT AUTHORITY\SYSTEM账户的令牌。
由于更改令牌的TokenSessionId值需要SeTcbPrivilege特权,CreateProcessAsUserA函数的调用需要SE_ASSIGNPRIMARYTOKEN_NAME特权。而NT AUTHORITY\SYSTEM账户拥有这两个特权,所以在更改令牌的TokenSessionId值和调用CreateProcessAsUserA函数之前先调用ImpersonateLoggedOnUser函数模拟NT AUTHORITY\SYSTEM账户。
0x03 为什么低权限用户无法窃取令牌
熟悉令牌窃取的应该都知道一般普通用户是无法窃取令牌来执行命令的,这就导致令牌窃取只能用来从Administrator权限提升到System权限或System权限到trustedinstaller权限等。
出现这种情况的原因之一是普通用户无法获取到System、Administrator等账户的令牌,管理员账户获取到的令牌有730个
而普通账户获取到的令牌只有345个
我把普通账户获取到的令牌都down下来后发现此普通账户获取到的令牌用户都是其本身,没有其它账户存在。
账户能获取到多少令牌,取决于令牌的安全描述符和完整性级别是否允许账户对令牌的请求。
还没有评论,来说两句吧...