付修鋒 賈張濤 楊鐵湃 安恒 金玉川 耿宏偉
關(guān)鍵詞 開源軟件 程序?qū)Ρ?代碼溯源 代碼復(fù)用
1引言
在行業(yè)規(guī)模不斷擴(kuò)大的過程中,軟件產(chǎn)品功能必須隨著用戶需求的增加而不斷地做出調(diào)整[1] 。而功能的迭代使得軟件內(nèi)部業(yè)務(wù)邏輯變得越來越復(fù)雜。
這就要求在資源有限的情況下,開發(fā)人員團(tuán)隊(duì)必須快速對(duì)這些復(fù)雜的需求做出應(yīng)對(duì),以滿足如今充滿競(jìng)爭(zhēng)的軟件市場(chǎng)。
起初,由一些IT 行業(yè)人員自發(fā)地用博客的形式將開發(fā)過程中的心得、經(jīng)驗(yàn)分享和代碼工程發(fā)布到互聯(lián)網(wǎng)中,構(gòu)成了早期的開源社群。如今,這些小型社群吸收了越來越多開源的支持者,他們是多方位、多元化、豐富的開源項(xiàng)目代碼的來源,從而集成了不斷壯大的開源生態(tài)系統(tǒng)。市面上熱度最高的開源社區(qū)有GitHub,CSDN,StackOverflow,據(jù)2021 年GitHub 年度報(bào)告顯示,2021 年GitHub 工程數(shù)目已經(jīng)達(dá)到1 億7000 萬,用戶年總量增長(zhǎng)30%,其中中國(guó)用戶755 萬,位居全球第二。豐富的、易獲取的代碼來源和龐大的用戶群體,方便了程序員快速、準(zhǔn)確地找到已有的代碼。通過對(duì)目標(biāo)代碼的復(fù)用和調(diào)試,我們可以在短時(shí)內(nèi)解決項(xiàng)目模塊開發(fā)“冷啟動(dòng)問題”,也減少了時(shí)間上的開銷。代碼復(fù)用即開發(fā)者通過復(fù)制、粘貼或引用調(diào)包等方式使用了已有的開源代碼[2~4] 。
開發(fā)人員在享受代碼復(fù)用帶來高效率開發(fā)快樂的同時(shí),也同樣意識(shí)到了背后所存在的安全隱患和侵權(quán)問題。最著名的就屬Oracle 與Google 由于代碼復(fù)用長(zhǎng)達(dá)數(shù)年的訴訟官司。從此可以看出,開發(fā)者必須了解項(xiàng)目工程中的復(fù)用代碼,哪怕僅僅是幾行代碼,都可能給公司帶來不可估量的損失,也會(huì)給后期項(xiàng)目維護(hù)帶來額外管理支出。
代碼溯源就是在復(fù)用代碼與已知代碼庫(kù)中,從代碼的相似性高的角度出發(fā),完成代碼追溯工作。對(duì)此,本文利用NLP 自然語(yǔ)言技術(shù)提出了一種精準(zhǔn)快速模塊溯源方法。通過統(tǒng)一代碼中的特定標(biāo)識(shí),將完整檢測(cè)樣本切分。在每一塊函數(shù)代碼塊映射成唯一的特征向量的基礎(chǔ)上,利用新穎的工程相似計(jì)算方法得出代碼溯源。實(shí)驗(yàn)結(jié)果表明,該方法能滿足開源代碼復(fù)用中相似度檢測(cè)的需求,且有著較高的準(zhǔn)確率。
2算法描述
由于一般商業(yè)軟件普遍采用“閉源”方式并不會(huì)提供軟件源代碼,所以我們主要研究二進(jìn)制代碼溯源。在本文中,我們將已知項(xiàng)目工程拆分成文件級(jí)和函數(shù)級(jí)兩個(gè)維度進(jìn)行溯源。通過歸納,總結(jié)二進(jìn)制文件的反匯編文件中函數(shù)跳轉(zhuǎn)標(biāo)識(shí),將工程文件中方法函數(shù)切分成多個(gè)代碼塊,并儲(chǔ)存在多個(gè)分詞序列中。利用NLP 技術(shù)將每一條代碼序列映射成統(tǒng)一潛在空間的二進(jìn)制特征向量。我們也可以獲取待測(cè)工程代碼二級(jí)制特征向量。兩者之間自然語(yǔ)言相似時(shí),其特征向量之間也應(yīng)該具有相似性,由此判斷兩個(gè)工程的溯源關(guān)系。快速溯源模型共包括3 個(gè)階段,即溯源數(shù)據(jù)預(yù)處理階段(圖1)、溯源函數(shù)特征提取階段以及溯源函數(shù)復(fù)用比對(duì)階段。
溯源數(shù)據(jù)預(yù)處理階段是將二進(jìn)制代碼文件用函數(shù)跳轉(zhuǎn)標(biāo)識(shí)進(jìn)行切分,再對(duì)每一種函數(shù)方法利用統(tǒng)一的變量格式進(jìn)行標(biāo)準(zhǔn)化,最終獲得多個(gè)統(tǒng)一格式的函數(shù)代碼塊。
函數(shù)溯源函數(shù)特征提取階段是以函數(shù)為基本單位,對(duì)函數(shù)代碼進(jìn)行語(yǔ)法、語(yǔ)義等數(shù)據(jù)分析,獲取函數(shù)文本中隱含的函數(shù)特征向量。函數(shù)特征向量必須滿足以下特點(diǎn):(1)相同的函數(shù)指令序列產(chǎn)生的函數(shù)特征編碼應(yīng)該是相同的,不同的函數(shù)指令序列應(yīng)產(chǎn)生不同的函數(shù)特征編碼;(2)匯編代碼通過特征編碼模型構(gòu)造出來的與函數(shù)代碼對(duì)應(yīng)的特征向量是唯一的,即每一個(gè)函數(shù)特征向量都能很好地表達(dá)對(duì)應(yīng)函數(shù)塊的特征;(3)相似的函數(shù)指令序列應(yīng)產(chǎn)生相似的函數(shù)特征編碼,以滿足相似函數(shù)的判定需求。
工程相似度計(jì)算階段是計(jì)算兩個(gè)對(duì)比函數(shù)特征向量的相似性,以判定溯源等級(jí)。
2.1溯源數(shù)據(jù)預(yù)處理
溯源數(shù)據(jù)預(yù)處理的對(duì)象是匯編代碼,所以要用反編譯工具將二進(jìn)制樣本逆向獲取匯編代碼;在項(xiàng)目工程中,多個(gè)相互調(diào)用方法的組成特定的功能函數(shù),而多個(gè)特定函數(shù)代碼塊集成一個(gè)匯編文件[5] 。
用P 表示獲取的匯編代碼,F(xiàn) 表示功能函數(shù),L 則表示代碼塊。由此可得,P = {F1,F(xiàn)2,F(xiàn)n }, F = {L1,L2,L,Lm }。隨后,我們對(duì)每一個(gè)代碼塊進(jìn)行了標(biāo)準(zhǔn)化,具體如圖1 所示。
在匯編代碼中有許多代碼函數(shù)的起始和終止的標(biāo)識(shí)符,如“near”和“end”,由此可以將匯編文件切分成多個(gè)代碼塊。匯編文件切分的代碼如下:
在函數(shù)loc_4023E7 中,根據(jù)跳轉(zhuǎn)指令“jnz”與跳轉(zhuǎn)地址“l(fā)oc_4023E7”將其劃分為2 個(gè)代碼塊,對(duì)第一個(gè)代碼塊,依據(jù)函數(shù)位置命名,如表1 所列。
2.2使用NLP 技術(shù)進(jìn)行代碼特征的提取
通過跳轉(zhuǎn)符拆分代碼塊序列,利用NLP 算法為開源工程中的每一個(gè)標(biāo)準(zhǔn)化工程文件序列、函數(shù)塊序列、代碼塊序列分別進(jìn)行編碼,獲取對(duì)應(yīng)的特征向量Embding。在函數(shù)特征構(gòu)造算法中,每一步的輸出均為下一步的輸入,具體實(shí)現(xiàn)步驟可分為5 個(gè)部分。
(1)將匯編文本中的代碼塊序列映射到潛在特征空間中:使用傳統(tǒng)哈希算法將每個(gè)特征分詞映射成一組長(zhǎng)度為64 位的二進(jìn)制數(shù)列。
(2)統(tǒng)計(jì)分詞權(quán)重:在一段代碼分詞序列中,某一個(gè)分詞出現(xiàn)的次數(shù)越多,可能其對(duì)整個(gè)代碼特征提取的影響越大。通過統(tǒng)計(jì)出分詞例如“sub,mov,js”出現(xiàn)的次數(shù)作為該分詞的權(quán)重值。
(3)獲取每一個(gè)分詞的加權(quán)向量:本文使用的是將“統(tǒng)計(jì)分詞權(quán)重”中獲取的分詞權(quán)重值與分詞哈希值相乘獲取分詞的加權(quán)向量。例如,通過哈希函數(shù)得到“mov”的6 位二進(jìn)制哈希向量(1,1,0,0,1,0),序列中“mov”出現(xiàn)的次數(shù)是6,其中比特值為0 的位替換成-1,則“mov”的加權(quán)向量是(6,6,-6,-6,6,-6)。
(4)合并:將上述加權(quán)向量的對(duì)應(yīng)位相加合并,壓縮數(shù)據(jù),此部分的輸出是一個(gè)含權(quán)值向量。
(5)降維:將合并后的向量降維,若該位的數(shù)值大于0,則出1,否則該位出0,最終計(jì)算出特征向量值。
2.3工程相似度計(jì)算
常見用來衡量代碼相似度的指標(biāo)主要有以下幾個(gè):歐氏距離、余弦相似度等。由于本文提出了一種基于函數(shù)代碼塊的特征提取方法。因此,將兩個(gè)函數(shù)塊分別得到對(duì)應(yīng)的函數(shù)特征向量,其計(jì)算原始文本相似程度則可以通過計(jì)算它們特征向量之間的距離來獲取。該距離是指函數(shù)指紋對(duì)應(yīng)比特取值不同的比特?cái)?shù)(函數(shù)指紋長(zhǎng)度需相同),具體計(jì)算公式如下:
公式中X 和Y 表示兩個(gè)長(zhǎng)度為n 位的函數(shù)指紋,M 表示函數(shù)指紋X 與Y 的異或結(jié)果,m 表示異或結(jié)果M 的第i 位的值。首先對(duì)兩個(gè)函數(shù)指紋X,Y 做按位異或操作,得到異或碼M。此步驟中,如果兩個(gè)函數(shù)指紋對(duì)應(yīng)位相同,則出“0”,若不同則出“1”。然后對(duì)M 的每一位求和,即可獲取兩函數(shù)指紋間的距離。我們通過大量的參考文獻(xiàn)的統(tǒng)計(jì),同時(shí)考慮方法的精確度和運(yùn)行效率的情況下,將評(píng)估兩個(gè)特征向量之間的距離閥值設(shè)定為3,即當(dāng)兩個(gè)代碼特征向量通過距離運(yùn)算得到的結(jié)果小于等于3 時(shí),我們認(rèn)為這兩個(gè)函數(shù)相似。
輸入:兩個(gè)工程的源代碼
輸出:兩個(gè)工程的代碼相似度S
3實(shí)驗(yàn)結(jié)果與分析
本文從工程相似度角度出發(fā)進(jìn)行程序?qū)Ρ确治觥榱俗C明方法的函數(shù)溯源能力,我們采用評(píng)估函數(shù)復(fù)用的準(zhǔn)確率作為模型的評(píng)估指標(biāo)。
二進(jìn)制代碼溯源驗(yàn)證主要通過針對(duì)同一軟件不同編譯版本和已知存在復(fù)用事實(shí)的不同軟件開展二進(jìn)制代碼溯源。具體驗(yàn)證過程如下:(1)將Chrome 瀏覽器(版本號(hào)49.0.2623.112) 入庫(kù);(2) 上傳Chrome瀏覽器(版本號(hào)44.0.2403.155)開展二進(jìn)制代碼溯源;(3)上傳Redcore 瀏覽器(版本號(hào)49.1.2623.213)開展二進(jìn)制代碼溯源。
通過前期數(shù)據(jù)處理分析,Chrome 瀏覽器44.0、Redcore 瀏覽器49.1 文件級(jí)和函數(shù)級(jí)個(gè)數(shù)如表2 所列。我們將Chrome 49.0 中的函數(shù)作為基準(zhǔn)代碼,Chrome 44.0,Redcore 49.1 中的每個(gè)函數(shù)作為溯源的對(duì)比函數(shù)。利用特定的對(duì)比方法判定兩個(gè)特征向量的相似性,對(duì)比結(jié)果如表3 所列。
利用代碼克隆檢測(cè)溯源方法對(duì)兩個(gè)工程中每一個(gè)函數(shù)進(jìn)行編碼得到函數(shù)特征向量,發(fā)現(xiàn)Chrome44.0,Redcore 49.1 與Chrome 49.0 文件級(jí)別高相似個(gè)數(shù)都是7 個(gè),兩者函數(shù)級(jí)別高相似函數(shù)個(gè)數(shù)分別為14個(gè)和13 個(gè)。同時(shí),由手動(dòng)查看三者之間相似文件和函數(shù)統(tǒng)計(jì)得出,該方法溯源準(zhǔn)確率為95.77%,因此本文的溯源方法具有很高的準(zhǔn)確率。
在待檢測(cè)的兩個(gè)工程中,存在大量的相似函數(shù)調(diào)用關(guān)系,例如chrome.dll,chrome_elf.dll 就被調(diào)用。導(dǎo)致它們雖然從界面上和操作流程上不大相同,檢測(cè)結(jié)果卻非常相似。因此,通過函數(shù)塊相似度計(jì)算判定溯源,比手工判定更加準(zhǔn)確和高效。
4結(jié)論
在工業(yè)軟件項(xiàng)目的開發(fā)過程中,由于復(fù)用代碼的同源判定通常依賴于人工經(jīng)驗(yàn)識(shí)別,導(dǎo)致同源判定工作效率較低。針對(duì)該問題,本文提出了一個(gè)能夠快速準(zhǔn)確提取二進(jìn)制工程文件特征的模型,并在此基礎(chǔ)上設(shè)計(jì)了一種代碼相似度對(duì)比方法,完成待檢測(cè)代碼的溯源。在已知代碼數(shù)據(jù)集的實(shí)驗(yàn)表明,該方法通過提取代碼特征,有助于減少出現(xiàn)復(fù)用代碼溯源誤報(bào)、漏報(bào)的情況。