天天看點

基于虛拟機的軟體保護技術

基于虛拟機的軟體保護技術不确定是否首先由vmprotect提出,但vmprotect毫無疑問是将這項技術大力推廣至人所周知。現在基于虛拟機的軟體保護技術已經成為現代軟體安全防護的必備功能之一。

本文并不打算對vmprotect或其它某款軟體安全套件進行深入讨論,而着眼于研究基于虛拟機的軟體保護技術的起源、思想和實作。

基于虛拟機的軟體保護技術

現有軟體保護技術概述

傳統的軟體保護技術,根據針對對象不同,可分為反靜态調試和反動态調試兩大類。反靜态調試主要針對對象為反彙編器。反彙編器通過面向特定平台的反彙編引擎(如PC平台即為X86反彙編引擎),将編譯器生成的二進制檔案還原成彙編代碼,有經驗的逆向工程師可以據此還原出算法等核心運算機制。反靜态調試主要是通過特定區段加密等方式,将核心資訊保護起來,隻在運作期才通過解密等算法動态還原,阻礙反彙編器靜态地将二進制檔案還原成彙編碼。

反動态調試主要針對對象為調試器,由于經過靜态加密的二進制碼最終必須解密才能執行,是以通過Ollydbg等動态調試器仍然可以加以檢視,反動态調試通過檢測調試器和屏蔽調試端口等各種反調試技術阻止逆向工程師通過調試器跟蹤軟體程序的運作情況,使得軟體的運作時狀況始終保持處于黑盒狀态。

被動型軟體保護概念上述兩種保護方案均采取主動出擊的政策,意圖“禦敵于國之外”,中心思想是一個“擋字”,阻止逆向工程是窺視軟體内部機理,但盾與矛的對抗總是無休止的,并沒有任何一種主動型軟體保護手段能真正徹底阻斷逆向工程,是以另一種“以人為本”的被動型軟體保護技術開始走向鬥争舞台的中央。

被動型軟體保護手段基于一個假設,即逆向工程師已經通過各種辦法突破了主動型軟體保護措施,可以随心所欲地觀察一切,此時“擋”已經擋不住了,隻能采取“藏”的政策,通過提高核心代碼的隐蔽性來提高逆向工程在閱讀反彙編代碼階段的時間成本投入,間接起到軟體保護的效果。被動型軟體保護手段具體實作方式主要有亂序和混淆。

亂序是指在在程式執行流中添加跳轉指令,如jmp,通過這些跳轉指令将一個從上至下執行的完整代碼塊切分成若幹執行先後順序不一緻的代碼片段。亂序能夠一定程度上增加反彙編代碼的閱讀難度,但若隻是簡單地植入無條件跳轉很容易被識别和去除,是以工業級的保護産品往往通過采用條件跳轉的方式增加識别難度。

基于虛拟機的軟體保護技術

混淆是指通過加入無意義代碼(又稱為花指令或垃圾代碼)或者有意義代碼,增加反彙編碼的了解難度。俗話說要藏好一棵樹,最好的地點一定是森林,混淆技術就是通過代碼膨脹增加反彙編代碼的總量,為隐藏核心代碼構造出一片代碼“森林”。早期增加的混淆代碼為無意義代碼,實作類nop操作的執行效果,如push指令和pop指令搭配使用,但這類代碼無實際意義,去除後并不會對軟體運作産生影響,是以有經驗的逆向工程師往往會首先去除這些無意義的混淆代碼才開始進行反彙編碼的閱讀工作,使混淆技術失去效果。為了確定混淆代碼不被去除,工業級的保護産品更傾向于采用有意義的混淆代碼,核心思想是等價替換,通過多條指令實作核心代碼中一條指令的效果,如最簡單的指派指令moveax,3可以替換成xoreax,eax;inceax;inceax;inceax這四條指令,操作效果一樣,但指令數量翻了兩番。被動型軟體保護技術究竟能否對軟體安全起到實質性的作用,業界一直存在争論。反對的觀點主要集中在認為被動型軟體保護技術隻是提高了閱讀反彙編代碼的難度和數量,讓人“眼花”而已,并沒有任何實質性的效果。本文認為,軟體安全不該簡單了解成讓軟體絕對安全不可攻破,而實際該是攻方與防方、投入與産出的反複博弈的過程,攻方人力的投入自然也是成本之一。一個人機關時間内閱讀代碼的數量是固定的,是以,提高了閱讀反彙編代碼的難度和數量,也就提高了閱讀反彙編代碼的時間,提高了攻方人力成本的投入,對軟體安全是有切實效果的。

虛拟機軟體保護技術

虛拟機軟體保護的思想

虛拟機軟體保護技術是被動型軟體保護技術的分支,具體來說是添加有意義的混淆代碼的一種變型使用。

虛拟機技術目前在軟體領域應用廣泛,根據應用層級不同,基本可分為硬體抽象層虛拟機、作業系統層虛拟機和軟體應用層虛拟機。用于保護軟體安全的虛拟機屬于軟體應用層虛拟機,同層的虛拟機還包括進階語言虛拟機,如java程式語言運作環境jvm和.net程式語言運作環境CLR,後者采用虛拟機的原因是便于移植,是以編譯器沒有直接生成可直接在機器上執行的nativecode,而改為生成中間代碼byte-code,再通過在不同機器環境下安裝對應版本的虛拟機對byte-code進行解釋執行,進而實作跨平台運作。

用于保護軟體安全的虛拟機采用類似的流程。虛拟機保護軟體首先會對被保護的目标程式的核心代碼進行“編譯”——需要注意的是,這裡被編譯的不是源檔案,而是二進制檔案——并生成效果等價的byte-code,然後為軟體添加虛拟機解釋引擎。使用者最終使用軟體時,虛拟機解釋引擎會讀取byte-code,并進行解釋執行,進而實作使用者體驗完全一緻的執行效果。

虛拟機軟體保護的實作

編譯生成byte-code

要設計一套虛拟機保護軟體,首先要設計一套虛拟機指令,也即是byte-code的指令集表,生成byte-code的過程,實際是将原始機器指令流等價轉譯成虛拟機指令流的過程。

虛拟機指令集表應滿足以下兩條設計原則:

第一條設計原則是虛拟機指令集表與原始機器指令集表越正交越好,安全系數越高。最壞的情況是虛拟機指令集表與原始機器指令集表為一一對應的關系,采用這種指令集的虛拟機保護程式安全系數趨近與零,對于逆向工程師而言隻需要進行簡單的換算,即可還原出原始代碼。

另一條設計原則是應盡可能地具備圖靈完備性,能夠完整地表達出原始機器指令的所有可能表達。圖靈完備性越好,則虛拟機保護引擎的保護的覆寫範圍越廣,健壯性越高。理想狀态下,虛拟機指令集應完整地實作對原始機器指令集的等價替代,需要完全滿足圖靈完備性。但實際上完整替代的代價過高甚至不太可能實作,如x86指令集的FCLEX、FPTAN等指令,仿真難度較高,且核心代碼使用這類指令的可能性很小,綜合效費比考慮,虛拟機指令集通常并不涵蓋這些“生僻”指令。對于不能仿真的指令,可以采取退出虛拟機執行,擷取執行結果再進入虛拟機的方法解決。

解釋執行byte-code

在軟體運作時,編譯産生的Byte-code由内嵌入軟體可執行檔案中的虛拟機解釋引擎,采用讀取-分派的方式解釋執行。

虛拟機解釋引擎分為兩大部分,分别為Dispatcher和handle。

基于虛拟機的軟體保護技術

Dispatcher的中文字面意思為“分派器”,相當于虛拟機解釋引擎的CPU,負責讀取Byte-code,并指派對應的handle進行解釋執行。Handle的中文字面意思為“處理”,實際作用為虛拟機指令通過平台nativecode(如PC平台即為x86指令)的實作。Handle的數量與虛拟機指令集的指令數量是一緻的。

虛拟機的進入和退出問題

軟體保護虛拟機與進階語言虛拟機并不完全一樣,主要展現在進階語言虛拟機由始至終均在虛拟機環境下執行,但軟體保護虛拟機必須經曆本地環境與虛拟機環境的切換,為了保證執行結果的一緻性,必須要求虛拟機環境能夠正确擷取和還原本地環境的執行上下文。較為便捷的方法是采用堆棧機模型,即虛拟機基于堆棧來進行資料操作。進入虛拟機前,先将本地環境壓棧,虛拟機直接以棧位址執行指令流操作,退出虛拟機後,再一一出棧,進而保證了上下文在不同執行環境的無縫切換。

繼續閱讀