dll 导出函数 下划线_内核中的代码完整性:深入分析ci.dll
前言
在某些場景中,如果我們希望在允許某個進程進行特定動作前,以一種可靠的方式確認(rèn)該進程是否可信,那么驗證該進程的Authenticode簽名是一個不錯的方式。用戶模式下的DLL wintrust提供了專門用于此目的的API。但是,如果我們需要在內(nèi)核模式下以一種可靠的方式來進行身份驗證,這時應(yīng)該如何進行呢?在以下的情況中,我們可能會遇到這樣的場景:1、應(yīng)用程序用戶模式部分不可用,可能是由于正處于開發(fā)過程的早期階段,也可能是由于運行失敗或配置出現(xiàn)問題。2、我們希望獲得對進程操作的內(nèi)聯(lián)訪問權(quán)限,以便在進程未驗證的情況下阻止它們。3、最典型的一種情況是Windows內(nèi)核在加載驅(qū)動程序時對驅(qū)動程序進行驗證,顯然這一過程必須要在內(nèi)核模式下完成。盡管在不少論壇上,都有人多次提問應(yīng)該如何操作,但我們還沒有在公開的地方找到解決該問題的任何實現(xiàn)。其中一些方案建議我們自行實現(xiàn),一些方案則建議將OpenSSL源導(dǎo)入到我們的項目中。而另外一種方案則將這個任務(wù)委托給用戶模式下的代碼。但是,上述所有替代方案都有明顯的缺點:1、在解析復(fù)雜的ASN1結(jié)構(gòu)時容易出現(xiàn)錯誤;2、不適合將大量源代碼導(dǎo)入驅(qū)動程序,因為OpenSSL中的每一個漏洞修復(fù)都會導(dǎo)致重新導(dǎo)入該代碼。3、進入用戶模式可能無效,并且用戶模式并非始終都可用。實際上,Microsoft內(nèi)核模式庫ci.dll中,就包含對文件進行身份驗證的功能。j00ru的研究表明,ntoskrnl通過CiInitialize()函數(shù)初始化CI模塊,該函數(shù)以回調(diào)列表填充函數(shù)指針結(jié)構(gòu)。如果我們可以使用這些函數(shù)或者其他CI導(dǎo)出來驗證正在運行的進程或文件的完整性和真實性,這將會成為內(nèi)核驅(qū)動程序的一個最佳方案。除了ntoskernel.exe之外,我們還發(fā)現(xiàn)了兩個驅(qū)動程序,它們都鏈接到ci.dll,并使用其導(dǎo)出文件:
鏈接到ci.dll的驅(qū)動程序
鏈接到ci.dll的驅(qū)動程序驅(qū)動程序可以鏈接到這個模塊,并且調(diào)用一些關(guān)鍵的函數(shù),例如CiValidateFileObject()。從函數(shù)名稱就可以看出,這樣的方式完全可以滿足我們的需求。在本文中,我們將通過一個代碼示例來詳細(xì)分析CI,可以以此作為進一步研究的基礎(chǔ)。
背景信息
我們建議各位讀者在詳細(xì)分析ci.dll之前,首先熟悉以下相關(guān)主題:1、PE安全目錄:PE中包含Authenticode簽名的部分;2、WIN_CERTIFICATE結(jié)構(gòu):Authenticode簽名之前的標(biāo)頭;3、PKCS 7 SignedData結(jié)構(gòu):Authenticode的基礎(chǔ)結(jié)構(gòu);4、X.509證書結(jié)構(gòu);5、證書時間戳:通過過期或吊銷證書來延長簽名使用周期的方法。
研究過程
在Windows 10上,CI會導(dǎo)出以下函數(shù):
CI導(dǎo)出功能如前所述,調(diào)用CiInitialize()將會返回一個名為g_CiCallbacks的結(jié)構(gòu),其中包含更多函數(shù)(詳情請參考[1][2][5])。而其中的一個函數(shù),CiValidateImageHeader(),將會被ntoskernel.exe用于加載驅(qū)動程序以驗證簽名的過程:
調(diào)用堆棧以在加載過程中驗證驅(qū)動程序簽名在我們的研究中,利用了導(dǎo)出的函數(shù)CiCheckSignedFile()以及與之交互的數(shù)據(jù)結(jié)構(gòu)。稍后我們將看到,這些數(shù)據(jù)結(jié)構(gòu)也出現(xiàn)在其他CI函數(shù)中,我們也可以將研究范圍擴展到這些其他的函數(shù)。
CiCheckSignedFile()
CiCheckSignedFile()可以接收8個參數(shù),但目前我們還不清楚這些參數(shù)的名稱是什么。但是,通過檢查內(nèi)部函數(shù),我們可以推斷出其參數(shù)。例如,我們可以檢查MinCryptGetHashAlgorithmFromWinCertificate():
檢查WIN_CERTIFICATE的結(jié)構(gòu)成員我們發(fā)現(xiàn),對于WIN_CERTIFICATE結(jié)構(gòu)來說,常量0x200和2是比較常見的值,該結(jié)構(gòu)為我們提供了第四個和第五個參數(shù)。我們可以通過類似的方式找到其余的輸入?yún)?shù)。而對于輸出參數(shù)來說,則方法完全不同,我們將在后文中詳細(xì)描述。進行一些逆向之后,我們得到了函數(shù)簽名:
NTSTATUS CiCheckSignedFile( __In__ const PVOID digestBuffer, __In__ int digestSize, __In__ int digestIdentifier, __In__ const LPWIN_CERTIFICATE winCert, __In__ int sizeOfSecurityDirectory, __Out__ PolicyInfo* policyInfoForSigner, __Out__ LARGE_INTEGER* signingTime, __Out__ PolicyInfo* policyInfoForTimestampingAuthority);該函數(shù)的工作方法如下:1、調(diào)用方位函數(shù)提供文件摘要(緩沖區(qū)和算法類型),以及指向Authenticode簽名的指針。2、該函數(shù)通過以下方式驗證簽名和摘要:(1)遍歷文件簽名,并獲取使用特定摘要算法的簽名;(2)驗證簽名(和證書),并提取其中顯示的文件摘要;(3)將提取的摘要與調(diào)用方提供的摘要進行比較。3、除了驗證文件簽名之外,該函數(shù)還為調(diào)用方提供有關(guān)已驗證簽名的各種詳細(xì)信息。該函數(shù)后面一部分的工作原理非常值得關(guān)注,因為僅僅知道文件已經(jīng)經(jīng)過正確簽名是不夠的,我們還需要知道是由誰進行簽名的。在下一節(jié)中,我們將解決這一問題。
PolicyInfo結(jié)構(gòu)
到目前為止,我們已經(jīng)將所有輸入?yún)?shù)輸入到CiCheckSignedFile()并且能夠進行調(diào)用。但是,我們除了其大小(在Windows 10 x64上為0x30)之外,對于PolicyInfo結(jié)構(gòu)幾乎一無所知。作為輸出參數(shù),我們希望該結(jié)構(gòu)能以某種方式提供有關(guān)簽名者身份的提示。因此,我們調(diào)用該函數(shù),并對內(nèi)存進行檢查,以確認(rèn)哪些數(shù)據(jù)填充到PolicyInfo之中。在內(nèi)存中,似乎包含一個地址和一些較大的數(shù)字。該結(jié)構(gòu)正在內(nèi)部函數(shù)MinCryptVerifyCertificateWithPolicy2()中填充:
填充PolicyInfo結(jié)構(gòu)該函數(shù)中的某些代碼似乎正在檢查該值是否在特定范圍之內(nèi)。對于證書驗證的過程來說,我們推測這個范圍是證書有效的時間范圍,事實上證明這是正確的:
檢查證書有效期這將引向以下結(jié)構(gòu):
typedef struct _PolicyInfo{int structSize; NTSTATUS verificationStatus; int flags; PVOID someBuffer; // later known as certChainInfo; FILETIME revocationTime; FILETIME notBeforeTime; FILETIME notAfterTime;} PolicyInfo, *pPolicyInfo;盡管證書的有效期非常值得關(guān)注,但是這并不能直接定位到簽名者。稍后我們將發(fā)現(xiàn),大多數(shù)信息都位于成員certChainInfo之中,我們將在稍后討論。
CertChainInfo緩沖區(qū)
在檢查PolicyInfo的內(nèi)存時,我們可以看到它指向結(jié)構(gòu)外部的內(nèi)存位置——動態(tài)分配的緩沖區(qū)。該分配位于I_MinCryptAddChainInfo()中,其函數(shù)名稱表明了緩沖區(qū)的用途。我們通過檢查其內(nèi)存布局來逆向這一緩沖區(qū):1、在前幾個字節(jié)中,有指向緩沖區(qū)內(nèi)部各個位置的指針。2、在這些指向的位置中,存在重復(fù)的模式和指向緩沖區(qū)內(nèi)部更遠(yuǎn)位置的指針。3、在最后指向的這些位置中,我們找到了一些文本,看起來像是證書的摘要。該緩沖區(qū)中包含有關(guān)整個證書鏈的數(shù)據(jù),既有解析格式(位于子結(jié)構(gòu)中),也有原始數(shù)據(jù)格式(包含證書、密鑰、EKU的ASN.1證書)。這一部分使調(diào)用方可以輕松地查看證書的主題、頒發(fā)者、證書鏈的組成,以及用于創(chuàng)建每個證書的哈希算法。為了更好地解釋這個緩沖區(qū)的格式,以及我們從中得到的子結(jié)構(gòu),我們將分析其在32位計算機上的內(nèi)存布局。如果使用32位計算機,可以減少混亂的情況,這里可以利用更少的填充字節(jié)來滿足對齊要求。下面是由Microsoft簽名的Notepad.exe的示例:
CertChainInfo緩沖區(qū)的內(nèi)存視圖我們在這里可以發(fā)現(xiàn):1、緩沖區(qū)的頂部有兩個4字節(jié)的數(shù)字。其中的一個表明在哪里可以找到一系列CertChainMember類型結(jié)構(gòu)的地址,另一個是可以指示其中有多少個結(jié)構(gòu)的計數(shù)器。2、第一個CertChainMember位于地址0x89BF45C8中(以黑色標(biāo)出),我們將其格式化如下:(1)在CertChainMembers的末尾,以藍(lán)色標(biāo)出的地址0x89BF4688處,有純文本格式的主題名稱。(2)在橙色標(biāo)出的地址0x89BF4699處,有純文本格式的發(fā)行者名稱。(3)在紅色箭頭指出的地址0x89BF46BE處,包含實際證書的ASN.1 blob的開頭。內(nèi)存以小端對齊的4字節(jié)為一組顯示,因此證書的前兩個字節(jié)實際上是0x3082,而不是如圖所示的0x3131。
typedef struct _CertChainMember{ int digestIdetifier; // e.g. 0x800c for SHA256 int digestSize; // e.g. 0x20 for SHA256 BYTE digestBuffer[64]; // contains the digest itself CertificatePartyName subjectName; // pointer to the subject name CertificatePartyName issuerName; // pointer to the issuer name Asn1BlobPtr certificate; // pointer to actual certificate in ASN.1} CertChainMember, * pCertChainMember;這就是我們之前所說的解析數(shù)據(jù)。我們無需自行解析證書,就可以獲取到主題或頒發(fā)者。該結(jié)構(gòu)中的最后一個字節(jié)指向緩沖區(qū)內(nèi)部更遠(yuǎn)的位置。接下來的96個字節(jié)包含第二個CertChainMember,出于可讀性的考慮,未將其標(biāo)出。其中包含有關(guān)鏈的下一個證書的信息。對于公鑰和EKU(擴展密鑰用法)來說,存在一系列類似的指針和結(jié)構(gòu)。換而言之,CI從證書中獲取了一些關(guān)鍵數(shù)據(jù),并且使其以子結(jié)構(gòu)的形式提供給調(diào)用方。但是,如果調(diào)用方還需要其他的一些內(nèi)容,那么其中還可能會包括未解析的原始數(shù)據(jù)。注意:PolicyInfo和CertChainInfo結(jié)構(gòu)都以結(jié)構(gòu)的大小開始。由于這些結(jié)構(gòu)是可以在OS版本之間實現(xiàn)擴展的,因此在嘗試訪問其他結(jié)構(gòu)成員之前,必須要檢查這里的大小。在存儲庫中的文件ci.h中,可以找到CertChainInfo緩沖區(qū)的完整分類和各種子結(jié)構(gòu)。
CiFreePolicyInfo()
該函數(shù)將釋放PolicyInfo的certChainInfo緩沖區(qū),該緩沖區(qū)由CiCheckSignedFile()和其他填充PolicyInfo結(jié)構(gòu)的CI函數(shù)分配。該函數(shù)還會重置其他結(jié)構(gòu)成員。在這里,必須要對其進行調(diào)用,以避免內(nèi)存泄漏。
CiFreePolicyInfo()的實現(xiàn)由于該函數(shù)會在內(nèi)部檢查是否有可用的內(nèi)存,因此即使是未填充PolicyInfo,也可以安全地對其進行調(diào)用。
CiValidateFileObject()
如前文所述,在調(diào)用CiCheckSignedFile()之前需要首先完成一些工作。調(diào)用方必須計算文件哈希值并解析PE,以便為函數(shù)提供簽名的位置。但是,函數(shù)CiValidateFileObject()可以為調(diào)用方完成這部分工作。我們不需要從頭開始,因為它與CiCheckSignedFile()共享一些參數(shù):
NTSTATUS CiValidateFileObject( __In__ struct _FILE_OBJECT* fileObject, __In__ int a2, __In__ int a3, __Out__ PolicyInfo* policyInfoForSigner, __Out__ PolicyInfo* policyInfoForTimestampingAuthority, __Out__ LARGE_INTEGER* signingTime, __Out__ BYTE* digestBuffer, __Out__ int* digestSize, __Out__int* digestIdentifier);該函數(shù)在內(nèi)核空間中映射文件,并提取其簽名:
通過CiValidateFileObject()在系統(tǒng)空間中映射文件。該函數(shù)還會計算文件摘要,如果為其提供了足夠長的非空緩沖區(qū),將會使用摘要來進行填充。注意:由于該函數(shù)僅在最新的Windows版本上添加,因此我們并未將研究的重點放在這個函數(shù)上。如果我們要繼續(xù)研究,我們會專注于分析其驗證的策略。在這里,使用了比CiCheckSignedFile()更為嚴(yán)格的策略,這意味著它有可能無法驗證通過此前經(jīng)過CiCheckSignedFile()驗證的PE。這里可能會受到第2個和第3個參數(shù)值的影響,但我們還沒有對其進行逆向。
GitHub Repo
為了演示如何利用ci.dll來驗證PE簽名,我們使用了GitHub存儲庫來對本文進行了補充。該存儲庫中,包含一個簡單的驅(qū)動程序,可以用于測試我們上述的研究成果:1、注冊用于新進程通知的回調(diào);2、嘗試使用ci.dll函數(shù)來驗證每個新進程的PE簽名;3、如果成功驗證了文件的簽名,驅(qū)動程序?qū)⒔馕鲚敵鯬olicyInfo結(jié)構(gòu),以提取簽名證書及其詳細(xì)信息。我們鼓勵大家嘗試使用這個repo,以初步了解CI,并擴大研究的范圍。
與CI鏈接
最后,我們要分析如何與這個未記錄的庫相鏈接的過程。盡管使用CI的過程看起來非??菰?#xff0c;但我們發(fā)現(xiàn)它并不簡單,如果大家對其中的更多函數(shù)進行擴展研究,可能需要執(zhí)行與本文相同的步驟。在與特定的dll鏈接時,通常使用廠商提供的導(dǎo)入庫。在我們的案例中,Microsoft并沒有提供.lib文件,我們必須自己生成該文件。在生成之后,該文件應(yīng)該作為鏈接器輸入添加到項目屬性中。下面是生成.lib文件所需的步驟。
64位
1、使用dumpbin實用程序從dll中獲取導(dǎo)出的函數(shù):
dumpbin /EXPORTS c:windowssystem32ci.dll2、創(chuàng)建一個.def文件,如下所示:LIBRARY ci.dllEXPORTSCiCheckSignedFileCiFreePolicyInfoCiValidateFileObject3、使用lib實用程序生成.lib文件:
lib /def:ci.def /machine:x64 /out:ci.lib32位
這里的情況比較棘手,因為在32位系統(tǒng)中,函數(shù)反射參數(shù)的總和(以字節(jié)為單位),例如:
CiFreePolicyInfo@4但是ci.dll會導(dǎo)出沒有這部分的函數(shù),因此我們需要創(chuàng)建一個.lib文件以進行這樣的轉(zhuǎn)換,所以我們使用了[3]和[4]文章中所描述的方法。1、如同64位中的第1步和第2步所述,創(chuàng)建一個.def文件。2、使用具有相同簽名的偽裝實體的函數(shù)stub創(chuàng)建一個C++文件。我們基本上可以模仿廠商從其代碼導(dǎo)出函數(shù)時的操作。例如:
extern "C" __declspec(dllexport) PVOID _stdcall CiFreePolicyInfo(PVOID policyInfoPtr){ return nullptr;}3、將其編譯成OBJ文件。4、使用lib實用工具生成.lib文件,這次使用OBJ文件:
Lib /def:ci.def /machine:x86 /out:ci.lib在GitHub存儲庫中,包含stub的代碼。
總結(jié)
本文演示了如何使用CI API中的一部分。我們通過這種方式,成功在內(nèi)核模式下驗證了Authenticode簽名,而無需再自行實現(xiàn)。我們希望本文能為大家對這個dll的后續(xù)研究鋪平道路。在這里,我想向?qū)Ρ酒恼绿峁椭膸孜谎芯咳藛T表示感謝,他們分別是Yuval Kovacs、Allie Mellen、Philip Tsukerman和Michael Maltsev。
參考文章
[1] Microsoft Windows FIPS 140 驗證安全策略文檔(https://csrc.nist.gov/csrc/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3093.pdf)[2] Windows驅(qū)動簽名繞過(作者:derusbi)(https://www.sekoia.fr/blog/windows-driver-signing-bypass-by-derusbi/)[3] 如何創(chuàng)建32位導(dǎo)入庫(https://qualapps.blogspot.com/2007/08/how-to-create-32-bit-import-libraries.html)[4] Q131313: 如何創(chuàng)建沒有.OBJ或源代碼的32位導(dǎo)入庫(https://jeffpar.github.io/kbarchive/kb/131/Q131313/)[5] j00ru關(guān)于CI的博客文章(https://j00ru.vexillium.org/2010/06/insight-into-the-driver-signature-enforcement/)
原文鏈接:https://www.anquanke.com/post/id/200478
總結(jié)
以上是生活随笔為你收集整理的dll 导出函数 下划线_内核中的代码完整性:深入分析ci.dll的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实现iframe_单点登录的三种实现方式
- 下一篇: 做diff_Virtual Dom am