基於ARM處理器的反彙編器軟件簡單設計及實現

寫在前面

2012年寫的畢業設計,僅供參考html

反彙編的目的

缺少某些必要的說明資料的狀況下, 想得到某些軟件系統的源代碼、設計思想及理念, 以便複製, 改造、移植和發展;ios

從源碼上對軟件的可靠性和安全性進行驗證,對那些直接與CPU 相關的目標代碼進行安全性分析;算法

涉及的主要內容

  1. 分析ARM處理器指令的特色,以及編譯之後可執行的二進制文件代碼的特徵;
  2. 將二進制機器代碼通過指令和數據分開模塊的加工處理;
  3. 分解標識出指令代碼和數據代碼;
  4. 而後將指令代碼反彙編並加工成易於閱讀的彙編指令形式的文件;

下面給出個示例,彙編源代碼,對應的二進制代碼,以及對應的反彙編後的結果編程

源代碼:sass

二進制代碼:安全

 

反彙編後的結果:數據結構

 

反彙編軟件要完成的工做就是在指令和數據混淆的二進制BIN文件中,分解並標識出指令和數據,而後反彙編指令部分,獲得易於閱讀的彙編文件,以下圖:ide

ARM體系結構及指令編碼規則分析

略,請參考相關資料,如ARM Limited. ARM Architecture Reference Manual [EB/OL]. http://infocenter.arm.com/等;函數

主要可參考下圖,ARM指令集的編碼:ui

ARM可執行二進制BIN文件分析

目前主要的ARM可執行文件種類:

ELF文件格式:Linux系統下的一種經常使用、可移植目標文件格式;

BIN文件:直接的二進制文件,內部沒有地址標記,裏面包括了純粹的二進制數據;通常用編程器燒寫時,從0開始,而若是下載運行,則下載到編譯時的地址便可;

HEX格式:Intel HEX文件是記錄文本行的ASCII文本文件;

本文主要研究BIN文件的反彙編;

BIN映像文件的結構

  ARM程序運行時包含RO,RW,ZI三部份內容,RO(READONLY),是代碼部分,即一條條指令,RW(READWRITE),是數據部分,ZI,是未初始化變量。其中RO和RW會包含在映像文件中,由於一個程序的運行是須要指令和數據的,而ZI是不會包含在映像文件的,由於其中數據都爲零,程序運行前會將這部分數據初始化爲零。
  ARM映像文件是一個層次性結構的文件,包括了域(region),輸出段(output section)和輸入段(input section)。一個映像文件由一個或者多個域組成,每一個域最多由三個輸出段(RO,RW,IZ)組成,每一個輸出段又包含一個或者多個輸入段,各個輸入段包含了目標文件中的代碼和數據。

  • 域(region):一個映像文件由一個或多個域組成。是組成映象文件的最大結構。所謂域指的就是整個bin映像文件所在的區域,又分爲加載域和運行域,通常簡單的程序只有一個加載域。
  • 輸出段(output section):有兩個輸出段,RO和RW。
  • 輸入段(input section):兩個輸入段,CODE和DATA部分,CODE部分是代碼部分,只讀的屬於RO輸出段,DATA部分,可讀可寫,屬於RW輸出段。

ARM的BIN映像文件的結構圖

舉一個例子,ADS1.2自帶的examples裏的程序

AREA Word, CODE, READONLY       ; name this block of code
num     EQU     20                ; Set number of words to be copied
        ENTRY                     ; mark the first instruction to call
start
        LDR     r0, =src            ; r0 = pointer to source block
        LDR     r1, =dst            ; r1 = pointer to destination block
        MOV     r2, #num            ; r2 = number of words to copy
wordcopy
        LDR     r3, [r0], #4            ; a word from the source
        STR     r3, [r1], #4            ; store a word to the destination
        SUBS    r2, r2, #1             ; decrement the counter
        BNE     wordcopy             ; ... copy more
stop
        MOV     r0, #0x18           ; angel_SWIreason_ReportException
        LDR     r1, =0x20026        ; ADP_Stopped_ApplicationExit
        SWI     0x123456            ; ARM semihosting SWI
        AREA BlockData, DATA, READWRITE
src      DCD     1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst      DCD     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        END

能夠看出,該程序由兩部分組成,CODE和DATA,即代碼部分和數據部分。其中代碼部分,READONLY,屬於RO輸出段;數據部分,READWRITE,屬於RO輸出段。

接下來再看看上述代碼通過編譯生成的BIN映像文件的二進制形式,及該映像文件反彙編後的彙編文件,以下圖:

   

  從圖中咱們很容易發現,BIN文件分紅了兩部分,指令部分和數據部分。先看一下左圖,從中咱們發現,BIN文件的第一條指令編碼是0xe59f0020,即右圖中的00000000h到00000003h,因爲存儲方式的緣由,小端模式,指令的低字節存放在低地址部分,不過這不影響咱們的分析。在BIN文件中從00000000h開始一直到00000027h都是指令部分,即RO輸出段,最後一條指令0xef123456存儲在在BIN文件的00000024h到00000027h。剩下的爲數據部分,即RW輸出段,有興趣的讀者能夠對照源代碼一一查找之間的對應關係。

ARM反彙編軟件設計要解決的主要問題

1、指令與數據的分離

  馮·諾依曼機器中指令和數據是不加區別共同存儲的,以 0、1 二進制編碼形式存在的目標代碼對於分析人員來講,很難讀懂其含義。二進制程序中指令和數據混合存放,按地址尋址訪問,反彙編若是採起線性掃描策略,將沒法判斷讀取的二進制編碼是指令仍是數據,從而沒法實現指令和數據的分離。
  那麼,怎樣才能實現指令和數據的分離?
  衆所周知,凡是指令,控制流是必經之處,凡是數據,數據流是必到之處,存取指令必定會訪問,對於通常指令,控制流是按地址順序遞增而走向的,只有在出現各類轉移指令時,控制流纔出現偏離。所以,抓住控制流這一線索,即跟蹤程序的控制流[9]走向而遍歷整個程序的每一條指令,從而達到指令與數據分開的目的。
    怎樣才能跟蹤程序的控制流呢?
  通常來講控制流與控制轉移指令有關,控制轉移指令通常可分爲兩大類:

  1. 單分支指令,即直接跳轉,如B;BL;MOV PC,**;LDR PC,**等等;
  2. 雙分支指令,有條件的跳轉,如BNE;MOVNE  PC,**等等

      

  當該指令爲雙分支指令時,會有兩個轉向地址,咱們把條件知足時的轉向地址稱爲顯示地址,條件不知足時的地址稱爲隱式地址。
     在跟蹤控制流的過程當中,還要設置三個表:
  (1)段表,將全部轉移指令除條件轉移的轉移地址填入此表包括本指令地址和轉向地址。其實能夠不要這個表,可是加進去,會獲得若干段的代碼段,比較清晰明瞭。
  (2)返回表,用於記錄程序調用時的返回地址。
  (3)顯示錶,碰到雙分支指令時,將其顯示地址和現場(程序各寄存器的值)填入該表中。
  以上都準備好以後,就能夠開始跟蹤程序控制流了具體步驟以下:

    (1)將程序的起始地址填入段表,且做爲當前地址。
    (2)按當前地址逐條分析指令類型,若非無條件轉移指令及二分支指令,則直至終結語句並轉(7), 不然轉(3)。
    (3)若爲無條件轉移指令(B指令,MOV PC,0x16等),則將此指令所在地址填段表,其顯式地址填段表,且將顯式地址做爲當前地址,而後轉(2),不然轉(4)。
    (4)若爲無條件轉移指令子程序調用指令(BL指令),則將此指令所在地址填段表,返回地址和現場信息填入返回表,顯式地址填段表,且將顯式地址做爲當前地址,而後轉 (2), 不然轉(5)。
    (5)若爲無條件轉移指令中的返回指令(MOV PC,LR),則在返回地址表中按「後進先出」原則找到返回地址,將此指令所在地址填段表,其返回地址填段表,且將返回地址做爲當前地址,而後轉(2),不然轉(6)。
    (6)若爲二叉點指令(BEQ,MOVEQ PC,0x16等等), 則將顯式地址和現場信息填入顯式表,而後將隱式地址做爲當前地址轉(2)。
    (7)判顯式表空否,若空則算法終止,不然從顯式表中按「先進先出」順序取出一個顯式地址做爲當前地址,且恢復當時的現場信息轉(2)。

  通過以上處理,能夠遍歷到全部的指令,且當訪問到該條指令後,要把改地址處的指令標記爲指令。接下來能夠採用線性掃描策略,當遇到標記爲指令的二進制代碼時,把它反彙編成彙編指令便可。

  不過,在實現跟蹤程序控制流過程當中還有一個比較難處理的問題,就是間接轉移類指令的處理,由於這類指令的轉移地址隱含在寄存器或內存單元中,沒法由指令碼自己判斷其值,並且這些隱含在寄存器內或內存單元中的值每每在程序執行時,被動態地進行設置或修改,所以很難判斷這類指令的轉移地址,從而難以完整的肯定程序的指令區。
    本軟件處理這個問題的方法是設置多個寄存器全局變量,在緊跟程序控制流的過程當中,實時更新各寄存器的值,所以能夠肯定這類指令的轉移地址。

2、代碼部分的反彙編

  ARM指令集中的天天指令都有一個對應的二進制代碼,如何從二進制代碼中翻譯出對應的指令,即代碼部分反彙編所要完成的工做。

  ARM 指令的通常格式能夠表示爲以下形式:

  <opcode>{condition}{S}<operand0>{!},<operand1>{, <operand2>}

  指令格式中<·>符號內的項是必須的,{·}符號內的項是可選的,/ 符號表示選其中之一,其中opcode 表示指令操做符部分,後綴 conditon、S 及!構成了指令的條件詞,operand0、operand一、operand2 爲操做數部分。指令反彙編就是將指令的各組成部分解析出來。

  爲了使指令反彙編更加清晰、高效,能夠採用分類的思想方法來解決該問題,把那些具備相同編碼特徵的指令分紅同一種類型的指令,而後爲每一類指令設計一個與之對應的處理函數。

  指令的反彙編的步驟,首先是判斷哪條指令,能夠由操做符及那些固定位來肯定,而後是指令條件域的翻譯,這個與二進制編碼也有惟一的對應關係,而後操做數的翻譯,在操做數的翻譯過程當中,可能涉及到各類操做數的計算方法,移位操做等狀況。

ARM反彙編軟件的設計

ARM反彙編軟件的整體設計方案以下圖:

       其中,各模塊主要完成如下功能:

  • 輸入模塊:要解決如何從外部文件中讀取二進制格式的文件,另外對讀進來的目標代碼要合理組織和存儲,便於接下來的後續處理。
  • 指令和數據分離模塊:在內存中對讀進來的二進制源代碼進行分析,指令流能夠到達的部分標識爲代碼,最後剩下的指令流未通過的部分爲數據,這樣就分離出代碼和數據。
  • 指令反彙編模塊:對分離出來的代碼段部分,按照各條指令編碼的對應關係進行反彙編,生成目標代碼對應的彙編文件,包括ARM指令地址,ARM指令,能夠用於閱讀。
  • 輸出模塊:即要將反彙編後的彙編文件顯示在窗口,且能夠生成文件在磁盤上。

ARM處理器反彙編軟件流程

ARM反彙編軟件的總體工做流程以下圖所示,首先是讀取目標二進制文件至內存,而後採用兩遍掃描的策略,第一遍掃描的目的是區分指令和數據,第二遍掃描是將指令部分反彙編成彙編指令形式,將數據部分直接翻譯成數據,最後將結果輸出顯示。

模塊設計-輸入模塊

輸入模塊的流程圖以下圖所示,首先從目標二進制代碼中讀取四個字節存放到Content對象裏,再將Content對象存放到Vector容器裏,而後按上述操做繼續讀取文件,直到文件結尾。這裏有一點要說明的是,因爲時間等緣由,本軟件只考慮32位ARM指令的反彙編,Thumb指令反彙編不會涉及,因此每次都讀取4個字節。

模塊設計-指令和數據分離模塊

指令和數據分離模塊的設計以下圖所示,因爲指令和數據分離模塊設計的關鍵是跟蹤程序的控制流,標識出每一條指令,因此此模塊的關鍵就是要遍歷每一條指令,而遍歷每一條指令的關鍵是要緊跟PC值。

關於段表,顯示錶,返回表的概念前面已經說明過了。另外,在程序流程圖中的「根據具體轉移指令,更新PC值,顯示錶,返回表」,這裏的具體狀況以下:

(1)若爲無條件轉移指令(B指令等,MOV PC,0x16),則將此指令所在地址填段表,其顯式地址填段表,且將顯式地址做爲當前PC地址。

(2)若爲無條件轉移指令子程序調用指令(BL指令),則將此指令所在地址填段表,返回地址填入返回地址表,顯式地址填段表,且將顯式地址做爲當前PC地址

(3)若爲無條件轉移指令中的返回指令(MOV PC,LR),則在返回地址表中按「後進先出」原則找到返回地址,將此指令所在地址填段表,其返回地址填段表,且將返回地址做爲當前PC地址。

(4)若爲二叉點指令(BEQ,MOVEQ PC,0x16等等), 則將顯式地址填入顯式地址表(還要保存當時的寄存器值),而後將隱式地址做爲當前PC地址。

模塊設計-反彙編模塊

在反彙編模塊中除了要反彙編指令,還要翻譯數據。總的設計思想是依次從裝滿對象的contentVector容器中依次取出對象,判斷該對象是指令仍是數據,指令的話,就反彙編成彙編指令形式,數據的話,直接翻譯成數據的值,以下圖所示。

上述流程圖是總的反彙編模塊,在圖中的指令反彙編部分,是該模塊的重點,也是整個反彙編軟件設計的重點,其流程圖以下圖所示。

模塊設計-輸出模塊

關於顯示模塊,比較簡單,直接把結果顯示出來便可,該模塊跟第三個模塊反彙編模塊聯繫最緊密,在反彙編模塊中,其實已經包含了輸出模塊。不過輸出模塊也有本身的特殊任務。好比說如何以十六進制形式顯示一個32位數(顯示地址的時候)以及如何顯示一個8位數(顯示讀進來數據的編碼,由於是一個字節一個字節讀進來的,存放的時候也是一個字節一個字節存放在Content對象中),以下圖就是一個顯示32位數的流程圖。

ARM反彙編軟件的具體實現

  ARM反彙編軟件主要有四個大模塊組成,二進制可執行代碼讀取模塊、指令和數據分離模塊、指令反彙編模塊、輸出模塊。本章節將結合程序中的源代碼主要介紹ARM反彙編軟件各模塊具體實現。因爲時間等因素影響,本軟件設計只考慮到了ARM指令,THUMB指令不在考慮範圍,不過其實都是一樣道理的事情,ARM指令可以實現的話,THUMB指令添加進去只是時間的問題。

1、數據組織方式

讀進來的內容的組織以下所示:

class Content
{
public:
        unsigned int            addr;                //地址
        bool                isInstruction;        //指令標記
        bool                isVisited;            //訪問標記    
        bool                isHasLable;            //是否有標籤標記
        unsigned int         firstB;                //第1個字節
        unsigned int         secondB;
        unsigned int         thirdB;
        unsigned int             fourthB;
};

在該類中,addr表示該內容的地址,是邏輯字節地址;isInstruction判斷該內容是不是指令代碼;isVisited判斷是否被訪問過;isHasLable,判斷該指令前面要不要加一個標籤,firstB表示內容從左到右表示的第一個字節,secondB、thirdB、fourthB以此類推。

讀源文件類

class MyRead
{
public:
           vector <Content>  contentVector;        //內容存儲容器
        void readSourceFile(char * addr);        //讀取源文件函數
};

內容容器contentVector,用於存儲從源文件讀進來的內容,4個字節爲一個元素;readSourceFile方法,讀取源文件二進制代碼,並按個字節一個存儲在容器裏。

指令編碼內容的組織

指令編碼的組織其實仍是很關鍵的,好的組織方式能夠大大節約後續工做的時間,後續開發或者維護都會變得更簡單。因爲篇幅關係,這裏將介紹一些比較經常使用到的指令內容的組織。這裏介紹的指令內容的組織都將採用結構體的形式,其實也能夠用類來實現。

typedef unsigned  int QByte;                    //32位無符號數

(1)SWI指令

typedef struct swi                                
{
        QByte comment : 24;
        QByte mustbe1111 : 4;
        QByte condition : 4;
} SWI;

SWI指令的編碼格式:

由指令的編碼格式咱們能夠看到,comment的內容表示immed_24,正好24位;mustbe1111是固定的,全部SWI指令的[27:24]都是1111,condition表示條件域,總共有16種狀況,用4位表示便可。

(2) 分支指令

typedef struct branch {        
        QByte offset : 23;
        QByte sign : 1;
        QByte L : 1;
        QByte mustbe101 : 3;
        QByte condition : 4;
} Branch;

分支指令的編碼格式:

從指令的編碼格式中,咱們發現,Offset實際上是肯定了轉向地址的絕對值;sign代表正負數;L用於區分是否要把返回地址寫入R14中;mustbe101代表這是一條分支指令,固定的,cond是條件域。

(3)加載/存儲指令

typedef struct singledatatrans {    
        QByte offset : 12;
        QByte Rd : 4;
        QByte Rn : 4;
        QByte L : 1;
        QByte W : 1;
        QByte B : 1;
        QByte U : 1;
        QByte P : 1;
        QByte I : 1;
        QByte mustbe01 : 2;
        QByte condition : 4;
} SingleDataTrans;

加載/存儲指令編碼格式:

其中offset, I, P, U, W, Rn共同決定計算出內存中的地址,Rd是目的寄存器,

L位用於區分是LDR仍是STR指令;B位用於區分unsigned byte (B==1) 和 a word (B==0)。

(4)數據處理指令

typedef struct dataproc {
        QByte operand2 : 12;
        QByte Rd : 4;
        QByte Rn : 4;
        QByte S : 1;
        QByte opcode : 4;
        QByte I : 1;
        QByte mustbe00 : 2;
        QByte condition : 4;
} DataProc;

對應的編碼格式以下:

其中operand2是第二操做數,其計算方式也有多種形式,可參考ARM手冊上的「Addressing Mode 1 - Data-processing operands on page A5-2」;Rd爲目的寄存;Rn爲第一操做數;S位用於區分是否影響CPSR;opcode用於明確是那種數據操做,如MOV,ADD等;I用於區分第二操做數是寄存器形式仍是當即數形式;mustbe00是固定的;Cond爲條件域。

段表、顯示錶、返回表的組織

爲簡單起見,本程序中這些表通通用容器Vector來實現

typedef struct Elem{
         int re[16];
          unsigned int addr;
}Elem;
static vector <unsigned int>     segmentTable;        //段表
static vector < unsigned int >             returnAddrTable;        //返回表
static vector <Elem>         showAddrTable;        //顯示錶

如上所述,其中Elem結構體用於存儲顯示錶裏面的元素。segmentTable爲段表,returnAddrTable爲返回表,showAddrTable爲顯示錶。

全部的填表操做都是用push_back 函數來實現的,還有就是操做顯示錶和返回表時要記住返回表是後進先出的。

指令共用體

typedef union access32 {
        QByte qbyte;
        SWI swi;                            //SWI instruction
        CPRegTrans cpregtrans;             // Coprocessor registesr transfers
        CPDataOp cpdataop;                    // Coprocessor data processing
// Coprocessor load/store and double register transfers[6]
        CPDataTrans cpdatatrans;  
//Branch and branch with link and change to Thumb
        Branch branch;                        
        Undefined4  undefined4;                //Undefined Ins[4]
        LoadStoreMultiple loadstoremultiple;    //load/store multiple
        Undefined47 undefined47;            //Undefined instruction [4,7]
        Undefined   undefined;                //Undefined instruction
//Load,store immediate offset,Load,store register offset
        SingleDataTrans singledatatrans;
        MSRImeToSReg  msrimetosreg;        //Move immediate to status register
        Undefined3    undefined3;                //Undefined instruction [3]
        DataProc dataproc;
        DataProcImmediate dataprocimme;
        MultiplyExtraLoadStore  multiextraloadstore;    //Multiplies, extra load/stores: 
        MiscellanesInstructOne    miscelinstrone;        //Miscellaneous instructions
        DataProcRegShift        dataprocregshift;
        MiscellanesInstructTwo    miscelinstrtwo;        //Miscellaneous instructions:            DataProcImmeShift     dataprocimmeshit;
} Access32;
View Code

這個指令共用體的設置應該說是十分巧妙的,涵蓋了全部類型的指令。在反彙編過程當中,首先是從目標二進制代碼中讀取一個32位數到內存,而後將這個32位數從內存拷貝到共用體變量中,因爲共用體的存儲空間是共享的,定義一個上面的共用體,其實也就只是4個字節大小的空間,可是他能夠指代任何一條指令,所以用這個共用體變量來判斷讀進來的32位數是那條指令很是方便和直觀,具體判斷的將在接下來的指令反彙編模塊介紹。

其餘數據結構

協處理器寄存器:

static const char *cregister[16] = {    
        "cr0",  "cr1",  "cr2",  "cr3",
        "cr4",  "cr5",  "cr6",  "cr7",
        "cr8",  "cr9",  "cr10", "cr11",
        "cr12", "cr13", "cr14", "cr15"
};

寄存器:

static const char *registers[16] = {
        "R0", "R1", "R2", "R3",
        "R4", "R5", "R6", "R7",
        "R8", "R9", "R10","R11",
        "R12", "R13", "R14", "PC"
};

條件域:

static const char *condtext[16] = {
        "EQ",                    //"Equal (Z)" 
        "NE",                    //"Not equal (!Z)" 
        "CS",                    //"Unsigned higher or same (C)" },
        "CC",                    //"Unsigned lower (!C)" },
        "MI",                    //"Negative (N)" },
        "PL",                    //"Positive or zero (!N)" },
        "VS",                    //"Overflow (V)" },
        "VC",                    // "No overflow (!V)" },
        "HI",                    // "Unsigned higher (C&!Z)" },
        "LS",                    //"Unsigned lower or same (!C|Z)" },
        "GE",                    // "Greater or equal ((N&V)|(!N&!V))" },
        "LT",                    //"Less than ((N&!V)|(!N&V)" },
        "GT",                    //"Greater than(!Z&((N&V)|(!N&!V)))" },
        "LE",                    //"Less than or equal (Z|(N&!V)|(!N&V))" },
        "",                        //"Always" },         //AL,能夠省略掉
        "NV"                   //, "Never - Use MOV R0,R0 for nop" }
};

數據處理指令的操做碼編碼,從0到15,之因此弄成如下形式是爲了增長程序的可讀性。

typedef enum operatecode
{
        AND,EOR ,SUB ,RSB                    //AND=0;    EOR=1.
           ,ADD,ADC ,SBC ,RSC
           ,TST, TEQ,CMP,CMN
           ,OPR ,MOV ,BIC,MVN
}OperCode;

數據處理指令字符:

static const char *opcodes[16] =
{
        "AND", "EOR", "SUB", "RSB",
        "ADD", "ADC", "SBC", "RSC",
        "TST", "TEQ", "CMP", "CMN",
        "ORR", "MOV", "BIC", "MVN"
};

注意裏面的順序,要跟其編碼規則對應。

2、二進制可執行代碼讀取模塊

二進制可執行代碼的讀取模塊主要完成從外部文件中讀取二進制代碼到內存中,並要合理組織數據。

讀取函數以下所示:

void MyRead::readSourceFile(char *addr)
{
        ifstream f1(addr,ios::binary);
        if(!f1)
        {
            cerr<<addr<<"不能打開"<<endl;
            return;
        }
        unsigned char x;    
        int k=0,i=0;
        while(f1.read(( char *)&x,1))
        {
            Content temp;                // 定義一個Content臨時對象
            if(i==0)                
            {
                temp.fourthB=x;            //一個字裏的第4個字節
            }
            else if(i==1)
            {
                temp.thirdB=x;         //一個字裏的第3個字節

        }
        else if(i==2)
        {
            temp.secondB=x;    //一個字裏的第2個字節
        }
        else if(i==3)                
        {
            temp.firstB=x;        //一個字裏的第1個字節
        }
        i++;
        if(i==4)                
        {
            i=0;
            temp.addr=4*k;         //給地址賦值
            temp.isVisited = false;
            temp.isHasLable = false;
            k++;
            this->contentVector.push_back(temp);        //將temp存放到容器裏
            Content temp;
        }
    }
    f1.close();
}
View Code

讀取模塊首先要作的是打開目標文件,而後一個字節一個字節的讀取進來,這裏所謂的第一個字節FirstB指的是一個32位數從左往右數算起第一個的,總共四個字節。而後要設置一個Content 類型的temp中間值,將讀進來的字節依次往temp賦值,賦完四個字節後,把地址,是否訪問標誌等等也都初始化一下;最後將這個temp中間值壓入contentVector容器中,這個容器裏面存放的就是目標代碼讀進來的二進制代碼,四個字節爲一個元素存放。

3、指令和數據分離並標識模塊

前面已經講了指令和數據分離的思想,主要是跟蹤程序的控制流,這裏講結合程序的實現繼續說明一下。首先看一下指令和數據分離的函數。

void MyDisassemble::separateI_D(MyRead &read)
{
        int k = 0;
        segmentTable.push_back(0);                        
        r[15] = 0;            //PC設爲0
        disPart(read);
        while(true)
        {
            if(k == showAddrTable.size()) break;        //顯示錶都訪問結束
            for(int i = 0;i<16;i++) r[i] = showAddrTable[k].re[i];    //恢復現場
            if(read.contentVector[((showAddrTable[k].addr)/4)].isVisited == false) 
//是否訪問 
disPart(read);    
                k++;
        }
}
void MyDisassemble::disPart(MyRead &read)
{
            for(int i =r[15]/4;;i = r[15]/4)
            {
                unsigned int foo=((read.contentVector[i].firstB)<<24)+
((read.contentVector[i].secondB)<<16)+
((read.contentVector[i].thirdB)<<8)+read.contentVector[i].fourthB;
                read.contentVector[i].isInstruction = true;    //設置是指令                            read.contentVector[i].isVisited = true;    //已訪問過
                disArm(foo,read);        //反彙編指令
                if ((0x0F&read.contentVector[i].firstB)==0x0f) //程序結束
                {    
                    segmentTable.push_back(r[15]);                break;
                }
                r[15]=r[15]+4;                                                }
}
View Code

上述separateI_D函數的目的是爲了跟蹤程序的控制流,遍歷每一條指令(有點相似有向圖的遍歷),從而標識出指令。首先是segmentTable.push_back(0),將起始地址填入段表;而後按當前地址逐條分析指令類型disPart(read);在disPart函數中,當訪問這條指令時,首先要作的是把該指令的isInstruction和isVisited標誌設置爲true;而後disARM,disARM是反彙編函數,這個函數能夠反彙編出該條指令是哪條指令,在下面的反彙編模塊將會有更加詳細的說明,在這裏只要知道他能夠識別出是哪條指令便可。

當反彙編出來的指令是無條件轉移指令時,以B指令爲例,要執行如下代碼:

segmentTable.push_back(r[15]);            
    segmentTable.push_back(r[15] + addr);    
r[15]=(r[15]+addr-4);    
    read.contentVector[(r[15]/4)].isHasLable = true;

r[15]指pc,r[15] + addr爲顯示地址。(1)首先將此指令所在地址填入段表,(2)而後顯示地址填入段表,(3)再而後顯示地址做爲當前地址,因爲已經設置PC自動加了,因此這裏先減一下,(4)同時轉向地址處的指令前面是一個標籤,把isHasLable設置爲true;

       若爲無條件轉移指令子程序調用指令,以BL爲例,要執行如下代碼:

r[14] = r[15] + 4;                    
    segmentTable.push_back(r[15]);            
    returnAddrTable.push_back(r[14]);        
    segmentTable.push_back(r[15] + addr);    
    r[15]=(r[15]+addr-4);
    read.contentVector[((r[15]+4)/4)].isHasLable = true;

各條語句的意思依次是

(1)保存返回地址到R14  

(2)此指令所在地址填入段表

(3)返回地址填入返回地址表

(4)顯示地址填入段表

(5)顯示地址做爲當前地址,因爲已經設置PC自動加了,因此這裏先減一下

(6)該地址處指令前面有個標籤。

若爲無條件轉移指令中的返回指令,以MOV PC,LR爲例,以下:

unsigned int raddr;
    if(returnAddrTable.size()>0) {
            raddr = returnAddrTable[returnAddrTable.size()-1];  
return AddrTable.pop_back(); } segmentTable.push_back(r[
15]); segmentTable.push_back(raddr); r[15] = raddr-4; read.contentVector[(raddr/4)].isHasLable = true;

依次作了如下事情,

(1)返回地址表中按「後進先出」原則找到返回地址

(2)刪除返回表最後一個元素

(3)指令所在地址填段表

(4)返回地址填段表

(5)設置當前的PC值

(6)設置標籤標誌

若爲二分支指令,以BNE爲例,以下:

Elem temp;temp.addr = r[15] + addr; for(int ii=0;ii<16;ii++) temp.re[ii] = r[ii];
    showAddrTable.push_back(temp);    
    read.contentVector[(( r[15] + addr)/4)].isHasLable = true;

依次作了如下事情,

(1)保存「現場」

(2)顯式地址填入顯式表(包括當時的寄存器值)

(3)設置標籤標誌。

       若不是轉移指令,則繼續按PC值自增下去,直到終結語句,而後還要判斷顯示錶是否空,這裏用了個K變量來實現,實際上並無真的從容器中刪除取出的顯示錶裏的元素。if(k == showAddrTable.size()) break;用來判斷是否顯示錶裏的地址都訪問過了。若是沒有,則繼續按這條指令的地址disPart下去。直到把顯示錶裏的地址都訪問一遍。

       執行完以上的代碼後,就實現了按控制流遍歷每條指令,是指令的基本上也都作上了標識。

4、指令反彙編模塊

指令反彙編模塊在宏觀上主要體如今disArm這個函數上,代碼以下:

 char * MyDisassemble::disArm(unsigned int uint,MyRead &read,int type)
{
    Access32 instruction;
    memcpy(&instruction, &uint,sizeof(uint));                                                        //拷貝數據
    if (0xF == instruction.swi.mustbe1111)                                                            //SWI指令 
    {
        decode_swi (instruction.swi);
    } 
    
    else if ( 0xE == instruction.cpregtrans.mustbe1110 )                                    // Coprocessor registesr transfers或者Coprocessor data processing
    {
        if (0x1 == instruction.cpregtrans.mustbe1)                                                //coprocessor register transfers
        {                     
            /* Coprocessor Register Transfer */
             decode_cpregtrans (instruction.cpregtrans);
        } 
        else                                                                                                        //coprocessor data processing
        {      
            /* Coprocessor Data Operation */
             decode_cpdataop (instruction.cpdataop);                                
        }
    }
    else if ( 0x6 == instruction.cpdatatrans.mustbe110  )                                        // Coprocessor load/store and double register transfers[6]
    {
        /* Coprocessor Data Transfer */
         decode_cpdatatrans (instruction.cpdatatrans);
    }
    else if ( 0x5 == instruction.branch.mustbe101 )                                                 //Branch and branch with link and change to Thumb //Branch and branch with link
    {
         decode_branch (instruction.branch,read,type);
    }
    else if (  (0xF==instruction.undefined4.condition1111) &&  ( 0x4==instruction.undefined4.mustbe100)   )    //Undefined Ins[4]
    {
         decode_unknown (instruction);
    }
    else if (0x4==instruction.loadstoremultiple.mustbe100)                                    //load/store multiple
    {
        /* load/store mutiple */
         decode_loadstoremultiple (instruction.loadstoremultiple);
    }
    else if (  (0xF==instruction.undefined47.condition1111)&& (1==instruction.undefined47.mustbe1) ) //Undefined Instruction[4,7]
    {
         decode_unknown (instruction);
    }
    else if ( (0x3==instruction.undefined.mustbe011)&& (1==instruction.undefined.mustbe1) ) //undefined Instruction
    {
        decode_unknown (instruction);
    }
    else if (0x1==instruction.singledatatrans.mustbe01)                                    //Load/Store register offset, Load /Store Immediate offset
    {
        /* Single Data  */
         decode_singledatatrans (instruction.singledatatrans,read, type);
    }
    else if ( ( 6==instruction.msrimetosreg.mustbe00110)&& (2==instruction.msrimetosreg.mustbe10)&& (0==instruction.msrimetosreg.SBO) )                            
    {                                                                                                                    //Move immediate to status register
         decode_msrimetoreg(instruction.msrimetosreg);
    }
    else if ( (0==instruction.undefined3.mustbe00)    && (1==instruction.undefined3.mustbe001)    && (2==instruction.undefined3.mustbe10) )                            
    {                                                                                                                        //undefined instruction[3]
         decode_unknown (instruction);

    }
    else if(1==instruction.dataprocimme.mustbe001)                                                //Data Processing immediate[2]
    {
         decode_dataproc (instruction.dataproc,read,type);

    }
    else if ( (0==instruction.multiextraloadstore.mustbe000)&& (1==instruction.multiextraloadstore.mustbeH1)&& (1==instruction.multiextraloadstore.mustbeL1) )                        
    {                                                                                                                    //Multipies ,extra load/stores 
        if ( 0 ==instruction.multiextraloadstore.dontcareB )
        {
            //Muptiply (accumulate
            //multiply long
            //Swap/swap byte
            //decode_multiply
            if ( 1==(instruction.multiextraloadstore.dontcareC & 0x10000))
            {
                SingleDataSwap *psingledataswap = (SingleDataSwap*)&instruction;
                if ( ( 0==psingledataswap->mustbe00)&&(9==psingledataswap->mustbe00001001) && (2==psingledataswap->mustbe00010) )
                {
                    decode_singledataswap ( *psingledataswap );
                }
            }
            else
            {
                //multiply
                Multiply*  pmultiply = (Multiply*)&instruction;
                if( (0==pmultiply->mustbe0000)&&(9==pmultiply->mustbe1001) )
                decode_multiply ( *pmultiply );
            }
        }
        else
        {//extra load store 
            decode_singledatatrans (instruction.singledatatrans, read,type);
        }
        /* Single Data Transfer */
        
    }
    else if ( ( 0== instruction.miscelinstrone.mustbe000)&&(0==instruction.miscelinstrone.mustbe0H)&&(0==instruction.miscelinstrone.mustbe0L)    &&(2==instruction.miscelinstrone.mustbe10)&&(1==instruction.miscelinstrone.mustbe1)     )                                
    {                                                                                                                            ///miscellaneous insturction
      decode_miscelinstrone( instruction.miscelinstrone );
    }
    else if ( (0==instruction.dataprocregshift.mustbe000)&& (0==instruction.dataprocregshift.mustbe0)     && (1==instruction.dataprocregshift.mustbe1) )                                                                                                                                                        
    {                                                                                                                    //data processing register shift[2]   
        decode_dataproc (instruction.dataproc,read,type);
    }
    else if( (0==instruction.miscelinstrtwo.mustbe000)&& (2==instruction.miscelinstrtwo.mustbe10)&&(0==instruction.miscelinstrtwo.mustbe0H)&& (0==instruction.miscelinstrtwo.mustbe0L) )                                        
    {                                                                                                                    //miscellaneous instructions;
        decode_miscelinstrone( instruction.miscelinstrone );
    }
    else if  ( (0==instruction.dataprocimmeshit.mustbe000)                                        //data processing immediate shift
        && (0==instruction.dataprocimmeshit.mustbe0 ) )
    {
         decode_dataproc (instruction.dataproc,read,type);
    }
    return (disasmstr);
}
View Code

在前面,咱們已經介紹了ARM指令的編碼規則,從圖2.2中,咱們能夠看到圖中每一行都是一種指令的集和,或者說都是同一類型的。當獲得一個32位數時,怎樣進行反彙編獲得其彙編形式的彙編指令呢?

(1)首先將這個32位數從內存中拷貝到instruction指令共用體變量中

memcpy(&instruction, &uint,sizeof(uint));之因此拷貝到這個共用體裏面是由於它已經定義了全部類別的指令,這樣比較直觀清晰。

(2)判斷該指令是哪一種指令,這步是有必定規則的,要從下往上以此判斷,具體可看附錄裏的代碼,目的是爲了考慮到全部的指令。區分是哪條指令的依據是圖2.2中的那些固定位,只要讀進來的這32位數中的某幾位跟表中的那幾位符合,就能夠肯定該指令屬於哪一類。好比說MOV  r0, #0,該指令編碼爲0xe3a00000,因爲[27:25]==001,因此爲Data processing immediate [2]這種類型,之因此不是Undefined instruction [3](它的[27:25]==001),是由於在判斷是否是Undefined instruction [3]前面已經判斷過不是了,在(1)裏已經說過要從下往上判斷,就是這個緣由。

(3)知道哪一種類型的指令後,要繼續區分是這種類型指令裏的哪條指令,而後再區分條件域,是否影響CPRS[13],操做數等等。以(2)裏的MOV  r0, #0爲例,因爲其[24:21]==1101,因此能夠判斷其爲MOV指令,條件域[31:28]==1110,表示老是執行,不需添加條件。20位S爲0,表示不影響CPRS,[15:12]==0000,表示目的寄存器爲R0,第二操做數也可計算得出爲0.最後反彙編的結果爲MOV  r0, #0。

下面將結合程序代碼重點探討一下數據處理指令中MOV指令的反彙編,由於這種指令是最多見的,明白了這種指令的反彙編,同他指令也可同理得出。

       上述已經講過,通過附錄裏disArm函數的判斷以後,就能夠知道是哪一種指令,所在知道了是數據處理指令後,要作如下工做。

首先判斷是否是MOV指令,if (dataproc.opcode == MOV) 便可完成判斷,在肯定是MOV指令後,還要判斷第二操做數的形式,是當即數形式仍是寄存器形式,這直接影響到後面的反彙編。if (dataproc.I)便可完成上述判斷,真的話說明是當即數,假的話,說明是寄存器形式。

若是是當即數形式,執行如下代碼:

sprintf(disasmstr, " %s%s%s %s, %s",opcodes[dataproc.opcode],
condtext[dataproc.condition],(sFlag[dataproc.S]),registers[dataproc.Rd], 
dataoperandIm(dataproc) );
updateReg(dataproc,-1,read);

其中disasmstr 保存的是反彙編後的字符,opcodes[dataproc.opcode]是操做符,condtext[dataproc.condition],是執行條件,(sFlag[dataproc.S])是S標誌位, registers[dataproc.Rd]爲目的寄存器,dataoperandIm(dataproc)這個函數執行後將返回第二操做數的值。函數源代碼以下,其目的很簡單,就是講一個8位數擴展成32位,再循環右移偶數位,獲得一個32位數。updateReg(dataproc,-1,read);是更新各寄存器的值,跟蹤程序控制流的時候須要。

static char *dataoperandIm (DataProc dataproc)                
{
        QByte data, rotate;
            rotate = (dataproc.operand2 >> 8) * 2;
            data = dataproc.operand2 & 0xFF;
            sprintf(workstr, " 0x%lX",
                (data >> rotate)|(data<<(32-rotate)));
        return (workstr);
}

若是是寄存器形式,將會比較複雜,由於第二操做數會有多種狀況,移位啊什麼的,反彙編過程以下。

       若是目的寄存器大於15,表示無效的指令。

if (dataproc.Rd > 15 )            
  sprintf(disasmstr, "Invalid opcode");

(1)寄存器的值就是操做數

        if ( 0==(dataproc.operand2&0xFF0) ) 
        {
            updateReg(dataproc,r[dataproc.operand2&0x0F],read);
            sprintf(disasmstr, " %s%s%s %s, %s",
                        opcodes[dataproc.opcode],
                        condtext[dataproc.condition],
                        (sFlag[dataproc.S]),
                        registers[dataproc.Rd],
                        registers[dataproc.operand2&0x0F]);
        }

(2)寄存器循環右移

    else if ( 0x70==(dataproc.operand2 &0x0F0) )  
{ sprintf(disasmstr,
" %s%s%s %s, %s, ror %s", opcodes[dataproc.opcode], condtext[dataproc.condition], (sFlag[dataproc.S]), registers[dataproc.Rd], registers[dataproc.operand2&0x0F], registers[(dataproc.operand2&0xF00)>>8] ); }

除此以外,還有如下狀況,因爲處理方式相似,這裏就不一一列出代碼了。

(3)<Rm>, LSL #<shift_imm>   當即數邏輯左移

(4)<Rm>, LSL <Rs>                寄存器邏輯左移

(5)<Rm>, LSR #<shift_imm>   當即數邏輯右移

(6)<Rm>, LSR <Rs>               寄存器邏輯右移

(7) <Rm>, ASR #<shift_imm> 當即數算數右移

(8) <Rm>, ASR <Rs>              寄存器算數右移

(9)<Rm>, ROR #<shift_imm>   當即數循環右移

通過上述處理後,基本上把MOV指令的全部狀況都考慮進去了,MOV指令的反彙編工做也基本完成,其餘種類型的指令基本上也是這樣一個流程。

其實,二進制機器代碼與彙編指令都是惟一對應的,指令的反彙編最重要的就是要找到這種對應關係,知道每一位所表示的意思,惟一肯定出操做符,操做數等。

5、輸出顯示模塊

       本軟件的輸出顯示模塊是用MFC完成的,在vs2008環境下新建一個MFC工程,選擇對話框框,而後添加菜單,編輯框,還有消息處理函數。主要處理三個消息,點擊Read File菜單項、Sequence Disassembling菜單項、ControlFlowDisassembling菜單項,分別用三個函數完成,OnFileSelectfile(),OnOperateSequence(),OnOperateControlflowdisassembling()。

順序反彙編策略的界面以下圖,其中左邊第一列是指令或數據的地址,從0開始;第二列是指令的二進制編碼,是存放在二進制可執行BIN文件裏的直接內容,第三列是反彙編後的彙編指令。由圖可見,採用順序掃描策略反彙編後的結果沒有區分出指令和數據,把許多數據也反彙編成指令,可讀性比較差,總體比較混亂。

基於控制流的反彙編界面以下圖,其中從左邊開始第一列是指令或數據的地址(有些地方有標籤),第二列若是是指令的話顯示指令的二進制編碼,若是是數據的話直接顯示數據的值,第三列顯示的是指令的彙編形式,數據不顯示。從圖中咱們能夠發現,基於控制流的反彙編技術比較好的實現了指令和數據的分離,總體感受相對清楚,可讀性較好。

  至此,本軟件的輸出模塊就結束了,其實,關於輸出模塊的設計還有不少能夠改進的地方。好比能夠實現當點擊某個菜單項時,反彙編出一條指令,同時在右邊的Other顯示界面顯示出其內部各個寄存器的值,即單步反彙編,相似調試過程;同時也能夠一步到底反彙編。因爲時間關係,本軟件在設計時,寄存器值的更新並無涉及全部的指令,只有當執行一些關鍵或簡單指令時才更新寄存器的值,因此右邊的界面也就沒有實現了。

不足

因爲時間、水平和經驗有限,許多方面仍有不足之處,有改進的餘地,好比

在跟蹤程序的控制流的過程當中,按理說當執行完每一條指令後,只要碰到會改變寄存器值的指令,都應該更新寄存器的值,但因爲時間等因素,只實現了部分指令;

並且在基於控制流的反彙編策略時,總共掃描了兩次目標代碼,效率比較低,理想的方法是隻掃描一遍;

另外本軟件沒有實現對THUMB指令的支持。

本文連接地址

http://www.cnblogs.com/chenpi/p/5199226.html

參考列表:

[1] 殷文建.面向ARM體系結構的代碼逆向分析關鍵技術研究[D]. 鄭州:中國人民解放軍信息工程大學,2010.

[2] 董仁飛 .反彙編技術在嵌入式軟件安全性分析中的應用[J]. 質量與可靠   性,2011,02:57-59.

[3] 方海義,陳利峯,塗時亮. 基於ARM嵌入式處理器的反彙編方法[J].計算機工程,2003,29(z1):159-161.

[4] 張不一樣,王虹,王開鑄.反彙編程序設計原理與實現方法[J].哈爾濱工業大學學報,1989,6:21-25.

[5] 李駒光. ARM應用系統開發詳解——基於S3C4510B的系統設計[M].清華大學出版社,2004:1-18.

[6] ARM Limited. ARM Architecture Reference Manual [EB/OL]. http://infocenter.arm.com/,2005.

[7] 許敏,陳前斌.靜態反彙編算法研究[J].計算機與數字工程,2007,35(5):13-16.

[8] 吳金波, 蔣烈輝, 趙鵬.基於控制流的靜態反彙編算法研究 [J]. 計算機工程與應用, 2005, 41(30):89-90.

[9] 吳金波,蔣烈輝.反靜態反彙編技術研究 [J]. 計算機應用, 2005, 25(3):623-625.

[10] 歐陽清華.反彙編原理及其實現技術[M].武漢大學出版社, 1992:13-48.

[11] 王長青.一種微處理器彙編和反彙編系統設計與研究[D]. 西安:西北工業大學,2001.

[12] B.Schwarz, S.Debray, G.Andrews. Disassembly of Executable Code Revisited[C]. Proceedings of the Ninth Working Conference on Reverse Engineering,2002:45-54.

[13] Andrew N.Sloss, Dominic Symes, Chris Wright. ARM System Developer’s Guide Designing and Optimizing System Software [M]. Morgan Kaufmann publications, 2004:47-53.

相關文章
相關標籤/搜索