保護機制小窺
【聲明】
我寫文章以交流為主,希望大家在轉載時能保持文章的完整性。
【前言】
這次以介紹保護機製為主,就不寫什麼脫殼方法了,其實以前我也誤導了不少善良的觀眾。瞭解一個加殼軟體最主要的應該是瞭解其保護機制,這也是我後來才領悟的一點。以下介紹的一點保護機制的知識。這裡只為了讓大家和我一起來共同學習和瞭解保護機制的知識。
水貨作者: ljttt
出廠日期: 2001-01-01 (水貨的東東一般都這樣寫出廠日期的)
①、首先,我們來看個比較簡單的自身保護方法。這段代碼如下:
0040CC54: 03F3 add esi,ebx <--esi指向某段代碼,ecx為代碼的長度(DWORD)
0040CC56: 8B06 mov eax,dword ptr [esi] <--取出代碼中的 4 個位元組
0040CC58: 33D0 xor edx,eax <--XOR異或
0040CC5A: F7D2 not edx <--取反
0040CC5C: 33D1 xor edx,ecx <--與ecx(代碼長度)異或
0040CC5E: 03D0 add edx,eax <--與代碼中的 4 個位元組相加
0040CC60: 83C604 add esi,00000004 <--定位到代碼中的下 4 個位元組
0040CC63: 49 dec ecx <--代碼長度減一
0040CC64: 75F0 jnz 0040CC56 <--迴圈
先來描述一下這段代碼的功能:可以看到這段代碼用 ESI 作為指標,用 ECX 儲存長度。把 ESI 指向的資料進行異或取反,最後在 EDX 中得到一個“值”。
那麼它和自身保護有什麼聯絡呢?我來簡單說一下,如果 ESI = 0040CC54, ECX = 0040CC64 - 0040CC54。那麼這段代碼是不是就是把自身的代碼進行運算(異或取反)呢? OK !
那麼如果你這段代碼中間設下一個斷點,會有什麼變化呢?運算得到的“值”會和原來未設斷時的相同嗎?
我們以SoftICE來簡單介紹一下吧。當你在SoftICE的調試環境下,設下一個斷點時,那麼這個斷點所在的代碼的第一個位元組就會變成 0xCC。
這樣由於設下斷點,代碼就被改變了,那麼運算的“值”就和未設斷時不一樣了。你可能想到有了這個值,就可以用比較的方法來判斷是否自身是否被設下斷點了。不錯,這是一種方法,但是這很容易被......在加殼軟體中,一般會把這個“值”作為還原密匙,來還原下一段代碼。
也就是所謂的 SMC 技巧來還原代碼。
(但是如果你在SoftICE中用 D 指令來顯示這行代碼,卻沒有發現變化。呵呵,但是,如果你再啟動另一個調試器TRW2000,來顯示這行代碼,再看看?呵呵,明白了嗎?)
②、SMC技巧來還原代碼。
在加殼軟體中,分段還原代碼是一種很普遍的方法了。也許你有過這樣的經曆。(怎麼,弄得象散文了?!)你跟蹤過某個軟體,發現了某段代碼,比如 CPUID (相應的十六進位代碼為 0x0F 0xA2)。卻發現當你用十六進位編輯軟體來尋找 0x0F 0xA2 時,卻怎麼也找不到,Why?
其實道理很簡單,就是在程式的可執行檔中儲存的是加密了的資料,只有程式在運行時才會由程式在某處由一段還原代碼來解密這段加密資料,(還原後的資料就是你在調試器中可以“看”到的真實的代碼)。然後,程式才執行這段還原後的代碼。現在你清楚了吧!比如象 ① 中 介紹的那段代碼是用來形成還原密匙的,只有密匙正確時,也就是說它先四下“打量”一下,當“發現”自身沒有被修改(或者被設下斷點)時,才“悄悄地”還原出另一段被加密了的代碼。然後繼續!
有點意思吧!在加殼軟體中,如果它“想”保護某段代碼,就會加密它,然後在程式運行時用另一段代碼形成密匙來還原它!這樣就防止你靜態分析它,當然為了防止你用動態跟蹤的方法,所以它還結合了 ① 中介紹的自身保護方法。這樣,如果你誤入陷阱(在其自身保護的某處設下了一個斷點),那麼還原出的代碼就是一堆垃圾代碼。如果這樣,你可不要寫信給作者,說他程式編得有問題哦?!
(當然 SMC 技巧可以被用作多種用途。不要大家有先入為主的概念,SMC的技巧既可以被加殼軟體用作自身保護的一種方法,也可以被Cracker作為一種破解的方法,所以......怎麼用,還在於你!......你想用來編“病毒”.....老天,為什麼天才總有點反叛因子!)
前面介紹了簡單的自身保護技巧,也許你早就想好了對付它的方法,比如:
不設任何斷點,用單步跟蹤的方法
或者先跟蹤一次記下正確的密匙,下次跟蹤時任意設斷,只在它取密匙時“給”個正確的給它。
或者用 BPM 斷點只跟蹤資料、不跟蹤代碼的方法進行動態跟蹤。
你還可以想得更多。。。。
當然,為了更好地保護自身,所以在加殼軟體中也不會只是這麼簡單地保護自己,你猜加殼軟體中又會用到什麼保護方法可以防止以上方法呢?
③、一種反跟蹤方法:API調用的變形。
你也許喜歡首先在某個關鍵的API函數處設斷,然後才進入程式碼中進行跟蹤。但是在加殼軟體中這種方法可就要小心了。
我們來看看這段代碼:
015F:00411B6A 33C0 XOR EAX,EAX <--ESI指向API函數地址的入口,如CreateFileA( )
015F:00411B6C AC LODSB <--擷取一個位元組
015F:00411B6D 3C50 CMP AL,50 <--判斷是否為 50
015F:00411B6F 720F JB 00411B80 <--小於 50 ,則跳轉到 00411B80
015F:00411B71 3C57 CMP AL,57 <--判斷是否為 57
015F:00411B73 770B JA 00411B80 <--大於 57 ,則跳轉到 00411B80
......
015F:00411BD3 897A01 MOV [EDX+01],EDI
015F:00411BD6 8B831F7B0000 MOV EAX,[EBX+00007B1F]
015F:00411BDC 8B8B237B0000 MOV ECX,[EBX+00007B23]
015F:00411BE2 8B93277B0000 MOV EDX,[EBX+00007B27]
015F:00411BE8 8BBB3B7B0000 MOV EDI,[EBX+00007B3B]
015F:00411BEE 8BB3377B0000 MOV ESI,[EBX+00007B37]
015F:00411BF4 8BAB337B0000 MOV EBP,[EBX+00007B33]
015F:00411BFA 8B9B2B7B0000 MOV EBX,[EBX+00007B2B]
015F:00411C00 E900000000 JMP 00411C05 <--此處的跳轉將被修改為API函數地址內的一條指令處
(說明:進入這段代碼時,ESI指向了某個 API 函數的入口地址,ESP指向的堆棧中壓入了該 API 函數需要的各個參數。由於代碼較長,沒有完整列出)
以上這段代碼的作用,就是通過分析(或者說反組譯碼) API 函數開始的部分代碼,然後把這部分代碼“複製”到自己的進程空間中執行後,再進入該 API 函數的內部某處代碼繼續執行 API 函數。
這樣,當你在此 API 函數入口處(比如: bpx CreateFileA)設斷時,就會“攔”不到它,為什嗎?因為,它不從 API 函數入口處執行。而是繞了個彎從“側門”進入的。瞧,加殼軟體多有意思!也許,這時你會想,那麼就在 API 函數的內部某處代碼設斷不就行了嗎?你要小心,加殼軟體有善良的,也有喜歡“惡作劇”的,它通過分析 API 函數開始部分的代碼,一方面進行“反組譯碼”,另一方面,如果你在它分析的代碼中設下了斷點時,也可能他會痛下殺手,因為斷點的代碼為 0xCC,它可不喜歡在 API 函數中出現這種指令。如果你由此當機了..........
④、反動態跟蹤的方法。
當然,加殼軟體也可能不只是把眼光放在防範你設斷點,也可能直接就把“眼光”放在防範調試狀態上了。比如最直接的就是檢測你的當前環境中是否載入了調試器或者是某些工具軟體。比如:
This method is most known as 'MeltICE' because it has been freely distributed
via www.winfiles.com. However it was first used by NuMega people to allow Symbol
Loader to check if SoftICE was active or not (the code is located inside nmtrans.dll).
The way it works is very simple:
It tries to open SoftICE drivers handles (SICE, SIWVID for Win9x, NTICE for WinNT)
with the CreateFileA API.
Here is a sample (checking for 'SICE'):
BOOL IsSoftIce95Loaded()
{
HANDLE hFile;
hFile = CreateFile( "////.//SICE", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle(hFile);
return TRUE;
}
return FALSE;
}
以上是摘自FrogsICE的文檔。利用 CreateFile( ) 開啟一些特殊的“檔案”,如果傳回值不是 -1,它就可以“發現”載入了SoftICE。當然這種方法由於可以在WIN98/NT下通用,所以很常見。
類似的,如果你把檢測的字串 //./SICE 改成
//./NTICE 檢測NT下的SoftICE
//./FILEMON 檢測FileMon
//./REGMON 檢測RegMon
//./TRW 檢測Trw
//./TRWDEBUG 檢測Trw
//./ICEDUMP 檢測IceDump
就可以“發現”其他的跟蹤了。
另一種常見的檢測SoftICE的方法如下,同樣摘自FrogsICE的文檔。
* * SOFTICE SHOULD NOT BE LOADED SO THAT FROGSICE CAN DETECT THIS METHOD * *
This method of detection of SoftICE (as well as the following one) is
used by the majority of packers/encryptors found on Internet.
It seeks the signature of BoundsChecker in SoftICE
mov ebp, 04243484Bh ; 'BCHK'
mov ax, 04h
int 3
cmp al,4
jnz SoftICE_Detected
其實檢測SoftICE方法有很多,在 FrogsICE 的文檔中介紹了一些,這裡的介紹只是窺其一斑而已。如果你有最“新”的反跟蹤方法,可一定要通知我哦。^_^
⑤、自身保護和反動態跟蹤相結合:
最後我們來看看這段代碼:
015F:0040DDD8 01FF ADD EDI,EDI
015F:0040DDDA C783CB18000090010000MOV DWORD PTR [EBX+000018CB],00000190
015F:0040DDE4 8BEB MOV EBP,EBX
015F:0040DDE6 BA561E0000 MOV EDX,00001E56
015F:0040DDEB 03D3 ADD EDX,EBX
015F:0040DDED 52 PUSH EDX
015F:0040DDEE 6467FF360000 PUSH DWORD PTR FS:[0000] <--SEH
015F:0040DDF4 646789260000 MOV FS:[0000],ESP <--SEH
015F:0040DDFA 89A3A3760000 MOV [EBX+000076A3],ESP
015F:0040DE00 BECD1E0000 MOV ESI,00001ECD
015F:0040DE05 03F3 ADD ESI,EBX
015F:0040DE07 8BFE MOV EDI,ESI <--EDI=40DECD
015F:0040DE09 B90F0A0000 MOV ECX,00000A0F <--ECX=0xA0F,ECX儲存的是迴圈次數
015F:0040DE0E 8B93FC760000 MOV EDX,[EBX+000076FC]
;--------------------------------------------------------------------------------------------------
;以下這一段代碼同 1 中介紹的代碼類似。
;也就是把程式碼片段 ( 015F:40D8E6 - 015F: 40DECA ) 之間的代碼和 EDX 的初始值進行運算來形成一個“密匙”
;結果仍然儲存在 EDX 中。
;--------------------------------------------------------------------------------------------------
015F:0040DE14 56 PUSH ESI <--迴圈開始處
015F:0040DE15 51 PUSH ECX <--入棧儲存,ECX儲存的是迴圈的次數=A0F。
015F:0040DE16 B979010000 MOV ECX,00000179
015F:0040DE1B BEE6180000 MOV ESI,000018E6
015F:0040DE20 03F3 ADD ESI,EBX <--ESI=40C000+18E6=40D8E6。(40C000是程式入口)
015F:0040DE22 8B06 MOV EAX,[ESI] <--取代碼中的 4 個位元組
015F:0040DE24 33D0 XOR EDX,EAX
015F:0040DE26 33D1 XOR EDX,ECX
015F:0040DE28 83C604 ADD ESI,04
015F:0040DE2B 49 DEC ECX <--迴圈次數減一,ECX初始值為179
015F:0040DE2C 75F4 JNZ 0040DE22 <--這一段是和前面介紹的相同的方法進行自身保護
015F:0040DE2E 59 POP ECX <--出棧
015F:0040DE2F 5E POP ESI <--出棧
;--------------------------------------------------------------------------------------------------
;這段代碼用 EDX 中的“密匙”來還原 EDI 指向中的被加密了的代碼,EDI初始值為 40DECD
;--------------------------------------------------------------------------------------------------
015F:0040DE30 AD LODSD
015F:0040DE31 33C2 XOR EAX,EDX
015F:0040DE33 AB STOSD
;--------------------------------------------------------------------------------------------------
;這段代碼用來進行反跟蹤
;--------------------------------------------------------------------------------------------------
015F:0040DE34 0F018BA57A0000 SIDT FWORD PTR [EBX+00007AA5] <--取IDTR的內容
015F:0040DE3B 8BB3A77A0000 MOV ESI,[EBX+00007AA7] <--取IDT表基地址
015F:0040DE41 894E08 MOV [ESI+08],ECX <--修改 int 1 的處理常式地址為 ECX,讓你死翹翹。
;--------------------------------------------------------------------------------------------------
;把“密匙”進行變換。
;--------------------------------------------------------------------------------------------------
015F:0040DE44 3393521E0000 XOR EDX,[EBX+00001E52]
015F:0040DE4A 8BF7 MOV ESI,EDI
015F:0040DE4C EB70 JMP 0040DEBE
......(省略)
015F:0040DEBE FF834E1E0000 INC DWORD PTR [EBX+00001E4E]
015F:0040DEC4 33D1 XOR EDX,ECX
;--------------------------------------------------------------------------------------------------
; 判斷迴圈是否結束,即此時後面的所有被加密的代碼都已經被還原
;--------------------------------------------------------------------------------------------------
015F:0040DEC6 49 DEC ECX <--迴圈次數減一,ECX初始值為A0F
015F:0040DEC7 0F8547FFFFFF JNZ 0040DE14 <--迴圈結束處
015F:0040DECD 5F POP EDI <--被加密了的代碼
015F:0040DECE 44 INC ESP <--未還原的代碼
這段代碼比較長,所以要看懂它得花點時間。這是一種把“反動態跟蹤”和“自身保護”結合的一種方法。
可以看到 015F:0040DECD 之後的代碼已經被加密了,這段代碼就是用來還原被加密了的代碼的。當這段代碼迴圈結束後,後面的被加密了的代碼就已經還原出來了,這就是 SMC 技巧的應用。在這段代碼中“密匙”是由前面所有的代碼來運算得到的。並且每迴圈一次就變換一次。這就是 1 中介紹的自身保護。防止你修改它的代碼或者設下斷點跟蹤。
另外,程式中還加入了一種“反動態跟蹤”的方法,就是修改了 單步中斷 的中斷處理常式的地址。這樣,你的跟蹤環境就被破壞了。
015F:0040DE34 0F018BA57A0000 SIDT FWORD PTR [EBX+00007AA5] <--取IDTR的內容
015F:0040DE3B 8BB3A77A0000 MOV ESI,[EBX+00007AA7] <--取IDT表基地址
015F:0040DE41 894E08 MOV [ESI+08],ECX <--修改 int 1 的處理常式地址為 ECX,讓你死翹翹。
這就是加殼軟體的特點,攻防結合。所以一不小心,你就可能落入了它設下的陷阱,見到了“如來佛”。所以為了防止被它送到西天。你就得下點功夫,瞭解自身保護、反跟蹤的特點和技巧。加殼軟體一般就是這樣一個陷阱重重的地方。當然加殼軟體的保護機制也有它的弱點,只要你的程式指令機器“讀”得懂,那麼動態跟蹤不行,就有靜態分析,或者動靜結合。所以才會各種相應的脫殼機的出現。當然這是一個“矛”和“盾”的關係,孰強孰弱,我想關鍵在於運用得當和推陳出新。比如,我常會結合MD5/RSA/BLOWFISH等密碼編譯演算法進行註冊碼計算,怎麼樣?!哈哈,然後用IF指令進行註冊判斷!?...................
【後記】
由於這些保護機制的方法出現的頻率比較多,所以介紹一下也無妨。其實,這些東西很早就有了。但是對於生產水貨的我,常常喜歡來點新瓶裝舊酒。另外,我對ANTI-DEBUG瞭解得還不全面,也希望能夠拋磚引玉,能引出更多的高手,讓大家來進行打假。