《軟體加密與解密》——軟體加軟體相似性比對

出版圈郭志敏發表於2012-04-18

有一類並不使用基於程式碼轉換演算法的軟體保護問題,我們將其統稱為軟體相似性分析。從概念上說,這些問題的共性是它們都依賴於你能否判定兩個程式是不是非常相似的,或者其中的一個是不是(部分)包含於另一箇中的。我們用下面這兩個函式來描述這一過程,相似度(similarity)和包含度(containment),如圖1-33所示。

enter image description here

程式碼剽竊

我們通常把學術領域裡的偷盜活動稱為剽竊——學生抄襲別人的作業、研究員冒用他人的研究成果……這一現象在每個人類的創造性工作領域中都會發生,總是會有人想“走走捷徑”、“借鑑”一下別人的創造性工作的成果。在藝術領域中就是複製。比如引用一段別人譜寫的樂曲,照搬別人傢俱的樣式,或者模仿他人的設計風格……迄今為止,已經有不少著名的作家、藝術家、音樂人因在其作品中含有太多其他同行的東西而被告上法庭。這句話中“太多”的準確含義就是離法律規定的標準實在是太遠了。其中一個著名的案例是關於John Fogerty的。在這個案例中,由於他出品的新專輯聽起來太像她自己以前的,已經申請了版權保護的作品了,而被指控抄襲了自己的作品(即自我抄襲)。

本書中,我們關心的是程式碼剽竊。這一現象經常發生在電腦科學技術領域。比如很多學生都覺得“參考”一下同學的作業比自己獨立完成作業要方便得多——壞學生Axel會把Doris同學的程式設計作業Q中的某一段複製下來,粘帖到他自己的作業P中,然後把P交給老師。如圖1-34所示。

enter image description here

如果學生很多,計算機課老師想要靠人工的方式逐一比較所有學生的作業,基本上是不現實的。但他可以寫一個工具(作為一個程式設計師,他也習慣於把事情儘量交給計算機處理),自動地把學生的作業逐一配對比較,比方說這個工具可以輸出圖1-35所示的結果,把比較的結果按相似度從高(最有可能是抄襲的)到低(最不可能抄襲)的順序排列出來。

enter image description here

自動比對是批量排除哪些基本不可能是抄襲的配對的最好方法,老師只要手工分析剩下的少數幾個極有可能是抄襲—被抄襲的作業對就可以了。

有些學生可能已經知道老師會用這種方法來對付他們 ,於是他們也會想辦法逃避檢測。他們可以進行一些簡單的程式碼轉換——比如改變某些變數的變數名,或者重新排列函式的呼叫順序都是一些常見的伎倆。因此對於一個軟體相似性比對工具來說,正確識別這類變換的能力是很重要的。

軟體作者鑑別

軟體作者鑑別(software forensics)就是要回答這樣一個問題:“在這些程式設計師中,是誰編寫了程式S?”為了回答這個問題,你必須先拿到所有可能編寫程式S的程式設計師的程式碼樣本,如圖1-36所示。

enter image description here

從這些樣本出發,你可以提取一些你認為可以唯一的標識每個程式設計師的特徵,然後把這些特徵和從程式S中提取出來的特徵相比較,以圖1-36為例,辨認的結果很可能就是圖1-37中給出的這個樣子,即程式S的作者最有可能是Axel,而不是Doris或者Carol。

enter image description here

圖1-37中f 是用來從程式中提取各類特徵集的函式。實踐中,特徵集中一般都含有每行程式碼的長度、函式的大小或者花括號的位置等資訊(儘管對這些特徵能否準確地指明作者的身份這一點還有激烈的爭論)。 儘管現在大多數的軟體鑑別演算法都是針對高階語言的原始碼的,但研究針對二進位制可執行檔案的鑑別方法也是很有前途的。例如,FBI抓到了一名編寫惡意軟體的疑犯,他們可能想要把這名疑犯的程式設計風格和所有已知的病毒相比較,以便儘可能多地找到這傢伙編寫的病毒,進而在法庭上提供更多對他不利的證據。

軟體“胎記”

在之前討論的案例中已經涉及了一些程式碼剽竊的內容,所謂“程式碼剽竊”就是你的競爭對手把你程式中的一個模組M複製到他自己的程式Q中,如圖1-38所示。

enter image description here

程式碼混淆和水印技術都能使實施這類攻擊變得很困難。攻擊者很難從經過混淆技術處理的P中找到M。即使找到了,也很難從P中把它“乾淨的”剝離出來——你可以把M和程式中的其他模組混在一起,使得任何自動程式碼提取工具在提取M的程式碼時都要不得不拖泥帶水地帶上大量與M不相干的程式碼。

你也可以在M中嵌入一個水印或者指紋。比方說,M是一個由第三方(你)開發的圖形顯示加速模組。你把它賣給了Doris,允許她在其開發的遊戲中使用它。要是哪天你在Axel的遊戲中發現了Doris的指紋,你就可以斷定Axel剽竊了你的程式碼,而且這些程式碼是複製自你賣給Doris的產品的。接下來你可以以程式碼剽竊的罪名把Axel送上法庭,也可以起訴Doris,因為她沒有踐行軟體使用許可協議,使用有效的保護措施防止你的程式碼被他人剽竊。

可是,出於種種原因,你可能選擇不對你的程式碼進行混淆處理,也不把水印嵌在它裡面。比如,程式碼的執行效率是一個至關重要的指標,又或者使用混淆技術會讓你除錯程式或對軟體進行質量評估變得非常困難。再或者你只是想知道一些很久之前編寫的 老程式碼是否有人剽竊。除了混淆和水印技術之外,你還可以在攻擊者的程式Q中搜尋一下,看看你的模組M是否包含在Q裡面。如圖1-39所示。

enter image description here

這一招很管用,除非你的對手已經預計到你可能會使用這一方法,並且針鋒相對的使用了一些程式碼轉換技術——比如對M(或者Q中的所有程式碼)進行混淆處理,使你難以在Q中找到M。如圖1-40所示。

enter image description here

經過一番複雜的程式碼轉換之後,只是簡單地搜尋Q就不管用了。如圖1-41所示。

enter image description here

圖1-42中的f 是從程式或者模組中提取特徵的函式,我把這些特徵稱為軟體“胎記”。之所以把它們稱為軟體“胎記”,是因為這些特徵是軟體與生俱來的,即使軟體經過了一些常見的程式碼轉換(比如程式碼優化或者混淆)也能保持不變的東西。

僅就我所知就至少有一個利用軟體“胎記”成功地證明程式碼剽竊的案例。20世紀80年代,IBM控告其競爭對手剽竊了PC-AT ROM上的程式碼[128]。IBM的人員作證道:對手程式往暫存器裡存入和讀取資料的順序 以及有關指令所使用的暫存器與IBM開發的程式使用暫存器的方法是一致的。而讀寫暫存器的順序以及所使用的暫存器實際上就是程式的“胎記”之一。同時他們也證明如果指令序列push R1; push R2; add 和 push R2; push R1; add在語法上是等價的,那麼如果一個程式使用了push R1; push R2; add這個序列的話,另一個程式也使用push R1; push R2; add這個序列的概率p就只有50% 。如果這個指令序列越長,使用的通用暫存器越多,那麼等價的指令序列就會越多,進而概率p的值就會進一步降低。要是指令序列足夠長,使用的通用暫存器足夠多,p就會降低到一個幾乎不可能發生的地步。

軟體“胎記”的案例

為了保證“胎記”的有效性,有關演算法必須從哪些不易被攻擊者篡改的程式的本質特徵著手提取“胎記”。一種很常用的方法是把程式對標準庫函式或者系統呼叫的使用情況當成軟體“胎記”予以提取。這一方法將在第10章中詳細討論,這裡只是用一個簡單的例子演示一下。基本思路是由於標準庫函式或者系統呼叫所實現的功能很難被攻擊者用自己的函式所代替,所以軟體對這些函式的使用情況可以被當成它的一個“胎記”。比如在Unix系統中寫一個檔案最終都是要通過呼叫write函式來實現,所以程式對write函式的使用情況就不會被任何保持語義不變的程式碼轉換演算法所修改。

我們來看下面這個C函式,這個函式從一個檔案中讀出兩個字串,並把它們都轉換成int型整數,最後輸出這兩個數的乘積。

enter image description here

enter image description here

攻擊者拿到這個函式之後,會把它複製到他自己的程式P中,然後進行一些程式碼轉換,妄圖糊弄我們。在如下所示的這個例子中,他把函式名改成了f,在函式中增加了對gettimeofday和getpag- esize這兩個函式的呼叫(使用這兩個函式不會影響到這個函式的功能和執行結果),改變了fclose 和atoi函式的呼叫順序,最後還額外多呼叫了一次fopen 和fclose函式。

enter image description here

要確定程式P中是否含有函式x,只要計算一下containment(bmi(x), bmi(P))就可以了,這個containment函式返回一個0.0到1.0之間的值,它表示P中含有x的機率。在這個示例中,我們看到儘管攻擊者用盡了各種方法去掩蓋其蹤跡:他修改函式被呼叫的順序,增加被呼叫函式的種類,改變各個函式被呼叫的頻率,但是他的剽竊行為還是被我們利用軟體“胎記”成功地識別了出來。

相關文章