Windows 反除錯技術——OpenProcess 許可權過濾

Editor發表於2018-04-11

本週我有了休息時間,來回顧一下反除錯技術。目前,Bug Bounty平臺上有大量程式依賴於客戶端應用,而且許多安全產品和遊戲反作弊引擎都採用了這些反除錯技術來阻止你除錯核心模組。我想有必要來分享其中一項反除錯技術,以及如何繞過它。 

本文所述的技術並不是一個安全漏洞,很明顯,如果攻擊者擁有了這個級別的系統訪問許可權,遊戲就已經結束了。他們只需要安裝一個 rookit 就夠了。 

文中我將以 AVG 產品為例。儘管我儘量避免過多地討論這一款產品,然而其他的反病毒解決方案和安全產品使用了完全相同的技術,所以相同的原則也同樣適用這些產品。

面臨什麼問題?


如果你以前嘗試過開啟 x64dbg,並把它附加到一個 AV(譯者注:AntiVirus) 元件中,通常會看到如下介面:(下圖是GIF動圖1)


Windows 反除錯技術——OpenProcess 許可權過濾


偵錯程式基本沒有附加成功,並停在了啟動頁。如果我們在偵錯程式內不採用附加的方式,而是直接啟動剛才的程式:(下圖是GIF動圖2)


Windows 反除錯技術——OpenProcess 許可權過濾


還是不行,出現了相同的結果。當程式剛要啟動時,除錯程式被踢出了。最後,我們試試 WinDBG,得到了下面的錯誤資訊:


Windows 反除錯技術——OpenProcess 許可權過濾


為了理解偵錯程式剛才做了什麼,同時發現哪裡出了問題,我們看一下 x64dbg 的原始碼(實際上,是 x64dbg 使用的除錯引擎 TitanEngine 的原始碼)。


__declspec(dllexport) bool TITCALL AttachDebugger(DWORD ProcessId, bool KillOnExit, LPVOID DebugInfo, LPVOID CallBack)

{

...

if(ProcessId != NULL && dbgProcessInformation.hProcess == NULL)

{

if(engineEnableDebugPrivilege)

{

EngineSetDebugPrivilege(GetCurrentProcess(), true);

DebugRemoveDebugPrivilege = true;

}

if(DebugActiveProcess(ProcessId))

{

...

}

}

}


從程式碼中發現,x64dbg 使用了一個 KernelBase.dll 提供的 Win32 函式 “DebugActiveProcess”。


DebugActiveProcess 的工作原理


DebugActiveProcess 函式用於在目標程式上開啟一個除錯會話。該函式的唯一引數是目標程式的PID。如果在 MSDN 上查閱該函式,可以看到如下的描述:

“The debugger must have appropriate access to the target process, and it must be able to open the process for PROCESS_ALL_ACCESS. 

DebugActiveProcess can fail if the target process is created with a security descriptor that grants the debugger anything less than full access. 

If the debugging process has the SE_DEBUG_NAME privilege granted and enabled, it can debug any process.” 

這裡,我們發現了導致除錯會話失敗的端倪。

上述的程式碼片段中,偵錯程式呼叫了 EngineSetDebugPrivilege 函式。那麼,來看看這個函式。


DWORD EngineSetDebugPrivilege(HANDLE hProcess, bool bEnablePrivilege)

{

DWORD dwLastError;

HANDLE hToken = 0;

if(!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))

{

...

}

...

if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))

{

...

}

tokenPrivileges.PrivilegeCount = 1;

tokenPrivileges.Privileges[0].Luid = luid;

if(bEnablePrivilege)

tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

else

tokenPrivileges.Privileges[0].Attributes = 0;

AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);

...

}


從上述程式碼中可以看到,SE_DEBUG_NAME 許可權已經設定到程式令牌(process token)上。

這意味著,呼叫 DebugActiveProcess 函式的要求已經滿足(譯者注:要求指的是MSDN中描述的 SE_DEBUG_NAME許可權要求)。

接著檢查,是否擁有對於目標程式的 PROCESS_ALL_ACCESS 許可權。


深入 DebugActiveProcess 內部


DebugActiveProcess 接受唯一的引數是“程式ID”。在該函式內部,使用程式ID 呼叫 ProcessIdToHandle,開啟目標程式的控制程式碼:

Windows 反除錯技術——OpenProcess 許可權過濾


進入 ProcessIdToHandle 函式內部,可以發現該函式僅僅是對 NtOpenProcess 的封裝:

Windows 反除錯技術——OpenProcess 許可權過濾

NftOpenProcess函式中有一個形參叫做“Desired Access”,即所需的訪問權。該引數的實參是 C3Ah。通過微軟官方文件發現,這個值是以下值的組合: 

PROCESS_CREATE_THREAD (譯者注:0x0002) 

PROCESS_VM_OPERATION (0x0008) 

PROCESS_VM_WRITE (0x0020) 

PROCESS_VM_READ (0x0010) 

PROCESS_SUSPEND_RESUME (0x0800) 

PROCESS_QUERY_INFORMATION (0x0400) 

於是,這次呼叫具備了除錯程式所需要的全部授權。 

到這裡,偵錯程式具備了 SE_DEBUG_NAME 授權,DebugActiveProcess 呼叫也給自身賦予了正確的訪問目標程式的許可權。

那麼是什麼阻止了附加過程呢?


ObRegisterCallbacks 簡介


我是在一個遊戲模組社群(譯者注:即遊戲mod社群)中第一次知道 ObRegisterCallbacks 函式的。在繞過反作弊和 DRM 驅動時,該函式被用於阻止修改或注入遊戲功能。 

按照微軟官方說法,ObRegisterCallbacks 是“這樣一個函式,它為執行緒、程式、桌面控制程式碼操作註冊了一系列回撥函式。”這是在作業系統核心態完成的。主要是給驅動程式開發者提供一種能力,用於在 OpenProcess 函式被呼叫時和返回時收到通知。 

 為什麼這個函式能夠用於阻止偵錯程式訪問 AV 程式呢?阻止 DebugActiveProcess 呼叫成功的其中一個方法就是,過濾掉 “呼叫NtOpenProcess“所需要的訪問許可權(譯者注:NtOpenProcess 函式有一個形參 DesiredAccess,這裡指的是,該引數對應的實參被過濾後,就不是所需要的值了)。通過移除偵錯程式“請求目標程式的 PROCESS_ALL_ACCESS 訪問權”的能力,我們就無法除錯一個程式。這也解釋了剛剛在 WinDBG看到的錯誤。

怎麼確認這就是問題所在呢?我們接著進入核心偵錯程式,觀察註冊的回撥函式是如何在 Ring-0 被處理的。(這裡不會詳細介紹如何使用核心偵錯程式,如果你需要一些資料,可以閱讀我之前的部落格)


深入 ObRegisterCallback 內部


當啟動核心除錯後,從 nt!ProcessType 開始分析:


kd> dt nt!_OBJECT_TYPE poi(nt!PsProcessType)

+0x000 TypeList         : _LIST_ENTRY [ 0xffffcb82`dee6cf20 - 0xffffcb82`dee6cf20 ]

+0x010 Name             : _UNICODE_STRING "Process"

+0x020 DefaultObject    : (null) 

+0x028 Index            : 0x7 ''

+0x02c TotalNumberOfObjects : 0x26

+0x030 TotalNumberOfHandles : 0xe8

+0x034 HighWaterNumberOfObjects : 0x26

+0x038 HighWaterNumberOfHandles : 0xea

+0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER

+0x0b8 TypeLock         : _EX_PUSH_LOCK

+0x0c0 Key              : 0x636f7250

+0x0c8 CallbackList     : _LIST_ENTRY [ 0xffffa002`d31bacd0 - 0xffffa002`d35d2450 ]


這個符號包含了一個指向 _OBJECT_TYPE 型別物件的指標,該物件定義了 “Process” 型別,幷包含了一個CallbackList屬性。

這個屬性值得我們注意。該屬性定義了一個回撥函式列表,其中儲存了由 ObRegisterCallbacks 註冊的函式。

之後,其中的每個函式都會在獲取程式控制程式碼時由核心呼叫。基於這個理解,我們將遍歷這個列表,找到阻止成功呼叫 OpenProcess 函式的回撥函式控制程式碼。


CallbackList 是一個 _LIST_ENTRY,指向 CALLBACK_ENTRY_ITEM 結構體。該結構體在微軟的文件中沒有說明,然而有一篇文章 “DOUGGEM’S GAME HACKING AND REVERSING NOTES” 給出了結構體的定義:


typedef struct _CALLBACK_ENTRY_ITEM {

LIST_ENTRY EntryItemList;

OB_OPERATION Operations;

CALLBACK_ENTRY* CallbackEntry;

POBJECT_TYPE ObjectType;

POB_PRE_OPERATION_CALLBACK PreOperation;

POB_POST_OPERATION_CALLBACK PostOperation;

__int64 unk;

}CALLBACK_ENTRY_ITEM, *PCALLBACK_ENTRY_ITEM;


結構體中的 PreOperation 引起了我們的注意。 


通過如下 WinDBG 命令,遍歷 CALLBACK_ENTRY_ITEM 列表:


!list -x ".if (poi(@$extret+0x28) != 0) { u poi(@$extret+0x28); }" (poi(nt!PsProcessType)+0xc8)


Windows 反除錯技術——OpenProcess 許可權過濾

在我的電腦上,有 4 個驅動程式通過 ObRegisterCallbacks 註冊了 PreOperation 回撥函式。


接著,我們通過 WinDBG 輸出驅動程式的名字:

!list -x ".if (poi(@$extret+0x28) != 0) { lmv a (poi(@$extret+0x28)) }" (poi(nt!PsProcessType)+0xc8)


Windows 反除錯技術——OpenProcess 許可權過濾


這 4 個驅動程式中,其中一個立刻引起了我們關注,很可能它就是問題的關鍵:avgSP.sys。


Windows 反除錯技術——OpenProcess 許可權過濾


可以判斷出:就是 “AVG self protection module” 模組在阻止我們將偵錯程式附加到程式中(更有可能的是,當反病毒引擎阻止惡意軟體時,產生了這樣的副作用)。接著,我們深入分析下這個驅動程式,找出其影響 OpenProcess 呼叫的痕跡。

首先,找到 ObRegisterCallbacks 函式,它註冊了一個函式控制程式碼:

Windows 反除錯技術——OpenProcess 許可權過濾


我們如果檢查這個剛註冊的函式控制程式碼,可以發現:


Windows 反除錯技術——OpenProcess 許可權過濾

在反彙編程式碼中,出現了一個幻數(Magic Number)A0121410。實際上,它表示以下許可權:

PROCESS_VM_READ (譯者注:0x0010)

PROCESS_QUERY_INFORMATION (0x0400)

PROCESS_QUERY_LIMITED_INFORMATION (0x1000)

READ_CONTROL (0x00020000L)

SYNCHRONIZE (0x00100000L)

其實,如果只設定這些許可權的話,則沒有進一步的許可權檢查操作,OpenProcess 函式繼續執行。然而,如果請求上述許可權白名單以外的許可權,還要執行一系列的檢查操作,最終在函式返回前,所需要的許可權被過濾掉。


Windows 反除錯技術——OpenProcess 許可權過濾


由於本文主要講解“識別和移除”這種鉤子的通用方法,所以我不打算深入驅動程式的細節了。


從上面的分析可知,我們發現有一個驅動程式在攔截和修改 OpenProcess 呼叫。

現在,已經找到問題根源,接下來就是從核心中拆下這個鉤子。


移除 OpenProcess 許可權過濾


為了移除 OpenProcess 的許可權過濾函式,首先需要找到過濾函式所在的 PreOperation 屬性的地址。輸入 WinDBG 命令:


!list -x ".if (poi(@$extret+0x28) != 0) { .echo handler at; ?? @$extret+0x28; u poi(@$extret+0x28); }" (poi(nt!PsProcessType)+0xc8)


Windows 反除錯技術——OpenProcess 許可權過濾

一旦發現了正確的屬性地址,我們使用下面的命令將其置為 NULL,以此來禁止回撥控制程式碼:


eq 0xffffa002`d31bacf8 0


此時,再次將偵錯程式附加到被除錯程式,可以得到如下介面:

Windows 反除錯技術——OpenProcess 許可權過濾


太棒了!看上去我們已經成功了。


嗯,幾乎是……我們稍加操作就可以發現大量錯誤,問題還沒有處理乾淨。

即使在上述介面,我們也可以看到暫存器的值都是0,並且出現了訪問衝突。這一定是漏掉了什麼。


記住還有執行緒


我們已經知道 ObRegisterCallbacks 函式可以給 OpenProcess 加上鉤子函式,還能做什麼呢?再次檢視官方文件發現,ObRegisterCallbacks 也可以給 OpenThread 加上鉤子。

Windows 反除錯技術——OpenProcess 許可權過濾

慶幸的是,很多工作已經完成了,我們只需要找到執行緒的鉤子函式所在的位置即可。這個位置恰好定義在 nt!PsThreadType 中。

修改一下剛才輸入的命令,觀察驅動程式(譯者注:指的是 avgSP.sys)是否為 OpenThread 函式新增了鉤子:

1

!list -x ".if (poi(@$extret+0x28) != 0) { .echo handler at; ?? @$extret+0x28; u poi(@$extret+0x28); }" (poi(nt!PsThreadType)+0xc8)

Windows 反除錯技術——OpenProcess 許可權過濾

真的有鉤子!和剛才的程式鉤子類似,我們使用 eq 命令移除鉤子:


eq 0xffffc581`89df32e8 0


再次附加偵錯程式到程式:(下圖是GIF動圖3)


Windows 反除錯技術——OpenProcess 許可權過濾

大功告成!可以開始正常除錯了。


希望本文有助於你瞭解這項反除錯技術。如果感興趣,還有很多 Bug Bounty 程式可供學習,包括 BugCrowd 平臺上 AVG 的一個例子(點選這裡)、Cylance、Sophos等等。(儘管我沒有把這些作為安全漏洞,但是 DKOM 不在討論範圍)(譯者注:DKOM,全稱是 Direct kernel object manipulation)。


參考資料

TitanEngine

DOUGGEM'S GAME HACKING AND REVERSING NOTES

AVG Bug Bounty

x64Dbg GitHub



原文:https://blog.xpnsec.com/anti-debug-openprocess/
本文由 看雪翻譯小組 yezhang 編譯 

相關文章