原文: 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。

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
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;
}
反編譯後的代碼為:
代碼解釋如下:
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;
}
反編譯後的結果為:
代碼解釋如下:
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");
}
}
反編譯後的結果為:
代碼解釋如下:
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;
}
}
反編譯後的結果為:
代碼解釋如下:
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