Google Zero Project Team 在2014年的最后一天,公开了一个未修补的提权漏洞,并公开了poc代码。
详情参见:《谷歌安全团队公开Windows 8.1权限提升漏洞POC》
这个函数NtApphelpCacheControl,允许建立兼容性数据缓存并在新的进程被创建时重新使用。下面是这个函数的第一个参数。
而在执行的过程中,普通用户只能查询,只有管理员权限的用户可以更新。
但它通过调用AhcVerifyAdminContext来验证Admin权限的时候,存在问题,只通过对比SID进行了判断,并没有深入判断Token的impersonation等级。
攻击者可以伪造一个类似的Token,从而进行攻击。
漏洞产生的函数AhcVerifyAdminContext如下:
PAGE:0000000000016D64 ; int __cdeclAhcVerifyAdminContext(char, int, char, int, char, int, PVOID P) PAGE:0000000000016D64 AhcVerifyAdminContextproc near ; CODE XREF:AhcDriverDispatchDeviceControl:loc_1C16Cp PAGE:0000000000016D64 ;AhcDriverDispatchDeviceControl+118p ... PAGE:0000000000016D64 PAGE:0000000000016D64 arg_0 = byte ptr 8 PAGE:0000000000016D64 arg_8 = byte ptr 10h PAGE:0000000000016D64 arg_10 = byte ptr 18h PAGE:0000000000016D64 P = qword ptr 20h PAGE:0000000000016D64 PAGE:0000000000016D64 push rbx PAGE:0000000000016D66 push rbp PAGE:0000000000016D67 push rsi PAGE:0000000000016D68 push rdi PAGE:0000000000016D69 sub rsp, 28h PAGE:0000000000016D6D mov rbx, gs:188h PAGE:0000000000016D76 mov edi, 0C0000022h PAGE:0000000000016D7B call cs:__imp_PsGetCurrentProcess PAGE:0000000000016D81 lea r9, [rsp+48h+arg_10] PAGE:0000000000016D86 lea r8, [rsp+48h+arg_0] PAGE:0000000000016D8B lea rdx, [rsp+48h+arg_8] PAGE:0000000000016D90 mov rcx, rbx PAGE:0000000000016D93 mov rbp, rax PAGE:0000000000016D96 xor esi, esi PAGE:0000000000016D98 call cs:__imp_PsReferenceImpersonationToken// 得到Token PAGE:0000000000016D9E mov rbx, rax PAGE:0000000000016DA1 test rax, rax PAGE:0000000000016DA4 jnz short loc_16DDC PAGE:0000000000016DA6 mov rcx, rbp PAGE:0000000000016DA9 call cs:__imp_PsReferencePrimaryToken PAGE:0000000000016DAF mov esi, 1 PAGE:0000000000016DB4 mov rbx, rax PAGE:0000000000016DB7 test rax, rax PAGE:0000000000016DBA jnz short loc_16DDC PAGE:0000000000016DBC lea r9, aFailedToGetEff ; "Failed to geteffective token" PAGE:0000000000016DC3 lea rdx, aAhcverifyadmin ;"AhcVerifyAdminContext" PAGE:0000000000016DCA mov r8d, 3A1h PAGE:0000000000016DD0 xor ecx, ecx PAGE:0000000000016DD2 call AhcTracePrintf PAGE:0000000000016DD7 jmp loc_16E6A PAGE:0000000000016DDC ; --------------------------------------------------------------------------- PAGE:0000000000016DDC PAGE:0000000000016DDC loc_16DDC: ; CODE XREF:AhcVerifyAdminContext+40j PAGE:0000000000016DDC ; AhcVerifyAdminContext+56j PAGE:0000000000016DDC lea r8, [rsp+48h+P] PAGE:0000000000016DE1 mov edx, 1 PAGE:0000000000016DE6 mov rcx, rbx PAGE:0000000000016DE9 call cs:__imp_SeQueryInformationToken PAGE:0000000000016DEF test eax, eax PAGE:0000000000016DF1 jns short loc_16E10 PAGE:0000000000016DF3 lea r9, aFailedToQueryT ; "Failed toquery token information.\n" PAGE:0000000000016DFA lea rdx, aAhcverifyadmin ;"AhcVerifyAdminContext" PAGE:0000000000016E01 mov r8d, 3A9h PAGE:0000000000016E07 xor ecx, ecx PAGE:0000000000016E09 call AhcTracePrintf PAGE:0000000000016E0E jmp short loc_16E4F PAGE:0000000000016E10 ;--------------------------------------------------------------------------- PAGE:0000000000016E10 PAGE:0000000000016E10 loc_16E10: ; CODE XREF: AhcVerifyAdminContext+8Dj PAGE:0000000000016E10 mov rax, cs:__imp_SeExports PAGE:0000000000016E17 mov rdx, [rsp+48h+P] PAGE:0000000000016E1C mov rcx, [rax] PAGE:0000000000016E1F mov rdx, [rdx] PAGE:0000000000016E22 mov rcx, [rcx+108h] //这里得到Sid PAGE:0000000000016E29 call cs:__imp_RtlEqualSid; 这里调用RtlEqualSid进行检查,看两个Token的Sid是否一致。 如果一致就返回正确了。 PAGE:0000000000016E2F test al, al PAGE:0000000000016E31 jnz short loc_16E40 PAGE:0000000000016E33 mov rcx, rbx PAGE:0000000000016E36 call cs:__imp_SeTokenIsAdmin PAGE:0000000000016E3C test al, al PAGE:0000000000016E3E jz short loc_16E42 PAGE:0000000000016E40 PAGE:0000000000016E40 loc_16E40: ; CODE XREF:AhcVerifyAdminContext+CDj PAGE:0000000000016E40 xor edi, edi PAGE:0000000000016E42 PAGE:0000000000016E42 loc_16E42: ; CODE XREF:AhcVerifyAdminContext+DAj PAGE:0000000000016E42 mov rcx, [rsp+48h+P] ; P PAGE:0000000000016E47 xor edx, edx ; Tag PAGE:0000000000016E49 call cs:__imp_ExFreePoolWithTag PAGE:0000000000016E4F PAGE:0000000000016E4F loc_16E4F: ; CODE XREF:AhcVerifyAdminContext+AAj PAGE:0000000000016E4F test rbx, rbx PAGE:0000000000016E52 jz short loc_16E6A PAGE:0000000000016E54 mov rcx,rbx PAGE:0000000000016E57 cmp esi, 1 PAGE:0000000000016E5A jnz short loc_16E64 PAGE:0000000000016E5C call cs:__imp_PsDereferencePrimaryToken PAGE:0000000000016E62 jmp short loc_16E6A PAGE:0000000000016E64 ;--------------------------------------------------------------------------- PAGE:0000000000016E64 PAGE:0000000000016E64 loc_16E64: ; CODE XREF:AhcVerifyAdminContext+F6j PAGE:0000000000016E64 call cs:__imp_PsDereferenceImpersonationToken PAGE:0000000000016E6A PAGE:0000000000016E6A loc_16E6A: ; CODE XREF:AhcVerifyAdminContext+73j PAGE:0000000000016E6A ;AhcVerifyAdminContext+EEj ... PAGE:0000000000016E6A mov eax, edi PAGE:0000000000016E6C add rsp, 28h PAGE:0000000000016E70 pop rdi PAGE:0000000000016E71 pop rsi PAGE:0000000000016E72 pop rbp PAGE:0000000000016E73 pop rbx PAGE:0000000000016E74 retn PAGE:0000000000016E74 AhcVerifyAdminContextendp
像这种提升权限的漏洞,要利用的话,需要对系统权限管理这块非常的熟悉。
Google的大牛们直接给出了POC, 现在我们来分析一下POC是如何实现利用的。
漏洞利用有如下几步:
(1)得到一个Local System的Token, Sid为:S-1-5-18的Token
(2)构造一个ApphelpCacheControlData的结构,里面包含regsvr32.dll包等相关的信息。
(3)将Token赋予本线程,通过调用SetThreadToken
(4)执行status =fNtApphelpCacheControl(AppHelpUpdate, &data);将data的数据更新到Cache中。
(5)执行ShellExecuteW(nullptr, verb, argv【1】, dllpath.c_str(), nullptr, SW_SHOW);行它会调用regsvr32.dll 并执行TestDll.dll中的代码。从而达到权限提升的目的(其实是绕过UAC)。
这个漏洞利用,其实是通过绕过UAC来执行代码的。
如果在普通用户权限下,执行操作的时候,会弹出UAC的提示。
所以必须是admin的用户,执行SetThreadToken的时候,其实已经将Local System的权限赋予了当前的线程。
但其实真正奇妙的是使用ComputeDefaults.exe,这个程序在直接使用的时候,是没有UAC的提示。
作者提示,这样没有UAC提示的程序还有很多。
所以通过它来调用regsvr32.dllTestDLL.dll这个,也不会有UAC的提示。
我们换做别的,比如cmd.exe,来执行的时候,是会有UAC的提示的。不会达到目的。
最后发一个执行成功的截图
https://code.google.com/p/google-security-research/issues/detail?id=118
【本文来源:程大壮 SP小编整理发布】