一、一般來講,加密就是加殼
我們經常考慮,一個可執行文件,怎么樣加密才能安全呢?
一般用的手段,是加殼。加殼工具的工作原理,就是把可執行文件的代碼與數據都進行加密變換,作為數據存放。生成的目標文件入口代碼是加殼軟件準備好的防跟蹤代碼。經過漫長的防跟蹤代碼后,會把原始可執行文件的代碼與數據段恢復,然后跳轉到原來的入口處,繼續運行。這樣做的缺點是,不管你的加密多強,防跟蹤代碼多牛,只要一運行,在內存中就全部恢復了。只要把內存映象dump下來,反匯編一下,就清清楚楚了。甚至有工具可以直接把dump下來的內存映象存為可執行文件。這樣加密就徹底失敗了。
簡單加殼是不安全的,這大家都知道了。我們一般把上述簡單的加殼方式叫“壓縮殼”。所以現在的加殼軟件都在上述“壓縮殼”的基礎上,多做了一些工作,比如:
* 防止內存被 dump 。這實際上是不可能做到的。因為Windows操作系統就不是一個安全系統,你怎么可能做到內存不被dump呢?曾有一個殼,我用了多種方法dump都不成功。但最后還是找到了一個方法成功dump了。我這才驚嘆dump原來有這么多種方法,真是防不勝防。
* 修改文件入口代碼。因為一般軟件都是用常用的幾種編譯器編譯生成的。如果加殼軟件知道你是用什么編譯器編的(這很容易),把入口代碼破壞掉,用另外一段功能類似的代碼替換它。這樣dump下來的代碼就比較難找到正確的入口,直接被存為一個EXE的可能性就小多了。但還是會被反匯編的。
* 還有一些加殼軟件,支持對一個或幾個重點函數加密。甚至使用了虛擬機。但他們都只能重點加密少數幾個函數,不可能把所有函數都加密。而且對這個函數還有很多要求。這可以想象。如果用匯編寫一個函數,不加ret它可能連函數結束地址都找不到,怎么可能加密呢?
盡管加殼軟件可以使用以上多種技術防止被跟蹤,分析,還原,但我認為,它們仍然沒沒擺脫“殼”的這個中心思想。以上的這些技術不過是在“殼”的大前提下所做的一些小的插曲。它仍然是不安全的
二、扭曲編譯的思想
做個比喻。加殼保護就好比是你桌上有寶貝,為了保護它,你在屋外圍了一圈鐵絲網。只要有人突破了這道鐵絲網,進入你的屋子,一眼就看到了桌上的寶貝。這當然不安全。
重點函數加密的思想,就好比是,我屋外圍了一圈鐵絲網,我還把寶貝放進了保險箱里。如果有人突破了鐵絲網,進入屋子,一眼就看到了保險箱。雖然保險箱不會被輕易打開,但他如果把保險箱搬走,慢慢分析呢?這也不夠安全。
最安全的,就是進了屋子,卻什么也找不著。沒有目標,這才是最讓人頭疼的。
現在的編譯器,都是追求生成高效率的運行代碼。這些代碼的模式基本一成不變。有經驗的程序員看反匯編代碼簡單跟看源碼一樣,毫無秘密可言。如果我們有一個編譯器,它的編譯目標不是為了高效,而是為了防止被讀懂,那該多好啊!我有C++源碼,我能看懂。一旦編譯,誰也別想通過反匯編看懂我想做什么,或者很難。遺憾的是,這樣的編譯器還沒有。
如果我們自己編一個這樣的編譯器呢?不現實。工作量太大了。即使是找一個開源的C++編譯器來改工作量也不得了。
直接做一個會加密的編譯器行不通。而一旦編譯連接生成EXE后,就只能加殼了。難道就沒有辦法了嗎?我想出一個主意,就是加密編譯的中間文件OBJ,輸出ASM文件,用ML編譯成OBJ,然后再link連接!
這個方法有幾個好處:
* OBJ文件格式相對簡單。不象處理C++源文件那么工作量大。
* OBJ文件中保留了很多源文件的信息,比如符號名,代碼與數據,標號等等。方便加密。這些信息很多在LINK的過程中被丟掉了。所以LINK為EXE后再處理就極不方便了。
* 這是一個全新的思想!對代碼的加密已經不限于加殼,而是加密每一個函數,每一條指令。再也沒有一目了然的匯編了。
* 可以很容易設定加密的強度。可以根據需要,對一部分代碼輕量級加密,而對另一部分代碼重點加密。
* 可以嵌套加密。重復使用幾種加密變換,無限制地使代碼膨脹。
* 因為是加密OBJ文件,所以不管DLL還是EXE都可順利加密,驅動程序也可以基于這個思想,我們的加密軟件就要出臺了!我們暫時叫它扭曲變換器 1.0
三、扭曲變換器
有了思想,就開始動手編碼。原以為OBJ文件格式是有文檔的,工程進度應該很快。沒想到其中還是有很多內容需要考慮。每每說這是最后一個問題,解決了就沒事了,卻總是后延。前前后后居然寫了差不多半年時間。
主要遇到的技術問題:
* 匯編器ML會把所有的代碼放到一個段中,這是不可以的。CL則通常是一個函數一個段。
* 匯編器ML不能生成 COMDAT 段。盡管文檔中講它支持COMMON,但加了這個關鍵字無效果。
* 匯編器ML不支持 WEAKEXTERN
* 匯編器ML只支持 defaultlib 這一個 drectve 關鍵字,其它 export, include 等關鍵字不支持.總之,CL編譯的OBJ其中有很多屬性是ML無法生成的。微軟的masm真的該升級了。
還有一些問題:
* 分不清代碼與數據。數據段中肯定是數據,但代碼段中卻有可能不是代碼,是數據。這時如果你試圖反匯編它,就會出錯。
* ?????不管怎樣,這些問題都一一解決了(別問我怎么做的)。
采用的代碼扭曲方法:
* 用 JMP 把代碼打亂。這已經不是什么新鮮的招數了,但它依然有效。
* 用 JMP 把多個函數纏繞在一起。這樣可以讓分析者找不到函數從什么地方開始,到什么地方結束。
* 把 call 改掉。破解者對 call 是極敏感的,這舉可以讓他找不到一個 call。
比如,我可以把 call sub1改為:
|
* 把 ret 改掉。破解者對 ret 是極敏感的,這舉可以讓他找不到一個 ret。比如,我可以把ret寫作:
|
再看這個,你能看懂是什么意思嗎:
|
這里出現了call和ret,但又不是一般所期望的那種。這里的call不代表你發現了一個函數調用。這里的ret不代表是一個函數的結束。
* 使用堆棧代替寄存器。比如:
|
你能看懂嗎?很明顯,這個變換是不可逆變換。因為它本來使用了哪個寄存器,已經沒有辦法知道了。
* ……還可以想出很多扭曲變換的方法。化繁為簡只有一種方法,化簡為繁可以有無窮多種方法。還有一些功能:
* 在C語言中,使用 #pragma code_seg(".code$curve_NoChange") 來指示后面的代碼不做任何加密。因為有時候有些代碼含有大量的循環,加密它會嚴重影響效率。
* 在C語言中,使用 #pragma code_seg(".code$curve_Max") 來指示后面的代碼重點加密。比如后面是與注冊算法相關。
現在的扭曲變換器我叫它1.0版,已經非常穩定了。我用它把VC6的庫文件LIB都處理了一遍,再用LIB.exe工具寫回LIB文件中,我們就有了一套加密后的庫。如果用這套庫來LINK你的軟件,分析者就很難從匯編中找到哪個是printf哪個是strcpy,IDA也無法識別MFC靜態鏈接的庫函數了。能把VC6的庫都加密一遍不出錯,我相信它已經很強壯很穩定了。
用它來加密我們做的一個共享軟件,就再也沒人寫出注冊機了。去讀懂大量的經過以上變換的代碼是不可想象的。但還是有人暴破了,真佩服他。我會不斷豐富加密方法,讓暴破者都放棄。
現在的扭曲變換器還只支持VC6使用的COFF格式的OBJ,下一步,要分析一下VS2005的OBJ格式,盡快支持它。
有幾點:
* 本軟件暫時不對外發布,只自己使用。如果有朋友想對自己的代碼加密,可以把OBJ文件發給我加密。防止所有的軟件都用這個加密,我們會IDA的就沒飯吃了 :(
* 現在只支持VC6的OBJ格式,就是 COFF. 下一步想支持VS2005的OBJ格式,遇到了問題。VS2005的debug編譯OBJ還是 COFF, 但 release 編譯的OBJ就不知道是什么格式了,找不到資料。有哪位朋友能幫助解決一下這個問題?謝謝。
我們以用戶名 12345678 錯誤的注冊碼 33333333 為例題,在40147E處下斷。我們可以看到,程序把我們的注冊碼算成了16進制的 12 12 12 12 AF AF AF AF 然后后面繼續,在40180A處把我們的注冊碼算成 15 16 17 18 B6 B7 B8 B9。然后在 405310 把我們的用戶名展成 31 32 33 34 35 36 37 38。
到此處為止,在堆棧里,把我們的用戶名、注冊碼展成堆棧地址 12FF18。
假注冊碼部分16進制:32 12 12 12 AF AF AF AF 15 16 17 18 B6 B7 B8 B9 1D 1E 1F 20 BE BF C0 C1 25 26 27 28 C6 C7 C8 00。
用戶名部分:31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37。
展開后就是用戶名與注冊碼混合運算了,在40226A 處可以看到運算的結果(這個過程最為復雜,的確沒時間仔細分析,所以沒細看)在 4083DD 處我們可以看到 SUB EAX,3467ABDE 這就是爆破口,把他改成 MOV EAX,00爆破成功!!!!反正就是爆破 4083DD處,把他改為 B8 00 00 00 00, 運行就OK了!!!
責任編輯 趙毅 zhaoyi@51cto.com TEL:(010)68476636-8001


