2008年9月28日 星期日

軟體防駭小招數

辛苦寫的程式一下被破解了,那種感覺真的是很XXX....

目前有很多加殼程式,但只要遇到低階語言高手,還是會被破解,只是多花點時間而已,所以我花了幾天,設法在高階語言上造成破解障礙。

一般來說破解者常用的方是就是修改程式內的日期,讓程式以為還沒到期,或是用跳躍的方式,讓程式跳過驗證序號或是日期的那一段函數,所以不管設多少關卡,只要破解者有耐心一一跳過,還是可以破解成功的,況且設太多關卡,對撰寫者來說也是一種困擾。

程式的執行是具有連續性的,某個動作後發生某個事件,這也讓破解人只要依著順序去找出關鍵位置就能輕易跳過,所以這個位置就是雙方鬥智的關鍵。

我想到的方法必須依賴到網路伺服器做驗證,一旦驗證端是在程式內,無論如何都會被跳過,所以改由伺服器來做驗證,以日期來說,程式先送軟體過期日給伺服器,這時第一個問題來了,破解者可以輕易的變造這個日期,所以日期在傳送之前必須先經過加密,例如

myDate:= 'alsdkfjsks233+==';
SendmyDate(myDate); ////傳送到伺服器

伺服器收到後做解密的動作然後進行驗證,關鍵就在此處,如果伺服器驗證後傳回布林值或是其他帶碼,程式收到後勢必還需要再做判斷,例如

if serverBack = true then 驗證過關 else 驗證失敗

像這樣判斷,一下子就被破解了,破解者只要直接跳到驗證成功,程式就會正常的執行下去,所以在程式中不可出現像這樣的判斷,我的作法是伺服器直接傳回Event名稱。

舉例來說,假設程式是寫在一個Timer的onTimer上,那麼這個Timer的EventName就由伺服器回傳,也就是說必須在程式中有兩個onTimer事件,一個是驗證過關 onGoodTimer ,一個是驗證失敗 onBadTimer ,而Timer在初始狀態下是不賦予任何事件的(也可以先賦予失敗事件),當伺服器驗證失敗後,直接傳 "onBadTimer" 回來,然後直接將這個字串所代表的事件賦予Timer,注意,千萬不要寫成

if serverBack = 'onBadTimer' then xxx (這跟前面沒兩樣)

必須直接賦予,代碼如下


procedure TmainForm.ExecMethod(OnObject: TObject; MethodName: string) ;
var
Routine: TMethod;
begin

//事件所在位置是mainForm,所以OnObject指定為mainForm

myTimer.OnTimer:= nil;

Routine.Data := Pointer(OnObject);
Routine.Code := OnObject.MethodAddress(MethodName) ; //由mainForm調出事件的指標位置
myTimer.OnTimer:= TNotifyEvent(Routine);


end;

執行:

ExecMethod(mainForm, ServerBack);


這樣一來,即使破解者跳過驗證這一段,Timer也無法得到正確的Event而變成沒有作用,即使破解者知道這個作法,也必須從你的程式碼中找出正確的事件來,這會讓他們很頭痛,況且他不一定會朝這方面去想,更增加了破解難度,如果再加上CodeVisual或Themida這種難纏的殼,軟體被破解的機率就比較小了。
由於Pascal跟C++是屬於編譯型程式,所以無法把ASC碼當作機械碼用,如果是直譯型程式(如Flash, JavaScript),用這方式就更方便了,甚至可以將整個Function放在Server,回傳時只要調用Eval函數轉換即可。