Android 逆向基礎知識

一. 初識Apk、Dalvik字節碼以及Smali

Dalvik(Android操作系統的虛擬機)

Dalvik VM是基於寄存器的,而JVM是基於棧的。 Dalvik有專屬的文件執行格式dex(dalvik executable),而JVM執行的則是Java字節碼,Dalvik VM比JVM速度更快,佔用空間更少。
通過Dalvik的字節碼我們不能直接看到原來的邏輯代碼,這時需要藉助如ApkTool或dex2jar+jd-gui工具來查看。但是,最終我們修改Apk需要操作的文件是 .smali文件,而不是導出來的Java文件重新編譯。

Smali–破解的重中之重

Smali、Baksmali分別是指安卓系統裏的Java虛擬機(Dalvik)所使用的一種 .dex 格式文件的彙編器、反彙編器。其語法是一種寬鬆式的Jasmin/dedexer語法,而且它實現了 .dex格式所有功能(註解、調試信息、線路信息等)。
當我們對Apk文件反編譯後,便會生成此類的文件。在Dalvik字節碼中,寄存器都是32位的,能夠支持任何類型,64位類型(Long/Double)用兩個寄存器表示;Dalvik字節碼有兩種類型:原始類型、引用類型(包括對象和數組)。

二. 初識Apk、Dalvik字節碼以及Smali

1. 原始類型:

B- - -byte

C- - -char

D- - - double

F- - - float

I- - -int

J- - -long

S- - - short

V- - -void

Z- - -boolean

XXX- - -array

Lxxx/yyy- - -object

最後兩項:數組的表示方式是:在基本類型前加上前中括號“[”,例如int數組和float數組分別表示爲:[I、[F;對象的表示則以L作爲開頭,格式是LpackageName/objectName;(注意必須有個分號跟在最後),例如String對象在smali中爲:Ljava/lang/String;,其中java/lang對應java.lang包,String就說定義在該包中的一個對象。

類裏面的內部類又如何在smali中引用呢?答案是LpackageName/objectNamesubObjectName;subObjectName;。也就是在內部類前加“”符號。

方法的定義一般爲:

Func-Name(Para-Type1Para-Typr2Para-Type3…)Return-Type

注意參數與參數之間沒有任何分隔符,例如:

1). hello ()V
     沒錯,這就是void hello()。

2). hello (III)Z
     這個則是boolean hello(int,int,int)。

3). hello (Z[I[ILjava/lang/String;J)Ljava/lang/String;
    上面表示:String hello(boolean,int[],int[],String,long)

2. Smali基本語法

 .field private isFlag:z             定義變量
 .method               方法
 .parameter           方法參數
 .prologue             方法開始
 .line 123               此方法位於第123行
 invoke-super       調用父函數
 const/high16 v0,0x7f03.      把0x7f03賦值給v0
 invoke-direct         調用函數
 return-void            函數返回void
 .end method         函數結束
 new-instance        創建實例
 iput-object            對象賦值
 iget-object            調用對象
 invoke-static         調用靜態函數

條件跳轉分支:

"if-eq vA,vB,:cond_" 如果vA不等於vB則跳轉到:cond_

"if-ne vA,vB,:cond_" 如果vA不等於vB則跳轉到:cond_

"if-lt vA,vB,:cond_" 如果vA小於vB則跳轉到:cond_

"if-gt vA,vB,:cond_" 如果vA大於vB則跳轉到:cond_

"if-le vA,vB,:cond_" 如果vA小於等於vB則跳轉到:cond_

"if-eqz vA,:cond_" 如果vA等於0則跳轉到:cond_

"if-nez vA,:cond_" 如果vA不等於0則跳轉到:cond_

"if-ltz vA,:cond_" 如果vA小於0則跳轉到:cond_

"if-gez vA,:cond_" 如果vA大於等於0則跳轉到:cond_

"if-gtz vA,:cond_" 如果vA大於0則跳轉到:cond_

"if-lez vA,:cond_" 如果vA小於等於0則跳轉到:cond_

三. 深入smali文件

Smali中的包信息:

  .class public Lcom/aaaaa;
  .super Lcom/bbbbb;
  .source "ccccc.java"

  這是一個由ccccc.java編譯得到的smali文件(第3行)
  它是com.aaaaa這個package下的一個類(第1行)
  繼承自com.bbbbb這個類(第2行)

smali中的聲明

一般來說在Smali文件中是這個樣子的:

image

   這個聲明是內部類的聲明:aaa這個類它有兩個成員內部類——qqq和www。

寄存器知識補充:

在smali裏的所有操作都必須經過寄存器來進行:本地寄存器用v開頭數字結尾的符號來表示,如v0、v1、v2、… 參數寄存器則使用p開頭數字結尾的符號來表示,如p0、p1、p2、… 特別注意的是,p0不一定是函數中的第一個參數,在非static函數中,p0代指“this”,p1表示函數的第一個參數,p2代表函數中的第二個參數 … 而在static函數中p0纔對應第一個參數(因爲Java的static方法中沒有this方法)。

寄存器簡單實例分析:

    const/4 v0,0x1
    iput-boolean v0,p0,Lcom/aaa;->IsRegistered:Z

  上面兩句smali代碼,首先它使用了v0本地寄存器,並把值0x1存到v0中,然後第二句用iput-boolean這個指令把v0中的值存放到com.aaa.IsRegistered這個成員變量中。

  即相當於:this.IsRegistered=true;(上面說過,在非static函數中p0代表的是“this”,在這裏就是com.aaa實例)。

smali中的成員變量:

 成員變量的格式是:

   .field public/private [static] [final] varName:<類型>

對於不同的成員變量也有不同的指令:

  一般來說,獲取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等。

  操作的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。

  沒有“-object”後綴的表示操作的成員變量對象是基本數據類型,帶“-object”表示操作的成員變量是對象類型,特別地,boolean類型則使用帶“-boolean”的指令操作。

Smali成員變量指令簡析(一)

 sget-object v0, Lcom/aaa;->ID:Ljava/lang/String;

 sget-object 就是用來獲取變量值並保存到緊接着的參數的寄存器中,在本例中,它獲取ID這個String類型的成員變量並放到v0這個寄存器中。
 注意:前面需要該變量所屬的類的類型,後面需要加一個冒號和該成員變量的類型,中間是“->”表示所屬關係。

Smali成員變量指令簡析(二)

 iget-object v0, p0, Lcom/aaa;->view:Lcom/aaa/view;

 可以看到iget-object指令比sget-object多了一個參數,就是該變量所在類的實例,在這裏就是p0即“this”。
 獲取array的話我們用aget和aget-object,指令使用和上述一致。

Smali成員變量指令簡析(三)

 put指令的使用和get指令是統一的,如下:

   const/4 v3,0x0

   sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer;

  相當於:this.timer=null;
  注意:這裏因爲是賦值object,所以是null

Smali成員變量指令簡析(四)

 .local v0, args:Landroid/os/Message;

 const/4 v1,0x12
 
 input v1,v0, Landroid/os/Message;->what:I

 相當於:args.what = 18;(args是Message的實例)

四. Smali函數分析

Smali中的函數調用

smali中的函數和成員變量一樣也分爲兩種類型,分別爲direct和virtual。

direct method和virtual method的區別:

簡單來說,direct method就是private函數,其餘的public和protected函數都屬於virtual method。所以在調用函數時,有invoke-direct、invoke-virtual,另外還有invoke-static、invoke-super以及invoke-interface等幾種不同的指令。

當然其實還有invoke-XXX/range指令的,這是參數多於4個的時候調用的指令,比較少見,瞭解下即可。

1)invoke-static:用於調用static函數的

例如:

invoke-static {}, Lcom/aaa;->CheckSignature()Z

這裏注意到invoke-static後面有一對大括號“{}”,其實是調用該方法的實例+參數列表,由於這個方法既不需要參數也是static的,所以{}內爲空,再看一個:

const-string v0, “NDKLIB”

invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

這個是調用static void System.loadLibrary(String)來加載NDK編譯的so庫的方法,同樣也是這裏v0就是參數“NDKLIB”了。

2)invoke-super:調用父類方法用的指令,一般用於調用onCreate、onDestroy等方法。

3)invoke-direct:調用private函數:

  invoke-direct {p0}, Landroid/app/TabActivity;-><init>()V

這裏init()就是定義在TabActivity中的一個private函數。

4)invoke-virtual:用於調用protected或public函數,同樣注意修改smali時不要錯用invoke-direct或invoke-static:

  sget-object v0, Lcom/dddd;->bbb:Lcom/ccc;
  invoke-virtual {v0,v1}, Lcom/ccc;->Messages(Ljava/lang/Object;)V

這裏相信大家都很清楚了:

 v0是bbb:Lcom/ccc
 v1是傳遞給Messages方法的Ljava/lang/Object參數。

5)invoke-xxxxx/range:當方法的參數多於5個時(含5個),不能直接使用以上的指令,而是在後面加上“/range”,range表示範圍,使用方法也有所不同:

invoke-direct/range {v0…v5}, Lcmb/pb/ui/PBContainerActivity;->h(ILjava/lang/CharSequence;Ljava/lang/String;Landroid/content/Intent;I)Z

需要傳遞v0到v5一共6個參數,這時候大括號內的參數採用省略形式,且需要連續。

Smali中函數返回的結果的操作:

在Java代碼中調用函數和返回函數結果可以用一條語句完成,而在Smali裏則需要分開來完成,在使用上述指令後,如果調用的函數返回非void,那麼還需要用到move-result(返回基本數據類型)和move-result-object(返回對象)指令。

const-string v0, “Eric”
   
invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;

move-result-object v2

v2保存的就是調用t方法返回的String字符串。

Smali中函數實體分析—if函數分析:

.method private ifRegistered()Z

.locals 2         //在這個函數中本地寄存器的個數

.prologue          //指定程序的開始處,混淆過後的代碼可能會沒有這一說明

const/4 v0, 0x1.       //v0賦值爲1

.local v0, tempFlag:Z

if-eqz v0, :cond_0             //判斷v0是否等於0,等於0則跳到cond_0執行

const/4 v1, 0x1              //符合條件分支

:goto_0                         //標籤

return v1                 //返回v1的值

:cond_0                 //標籤

const/4 v1, 0x0         //cond_0分支

goto :goto_0            //跳到goto_0執行 即返回v1的值 這裏可以改成return v1,也是一樣的

.end method

Smali中函數實體分析—for函數分析:

const/4 v0, 0x0              //v0=0

.local v0, i:I

:goto_0

if-lt v0, v3, :cond_0      //v0小於v3 則跳轉到cond_0並執行分支:cond_0

return-void

:cond_0                 //標籤

iget-object v1, p0, Lcom/aaa/MainActivity;->listStrings:Ljava/util/List;      //引用對象

const-string v2, “Eric”

invoke-interface {v1, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z     //List是接口,執行接口方法add

add-int/lit8 v0, v0, 0x1          //將第二個v0寄存器中的值,加上0x1的值放入第一個寄存器中,實現自增長

goto :goto_0                 //回去:goto_0標籤

六. 瞭解JAVA反編譯工具

總的來說Java程序和Android程序的區別在於Android程序是基於組件、基於配置的。

常量池是由常量項(cp_info)組成的。常量池類似於結構體,主要有兩個元素。一個是tag,還有一個是info[]數組。tag用來判定info[]數組的類型。tag對應的類型圖如下:

class_index的作用就是指向CONSTANT_class_index

name_and_type_index的作用就是指向CONSTANT_NameAndType_info。

sget-object 就是用來獲取變量值並保存到緊接着的參數的寄存器中。

invoke-direct:沒有被覆蓋方法的調用,即不用動態根據實例所引用的調用,編譯時,靜態確認的,一般是private或方法。

invoke-virtual:虛方法調用,調用的方法運行時確認實際調用,和實例引用的實際對象有關,動態確認的,一般是帶有修飾符protected或public的方法。

Move-result-object v2 就是將上一條指令的結果存放在v2寄存器中。

ARM彙編

ARM具有31個通用寄存器,6個狀態寄存器

ARM處理器支持以下運行模式

1. 用戶模式:ARM處理器正常的程序執行狀態。
2. 快速中斷模式:用於高速數據傳輸或通道處理。
3. 外部中斷模式:用於通用的中斷處理。
4. 管理模式:操作系統使用的保護模式。
5. 數據訪問終止模式:當數據或指令預取終止時進入該模式,可用於模擬存儲及存儲保護。
6. 系統模式:運行具有特權的操作系統任務。
7. 未定義指令中止模式:當未定義的指令執行時進入該模式。

ARM彙編語言程序結構

#####(1)處理器架構定義

.arch armv5te  @處理器架構
.fpu softvfp   @協處理器類型
.ebi_attribute 20,1    @接口屬性
.ebi_attribute 21,1
.ebi_attribute 23,1
.ebi_attribute 24,1
.ebi_attribute 25,1
.ebi_attribute 26,1
.ebi_attribute 30,1
.ebi_attribute 18,1

.arch指定了ARM處理器架構。

.armv5te表示本程序在armv5te架構處理器上運行。

.fpu指定了協處理器的類型。

softvfp表示使用浮點運算庫來模擬協處理器運算。

.ebi_attribute 指定了一些接口屬性。

(2)段定義
.section    定義只讀數據,屬性是默認
.text       定義了代碼段
(3)註釋與標號

註釋方法:

/*...*/    多行註釋
@          單行註釋

標號:<標號名>:

例如:

loop:
...
end loop
(4)彙編器指令

程序中所有以"."開頭的指令都是彙編指令,它們不屬於ARM指令集。

部分彙編器指令:

.file: 指定了源文件名
.align: 代碼對齊方式
.ascii: 聲明字符串
.global: 聲明全局變量
.type: 指定符號的類型
(5)字符串與參數傳遞

聲明函數的方法:

.global 函數名
.type 函數名,%function
函數名:
    <...函數體...>
聲明一個實現兩個數相加的函數的代碼:
.global MyAdd
.type MyAdd,%function
MyAdd:
    add r0,r0,r1
    mov pc,lr

ARM彙編規定:R0-R3這4個寄存器用來傳遞函數調用的第1到第4個參數,超過的參數通過堆棧來傳遞。

(6)ARM處理器尋址方式
  1. 立即尋址

    mov R0,#1234  @# 作爲前綴,表示十六進制時以“0x”開頭
    
  2. 寄存器尋址

    mov R0,R1
    
  3. 寄存器位移尋址

    五種位移操作:

    (1)LSL:邏輯左移,移位後寄存器空出的低位補0

    (2)LSR:邏輯右移,移位後寄存器空出的高位補0

    (3)ASR:算數右移,移動過程中符號位不變。如果操作數是整數,則移位後空出的高位補0,否則補1

    (4)ROR:循環右移,移位後移出的低位填入移位空出的高位

    (5)RRX:帶擴展的循環右移,操作數右移移位,移位空出的高位用C標誌的值填充。

    例如:

    mov R0,R1,LSL #2
    
  4. 寄存器間接尋址

    LDR R0,[R1]
    
  5. 基址尋址

    LDR R0,[R1,#-4]
    
  6. 多寄存器尋址

    LDMIN R0,{R1,R2,R3,R4}
    LDM是數據加載指令
    指令的後綴IA表示每次執行完成加載操作後R0寄存器的值自增1
    ARM中,字表示的是一個32位
    R1=[R0]
    R2=[R0+#4]
    R3=[R0+#8]
    R4=[R0+#12]
    

    注:+#4是因爲32位佔4個字節

  7. 堆棧尋址

    STMFD SP!,{R1-R7,LR}  入棧,多用於保存子程序“現場”
    LDMFD SP!,{R1-R7,LR}  出棧,多用於回到子程序現場
    
  8. 塊拷貝尋址

    塊拷貝可實現連續地址數據從存儲器的某一位置拷貝到另一位置。

    LDMIN R0! , {R1-R3}  @從寄存器指向的存儲單元中讀取3個字到R1-R3寄存器。
    
  9. 相對尋址

    程序計數器PC的當前值爲基地址,指令中的地址標號作爲偏移量,將兩者相加之後得到操作數的有效地址。

ARM和Thumb指令

Thumb是16位的ARM彙編。

如同樣的beq,bne這兩個彙編指令,用ARM的4個HEX數表示時,其HEX值爲0A,1A,而當用2個HEX數表示時,其HEX值爲D0,D1。

在動態調試的時候,IDA無法分辨ARM和Thumb指令。所以需要人工去進行糾正和調整。

ARM寄存器

R0-R7:    通用寄存器
R8-R10:   不常用的通用寄存器
R11:      基址寄存器(FP)
R12:      暫時寄存器(IP)
R13:      堆棧指針(SP)
R14:      鏈接寄存器(LR)
CPSR:     狀態寄存器

ARM指令集

B      無條件跳轉
BL     帶鏈接的無條件跳轉
BLX    帶狀態的無條件跳轉
BNE    不相等跳轉
BEQ    相等跳轉

寄存器交互指令

LDR                    #從存儲器中加載數據到寄存器
LDR R1, [R2]           #把R2指向的地址的數據給R1

STR                    #把寄存器的數據存儲到存儲器
STR R1, [R2]           #在R2指向的地址存儲R1

LDM                    #將存儲器的數據加載到一個寄存器列表
LDM R0, {R1,R2,R3}     #把R0中的數據一次加載到R1,R2,R3

SDM                    #將一個寄存器列表的數據存儲到指定的存儲器
SDM R0, {R1,R2,R3}     #把R1,R2,R3加載到R0單元

PUSH                   #入棧
POP                    #出棧

數據傳送指令

MOV       #將立即數或寄存器的數據傳送到目標寄存器
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章