C++ code colored by C++2HTML
				
						來(lái)源:http://www.neeao.com/blog/article-4087.html
# 標(biāo)題:扭曲變換加密
# 作者:劉濤濤
# 來(lái)自:liutaotao.com
# 更新:2006年11月6日  
#

一,一般來(lái)講,加密就是加殼 我們經(jīng)??紤],一個(gè)可執(zhí)行文件,怎么樣加密才能安全呢? 一般用的手段,是加殼。加殼工具的工作原理,就是把可執(zhí)行文件的代碼與 數(shù)據(jù)都進(jìn)行加密變換,作為數(shù)據(jù)存放。生成的目標(biāo)文件入口代碼是加殼軟件 準(zhǔn)備好的防跟蹤代碼。經(jīng)過(guò)漫長(zhǎng)的防跟蹤代碼后,會(huì)把原始可執(zhí)行文件的代碼 與數(shù)據(jù)段恢復(fù),然后跳轉(zhuǎn)到原來(lái)的入口處,繼續(xù)運(yùn)行。這樣做的缺點(diǎn)是,不管 你的加密多強(qiáng),防跟蹤代碼多牛,只要一運(yùn)行,在內(nèi)存中就全部恢復(fù)了。只要 把內(nèi)存映象dump下來(lái),反匯編一下,就清清楚楚了。甚至有工具可以直接把 dump下來(lái)的內(nèi)存映象存為可執(zhí)行文件。這樣加密就徹底失敗了。 簡(jiǎn)單加殼是不安全的,這大家都知道了。我們一般把上述簡(jiǎn)單的加殼方式叫“壓縮殼”。 所以現(xiàn)在的加殼軟件都在上述“壓縮殼”的基礎(chǔ)上,多做了一些工作,比如: * 防止內(nèi)存被 dump 。這實(shí)際上是不可能做到的。因?yàn)閃indows操作系統(tǒng)就不是
一個(gè)安全系統(tǒng),你怎么可能做到內(nèi)存不被dump呢?曾有一個(gè)殼,我用了多種方法
dump都不成功。但最后還是找到了一個(gè)方法成功dump了。我這才驚嘆dump原來(lái)有
這么多種方法,真是防不勝防。 * 修改文件入口代碼。因?yàn)橐话丬浖际怯贸S玫膸追N編譯器編譯生成的。如果
加殼軟件知道你是用什么編譯器編的(這很容易),把入口代碼破壞掉,用另外一
段功能類似的代碼替換它。這樣dump下來(lái)的代碼就比較難找到正確的入口,直接
被存為一個(gè)EXE的可能性就小多了。但還是會(huì)被反匯編的。 * 還有一些加殼軟件,支持對(duì)一個(gè)或幾個(gè)重點(diǎn)函數(shù)加密。甚至使用了虛擬機(jī)。但他們
都只能重點(diǎn)加密少數(shù)幾個(gè)函數(shù),不可能把所有函數(shù)都加密。而且對(duì)這個(gè)函數(shù)還有很多
要求。這可以想象。如果用匯編寫一個(gè)函數(shù),不加ret它可能連函數(shù)結(jié)束地址都找不到,
怎么可能加密呢 ****** 盡管加殼軟件可以使用以上多種技術(shù)防止被跟蹤,分析,還原,但我認(rèn)為,它們?nèi)匀粵](méi) 沒(méi)擺脫“殼”的這個(gè)中心思想。以上的這些技術(shù)不過(guò)是在“殼”的大前提下所做的一些 小的插曲。它仍然是不安全的 二,扭曲編譯的思想 做個(gè)比喻。加殼保護(hù)就好比是你桌上有寶貝,為了保護(hù)它,你在屋外圍了一圈鐵絲網(wǎng)。只 要有人突破了這道鐵絲網(wǎng),進(jìn)入你的屋子,一眼就看到了桌上的寶貝。這當(dāng)然不安全。 重點(diǎn)函數(shù)加密的思想,就好比是,我屋外圍了一圈鐵絲網(wǎng),我還把寶貝放進(jìn)了 保險(xiǎn)箱里。如果有人突破了鐵絲網(wǎng),進(jìn)入屋子,一眼就看到了保險(xiǎn)箱。雖然保險(xiǎn)箱不會(huì)被輕 易打開,但他如果把保險(xiǎn)箱搬走,慢慢分析呢?這也不夠安全。 最安全的,就是進(jìn)了屋子,卻什么也找不著。沒(méi)有目標(biāo),這才是最讓人頭疼的。 現(xiàn)在的編譯器,都是追求生成高效率的運(yùn)行代碼。這些代碼的模式基本一成不變。有經(jīng)驗(yàn) 的程序員看反匯編代碼簡(jiǎn)單跟看源碼一樣,毫無(wú)秘密可言。如果我們有一個(gè)編譯器,它的編譯 目標(biāo)不是為了高效,而是為了防止被讀懂,那該多好啊!我有C++源碼,我能看懂。一旦編譯,
誰(shuí)也別想通過(guò)反匯編看懂我想做什么,或者很難。遺憾的是,這樣的編譯器還沒(méi)有。

如果我們自己編一個(gè)這樣的編譯器呢?不現(xiàn)實(shí)。工作量太大了。即使是找一個(gè)開源的C++編譯器
來(lái)改工作量也不得了。

直接做一個(gè)會(huì)加密的編譯器行不通。而一旦編譯連接生成EXE后,就只能加殼了。難道就沒(méi)有辦法
了嗎?我想出一個(gè)主意,就是加密編譯的中間文件OBJ,輸出ASM文件,用ML編譯成OBJ,然后再link連接! 這個(gè)方法有幾個(gè)好處: * OBJ文件格式相對(duì)簡(jiǎn)單。不象處理C++源文件那么工作量大。 * OBJ文件中保留了很多源文件的信息,比如符號(hào)名,代碼與數(shù)據(jù),標(biāo)號(hào)等等。方便加密。這些信
息很多在LINK的過(guò)程中被丟掉了。所以LINK為EXE后再處理就極不方便了。 * 這是一個(gè)全新的思想!對(duì)代碼的加密已經(jīng)不限于加殼,而是加密每一個(gè)函數(shù),每一條指令。再也
沒(méi)有一目了然的匯編了。 * 可以很容易設(shè)定加密的強(qiáng)度??梢愿鶕?jù)需要,對(duì)一部分代碼輕量級(jí)加密,而對(duì)另一部分代碼重點(diǎn)
加密。 * 可以嵌套加密。重復(fù)使用幾種加密變換,無(wú)限制地使代碼膨脹 * 因?yàn)槭羌用躉BJ文件,所以不管DLL還是EXE都可順利加密,驅(qū)動(dòng)程序也可以

基于這個(gè)思想,我們的加密軟件就要出臺(tái)了!我們暫時(shí)叫它扭曲變換器 1.0 三,扭曲變換器 有了思想,就開始動(dòng)手編碼。原以為OBJ文件格式是有文檔的,工程進(jìn)度應(yīng)該很快。沒(méi)想到其中還是 有很多內(nèi)容需要考慮。每每說(shuō)這是最后一個(gè)問(wèn)題,解決了就沒(méi)事了,卻總是后延。前前后后居然寫 了差不多半年時(shí)間。 主要遇到的技術(shù)問(wèn)題: * 匯編器ML會(huì)把所有的代碼放到一個(gè)段中,這是不可以的。CL則通常是一個(gè)函數(shù)一個(gè)段。 * 匯編器ML不能生成 COMDAT 段。盡管文檔中講它支持COMMON,但加了這個(gè)關(guān)鍵字無(wú)效果。 * 匯編器ML不支持 WEAKEXTERN * 匯編器ML只支持 defaultlib 這一個(gè) drectve 關(guān)鍵字,其它 export, include 等關(guān)鍵字不支持. 總之,CL編譯的OBJ其中有很多屬性是ML無(wú)法生成的。微軟的masm真的該升級(jí)了。

還有一些問(wèn)題: * 分不清代碼與數(shù)據(jù)。數(shù)據(jù)段中肯定是數(shù)據(jù),但代碼段中卻有可能不是代碼,是數(shù)據(jù)。這時(shí)如果你試圖
反匯編它,就會(huì)出錯(cuò)。 * ????? 不管怎樣,這些問(wèn)題都一一解決了(別問(wèn)我怎么做的)

采用的代碼扭曲方法: * JMP 把代碼打亂。這已經(jīng)不是什么新鮮的招數(shù)了,但它依然有效。 * JMP 把多個(gè)函數(shù)纏繞在一起。這樣可以讓分析者找不到函數(shù)從什么地方開始,到什么地方結(jié)束。 * call 改掉。破解者對(duì) call 是極敏感的,這舉可以讓他找不到一個(gè) call。比如,我可以把 call sub1
改為: mov eax, offset sub1 + 3 push offset @1 sub eax, 3 jmp eax @1:
*
ret 改掉。破解者對(duì) ret 是極敏感的,這舉可以讓他找不到一個(gè) ret。比如,我可以把ret寫作 push ecx mov ecx, [esp+4] add esp,8 jmp ecx * 改條件跳。條件跳也是極敏感的指令,比如我們可以把 cmp reg1, reg2 jge L_DST
L_NEXT: 寫作: push eax mov eax, reg1 sub eax, reg2 shr eax, 1fh neg eax and eax, L2 - L1 add eax, L1 jmp eax L1: pop eax jmp L_DST
L2: pop eax L_NEXT: 再看這個(gè),你能看懂是什么意思嗎 push offset @@L - offset L_3 + 23h jmp L_1
L_2: jz L_3 ret 4 L_3: add dword ptr [esp+4], offset L_3 - 23h add esp,4 ret L_1: call L_2 ... 這里出現(xiàn)了callret,但又不是一般所期望的那種。這里的call不代表你發(fā)現(xiàn)了一個(gè)函數(shù)調(diào)用。
這里的ret不代表是一個(gè)函數(shù)的結(jié)束。 * 使用堆棧代替寄存器。比如: MOV EAX, DWORD PTR [ECX+0AD8h] PUSH EAX MOV ECX, DWORD PTR [EAX] 可以寫作: PUSH EAX PUSH ECX MOV EAX, DWORD PTR [ESP] ADD EAX, 0AD8h MOV EAX, DWORD PTR [EAX] MOV DWORD PTR [ESP+04h], EAX PUSH DWORD PTR [ESP+04h] MOV EAX, DWORD PTR [ESP] MOV DWORD PTR [ESP+08h], EAX MOV EAX, DWORD PTR [ESP] MOV EAX, DWORD PTR [EAX] MOV DWORD PTR [ESP+04h], EAX MOV EAX, DWORD PTR [ESP] MOV ECX, DWORD PTR [ESP+04h] ADD ESP, 08h
你能看懂嗎?很明顯,這個(gè)變換是不可逆變換。因?yàn)樗緛?lái)使用了哪個(gè)寄存器,已經(jīng)沒(méi)有辦法知道了。 * ……還可以想出很多扭曲變換的方法。化繁為簡(jiǎn)只有一種方法,化簡(jiǎn)為繁可以有無(wú)窮多種方法。

還有一些功能: * 在C語(yǔ)言中,使用 #pragma code_seg(".code$curve_NoChange") 來(lái)指示后面的代碼不做任何加密。因
為有時(shí)候有些代碼含有大量的循環(huán),加密它會(huì)嚴(yán)重影響效率。 * 在C語(yǔ)言中,使用 #pragma code_seg(".code$curve_Max") 來(lái)指示后面的代碼重點(diǎn)加密。比如后面是
與注冊(cè)算法相關(guān)。

現(xiàn)在的扭曲變換器我叫它1.0版,已經(jīng)非常穩(wěn)定了。我用它把VC6的庫(kù)文件LIB都處理了一遍,再用LIB.exe工具
寫回LIB文件中,我們就有了一套加密后的庫(kù)。如果用這套庫(kù)來(lái)LINK你的軟件,分析者就很難從匯編中找到哪
個(gè)是printf哪個(gè)是strcpy,IDA也無(wú)法識(shí)別MFC靜態(tài)鏈接的庫(kù)函數(shù)了。能把VC6的庫(kù)都加密一遍不出錯(cuò),我
相信它已經(jīng)很強(qiáng)壯很穩(wěn)定了。

用它來(lái)加密我們做的一個(gè)共享軟件,就再也沒(méi)人寫出注冊(cè)機(jī)了。去讀懂大量的經(jīng)過(guò)以上變換的代碼是不可
想象的。但還是有人暴破了,真佩服他。我會(huì)不斷豐富加密方法,讓暴破者都放棄。

現(xiàn)在的扭曲變換器還只支持VC6使用的COFF格式的OBJ,下一步,要分析一下VS2005的OBJ格式,盡快支持它。

我經(jīng)常喜歡反匯編一下,分析點(diǎn)什么東西。我有不少朋友也經(jīng)常在做反匯編或破解的工作。我不希望扭曲變換
器在網(wǎng)上一公布,被廣泛使用。有一天我想分析點(diǎn)什么卻無(wú)法下手。所以,這軟件暫時(shí)還不提供下載,也不出售。
如果你有一段小程序想測(cè)試一下,可以把OBJ發(fā)給我,我加密一下給你。如果你有一個(gè)商業(yè)項(xiàng)目需要安全地加密,
也可以談?wù)劇?br />
附上一個(gè)為CCG寫的crackme,用扭曲變換器加密,帶部分源碼,供參考。
http://liutaotao.com/CrackMe.zip