INTENT2022--一道包含12個(gè)反調(diào)試反虛擬機(jī)操作的ctf題解
作者:selph
從一道Re題學(xué)習(xí)12種反調(diào)試反虛擬技術(shù)
題目:AntiDebuggingEmporium
來源:INTENT CTF 2022 Re
這個(gè)題目很有意思,里面出現(xiàn)了總共12個(gè)反調(diào)試反虛擬機(jī)的操作,本文內(nèi)容分兩部分,前部分是題解,后部分是這12個(gè)反調(diào)試反虛擬機(jī)手法分析
題解
程序邏輯分析
文件信息:是個(gè)64位的Windows控制臺(tái)程序,VS2022編譯的
主函數(shù):
可以看到,邏輯很簡單,
1.首先是調(diào)用一個(gè)函數(shù)等待一個(gè)對(duì)象執(zhí)行完成
2.然后提示輸入flag,
3.對(duì)一個(gè)數(shù)組的值進(jìn)行判斷,如果所有的值都是0,則處理輸入字符串輸出flag
這里去看看這個(gè)數(shù)組的值來自哪里:
通過交叉引用,發(fā)現(xiàn)這個(gè)數(shù)組在多個(gè)地方被賦值,基本上均在StartAddress這個(gè)函數(shù)里
經(jīng)分析,這里的這個(gè)數(shù)組保存的就是檢測(cè)虛擬機(jī)和調(diào)試器的情況,檢測(cè)到了則會(huì)有值被賦值為1:
這個(gè)函數(shù)首先從當(dāng)前文件的資源里讀取了二進(jìn)制數(shù)據(jù),保存起來,然后進(jìn)行了累計(jì)12個(gè)反虛擬機(jī)和反調(diào)試的函數(shù),這部分內(nèi)容我們?cè)诤笪倪M(jìn)行詳細(xì)分析
隨便點(diǎn)開一個(gè):
可以看到,這里對(duì)一個(gè)數(shù)組的某個(gè)位置進(jìn)行賦值了,這個(gè)賦值后面會(huì)用到
現(xiàn)在看一下StartAddress這個(gè)函數(shù)是在什么時(shí)候被調(diào)用的:
通過交叉引用可以看到,在TLS回調(diào)函數(shù)中調(diào)用,TLS回調(diào)函數(shù)會(huì)在主函數(shù)執(zhí)行前先執(zhí)行,這里的hHandle就是主函數(shù)里等待的對(duì)象
也就是說,這個(gè)程序會(huì)先進(jìn)行反調(diào)試反虛擬機(jī)的操作,然后檢測(cè)完之后,獲取用戶輸入,進(jìn)行處理
接下來看用戶輸入是被如何處理的:
這里首先是用資源二進(jìn)制數(shù)據(jù)和一個(gè)數(shù)組的對(duì)應(yīng)值進(jìn)行相加(這個(gè)數(shù)組的值就是反調(diào)試函數(shù)里檢測(cè)成功后賦值的)
然后進(jìn)行校驗(yàn),校驗(yàn)是通過異或進(jìn)行的,這里用到一個(gè)哈希值,這個(gè)哈希值是對(duì)資源數(shù)據(jù)進(jìn)行校驗(yàn)得到的,不去修改資源,則這里是固定值,所以可以直接動(dòng)態(tài)調(diào)試得到這個(gè)值,然后異或用戶輸入,結(jié)果是資源數(shù)據(jù)計(jì)算后的值
邏輯理清楚了,現(xiàn)在要做的事情就是:
1.拿到資源二進(jìn)制數(shù)據(jù)
2.拿到反調(diào)試數(shù)組
3.拿到哈希值
4.計(jì)算flag
拿到資源數(shù)據(jù)
直接從die中就能得到:
拿到反調(diào)試數(shù)組和哈希值
把程序在物理機(jī)上跑起來,讓程序卡在scanf里,這個(gè)時(shí)候調(diào)試器和虛擬機(jī)的檢測(cè)已經(jīng)結(jié)束了,反調(diào)試數(shù)組應(yīng)該也計(jì)算好了
這個(gè)時(shí)候直接調(diào)試器附加,查看數(shù)組的地址:14000DBE0
拿到64字節(jié)的數(shù)據(jù)
哈希值也是保存在全局變量里的,可以直接拿到地址000000014000DB88,去查看即可:
計(jì)算flag
該有的都有了,寫代碼生成flag即可:
// antidebuggingemporium.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。
//
#include?
#include?
#include?
unsigned?char?RescourceData[68]?=?{
????0x72,?0x6C,?0xCA,?0x03,?0x75,?0x76,?0xE5,?0x00,?0x00,?0x43,?0x00,?0x00,?0x55,?0x16,?0xEA,?0x77,
????0x0B,?0x4C,?0xC1,?0x77,?0x48,?0x7D,?0x00,?0x00,?0x00,?0x7D,?0x00,?0x00,?0x00,?0x5B,?0xC1,?0x31,
????0x08,?0x43,?0xEE,?0x76,?0x55,?0x7D,?0xAF,?0x28,?0x64,?0x15,?0xF6,?0x75,?0x64,?0x00,?0x00,?0x00,
????0x64,?0x00,?0x00,?0x00,?0x52,?0x4C,?0xED,?0x71,?0x64,?0x00,?0x00,?0x00,?0x00,?0x00,?0xEA,?0x3F,
????0x46,?0x22,?0x3F,?0x46
};
unsigned?char?antiDbgNum[68]?=?{
????0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x77,?0x56,?0x00,?0xF9,?0x77,?0x00,?0x00,?0x00,?0x00,
????0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0xA9,?0x2E,?0x08,?0x00,?0xAE,?0x28,?0x57,?0x00,?0x00,?0x00,
????0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x00,?0x55,?0xAA,?0x34,
????0x00,?0x16,?0xF9,?0x27,?0x00,?0x00,?0x00,?0x00,?0x00,?0x50,?0xAD,?0x27,?0x57,?0x13,?0x00,?0x00,
????0x00,?0x00,?0x00,?0x00
};
unsigned?char?flag[68]?=?{?0?};
unsigned?int?hashsum?=?0x469E223B;
int?main()
{
????for?(int?i?=?0;?i?<?68;?++i)
????{
??????? RescourceData[i]?+=?antiDbgNum[i];
????}
????for?(int?j?=?0;?j?<?68;?j?+=4)
????{
????????*(DWORD*)&flag[j]?=?(hashsum?^?*(DWORD*)&RescourceData[j]);// 00000000469E223B
????}
??? printf("%s",?flag);
}
// INTENT{1mag1n4t10n_1s_7h3_0nly_w3ap0n_1n_7h3_w4r_4gains7_r3al1ty}
反調(diào)試反虛擬機(jī)手法分析
這里依次分析那12個(gè)反調(diào)試反虛擬機(jī)的函數(shù)
0x0-反虛擬機(jī)-檢測(cè)CPU核心數(shù)
這里通過GetSystemInfo API獲取系統(tǒng)信息,這里判斷系統(tǒng)的處理器核心數(shù)量,現(xiàn)在的用戶電腦CPU核心都是4核往上的,一般只有在虛擬機(jī)里可能會(huì)遇到只分配了1核的情況
0x1-反調(diào)試器-檢測(cè)PEB標(biāo)志位1
看到這個(gè)+2偏移就很容易想到PEB里的BeingDebugged標(biāo)志位
不過這里是使用ZwQueryInformationProcess獲取PEB的:
0x2-反調(diào)試器-檢測(cè)PEB標(biāo)志位2
這個(gè)寫的更直接,這個(gè)NtCurrentPeb()本質(zhì)上就是從gs[0x60]處取值,得到的就是peb地址,這里檢測(cè)的依然是BeingDebugged標(biāo)志位
0x3-反調(diào)試器-檢測(cè)PEB標(biāo)志位3
這里使用了PEB的另一個(gè)標(biāo)志位NtGlobalFlag,位置是偏移0xBC的地方,這里IDA的F5顯示有問題,在反匯編里可以到是:add???? rax, 0BCh
0x4-反調(diào)試器-檢測(cè)PEB標(biāo)志位4
通過API的方式檢測(cè)PEB偏移2位置的BeingDebugged的值
0x5-反虛擬機(jī)-cpuid 1
cpuid指令,通過rax傳遞功能號(hào),將返回值保存在eax,ebx,ecx,edx里
當(dāng)功能號(hào)是1的時(shí)候,ecx的最高位表示當(dāng)前是否在虛擬機(jī)里
0x6-反虛擬機(jī)-cpuid 0x40000000
當(dāng)功能號(hào)是0x40000000時(shí),rbx rcx rdx里返回的是一個(gè)cpu名稱
然后接下來檢測(cè)是否是常見的虛擬機(jī)的cpu名稱
0x7-反虛擬機(jī)-cpuid 0
功能號(hào)是0時(shí)候,是另一種顯示cpu相關(guān)信息的方法,依然是檢測(cè)是否出現(xiàn)虛擬機(jī)常見字符
0x8-反調(diào)試器-rdtsc
通過rdtsc指令獲取時(shí)間,當(dāng)兩次獲取時(shí)間間隔過大,可以認(rèn)為有調(diào)試器干擾了程序的正常執(zhí)行
0x9-反調(diào)試器-窗口檢測(cè)
檢測(cè)是否存在調(diào)試器的窗口,如果存在,則認(rèn)為有調(diào)試行為
0xA-反調(diào)試器-異常處理
這里使用SetUnhandledExceptionFilter API設(shè)置無法處理的異常的處理函數(shù)
通常情況下,當(dāng)異常無法處理的時(shí)候會(huì)進(jìn)入該函數(shù)去處理,但是有調(diào)試器存在,則會(huì)直接由調(diào)試器接管,不進(jìn)入該函數(shù)
處理的內(nèi)容是:
效果是跳過某些指令往下執(zhí)行:
這里跳過了這個(gè)jmp,以至于下面的0x57能正常賦值到數(shù)組里
0xB-反虛擬機(jī)-設(shè)備檢測(cè)
這里通過API:SetupDiGetClassDevsExW 獲取設(shè)備集
然后通過API:SetupDiEnumDeviceInfo 枚舉設(shè)備
使用API:SetupDiGetDeviceRegistryPropertyW 對(duì)每一個(gè)設(shè)備獲取其屬性
對(duì)獲取到的信息,去判斷是否包含這幾個(gè)虛擬機(jī)相關(guān)的特征字符串,來判斷是否位于虛擬機(jī)內(nèi)部