iT邦幫忙

2022 iThome 鐵人賽

DAY 29
0
Security

我逆向你逆向我的逆向工程膩系列 第 29

Dx29 - 動態反逆向技術

  • 分享至 

  • xImage
  •  

與靜態反逆向技術相比,動態的更加難以反制。一直阻擋 Debugger 的行動與各種花樣百出的技術是分析時的噩夢。它的目標是隱藏和保護主要的代碼,通常被反逆向技術偵測到後,干擾行為也會開始干擾反編譯器

這邊來講講一些常見的動態反逆向技術

此次的程式與原始碼都放在 :
https://github.com/Dinlon5566/IT_Reverse_Engineering/tree/main/Dx29


Timing Check

很簡單直白的反逆向方法,主要就是檢查某段代碼的執行時間來判斷。若在其中有出現反編譯行為,就會使指令執行的時間比正常執行時間長多了。

https://ithelp.ithome.com.tw/upload/images/20221013/20135675pnosJRICda.png

clock( )

首先這是最簡單的演示。透過 clock( ) 來取得自 CRT 初始化以來所消耗的時間。

那程式的話就用 loop 來代替一般的程式。

#include <iostream>
#include <Windows.h>
#include <time.h>
int main()
{
	clock_t start, finish;

	printf("Clock start\n");
	start = clock();
	
	for (int i = 0; i < INT_MAX; i++) {}

	printf("Clock Stop\n");
	finish = clock();

	printf("%lf\n",double (finish - start)/CLOCKS_PER_SEC);
	MessageBox(0,L"",L"",0);
}

先嘗試一般執行看看 :

https://ithelp.ithome.com.tw/upload/images/20221013/20135675PRq9fvVloS.png

花了大概三秒多。

此時利用 x64dbg 來執行,在 loop 段稍微暫停一下後再繼續 :

https://ithelp.ithome.com.tw/upload/images/20221013/201356758DY349O9Ul.png

可以看到比一般執行長多了。

但是這樣的計時太不精準,於是通常會使用下面這個方法。

RDTSC

clock( ) 相比,RDTSC 直接用了 CPU 對於每個 Clock Cycle 進行記數,這樣可以計算出從計時點A 到計時點B 的每一個 tick。可以利用組語的 RDTSC 指令來取得 TSC ( Time Stamp Counter ) 中的時間。

這邊解釋一下 RDTSC 指令 : 時間計數器在64位元的 MSR 中,這個指令會把高 32 位元存在 EDX,低 32 位元放在 EAX

除了使用組合語言指令,也有 C 上的 __rdtsc 可以使用。
https://learn.microsoft.com/zh-tw/cpp/intrinsics/rdtsc?view=msvc-170

這邊展示用 __rdtsc( ) 與組合語言的用法,這兩個 return 的東西是一樣的。

UINT64 rdtsc() {
	UINT64 res;
	res = __rdtsc();
	return res;
}

UINT64 AsmRdtsc() {

	UINT64 res;
	UINT32 highByte,lowByte;
	
	__asm {
		RDTSC
		mov highByte,edx
		mov lowByte, eax
	}

	res = highByte;
	res <<= 0x20;
	res |= lowByte;
	return res;
}

這邊做一個直接取用 __rdtsc() 來計時的偵測器。

int main() {

	UINT64 T1 = __rdtsc();
	for (int i = 0; i < 50; i++) {}
	UINT64 T2 = __rdtsc();

	UINT64 delta = T2 - T1;
	printf("Time = %lld\n",delta);
	if (delta > 0xFFFF) {
		printf("OUT!\n");
		system("pause");
		return 0;
	}
	printf("SAVE~ : )\n");
	system("pause");
	return 0;
}

來看三種情況 :

  • 直接執行

https://ithelp.ithome.com.tw/upload/images/20221013/20135675pXNePU0OMr.png

  • 在 Visual Code 執行 ( 不設 BP )

https://ithelp.ithome.com.tw/upload/images/20221013/20135675JdnRpwVug2.png

  • 在 x32bdg 用 ctrl + F8 自動逐步執行 ( 實際上約0.6秒 )

https://ithelp.ithome.com.tw/upload/images/20221013/20135675MDVVvyg5JC.png

可以看到在不同情況下時間會在不同的區間,可以依據區間來判斷該程式是否被逆向分析中。

而這類破解的方法有 :

  • 直接 run 過,讓計時器判斷區間是正常執行的。
  • 操作時間比較的值,或是把比較結果改成正常結果。
  • 利用 kernel 驅動程式直接廢掉 RDTSC

雖然要查出並破解這份範例程式的難度不難,但實際上這個技術會穿插在程式的各處,導致判斷起來非常困難。


Excption ( 例外 )

一般的程序若是出現了異常的時候,會透過 SEH 的機制使 OS 接收到例外中斷,然後調用終止處理常式處裡。但是程序若運行在 Deubgger 下,例外將會傳給 Debugger 的 例外狀況處理常式( 類似用 try 包住整份程式 );透過這個特徵可以用來判斷程序是正常運行還是在反編譯環境。

SEH ( 結構例外狀況處裡 ) 說明手冊
https://learn.microsoft.com/zh-tw/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-170
利用 GetExceptionCode 取的的例外狀況代碼
https://learn.microsoft.com/zh-tw/windows/win32/debug/getexceptioncode

EXECEPTION_BREAKPOINT

EXECEPTION_BREAKPOINT ( 中斷點例外 ) 是在我們進行 Debugger 中很常使用的功能,當我們按下反編譯器旁邊的紅點,或是在 x64bdg 上停在某一個點的時候都會把到這樣的例外。

這邊演示若在程式碼中插入 0xCC 這個之前在 API Hook 中使用的 INT3 斷點,反編譯器將會停止在該位置。

	printf("STOP\n");
	__asm {
		int 0x03
	}
	printf("Continue\n");

在 x32dbg 按下 F9,就算沒有設定 BP 也會停在 0x40104E 位置。而正常開啟的話則會直接退出不顯示"Continue"。

https://ithelp.ithome.com.tw/upload/images/20221013/20135675D8fMAGM4S4.png

那,要甚麼透過這個方法來判斷是否有 Debugger 跑過呢 ? 其實很簡單,在異常前後設置可以接收例外的指令,當例外觸發後的例外接收器接有接收到的例外則是正常狀況;若是例外接收器沒有接到例外就可以判斷是運行在 Debugger ( 畢竟正常狀況下例外不會自己解決 )。

https://ithelp.ithome.com.tw/upload/images/20221013/20135675kJ8fN6W4bA.png

在 C++ 中可以利用 tryexceptfinally 關鍵字來接收例外,而組合語言上可以利用這幾行來設置

PUSH Handler // 設定的處裡位置
PUSH DWORD PTR FS:[0]
MOV DWORD PTR FS:[0],ESP

並且需要在程序中止前刪除掉 SEH

POP DWORD PTR FS:[0]
ADD ESP, 4

那來試試看應用在 C++ 上面吧 ,重點看在 __asm 中的處裡方式吧

#include <iostream>
#include "windows.h"

void debugerFound() {
	MessageBox(0, L"Debuger Found", L"Excption_SEH", 0);
}

int main()
{
	printf("Start");

	__asm {
		// 設置 SEH
		push handler
		push DWORD PTR fs : [0]
		mov DWORD PTR fs:[0], esp

		// 觸發 INT3,應該要直接跳到 handler 來處理異常
		int 0x03

		// 若是異常被處裡就會跑到這裡
		call debugerFound
		mov eax,0xFFFFFFFF // 直接讓你非法存取
		jmp eax

	handler:
		// 處裡異常
		mov eax, dword ptr ss : [esp + 0xc]
		mov ebx, normal_code
		mov dword ptr ds : [eax + 0xb8] , ebx
		xor eax, eax
		retn

	normal_code :
		// 拔除 SEH
		pop dword ptr fs : [0]
		add esp, 4

	}

	MessageBox(0, L"Save", L"Excption_SEH", 0);
}

利用 ollyDbg ( 因為 x64dbg 會自動把例外接收後又把例外放回去 ) 來執行這份程式,會發現中斷點被觸發後被跳到了非法存取 ( EIP = 0xFFFFFFFF )。

https://ithelp.ithome.com.tw/upload/images/20221013/20135675xgL8vm2yNU.png


今天就到這邊,明天帶給大家關於陷阱標誌的反逆向技術。


上一篇
Dx28 - 靜態反逆向技術
下一篇
Dx30 - 更多的 SEH 反逆向技術
系列文
我逆向你逆向我的逆向工程膩31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言