天天看點

Android SO逆向1-ARM介紹0x00 概述0x01 ARM寄存器0x02 彙編語言0x03 建立Android NDK程式

原文: http://drops.wooyun.org/mobile/10009

0x00 概述

把之前學習SO逆向的筆記分享出來,内容比較簡單,大牛就可以略過了。

0x01 ARM寄存器

1.1.通用寄存器

1.未分組寄存器:R0~R7
2.分組寄存器:R8~812
R13:SP,常用作堆棧指針,始終指向堆棧的頂部,當一個資料(32位)推入堆棧時,SP(R13的值減4)向下浮動指向下一個位址,即新的棧頂,當資料從堆棧中彈出時,SP(R13的值加4)向上浮動指向新的棧頂。
R14:連接配接寄存器(LR),當執行BL子程式調用指令時,R14中得到R15(程式計數器PC)的備份,其他情況下,R14用作通用寄存器。
R15:程式計數器(PC):用于控制程式中指令的執行順序。正常運作時,PC指向CPU運作的下一條指令。每次取值後PC的值會自動修改以指向下一條指令,進而保證了指令按一定的順序執行。當程式的執行順序發生改變(如轉移)時,需要修改PC的值。
      

1.2.狀态寄存器

CPSR(R16):目前程式狀态寄存器,用來儲存ALU中的目前操作資訊,控制允許和禁止中斷、設定處理器的工作模式等。
SPSRs:五個備份的程式狀态寄存器,用來進行異常處理。當異常發生時,SPSR用于儲存CPSR的目前值,從異常退出時可由SPSR來恢複CPSR。
      
Android SO逆向1-ARM介紹0x00 概述0x01 ARM寄存器0x02 彙編語言0x03 建立Android NDK程式

N、Z、C、V均為條件碼标志位,他們的内容可被運算的結果所改變。

N:正負标志,N=1表示運算的結果為負,N=0表示運算的結果為正或0
Z:零标志,Z=1表示運算的結果為0,Z=0表示運算的結果為非0
C:進位标志,加法運算産生了進位時則C=1,否則C=0
  借位标志,減肥運算産生了借位則C=0,否則C=1
V:溢出标志,V=1表示有溢出,V=0表示無溢出
      

1.3.位址空間

程式正常執行時,每執行一條ARM指令,目前指令計數器增加4個位元組。
      

0x02 彙編語言

2.1.彙編指令格式

<opcode>{<cond>}{S}<Rd>,<Rn>{,<OP2>}
格式中<>的内容必不可少,{}中的内容可省略
<opcode>:表示操作碼,如ADD表示算術加法
{<cond>}:表示指令執行的條件域,如EQ、NE等。
{S}:決定指令的執行結果是否影響CPSR的值,使用該字尾則指令執行的結果影響CPSR的值,否則不影響
<Rd>:表示目的寄存器
<Rn>:表示第一個操作數,為寄存器
<op2>:表示第二個操作數,可以是立即數、寄存器或寄存器移位操作數





例子:ADDEQS R0,R1,#8;其中操作碼為ADD,條件域cond為EQ,S表示該指令的執行影響CPSR寄存器的值,目的寄存器Rd為R0,第一個操作數寄存器Rd為R1,第二個操作數OP2為立即數#8
      

2.2.指令的可選字尾

S:指令執行後程式狀态寄存器的條件标志位将被重新整理
    ADDS R1,R0,#2
!:指令中的位址表達式中含有!字尾時,指令執行後,基址寄存器中的位址值将發生變化,變化的結果是:基址寄存器中的值(指令執行後)=指令執行前的值 + 位址偏移量
    LDR R3,[R0,#2]!    指令執行後,R0 = R0 + 2
      

2.3.指令的條件執行

指令的條件字尾隻是影響指令是否執行,不影響指令的内容
      
條件碼 助記符字尾 标志 含義
0000 EQ Z置位 相等
0001 NE Z清零 不相等
0010 CS C指令 無符号數大于或等于
0011 CC C清零 無符号數小于
0100 MI N置位 負數
0101 PL N清零 正數或零
0110 VS V置位 溢出
0111 VC V清零 未溢出
1000 HI C置位Z清零 無符号數大于
1001 LS C清零Z置位 無符号數小于或等于
1010 GE N等于V 帶符号數大于或等于
1011 LT N不等于V 帶符号數小于
1100 GT Z清零且(N等于V) 帶符号數大于
1101 LE Z置位或(N不等于V) 帶符号數小于或等于
1110 AL 忽略 無條件執行

例子:ADDEQ R4,R3,#1 相等則相加,即CPSR中Z置位時該指令執行,否則不執行。

2.4.ARM指令分類

助記符 指令功能描述 助記符 指令功能描述
ADC 帶進位加法指令 MRC 從協處理器寄存器到ARM寄存器的資料傳輸指令
ADD 加法指令 MRS 傳送CPSR或SPSR的内容到通用寄存器指令
AND 邏輯與指令 MSR 傳送通用寄存器到CPSR或SPSR的指令
B 分支指令 MUL 32位乘法指令
BIC 位清零指令 MLA 32位乘加指令
BL 帶傳回的分支指令 MVN 資料取反傳送指令
BLX 帶傳回和狀态切換的分支指令 ORR 邏輯或指令
BX 帶狀态切換的分支指令 RSB 逆向減法指令
CDP 協處理器資料操作指令 RSC 帶錯位的逆向減法指令
CMN 比較反值指令 SBC 帶錯位減法指令
CMP 比較指令 STC 協處理器寄存器寫入存儲器指令
EOR 異或指令 STM 批量記憶體字寫入指令
LDC 存儲器到協處理器的資料傳輸指令 STR 寄存器到存儲器的資料存儲指令
LDM 加載多個寄存器指令 SUB 減法指令
LDR 存儲器到寄存器的資料加載指令 SWI 軟體中斷指令
MCR 從ARM寄存器到協處理器寄存器的資料傳輸指令 TEQ 相等測試指令
MOV 資料傳送指令 TST 位測試指令

2.5.ARM尋址方式

尋址方式就是根據指令中操作數的資訊來尋找操作數實際實體位址的方式
      

2.5.1立即數尋址

MOV R0,#15       #15就是立即數
      

2.5.2寄存器尋址

ADD R0, R1, R2    将R1和R2的内容相加,其結果存放在寄存器R0中
      

2.5.3寄存器間接尋址

LDR R0, [R4]      以寄存器R4的值作為操作數的位址,在存儲器中取得一個操作數存入寄存器R0中
      

2.5.4寄存器移位尋址

ADD R0,R1,R2,LSL #1    将R2的值左移一位,所得值與R1相加,存放到R0中
MOV R0,R1,LSL R3    将R1的值左移R3位,然後将結果存放到R0中
      

2.5.5基址變址尋址

LDR R0,[R1,#4]    将R1的值加4作為操作數的位址,在存儲器中取得操作數放入R0中
LDR R0,[R1,#4]!   将R1的值加4作為操作數的位址,在存儲器中取得操作數放入R0中,然後R1 = R1+4
LDR R0,[R1],#4    R0 = [R1],R1 = R1 +4
LDR R0,[R1,R2]   R0 = [R1+R2]
      

2.5.6.多寄存器尋址

一條指令可以完成多個寄存器值的傳送(最多可傳送16個通用寄存器),連續的寄存器用“-”,否則用“,”
LDMIA R0!,{R1 - R4}   R1 = [R0],R2=[R0+4],R3=[R0+8],R4=[R0+12]
字尾IA表示在每次執行玩加載/存儲操作後,R0按自長度增加。
      

2.5.7.相對尋址

以程式計數器PC的目前值為基位址,指令中的位址标号作為偏移量,将兩者相加之後得到操作數的有效位址,如下圖的BL分支跳轉
     BL proc        跳轉到子程式proc處執行
     ...
proc MOV R0,#1
     ...
      

2.5.8.堆棧尋址

按先進先出的方式工作,堆棧指針用R13表示,總是指向棧頂,LDMFD和STMFD分别表示POP出棧和PUSH進棧
STMFD R13!,{R0 - R4};
LDMFD R13!,{R0 - R4};
      

2.6.資料處理指令

2.6.1. MOV指令

MOV {<cond>}{S} Rd,op2          将op2傳給Rd
MOV R1, R0          将寄存器R0的值傳到寄存器R1
MOV PC,R14          将寄存器R14的值傳到PC,常用于子程式傳回
MOV R1,R0,LSL #3    将寄存器R0的值左移3位後傳給R1
MOV R0,#5           将立即數5傳給R0
      

2.6.2. MVN指令

MVN {<cond>}{S}Rd, op2          将op2取反傳給Rd
MVN R0,#0           将0取反後傳給R0,R0 = -1
MVN R1,R2           将R2取反,結果儲存到R1
      

2.6.3. 移位指令

LSL 邏輯左移
LSR 邏輯右移
ASR 算術右移
ROR 循環右移
RRX 帶擴充的循環右移
      

2.6.4. ADD加法指令

ADD{<cond>}{S}Rd, Rn, op2
ADD R0,R1,R2            R0 = R1 + R2
ADD R0,R1,#5            R0 = R1 + 5
ADD R0,R1,R2,LSL #2     R0 = R1 + (R2左移2位)
      

2.6.5. ADC帶進位加法指令

ADC{<cond>}{S} Rd,Rn,op2    将Rn的值和操作數op2相加,再加上CPSR中C條件标志位的值,并将結果儲存到Rd中
例:用ADC完成64位加法,設第一個64位操作數儲存在R2,R3中,第二個64位操作數放在R4,R5中,結果儲存在R0,R1中
ADDS R0,R2,R4       低32位相加,産生進位
ADC R1,R3,R5        高32位相加,加上進位
      

2.6.6. SUB減法指令

SUB{<cond>}{S} Rd,Rn,op2    Rd = Rn - op2
SUB R0,R1,R2            R0 = R1 - R2
SUB R0,R1,#6            R0 = R1 -6
SUB R0,R2,R3,LSL #1     R0 = R2 - (R3左移1位)
      

2.6.7. SBC帶借位減法指令

SBC{<cond>}{S} Rd,Rn,op2    把Rn的值減去操作數op2,再減去CPSR中的C标志位的反碼,并将結果儲存到Rd中,Rd = Rn - op2 - !C
例:用SBC完成64位減法,設第一個64位操作數儲存在R2,R3中,第二個64位操作數放在R4,R5中,結果儲存在R0,R1中
SUBS R0,R2,R4       低32位相減,S影響CPSR
SBC R1,R3,R5        高32位相減,去除C的反碼
      

2.6.8. RSC帶借位的逆向減法指令

RSC{<cond>}{S} Rd,Rn,op2    把操作數op2減去Rn,再減去CPSR中的C标志位的反碼,并将結果儲存到Rd中,Rd = op2 - Rn - !C
      

2.6.9. 邏輯運算指令

AND{<cond>}{S} Rd,Rn,op2    按位與,Rd = Rn AND op2
ORR{<cond>}{S} Rd,Rn,op2    按位或,Rd = Rn OR op2
EOR{<cond>}{S} Rd,Rn,op2    按位異或,Rd = Rn EOR op2
      

2.6.10. CMP比較指令

CMP{<cond>}{S} Rd,Rn,op2    将Rn的值和op2進行比較,同時更新CPSR中條件标志位的值(實際上是執行一次減法,但不存儲結果),當操作數Rn大于op2時,則此後帶有GT字尾的指令将可以執行(根據相應的指令判斷是否執行,如GT,LT等)。
CMP R1,#10                  比較R1和10,并設定CPSR的标志位
ADDGT R0,R0,#5              如果R1>10,則執行ADDGT指令,将R0加5
      

2.6.11. CMN反值比較指令

CMN{<cond>}{S} Rd,Rn,op2    将Rn的值和op2取反後進行比較,同時更新CPSR中條件标志位的值(實際上将Rn和op2相加),後面的指令就可以根據條件标志位決定是否執行。  
CMN R0,R1       将R0和R1相加,并設定CPSR的值
      

2.6.12. MUL/MLA/SMULL/SMLAL/UMULL/UMLAL乘法指令

MUL     32位乘法指令
MLA     32位乘加指令
SMULL   64位有符号數乘法指令
SMLAL   64位有符号數乘加指令
UMULL   64位無符号數乘法指令
UMLAL   64位無符号數乘加指令
MUL{<cond>}{S} Rd,Rm,Rs         Rd = Rm * Rs
MULS R0,R1,R2
MLA{<cond>}{S} Rd,Rm,Rs,Rn      Rd = (Rm * Rs) + Rn
MLAS R0,R1,R2,R3
      

2.7.資料加載與存儲指令

助記符 說明 操作
LDR{}Rd,addr 加載字資料 Rd = [addr]
LDRB{}Rd,addr 加載無符号位元組資料 Rd = [addr]
LDRT{}Rd,addr 以使用者模式加載字資料 Rd = [addr]
LDRBT{}Rd,addr 以使用者模式加載無符号位元組資料 Rd = [addr]
LDRH{}Rd,addr 加載無符号半字資料 Rd = [addr]
LDRSB{}Rd,addr 加載有符号位元組資料 Rd = [addr]
LDRSH{}Rd,addr 加載有符号半字資料 Rd = [addr]
STR{}Rd,addr 存儲字資料 [addr] = Rd
STRB{}Rd,addr 存儲位元組資料 [addr] = Rd
STRT{}Rd,addr 以使用者模式存儲字資料 [addr] = Rd
STRBT{}Rd,addr 以使用者模式存儲位元組資料 [addr] = Rd
STRH{}Rd,addr 存儲半字資料 [addr] = Rd
LDM{}{type}Rn{!},regs 多寄存器加載 reglist = [Rn...]
STM{}{type}Rn{!},regs 多寄存器存儲 [Rn...] = reglist
SWP{}Rd,Rm,[Rn] 寄存器和存儲器字資料交換 Rd=[Rn],[Rn]=Rm(Rn!=Rd或Rm)
SWP{}B Rd,Rm,[Rn] 寄存器和存儲器位元組資料交換 Rd = [Rn],[Rn] = Rm(Rn!=Rd或Rm)

2.7.1. LDR/STR字資料加載/存儲指令

LDR/STR{<cond>}{T}Rd,addr       LDR指令用于從存儲器中将一個32位的字資料加載到目的寄存器Rd中,當程式計數器PC作為目的寄存器時,指令從存儲器中讀取的字資料被當做目的位址,進而可以實作程式流程的跳轉。
STR指令用于從源寄存器中将一個32位的字資料存儲到存儲器中,和LDR相反。字尾T可選。
LDR R4,START            将存儲位址為START的字資料讀入R4
STR R5,DATA1            将R5存入存儲位址為DATA1中
LDR R0,[R1]             将存儲器位址為R1的字資料讀入存儲器R0
LDR R0,[R1,R2]          将存儲器位址為R1+R2的字資料讀入存儲器R0
LDR R0,[R1,#8]          将存儲器位址為R1+8的字資料讀入存儲器R0
LDR R0,[R1,R2,LSL #2]   将存儲器位址為R1+R2*4的字資料讀入存儲區R0
STR R0,[R1,R2]!         将R0字資料存入存儲器位址R1+R2的存儲單元中,并将新位址R2+R2寫入R2
STR R0,[R1,#8]!         将R0字資料存入存儲器位址R1+8的存儲單元中,并将新位址R2+8寫入R2
STR R0,[R1,R2,LSL #2]   将R0字資料存入存儲器位址R1+R2*4的存儲單元中,并将新位址R2+R2*4寫入R1
LDR R0,[R1],#8          将存儲器位址為R1的字資料讀入寄存器R0,并将新位址R1+8寫入R1  
LDR R0,[R1],R2          将存儲器位址為R1的字資料讀入寄存器R0,并将新位址R1+R2寫入R1
LDR R0,[R1],R2,LSL #2   将存儲器位址為R1的字資料讀入寄存器R0,并将新位址R1+R2*4寫入R1
      

2.7.2. LDRB/STRB位元組資料加載/存儲指令

LDRB/STRB{<cond>}{T}Rd,addr         LDRB指令用于從存儲器中将一個8位的位元組資料加載到目的寄存器中,同時将寄存器的高24位清零,當程式計數器PC作為目的寄存器時,指令從存儲器中讀取的字資料被當做目的位址,進而可以實作程式流程的跳轉。
STRB指令用于從源寄存器中将一個8位的位元組資料存儲到存儲器中,和LDRB相反。字尾T可選。
      

2.7.3. LDRH/STRH半字資料加載/存儲指令

LDRH/STRH{<cond>}{T}Rd,addr         LDRH指令用于從存儲器中将一個16位的半字資料加載到目的寄存器中,同時将寄存器的高16位清零,當程式計數器PC作為目的寄存器時,指令從存儲器中讀取的字資料被當做目的位址,進而可以實作程式流程的跳轉。
STRH指令用于從源寄存器中将一個16位的半字資料存儲到存儲器中,和LDRH相反。字尾T可選。
      

2.7.4. LDM/STM批量資料加載/存儲指令

LDM/STM{<cond>}{<type>}Rn{!},<regs>{^}      LDM用于從基址寄存器所訓示的一片連續存儲器中讀取資料到寄存器清單所指向的多個寄存器中,記憶體單元的起始位址為基址寄存器Rn的值,各個寄存器由寄存器清單regs表示,該指令一般用于多個寄存器資料的出棧操作
STM用于将寄存器清單所指向的多個寄存器中的值存入由基址寄存器所指向的一片連續存儲器中,記憶體單元的起始位址為基址寄存器Rn的值,各個寄存器又寄存器清單regs表示。該指令一般用于多個寄存器資料的進棧操作。
type表示類型,用于資料的存儲與讀取有以下幾種情況:
IA:每次傳送後位址值加。
IB:每次傳送前位址值加。
DA:每次傳送後位址值減。
DB:每次傳送前位址值減。
用于堆棧操作時有如下幾種情況:
FD:滿遞減堆棧
ED:空遞減堆棧
FA:滿遞增堆棧
EA:空遞增堆棧
      

2.7.5. SWP字資料交換指令

SWP{<cond>}<Rd>,<Rm>,[<Rn>]         Rd = [Rn],[Rn] = Rm,當寄存器Rm和目的寄存器Rd為同一個寄存器時,指令交換該急促親和存儲器的内容
SWP R0,R1,[R2]          R0 = [R2],[R2] = R1
SWP R0,R0,[R1]          R0 = [R1],[R1] = R0
SWPB指令用于将寄存器Rn指向的存儲器中的位元組資料加載到目的寄存器Rd中,目的寄存器的高24位清零,同時将Rm中的字資料存儲到Rn指向的存儲器中。
      

2.8.分支語句

助記符 說明 操作
B{cond}label 分支指令 PC<-label
BL{cond}label 帶傳回的分支指令 PC<-label,LR=BL後面的第一條指令位址
BX{cond}Rm 帶狀态切換的分支指令 PC = Rm & 0xffffffe,T=Rm[0] & 1
BLX{cond}label Rm 帶傳回和狀态切換的分支指令 | PC=label,T=1 PC; PC = Rm & 0xffffffe,T=Rm[0] & 1;LR = BLX後面的第一條指令位址

2.8.1. 分支指令B

B{<cond>}label          跳轉到label處執行,PC=label

例子:
backword    SUB R1,R1,#1
            CMP R1,#0           比較R1和0
            BEQ forward         如果R1=0,跳轉到forware處執行
            SUB R1,R2,#3
            SUB R1,R1,#1
forward     ADD R1,R2,#4
            ADD R2,R3,#2
            B backword          無條件跳轉到backword處執行
      

2.8.2. 帶傳回的分支指令BL

BL{<cond>}label         在跳轉之前,将PC的目前内容儲存在R14(LR)中儲存,是以,可以通過将R14的内容重新加載到PC中,傳回到跳轉指令之後的指令處執行。該指令用于實作子程式的調用,程式的傳回可通過把LR寄存器的值複制到PC寄存器中來實作。
例子:
BL func             跳轉到子程式
ADD R1,R2,#2        子程式調用完傳回後執行的語句,傳回位址
....
func                子程式
...
MOV R15,R14         複制傳回位址到PC,實作子程式的傳回
      

2.8.3. 帶狀态切換的分支指令BX

BX{<cond>} Rm       當執行BX指令時,如果條件cond滿足,則處理器會判斷Rm的位[0]是否為1,如果為1則跳轉時自動将CPSR寄存器的标志T置位,并将目标位址的代碼解釋為Thumb代碼來執行,則處理器會切換到Thumb狀态,反之,若Rm的位[0]為0,則跳轉時自動将CPSR寄存器的标志T複位,并将目标位址處的代碼解釋為ARM代碼來執行,即處理器會切換到ARM狀态。
      

注意:bx lr的作用等同于mov pc,lr。即跳轉到lr中存放的位址處。 非零值存儲在R0中傳回。

那麼lr存放的是什麼位址呢?lr就是連接配接寄存器(Link Register, LR),在ARM體系結構中LR的特殊用途有兩種:一是用來儲存子程式傳回位址;二是當異常發生時,LR中儲存的值等于異常發生時PC的值減4(或者減2),是以在各種異常模式下可以根據LR的值傳回到異常發生前的相應位置繼續執行。  

當通過BL或BLX指令調用子程式時,硬體自動将子程式傳回位址儲存在R14寄存器中。在子程式傳回時,把LR的值複制到程式計數器PC即可實作子程式傳回。

2.9堆棧

2.9.1. 進棧出棧

出棧使用LDM指令,進棧使用STM指令。LDM和STM指令往往結合下面一些參數實作堆棧的操作。
FD:滿遞減堆棧。
ED:空遞減堆棧。
FA:滿遞增堆棧。
EA:空遞增堆棧。
滿堆棧是指SP(R13)指向堆棧的最後一個已使用位址或滿位置(也就是SP指向堆棧的最後一個資料項的位置);相反,空堆棧是指SP指向堆棧的第一個沒有使用的位址或空位置。
LDMFD和STMFD分别指POP出棧和PUSH入棧
      

2.9.2. PUSH指令

PUSH{cond} reglist      PUSH将寄存器推入滿遞減堆棧
PUSH {r0,r4-r7}         将R0,R4-R7寄存器内容壓入堆棧
      

2.9.3. POP指令

POP{cond} reglist       POP從滿遞減堆棧中彈出資料到寄存器
POP {r0,r4-r7}          将R0,R4-R7寄存器從堆棧中彈出
      

0x03 建立Android NDK程式

3.1. NDK程式建立過程

1.建立一個android程式。
2.在程式右鍵-->Android Tools-->Add Native Support(需要ADT配置好NDK的路徑,新版的ADT沒有配置NDK的地方,需要安裝NDK的jar包)-->命名so檔案(比如叫HelloJNI)。然後就會在程式中建立jni目錄,包含了我們要寫的NDK程式檔案。
3.在src的包中(比如叫com.example.hellojni)中建立java檔案,後面會示範幾個執行個體。
4.在程式根目錄下建立檔案build_headers.xml,使用ANT editor打開(ADT需要安裝ANT),使用alt+/鍵調出自動提示,選擇Bulidfile template建立模闆檔案。後面會給出代碼執行個體。
5.打開ANT工具,選擇第一個"增加"按鈕(一個小的加号),然後将build_headers.xml添加進來,ANT中就會增加HelloJNI,每次修改源碼後,輕按兩下HelloJNI,就會自動修改/jni目錄下的檔案。
      

3.2. 編寫程式

3.2.1. CLASS檔案

在com.example.hellojni包中建立class檔案,本文一次建立多個執行個體,共參考。

GetInt.java代碼為

package com.example.hellojni;
public class GetInt {
    public static native int getInt();
}
      

GetString.java代碼為

package com.example.hellojni;    

public class GetString {

    public static native String getStr();

    public native String getString();

    public native int add(int a, int b);    

}
      

GetFor.java代碼為

package com.example.hellojni;    

public class GetFor {
    public static native int getFor1(int n);
    public static native int getFor2(int n);
}
      

GetIfElse.java代碼為

package com.example.hellojni;    

public class GetIfElse {
    public static native String getIfElse(int n);
}    
      

GetWhile.java代碼為

package com.example.hellojni;    

public class GetWhile {
    public static native int getWhile(int n);
}
      

GetSwitch.java代碼為

package com.example.hellojni;    

public class GetSwitch {
    public static native int getSwitch(int a,int b,int i);
}
      

MainActivity.java代碼為

package com.example.hellojni;    

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;    


public class MainActivity extends ActionBarActivity {

    private TextView tv;    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv = (TextView) findViewById(R.id.tv);
        //tv.setText(String.valueOf(GetInt.getInt()));
        //tv.setText(GetString.getStr());
        //tv.setText(String.valueOf(GetFor.getFor1(4)));
        //tv.setText(String.valueOf(GetFor.getFor2(4)));
        //tv.setText(String.valueOf(GetWhile.getWhile(5)));
        //tv.setText(GetIfElse.getIfElse(20));
        tv.setText(String.valueOf(GetSwitch.getSwitch(4,2,2)));

    }

    static{
        System.loadLibrary("HelloJNI");
    }
}
      

build_headers.xml代碼為

<?xml version="1.0" encoding="UTF-8"?>
    <!-- ====================================================================== 
     2014-10-28 上午8:05:50                                                            

     HelloJNI    
     description

     0xExploit                                                                
     ======================================================================     -->
<project name="HelloJNI" default="BuildAllHeaders">
    <description>
            description
    </description>    

    <!-- ================================= 
          target: BuildAllHeaders              
         ================================= -->
    <target name="BuildAllHeaders">
        <antcall target="BuildGetStringHeader"></antcall>
        <antcall target="BuildGetIntHeader"></antcall>
        <antcall target="BuildGetForHeader"></antcall>
        <antcall target="BuildGetWhileHeader"></antcall>
        <antcall target="BuildGetIfElseHeader"></antcall>
        <antcall target="BuildGetStringHeader"></antcall>
    </target>    

    <!-- - - - - - - - - - - - - - - - - - 
          target: depends                      
         - - - - - - - - - - - - - - - - - -->
    <target name="BuildGetStringHeader">
        <javah destdir="./jni" classpath="./bin/classes/" class="com.example.hellojni.GetString"></javah>
    </target>    

    <target name="BuildGetIntHeader">
        <javah destdir="./jni" classpath="./bin/classes/" class="com.example.hellojni.GetInt"></javah>
    </target>

    <target name="BuildGetForHeader">
            <javah destdir="./jni" classpath="./bin/classes/" class="com.example.hellojni.GetFor"></javah>
    </target>

    <target name="BuildGetWhileHeader">
            <javah destdir="./jni" classpath="./bin/classes/" class="com.example.hellojni.GetWhile"></javah>
    </target>

    <target name="BuildGetIfElseHeader">
            <javah destdir="./jni" classpath="./bin/classes/" class="com.example.hellojni.GetIfElse"></javah>
    </target>

    <target name="BuildGetSwitchHeader">
            <javah destdir="./jni" classpath="./bin/classes/" class="com.example.hellojni.GetSwitch"></javah>
    </target>    

</project>
      

然後輕按兩下ANT中的HelloJNI,然後F5重新整理工程項目,可以看到jni目錄下,多出6個檔案,com_example_hellojni_GetFor.h等,此檔案裡面就是函數.h接口檔案,是沒有具體代碼的,需要把裡面的函數複制到jni目錄下的HelloJNI.cpp檔案中,然後去實作函數的具體部分。

HelloJNI.cpp的代碼為

#include <jni.h>
#include <com_example_hellojni_GetInt.h>
#include <com_example_hellojni_GetString.h>
#include <com_example_hellojni_GetFor.h>
#include <com_example_hellojni_GetIfElse.h>
#include <com_example_hellojni_GetWhile.h>
#include <com_example_hellojni_GetSwitch.h>    

int nums[5] = {1, 2, 3, 4, 5};    

JNIEXPORT jstring JNICALL Java_com_example_hellojni_GetString_getStr
  (JNIEnv *env, jclass){
    return env->NewStringUTF("static method call");
}    

/*
 * Class:     com_example_hellojni_GetString
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_GetString_getString
  (JNIEnv *env, jobject){
    return env->NewStringUTF("method call");
}    

/*
 * Class:     com_example_hellojni_GetString
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_example_hellojni_GetString_add
  (JNIEnv *, jobject, jint a, jint b){
    return a+b;
}    


JNIEXPORT jint JNICALL Java_com_example_hellojni_GetInt_getInt
  (JNIEnv *, jclass){
    return 8;
}    

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetFor_getFor1
  (JNIEnv *, jclass, jint n){
    int i = 0;
    int s = 0;
        for (i = 0; i < n; i++){
            s += i * 2;
        }
        return s;
}    

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetFor_getFor2
  (JNIEnv *, jclass, jint n){
    int i = 0;
        int s = 0;
        for (i = 0; i < n; i++){
            s += i * i + nums[n-1];
        }
        return s;
}
JNIEXPORT jint JNICALL Java_com_example_hellojni_GetWhile_getWhile
  (JNIEnv *, jclass, jint n){
        int i = 1;
        int s = 0;
        while(i <= n){
            s += i++;
        }
        return s;
}
JNIEXPORT jstring JNICALL Java_com_example_hellojni_GetIfElse_getIfElse
  (JNIEnv *env, jclass, jint n){
        if(n < 16){
            return env->NewStringUTF("he is a boy");
        } else if(n < 30){
            return env->NewStringUTF("he is a young man");
        } else if(n < 45){
            return env->NewStringUTF("he is a strong man");
        } else{
            return env->NewStringUTF("he is an old man");
        }
}
JNIEXPORT jint JNICALL Java_com_example_hellojni_GetSwitch_getSwitch
  (JNIEnv *, jclass, jint a, jint b, jint i){
    switch (i){
        case 1:
            return a + b;
            break;
        case 2:
            return a - b;
            break;
        case 3:
            return a * b;
            break;
        case 4:
            return a / b;
            break;
        default:
            return a + b;
            break;
        }
}
      

以上就是一些執行個體的代碼,下面就來分析逆向後的ARM代碼。以下反彙編代碼都是通過IDA得到的,至于IDA的使用方法,大家可以看看書。

3.2.2. getInt()方法

getInt()的方法代碼如下:

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetInt_getInt
  (JNIEnv *, jclass){
    return 8;
}
      

反編譯後的代碼為:

EXPORT Java_com_example_hellojni_GetInt_getInt
Java_com_example_hellojni_GetInt_getInt
MOVS    R0, #8      ;R0 = 8
BX      LR          ;子程式傳回R0
      

3.2.3. getStr()方法

getStr()的方法代碼如下:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_GetString_getStr
  (JNIEnv *env, jclass){
    return env->NewStringUTF("static method call");
}
      

反編譯後的代碼為:

EXPORT Java_com_example_hellojni_GetString_getStr
Java_com_example_hellojni_GetString_getStr
PUSH    {R3,LR}                                 ;将R3和LR入棧
LDR     R2, [R0]                                ;[R0]是JNIEnv,R2=*env,RO一般是放傳回值的,調用函數後會被覆寫的,是以要複制出去
LDR     R1, =(aStaticMethodCa - 0xF7A)          
MOVS    R3, #0x29C                              ;R3=0x29C
ADD     R1, PC          ; "static method call"  ;R1="static method call"
LDR     R3, [R2,R3]     ;R2偏移R3,是NewStringUTF,可以檢視JNI API(Android軟體安全與逆向分析7.6節也有介紹),如下圖所示,所有的函數在附件中。
BLX     R3              ;調用NewStringUTF函數,第一個參數R0,是JNIEnv,子程式傳回,第二個參數是R1
      
Android SO逆向1-ARM介紹0x00 概述0x01 ARM寄存器0x02 彙編語言0x03 建立Android NDK程式

3.2.3. getFor1()方法

getFor1()的方法代碼如下:

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetFor_getFor1
  (JNIEnv *, jclass, jint n){
    int i = 0;
    int s = 0;
        for (i = 0; i < n; i++){
            s += i * 2;
        }
        return s;
}
      

反編譯後的代碼為:

Android SO逆向1-ARM介紹0x00 概述0x01 ARM寄存器0x02 彙編語言0x03 建立Android NDK程式

代碼解釋如下:

EXPORT Java_com_example_hellojni_GetFor_getFor1
Java_com_example_hellojni_GetFor_getFor1
               MOVS    R0, #0       ;R0 = 0
               MOVS    R3, R0       ;R3 = 0
               B       loc_FB0      ;跳轉到loc_FB0
; ---------------------------------------------------------------------------
loc_FAA                                 ; CODE XREF: Java_com_example_hellojni_GetFor_getFor1+Ej
               LSLS    R1, R3, #1       ;R1=R3左移一位(即R1=R3*2)
               ADDS    R0, R0, R1       ;R0=R0+R1
               ADDS    R3, #1           ;R3=R3+1
loc_FB0                                 ; CODE XREF: Java_com_example_hellojni_GetFor_getFor1+4j
                CMP     R3, R2      ;比較R3和R2,R2是第一個參數,即n
                BLT     loc_FAA     ;如果R3<R2,跳到loc_FAA
                BX      LR          ;否則,子程式傳回R0
;End of function Java_com_example_hellojni_GetFor_getFor1
      

3.2.4. getWhile()方法

getWhile()的函數代碼如下:

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetWhile_getWhile
  (JNIEnv *, jclass, jint n){
        int i = 1;
        int s = 0;
        while(i <= n){
            s += i++;
        }
        return s;
}
      

反編譯後的結果為:

Android SO逆向1-ARM介紹0x00 概述0x01 ARM寄存器0x02 彙編語言0x03 建立Android NDK程式

代碼解釋如下:

EXPORT Java_com_example_hellojni_GetWhile_getWhile
 Java_com_example_hellojni_GetWhile_getWhile
                 MOVS    R0, #0         ;R0 = 0
                 MOVS    R3, #1         ;R3 = 1
                 B       loc_FEA        ;跳轉到loc_FEA
 ; 
-------------------------------------------------------------
 loc_FE6                                 ; CODE XREF: 
le_hellojni_GetWhile_getWhile+Cj
                 ADDS    R0, R0, R3   ;R0=R0+R3
                 ADDS    R3, #1           ;R3=R3+1
 loc_FEA                                 ; CODE XREF: 
le_hellojni_GetWhile_getWhile+4j
                 CMP     R3, R2         ;比較R3和R2,R2為第一個參數,即n
                 BLE     loc_FE6        ;如果R3<R2,跳轉到loc_FE6
                 BX      LR             ;否則傳回結果R0
 ; End of function Java_com_example_hellojni_GetWhile_getWhile
      

3.2.5. getIfElse()方法

getIfElse()的代碼如下

JNIEXPORT jstring JNICALL Java_com_example_hellojni_GetIfElse_getIfElse
  (JNIEnv *env, jclass, jint n){
        if(n < 16){
            return env->NewStringUTF("he is a boy");
        } else if(n < 30){
            return env->NewStringUTF("he is a young man");
        } else if(n < 45){
            return env->NewStringUTF("he is a strong man");
        } else{
            return env->NewStringUTF("he is an old man");
        }
}
      

反編譯後的結果為:

Android SO逆向1-ARM介紹0x00 概述0x01 ARM寄存器0x02 彙編語言0x03 建立Android NDK程式

代碼解釋如下:

EXPORT Java_com_example_hellojni_GetIfElse_getIfEls
 Java_com_example_hellojni_GetIfElse_getIfElse
                 PUSH    {R4,LR}            ;R4,LR入棧。
                 MOVS    R3, #0xA7          ;R3=167
                 LDR     R4, [R0]           ;[R0]是JNIEnv,此處是R4=*env
                 LSLS    R3, R3, #2     ;R3=R3左移2位
                 CMP     R2, #0xF           ;比較R2(即n)和16
                 BGT     loc_1002           ;如果R2>16,跳轉到loc_1002
                 LDR     R1, =(aHeIsABoy - 0x1002)  ;和下一條指令一起,将R1="he is a boy"
                 ADD     R1, PC          ; "he is a boy"
                 B       loc_101A           ;跳轉到loc_101A
 ; 
-------------------------------------------------------------
 loc_1002                                ; CODE XREF: 
le_hellojni_GetIfElse_getIfElse+Aj
                 CMP     R2, #0x1D
                 BGT     loc_100C
                 LDR     R1, =(aHeIsAYoungMan - 0x100C)
                 ADD     R1, PC          ; "he is a young man"
                 B       loc_101A
 ; 
-------------------------------------------------------------
 loc_100C                                ; CODE XREF: 
le_hellojni_GetIfElse_getIfElse+14j
                 CMP     R2, #0x2C
                 BGT     loc_1016
                 LDR     R1, =(aHeIsAStrongMan - 0x1016)
                 ADD     R1, PC          ; "he is a strong man"
                 B       loc_101A
 ; 
-------------------------------------------------------------
 loc_1016                                ; CODE XREF: 
le_hellojni_GetIfElse_getIfElse+1Ej
                 LDR     R1, =(aHeIsAnOldMan - 0x101C)
                 ADD     R1, PC          ; "he is an old man"
 loc_101A                                ; CODE XREF: 
le_hellojni_GetIfElse_getIfElse+10j
                                         ; 
le_hellojni_GetIfElse_getIfElse+1Aj ...
                 LDR     R3, [R4,R3]        ;R4的偏移R3*4,是NewStringUTF
                 BLX     R3                 ;子程式傳回,第一個參數是R0,第二個參數是R1
                 POP     {R4,PC}            ;一般是和第一行執行相反的出棧動作.将LR放入到PC,PC是下一條指令的位址,改變它的值也就相當跳轉.
 ; End of function Java_com_example_hellojni_GetIfElse_getIfElse
      

3.2.6. getSwitch()方法

getSwitch()的代碼如下:

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetSwitch_getSwitch
  (JNIEnv *, jclass, jint a, jint b, jint i){
    switch (i){
        case 1:
            return a + b;
            break;
        case 2:
            return a - b;
            break;
        case 3:
            return a * b;
            break;
        case 4:
            return a / b;
            break;
        default:
            return a + b;
            break;
        }
}
      

反編譯後的結果為:

Android SO逆向1-ARM介紹0x00 概述0x01 ARM寄存器0x02 彙編語言0x03 建立Android NDK程式

代碼解釋如下:

EXPORT Java_com_example_hellojni_GetSwitch_getSwitch
 Java_com_example_hellojni_GetSwitch_getSwitch
 arg_0           =  0
                 PUSH    {R3,LR}
                 LDR     R1, [SP,#8+arg_0]
                 ADDS    R0, R2, R3
                 SUBS    R1, #1
                 CMP     R1, #3          ; switch 4 cases
                 BHI     locret_105C     ; jumptable 0000103E default case,跳轉到default,此時傳回R0,R0=R2+R3
                 MOVS    R0, R1
                 BL      __gnu_thumb1_case_uqi ; switch jump
 ; 
-------------------------------------------------------------
 jpt_103E        DCB 2                   ; jump table for switch statement
                 DCB 4
                 DCB 6
                 DCB 9
 ; 
-------------------------------------------------------------
 loc_1046                                ; CODE XREF: 
le_hellojni_GetSwitch_getSwitch+Ej
                 ADDS    R0, R2, R3      ; jumptable 0000103E case 0
                 B       locret_105C     ; jumptable 0000103E default case
 ; 
-------------------------------------------------------------
 loc_104A                                ; CODE XREF: 
le_hellojni_GetSwitch_getSwitch+Ej
                 SUBS    R0, R2, R3      ; jumptable 0000103E case 1
                 B       locret_105C     ; jumptable 0000103E default case
 ; 
-------------------------------------------------------------
 loc_104E                                ; CODE XREF: 
le_hellojni_GetSwitch_getSwitch+Ej
                 MOVS    R0, R3          ; jumptable 0000103E case 2
                 MULS    R0, R2
                 B       locret_105C     ; jumptable 0000103E default case
 ; 
-------------------------------------------------------------
 loc_1054                                ; CODE XREF: 
le_hellojni_GetSwitch_getSwitch+Ej
                 MOVS    R0, R2          ; jumptable 0000103E case 3
                 MOVS    R1, R3
                 BLX     __divsi3
 locret_105C                             ; CODE XREF: 
le_hellojni_GetSwitch_getSwitch+Aj
                                         ; 
le_hellojni_GetSwitch_getSwitch+18j ...
                 POP     {R3,PC}         ; jumptable 0000103E default case
 ; End of function Java_com_example_hellojni_GetSwitch_getSwitch
      

繼續閱讀