Android SO逆向1-ARM介绍
原文: 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為立即數#82.2.指令的可選后綴
S:指令執行后程序狀態寄存器的條件標志位將被刷新ADDS R1,R0,#2 !:指令中的地址表達式中含有!后綴時,指令執行后,基址寄存器中的地址值將發生變化,變化的結果是:基址寄存器中的值(指令執行后)=指令執行前的值 + 地址偏移量LDR R3,[R0,#2]! 指令執行后,R0 = R0 + 22.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傳給R02.6.2. MVN指令
MVN {<cond>}{S}Rd, op2 將op2取反傳給Rd MVN R0,#0 將0取反后傳給R0,R0 = -1 MVN R1,R2 將R2取反,結果保存到R12.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 - !C2.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 op22.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加52.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,R32.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寫入R12.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,#1CMP R1,#0 比較R1和0BEQ forward 如果R1=0,跳轉到forware處執行SUB R1,R2,#3SUB R1,R1,#1 forward ADD R1,R2,#4ADD R2,R3,#2B 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的值返回到異常發生前的相應位置繼續執行?! ?/p>
當通過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; @Overrideprotected 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 description0xExploit ====================================================================== --> <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 ;子程序返回R03.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,子程序返回,第二個參數是R13.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_getFor1MOVS R0, #0 ;R0 = 0MOVS R3, R0 ;R3 = 0B loc_FB0 ;跳轉到loc_FB0 ; --------------------------------------------------------------------------- loc_FAA ; CODE XREF: Java_com_example_hellojni_GetFor_getFor1+EjLSLS R1, R3, #1 ;R1=R3左移一位(即R1=R3*2)ADDS R0, R0, R1 ;R0=R0+R1ADDS R3, #1 ;R3=R3+1 loc_FB0 ; CODE XREF: Java_com_example_hellojni_GetFor_getFor1+4jCMP R3, R2 ;比較R3和R2,R2是第一個參數,即nBLT loc_FAA ;如果R3<R2,跳到loc_FAABX LR ;否則,子程序返回R0 ;End of function Java_com_example_hellojni_GetFor_getFor13.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_getWhileJava_com_example_hellojni_GetWhile_getWhileMOVS R0, #0 ;R0 = 0MOVS R3, #1 ;R3 = 1B loc_FEA ;跳轉到loc_FEA; -------------------------------------------------------------loc_FE6 ; CODE XREF: le_hellojni_GetWhile_getWhile+CjADDS R0, R0, R3 ;R0=R0+R3ADDS R3, #1 ;R3=R3+1loc_FEA ; CODE XREF: le_hellojni_GetWhile_getWhile+4jCMP R3, R2 ;比較R3和R2,R2為第一個參數,即nBLE loc_FE6 ;如果R3<R2,跳轉到loc_FE6BX LR ;否則返回結果R0; End of function Java_com_example_hellojni_GetWhile_getWhile3.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_getIfElsJava_com_example_hellojni_GetIfElse_getIfElsePUSH {R4,LR} ;R4,LR入棧。MOVS R3, #0xA7 ;R3=167LDR R4, [R0] ;[R0]是JNIEnv,此處是R4=*envLSLS R3, R3, #2 ;R3=R3左移2位CMP R2, #0xF ;比較R2(即n)和16BGT loc_1002 ;如果R2>16,跳轉到loc_1002LDR 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+AjCMP R2, #0x1DBGT loc_100CLDR R1, =(aHeIsAYoungMan - 0x100C)ADD R1, PC ; "he is a young man"B loc_101A; -------------------------------------------------------------loc_100C ; CODE XREF: le_hellojni_GetIfElse_getIfElse+14jCMP R2, #0x2CBGT loc_1016LDR R1, =(aHeIsAStrongMan - 0x1016)ADD R1, PC ; "he is a strong man"B loc_101A; -------------------------------------------------------------loc_1016 ; CODE XREF: le_hellojni_GetIfElse_getIfElse+1EjLDR 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,是NewStringUTFBLX R3 ;子程序返回,第一個參數是R0,第二個參數是R1POP {R4,PC} ;一般是和第一行執行相反的出棧動作.將LR放入到PC,PC是下一條命令的地址,改變它的值也就相當跳轉.; End of function Java_com_example_hellojni_GetIfElse_getIfElse3.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_getSwitchJava_com_example_hellojni_GetSwitch_getSwitcharg_0 = 0PUSH {R3,LR}LDR R1, [SP,#8+arg_0]ADDS R0, R2, R3SUBS R1, #1CMP R1, #3 ; switch 4 casesBHI locret_105C ; jumptable 0000103E default case,跳轉到default,此時返回R0,R0=R2+R3MOVS R0, R1BL __gnu_thumb1_case_uqi ; switch jump; -------------------------------------------------------------jpt_103E DCB 2 ; jump table for switch statementDCB 4DCB 6DCB 9; -------------------------------------------------------------loc_1046 ; CODE XREF: le_hellojni_GetSwitch_getSwitch+EjADDS R0, R2, R3 ; jumptable 0000103E case 0B locret_105C ; jumptable 0000103E default case; -------------------------------------------------------------loc_104A ; CODE XREF: le_hellojni_GetSwitch_getSwitch+EjSUBS R0, R2, R3 ; jumptable 0000103E case 1B locret_105C ; jumptable 0000103E default case; -------------------------------------------------------------loc_104E ; CODE XREF: le_hellojni_GetSwitch_getSwitch+EjMOVS R0, R3 ; jumptable 0000103E case 2MULS R0, R2B locret_105C ; jumptable 0000103E default case; -------------------------------------------------------------loc_1054 ; CODE XREF: le_hellojni_GetSwitch_getSwitch+EjMOVS R0, R2 ; jumptable 0000103E case 3MOVS R1, R3BLX __divsi3locret_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總結
以上是生活随笔為你收集整理的Android SO逆向1-ARM介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓APP动态调试技术
- 下一篇: Android SO逆向2-实例分析