压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

NtApphelpCacheControl漏洞分析

  起因:

  Google Project Zero團隊的新晉成員James Forshaw在9月30日向微軟提交了名為“Windows: Elevation of Privilege inahcache.sys/NtApphelpCacheControl”的安全問題,并且在Google的漏洞公開期限(90天)后,也就是2014年12月29日(北京時間的12月30日)公開了此問題的細節。

  在這里可以看到他針對該問題的一個簡單描述和攻擊驗證代碼:https://code.google.com/p/google-security-research/issues/detail?id=118

  針對這個漏洞的爭論很多,很多觀眾在爭論微軟和Google對待安全漏洞的做法,90天公開策略是否合情合理等等,也有很多技術人員在爭論這個安全問題是否是嚴格意義上的權限提升漏洞。后者的一個主要原因是James Forshaw在演示這個漏洞時,是通過此漏洞來劫持UAC默認級別下自動提權的ComputerDefaults程序,從而實現在默認UAC設置下靜默從中完整性級別啟動高完整性級別的程序。

  通常來說,微軟安全響應中心(MSRC)不認為UAC默認級別下中完整性級別到高完整性級別的提示繞過屬于安全漏洞的(參考MSRC的”如何定義安全漏洞“ http://technet.microsoft.com/library/cc751383.aspx)。

  但是,最近MSRC也將一些可以穿透InternetExplorer保護模式(PM)或增強保護模式(EPM)沙箱的安全問題(實際上是從低完整性級別穿透至中完整性級別的問題)作為安全漏洞來修補(例如CVE-2014-6349)。所以我們先擱置是否安全漏洞的這個爭議,深入分析下這個漏洞涉及的原理和問題。

  漏洞分析、成因與利用

  這個漏洞的基本原因,Google的這篇簡單說明里已經講得比較準確了,簡單來說,就是NtApphelpCacheControl這個系統調用,在調用者模擬System權限時,沒有正確識別調用者的Token,導致其本來僅提供給管理員和系統程序調用的接口,可以被低權限程序誤用,并借助該調用的相關機制,劫持高權限程序,實現權限提升。

  看完這個原因后,你可能會有疑問,這個系統調用是做什么的?什么是Apphelp?Token的判斷具體哪里有問題?Apphelp的哪項機制如何能被利用、以及是如何劫持高權限進程的?接下來筆者就將深入地介紹這些內容,詳細地解答這些問題。

  1. Apphelp與NtApphelpCacheControl

  在Windows 8.1 系統上,NtApphelpCacheControl這個系統調用,顧名思義,可以控制系統中Apphelp規則的快速緩存數據。

  Apphelp是微軟從WindowsXP操作系統開始引入的一項兼容性解決方案,官方的名字是”Application Compatibility Database”(應用程序兼容性數據庫)。這套解決方案的目的是減少操作系統的向下兼容成本,不是在操作系統層面,而是通過這套兼容數據庫引擎提供包括Shim(Hook)在內的大量控制功能,來做到實時地識別和修改特定存在兼容性問題的流行軟件程序,使其能夠兼容新的操作系統。

  關于這套機制,微軟官方從Windows7開始, 有一些功能性的介紹文檔(http://technet.microsoft.com/en-us/library/ee461265(v=ws.10).aspx),也可以通過官方的”Microsoft Application Compatibility Toolkit”(ACT),在Windows7以上的操作系統上方便里查看、修改和添加本地的兼容性數據庫。在此之前,Alex ionescu和一些其他的國內外研究者也深入地研究過兼容性數據庫和Shim機制(http://www.alex-ionescu.com/?p=39)。

  微軟操作系統的各個版本積累了針對應用程序的大量兼容性修改的方法和基礎數據,使這些應用程序可以流暢地運行在新的操作系統中,這為微軟的操作系統在世界范圍內的復雜環境中廣泛運行和推廣起到了重要的作用。在Windows Vista發布后,它帶來的海量革新的同時,也導致大量應用程序出現兼容性問題,成為該系統被人詬病的原因之一。為此,在其后的Windows7操作系統開發過程中,微軟加大了對應用程序兼容性修復方面的重視和投入,不僅更廣泛地收集和編制應用程序兼容性修復規則,也對這套兼容性機制做了升級換代。

  本次出現問題的NtApphelpCacheControl也是支持這套機制的一個新的接口。這已經不是該函數第一次出現安全漏洞, 在j00ru Syscan2013上的關于Bochspwn的議題上,就公開了一個涉及NtApphelpCacheControl處理緩存代碼中存在的競爭條件漏洞(CVE-2013-1278)。閱讀下面的內容的同時,建議讀者能看一看這個slides中NtApphelpCacheControl相關的內容(75~94頁)(http://vexillium.org/dl.php?syscan_slides.pdf) 以及James的POC源碼。

  為什么Apphelp需要這個接口呢?這還需要從XP系統說起,應用程序兼容性的主數據庫實際存儲是在Windows安裝目錄下的AppPatchsysmain.sdb文件中的,但是每次進程啟動、模塊加載時都去檢查這個數據文件顯然開銷太大。為了能快速處理針對這些可執行程序的兼容性規則,就需要在內存中緩存這個數據庫的機制。在Windows XP時代, Apphelp使用一塊名為ShimSharedMemory的全局共享Section來在不同進程之間共享識別、修改可執行程序的兼容性數據內存。

  從Windows 2003開始,NtApphelpCacheControl函數被引入系統調用中,作為apphelp的內核入口,在內核中提供了整套新的應用程序兼容性數據庫檢索、緩存的功能,使得應用程序可以跨Session、跨隔離,并且快速查詢、應用兼容性數據庫中的規則和處理方案,其緩存功能也實現了在盡量減少性能消耗的同時,快速地針對系統內已知的存在兼容問題的程序進行更快速地修復。

  在Windows2003,Windows Vista,Windows7和Windows8操作系統上,NtApphelpCacheControl是實現在系統內核ntoskrnl內部的,從Windows8.1操作系統開始,為了能夠減少升級成本,內核將該系統調用的絕大部分最終實現在了一個新的內核模式驅動程序ahcache.sys內部,系統內核通過發送設備控制命令給該驅動來實現該接口的絕大部分功能,在Windows10技術預覽版上,我們看到這個驅動又進行了一次比較大的更新。

  NtApphelpCacheControl的原型為:

  NTSTATUS NtApphelpCacheControl(APPHELPCOMMAND Command , PVOID CommandData);

  這個Command提供了針對應用程序兼容性數據庫緩存的查看、添加、刪除等等一系列功能。其中Command的枚舉功能和CommandData的數據結構,J00ru的議題和James的源碼中都提供了一些,但都不全面也有不少錯誤,通過IDA查看實現代碼很容易就可以看清楚,這里我將這些功能的Command id、對應的控制碼(適用于 Windows8.1)和對應實現的功能整理匯總后列出:

  enum APPHELPCOMMAND

  {

  AppHelpCahceLookup,                //  IoControlCode: 0x220003*

  AppHelpCahceRemove,                //  IoControlCode:  0x220007*

  AppHelpCahceUpdate,                //  IoControlCode:  0x22000B*

  AppHelpCacheFlush                  //  IoControlCode: 0x22000F*

  AppHelpCacheDump,                  //  IoControlCode: 0x220013

  AppHelpCacheNotifyStart ,          //  IoControlCode: 0x220017

  AppHelpCacheNotifyStop,            //  IoControlCode: 0x22001B

  AppHelpCahceForward,               //  IoControlCode:  0x22001F

  AppHelpCacheQuery,                 //  IoControlCode: 0x220023

  AppHelpQueryModule,                //  IoControlCode: 0x220027

  AppHelpRefresh,                    //  IoControlCode: 0x22002B

  AppHelpCheckForChange,             //  IoControlCode: 0x22002F

  AppHelpQueryHwId,

  };

  James在源碼中定義了相關結構,不過看上去對這個結構的了解也不是很深入。*:James的POC源碼中,對這四個控制碼少了一個0

  AppHelpCacheLookup:在Apphelp cache中尋找匹配的、需要處理的記錄。

  AppHelpCacheRemove: 刪除匹配的Apphelp Cache記錄

  AppHelpCahceUpdate:插入記錄到Apphelp Cache

  AppHelpCacheFlush:Flush AppHelp cache,將AppHelpCache的緩存數據清空(根據Flush的標志不同,不一定真的刪除內存中的數據,只是去掉某些標記),并刷新到磁盤(注冊表)上。

  James源碼中認為這個命令是沒用處的AppHelpEnum。這是錯誤的 ,apphelp!ShimFlushAppcompatCache->kernelbase!BaseFlushAppcompatCache->kernel32!BaseFlushAppcompatCacheWorker  還在使用這個來清空shim的兼容數據數據。他應該是將AppHelCacheFlush看漏了,源碼里對于NotifyStartEnum等后面的幾個都差了一個數。

  AppHelpCacheDump:這才是一個“沒用”的功能, AppHelp會枚舉緩存中的數據,針對枚舉的項目并不做操作。筆者猜測這個命令之所以叫“Dump”,可能是一個調試功能。于是筆者從WDK中找了個checked build的內核來看了下。果然在checked build內核中,會枚舉緩存中的元素,并逐個打印出記錄要去匹配的文件名記錄。在Free build中,對應的代碼被去掉了,所以成了一個看似無用的命令,實際在checked build,微軟的開發人員可能是有對應的工具去通過這個接口方便地觀察緩存中都有那些記錄。

  AppHelpCacheNotifyStart/AppHelpCacheNotifyStop:這兩個命令主要用于同AppHelp的服務通訊,AppHelp內核會通過AhcPort這個ALPC Port同Application Experience服務通訊。這兩個命令用于重連/停止 同服務的LPC通訊。

  AppHelpCacheForward: 用于將緩存可執行程序的信息排隊,然后轉發給Application Experience服務處理

  AppHelpCacheQuery: 用于獲取Shim Cache中的數據內容、ShimCache的相關統計和隊列。

  AppHelpQueryModule/AppHelpCacheRefresh/AppHelpCheckForChange/AppHelpQueryHwId:這些是Windows8.1新增的命令,和這里就不詳細介紹了。

  下面介紹這個調用中重要的數據CommandBuffer的結構,這個結構的數據驅動了Lookup/Remove/Update/Forward等大部分基本命令。

  這個CommandBuffer的結構(我這里稱為APPHELP_COMMANDDATA),是由三個結構體組成的:

  其中第一個結構體用于存儲整個shim緩存和統計內容,被AppHelpCacheQuery命令使用;

  第二個結構體用于在AppHelpCacheLookup/AppHelpCacheRemove/AppHelpCacheUpdate這三個命令時來告訴接口檢索、刪除和添加的項目內容;

  第三部分則用于AppHelpCacheForward命令,提供forward,用于提供轉發給服務的相關數據。

  無論是哪個命令,CommandBuffer都同時包含著三個結構體,這也是為什么J00ru的議題和James的源碼里都提到提交的緩存結構前面有大量無用的0數據的原因。

  下面給出這個結構的定義:

  typedef struct APPHELP_COMMANDDATA{

  APPHELP_CACHE_QUERY QueryData;                  //Query full data

  APPHELP_CACHE_ENTRY EntryData ;                 //Lookup/Remove/Update entry

  APPHELP_CACHE_FORWARD ForwardData;             //Data for forward

  } APPHELP_COMMANDDATA, *PAPPHELP_COMMANDDATA;

  其中APPHELP_CACHE_QUERY/APPHELP_CACHE_FORWARD的結構和本次漏洞無關,就留給感興趣的讀者去研究了。

  需要注意的是,QueryData的數據長度會影響后面的EntryData(這個長度在Win7Win8上不相同),Win7上是0×90,Win8.1上James已經提供了是0×98(這也是為什么Win7上這個POC無法工作的原因之一)。這點看一看ApphelpCacheControlValidateParameters或AhcValidateAndGetParameters就可以了解了。

  這里提供一下APPHELP_CACHE_ENTRY的數據結構:

  typedef struct APPHELP_CACHE_ENTRY{

  DWORD Flags ;

  ULONG CacheReturnFlags ;

  HANDLE FileHandle;

  HANDLE ProcessHandle ;

  //Only in Win8/Win8.1UNICODE_STRING FileName;

  UNICODE_STRING PackageFullName;

  //only in Win8/Win8.1DWORD  DataBufferSize;

  PVOID DataBuffer;

  } APPHELP_CACHE_ENTRY, *PAPPHELP_CACHE_ENTRY;

  FileHandle/FileName是對應要進行處理的可執行程序(包括模塊)的對應句柄和文件名,之所以要提供文件句柄,是因為內核使用一個AVL Table來存儲Entry數據,使用文件句柄的FileTime來做其中平衡樹的索引。其中 Flags/CacheReturnFlags分別指明了這條Cache的功能和作用,尤其是對于DataBufferSize/DataBuffer是否有效的控制,是否在關機時保存到注冊表中等。

  DataBufferSize和DataBuffer描述了Entry相關的規則數據的內容。

  ProcessHandle和PackageFullName僅在Windows8及以后的操作系統上使用。

  DataBuffer可以保存由RING3存儲的數據結構,一般來說由APPHELP_CACHEUPDATE更新到內核緩存中,APPHELP_CACHELOOKUP獲取后使用,Shim引擎使用的數據結構,我稱為APPHELP_LOOKUP_RESULT(即James源碼中的APPHELP_QUERY結構),如下:

  #define MAX_EXE_TAGS 16

  #define MAX_LAYER_TAGS 8

  typedef struct APPHELP_LOOKUP_RESULT

  {

  DWORD ExeTags[MAX_EXE_TAGS];

  DWORD ExeFlags[MAX_EXE_TAGS];

  DWORD LayerTags[MAX_LAYER_TAGS];

  DWORD LayerFlags;

  DWORD AppHelpTag;

  DWORD ExeTagsCount ;

  DWORD LayerTagsCount;

  GUID ExeGuid;

  DWORD Flags2;

  DWORD Unknown;

  DWORD Unknown2;

  GUID Guid2[16];

  };

  如James源碼中所使用的,  其中ExeTags指明了在sdb數據中的修復方案的tag id,ExeTagsCount是tag的數量,最多16個, LayerTags是Layer規則的數據。

  通過這些命令和數據結構,我們大概了解了NtAppHelpCacheControl提供的能力,那么RING3是如何使用它的呢?

  簡單地說,Ring3的Module Loader等模塊在模塊加載、進程啟動等事件觸發時,會調用apphelp.dll中的相關接口, apphelp.dll再調用kernel32/kernelbase內BasepShim*相關的API,通過NtAppHelpCacheControl中的CacheLookup功能查詢模塊的可執行文件是否在緩存的數據中,如果存在就按照規則數據庫中對應的規則處理,相關的數據通過CacheForward發送到服務進程后,會調用CacheUpdate功能緩存被使用的數據,加快二次查詢的速度。

  Token相關的問題

  了解了AppHelpCache的接口和基本工作原理后,我們很容易可以明白,如果可以低權限的程序可以調用AppHelpCacheUpdate命令添加緩存,那么可以劫持任意高權限程序,利用shim規則對其進行修改,實現權限提升。

  那么我們回來看看這個漏洞的成因,如James在說明里提到的,這個漏洞的原因是因為用于驗證AppHelpCacheUpdate是否運行被調用的AhcVerifyAdminContext函數,驗證Token存在問題,這個函數很簡單,我們使用Hex-rays decomplier得到偽代碼:

  NTSTATUS AhcVerifyAdminContext()

  {

  retstatus = STATUS_ACCESS_DENIED;

  CurrentThread = KeGetCurrentThread();

  CurrentProcess = PsGetCurrentProcess();

  TokenType = 0;

  TokenObj = PsReferenceImpersonationToken(CurrentThread, &CopyOnOpen, &EffectiveOnly, &ImpersonationLevel);

  if ( TokenObj || (TokenObj = PsReferencePrimaryToken(CurrentProcess), TokenType = 1, TokenObj) )

  {

  if ( SeQueryInformationToken(TokenObj, 1, &TokenUserInformation) >= 0 )

  {

  if ( RtlEqualSid(_SeExports->SeLocalSystemSid, TokenUserInformation->User.Sid) || SeTokenIsAdmin(TokenObj) )

  {

  retstatus = STATUS_SUCCESS;

  }

  ExFreePoolWithTag(TokenUserInformation, 0);

  }

  else

  {

  AhcTracePrintf(0, "AhcVerifyAdminContext", 937, "Failed to query token information.
", v5);

  }

  if ( TokenObj )

  {

  if ( TokenType == 1 )

  {

  PsDereferencePrimaryToken(TokenObj);

  }

  else

  {

  PsDereferenceImpersonationToken(TokenObj);

  }

  }

  }

  else

  {

  AhcTracePrintf(0, "AhcVerifyAdminContext", 929, "Failed to get effective token", v5);

  }

  return retstatus;

  }

  函數的功能很簡單,首先試圖獲取線程模擬的token,如果線程的模擬token不存在,就獲取當前進程的主token對象,獲取token對象后, 通過SeQueryInformationToken(TokenUser)獲得Token的用戶信息。

  接著,對比如果下面token對象符合下面兩種情況的任一種,就返回STATUS_SUCCESS允許AppHelpCacheUpdate操作,否則就返回STATUS_ACCESS_DEIND拒絕操作:

  1.Token的用戶SID匹配LocalSystem的SID

  ??2.Token通過SeTokenIsAdmin的驗證,也就是token用戶組內有啟用的Adminsitrator組。

  那么這個對比就是James所說的驗證Token有問題的部分,有問題的原因就如James所說, 是因為沒有去判斷ImpersonationLevel。

  這是因為被模擬的Token的SID并不能決定Token就真的擁有對應SID的權利,可以參考微軟關于SECURITY_IMPERSONATION_LEVEL的MSDN解釋(http://msdn.microsoft.com/en-us/library/windows/hardware/ff556631(v=vs.85).aspx )。

  低于SecurityImpersonation的模擬token其實并不是以被模擬的Client的安全上下文運行的,所以僅僅通過SID來判斷調用者是否具備LocalSystem的權限是不夠,類似的問題其實是Windows的內核、內核模式驅動程序中還有不少地方存在,感興趣的讀者可以再進行一些挖掘。

  以James源碼的方法為例,通過Bits服務的BackgroundCopyManager接口創建一個下載任務后,代碼將自己的Notify對象設置為任務的通知接口。

  此時服務在下載通知時會調用對應的對象,接著combase通過LPC回調試圖調用對象的接口函數,而在調用前,就會使用RpcImpersonateClient->NtAlpcImpersonateClientOfPort,由AlpcpImpersonateMessage來為執行回調接口準備Alpc port指定的安全上下文。

  由于Bits的OLE Port指定的允許的SecurityQos->ImpersonationLevel級別的模擬,模擬完成后調用到James的處理代碼,他的代碼打開并保存了線程的token句柄,由此獲得了一個SecurityIdentification級別的Token句柄 ,Token是來自Bits服務的安全上下文,因此Token的SID自然也就是NT AUTHORITYSYSTEM(LocalSystem)

  這里Bits服務本身是沒有安全問題的,因為ALPC獲得和模擬的token只是SecurityIdentification級別,即使模擬這個token(就如James的代碼后面所做的),也無法以System權限上下文工作,在訪問對象ACL時,會被拒絕訪問(可以參考SeAccessCheck中的實現和判斷),也不會被識別為admin/system(可以參考SeTokenIsAdmin在Windows7以上操作系統的實現),但是NtAppHelpCacheControl這里只判斷Token SID的方式,就導致了安全檢查被繞過,也是這個漏洞的根本原因。

  漏洞的利用

  James提供的POC針對這個漏洞利用的方法是劫持一個UAC默認級別下會不區分命令行,無提示自動提權的程序ComputerDefaults.exe,找到regsv32修復的的sdb tag(通過替換模塊鏡像為regsvr32來進行兼容修復),并將其設置到apphelp緩存中,這樣,在啟動ComputerDefaults.exe的時候,實際啟動的可執行程序的鏡像就被Apphelp替換成了regsvr32.exe ,而啟動的時候命令行上加上想要注入高權限進程的dll文件,就會使得regsvr32.exe在高完整性級別上加載我們想要加載的DLL,James的DLL代碼里實現的是啟動一個計算器程序。

  這個利用方法引來了一些爭議,很多人認為這樣只證明可以繞過UAC默認級別下中完整性級別到高完整性級別的彈框,通常來說不能說是安全漏洞,而且實際之前也有很多公開的技巧可以繞過UAC提示。

  但就如James自己說的,UAC的繞過僅僅只是個方便的演示方式,想一想測一測就能發現,這個漏洞還有更多的利用方式。

  以IE11 的沙箱(PM)為例,這個漏洞需要的相關功能(包括BITS Token的獲取、NtAppHelpCacheControl的調用、sdb Tag的獲取等等)都是可以在IE11沙箱的低完整性級別下執行的,那么惡意代碼一旦進入沙箱,就可以利用這個漏洞劫持一個常用的中完整性級別程序(甚至系統中經常被啟動的高完整性、系統完整性級別程序),就可能通過這個漏洞穿透沙箱,在沙箱外執行惡意代碼。使用icacls.exe /setintegritylevel 給測試程序設置Low完整性級別后,可以很容易證實這點。

  即使是在通過Users用戶組的賬戶登錄的情況下,如果同時或之后有管理員賬戶登錄,由于這個AppHelp Cache是全局跨Session的,因此管理員運行的程序也會可能受到劫持的影響,發生權限提升。

  這些都是通過James的測試程序就能完成的攻擊,但是這個攻擊的一個缺點是必須要有高權限的程序去啟動, 那么還有更好的利用方式么?

  肯定是有的,大家看到剛才我們介紹的AppHelpCacheFlush功能了吧,這個接口的調用者驗證也存在和 AppHelpCacheUpdate同樣的問題,那么通過這個接口就可以直接將內存中已經添加的AppHelpCache刷入注冊表中(HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerAppCompatCache下AppCompatCache),這樣下次開機時就會加載并應用添加的規則,這樣重新啟動后就可以更方便的劫持想要劫持的程序。

  同時,通過調用低完整性級別/低權限賬戶或AppContainer完整性級別下允許調用的某些服務LPC/COM接口,也可以使其啟動特定的高權限程序,并可能進行劫持。

  這些技術含量不高,已經提供了這么多Tips,再具體的方式就需要大家發揮想象力了,最后再提點細節:AppHelp規則不僅能應用于EXE進程創建,也可以應用在指定的任意模塊加載時。

  Windows7的問題

  James在說明里提到了Windows7的問題,在Windows7上, AppHelpCacheUpdate這條指令專門被一個特別的ApphelpCacheVerifyContext檢查所保護(對于其他被保護接口使用的是ApphelpCacheVerifyAdminContext,和Windows8/8.1一樣存在這個安全漏洞),這個檢查更嚴格,僅在主TOKEN具備TCB特權時才可以通過,代碼如下:

  NTSTATUS ApphelpCacheVerifyContext()

  {

  status = STATUS_SUCCESS;

  if ( PsGetCurrentThreadPreviousMode() && SeSinglePrivilegeCheck(SeTcbPrivilege, UserMode)  == FALSE)

  status = STATUS_ACCESS_DENIED;

  return status;

  }

  看上去這個檢查函數是無法繞過了,James同時提到,這個檢查是在CommandBuffer中的某些Flags匹配時,才會進行,也許可以繞過,那么實際如何呢?

  我們逆向NtAppHelpCacheControl在Win7上的相關實現可以得知,在CommandBuffer->Flags的Bit 2,3為1時,是不進行這個檢查的,這樣的Entry是可以通過AppHelpCacheUpdate的檢查,加入緩存中的。

  但是,我們再來看AppHelpCacheLookup的相關實現就不難發現, 僅當entry->Flags的bit 0為1時,Lookup才為調用者返回CommandBuffer->DataBuffer和DataBufferSize,而就像在第一節里提到的,這兩個域描述了APPHELP_LOOKUP_RESULT數據結構,沒有這個數據我們無法指定要應用的規則。

  所以至少通過目前的分析來看,通過修改Flags的方法,是無法將達成我們的劫持數據加入緩存并生效的目的的,暫時可以認為這個漏洞難以在Windows7上實現利用。

 

上一篇:智能無懼挑戰 山石網科轟動RSA2015

下一篇:移動互聯網野蠻發展導致網民隱私不在