在内网渗透中令牌窃取通常用于从Administrator权限提升到System权限或者用于获取trustedinstaller等权限,令牌窃取通过Metasploit框架提供的载荷很容易实现,在学习了Windows访问控制的内容之后,可以自己实现一个令牌窃取的工具,比如 JCTokenUtil、SharpToken、incognito,这篇文章是笔者学习令牌窃取的原理和实现的产出。
令牌列举就是列举本地计算机上所有的访问令牌,NtQuerySystemInformation
API是用来检索指定的系统信息的,NtQuerySystemInformation
API的原型如下
__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
结构表示此进程的若干个线程。
通过NtQuerySystemInformation
API获取到计算机上所有的进程后,可以通过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
函数来获取进程本身的令牌句柄。
比如我们想窃取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账户。
熟悉令牌窃取的应该都知道一般普通用户是无法窃取令牌来执行命令的,这就导致令牌窃取只能用来从Administrator
权限提升到System
权限或System
权限到trustedinstaller
权限等。
出现这种情况的原因之一是普通用户无法获取到System
、Administrator
等账户的令牌,管理员账户获取到的令牌有730个
而普通账户获取到的令牌只有345个
我把普通账户获取到的令牌都down下来后发现此普通账户获取到的令牌用户都是其本身,没有其它账户存在。
账户能获取到多少令牌,取决于令牌的安全描述符和完整性级别是否允许账户对令牌的请求。