新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > android平臺(tái)arm指令學(xué)習(xí)和調(diào)試

android平臺(tái)arm指令學(xué)習(xí)和調(diào)試

作者: 時(shí)間:2016-11-09 來(lái)源:網(wǎng)絡(luò) 收藏
*/

一、Ndk下內(nèi)聯(lián)匯編

跟vc下一樣,ndk編譯環(huán)境下也能使用內(nèi)聯(lián)匯編,如下:

本文引用地址:http://m.butianyuan.cn/article/201611/317575.htm

include

intmy_thumb(intdummy)

{

__asm__(

“movr0,#1tn”

“movr1,#2tn”

“addr0,r0,r1tn”

“bxlr”

);

}

intmain()

{

intn=my_thumb(12);

printf(“result:%08x”,n);

return0;

}

再看看使用標(biāo)簽的例子:

#include

intmy_thumb(intdummy)

{

__asm(

“movr0,#0x1tn”

“ldrr0,__starttn”

“adrr0,__starttn”

“ldrr0,=__starttn”

“__start:tn”

“noptn”

);

}

intmain()

{

intn=my_thumb(12);

printf(“result:%08x”,n);

return0;

}

二、編譯

創(chuàng)建android.mk文件,然后使用ndk-build進(jìn)行編譯

1)編譯一個(gè)可執(zhí)行程序,Android.mk文件的內(nèi)容如下:

LOCAL_PATH:=$(callmy-dir)

include$(CLEAR_VARS)

LOCAL_MODULE:=helloa

LOCAL_SRC_FILES:=testHello.c

include$(BUILD_EXECUTABLE)

2)編譯一個(gè)so,android.mk文件內(nèi)容如下:

LOCAL_PATH:=$(callmy-dir)

include$(CLEAR_VARS)

LOCAL_MODULE:=testJniSo

LOCAL_SRC_FILES:=testJniSo.c

include$(BUILD_SHARED_LIBRARY)

3)必須有一個(gè)jni目錄,目錄結(jié)構(gòu):

4)這樣之后,就可以在cmd窗口中,切換到代碼目錄,使用ndk-build命令進(jìn)行編譯。

三、調(diào)試可執(zhí)行程序

1)Push編譯好的elf到樣機(jī)中

2)使用gdbserver啟動(dòng)該文件

“/data/local/gdbserver:12345/data/local/helloa”

3)adbforwardtcp:12345tcp:12345

4)啟動(dòng)gdb

5)Targetremote127.0.0.1:12345

6)Ni是單步,setdisassemble-nexton下一句指令顯示反匯編,使用setarmforce-modearm或者setarmforce-modethumb讓gdb切換thumb和arm代碼顯示。

7)Display/i$pc顯示當(dāng)前的代碼

8)Continue就是windbg的f5,od的f9

9)Infobreakpoints顯示斷點(diǎn),而delete刪除斷點(diǎn)disable禁用斷點(diǎn)

10)Disas0xAAAA,+20(20字節(jié)的數(shù)據(jù))顯示反匯編

11)調(diào)試比較



Gdbarm和ida反匯編比較:

四、調(diào)試so

以愛加密為例,進(jìn)行調(diào)試,范例是看雪求助帖給出的,鏈接如下:

http://bbs.pediy.com/showthread.php?t=184375

調(diào)試方法

1)So中實(shí)現(xiàn)的方法,程序跑起來(lái)再附加的話,該方法可能已經(jīng)執(zhí)行完??囱┥辖o出方法步驟較多,應(yīng)用和底層都調(diào)試,有點(diǎn)復(fù)雜如下:

我試出來(lái)的方法,對(duì)so文件進(jìn)行修改,讓其在入口處直接調(diào)用它本身。需要注意的是,必須在so的入口函數(shù)處修改,如果在內(nèi)部調(diào)用函數(shù)修改,app會(huì)自動(dòng)退出。

2)gdb的調(diào)試,使用ndk下自帶的gdbserver和gdb進(jìn)行調(diào)試

3)將修改的so導(dǎo)入到手機(jī)該應(yīng)用的lib目錄下,覆蓋之前的so文件。然后運(yùn)行app,出現(xiàn)只有邊界的黑框。Gdbserver附加,使用set命令,將bl調(diào)用自身的命令改回去,則可以進(jìn)行正常調(diào)試了。

4)模塊基地址的獲取cat/proc/pid/maps找到

5)Dump內(nèi)存的命令式dumpbinarymemoryc:xxxstartAdendAd

6)小細(xì)節(jié)要注意的是:.init_proc的地址是0x31cb9,ida直接點(diǎn)過去,是一堆數(shù)據(jù),摁c無(wú)法轉(zhuǎn)成代碼

因?yàn)?strong>arm指令和thumb指令是2字節(jié)或者4字節(jié)對(duì)齊的,所以,改地址加1應(yīng)該是真正的地址。將該地址修改為調(diào)用自身,然后gdb附加,成功斷下來(lái),然后輸入“ir$lr”查看返回值,如下

該地址恰巧是在linker模塊中,

此處的在linker中的代碼如下:

7)至于為什么系統(tǒng)的linker會(huì)調(diào)用.init_proc,不得而知。該so的oep為空,沒有section表,但是有dynamicsection表。自己寫一個(gè)so,并沒有函數(shù)“init_proc”的導(dǎo)出,需要再研究下elf文件格式。

自解碼

本身的so是經(jīng)過加密的,解密的代碼是在.init_proc+1的位置,字節(jié)碼的代碼較長(zhǎng),部分代碼如下:

1)windows下逃避斷點(diǎn)的方法,就是利用“codeshadow”的方式,在申請(qǐng)內(nèi)存,然后將代碼拷貝到指定的申請(qǐng)的空間上執(zhí)行,這樣,直接斷原代碼是斷不到的。但是,直接搜索二進(jìn)制就可以找到這樣的鏡像代碼。

如下,之前應(yīng)該運(yùn)行的代碼:

拷貝到其它地方執(zhí)行的代碼:

2)計(jì)算指定函數(shù)的匯編指令長(zhǎng)度:代碼要進(jìn)行拷貝執(zhí)行,必須計(jì)算拷貝代碼的長(zhǎng)度。愛加密計(jì)算拷貝代碼的長(zhǎng)度方式是通過call來(lái)計(jì)算的。函數(shù)實(shí)現(xiàn)的排布如下:

__asmcalltargetFunc

xxxxxx

xxxxxx

xxxxxx

xxxxxx

xxxxxx

xxxxxx

VoidtargetFunc

而call的指令是:

00f0是指令opcode,c0與跳轉(zhuǎn)的距離有關(guān),計(jì)算公式是(targetAddr–curAddr)/2–2=opdataLen。那么知道c0就知道中間xxx代碼的長(zhǎng)度了。

3)愛加密處理的數(shù)據(jù)有三塊,第一塊未知,第二塊是壓縮過的指令數(shù)據(jù),第三塊是需要拷貝到堆上的代碼,這三塊數(shù)據(jù)總長(zhǎng)度為0x29e3c,使用mmap2進(jìn)行申請(qǐng)映射內(nèi)存,如下:

然后將三塊數(shù)據(jù)拷貝到映射的內(nèi)存上,

在此之后,數(shù)據(jù)內(nèi)存排布:

AAAAAAAAA

BBBBBBBBBB

CCCCCCCCCC

愛加密還原代碼前,處理了三塊數(shù)據(jù),如上。其中,“AAAAAAAAAA”數(shù)據(jù)是一組未使用過的數(shù)據(jù),具體功能未知,長(zhǎng)度為0x6b0;“BBBBBBBBBB”代表的是lzma壓縮過的數(shù)據(jù);“CCCCCCCCCC”代表的是拷貝函數(shù)的數(shù)據(jù)(一共拷貝三個(gè)函數(shù)的代碼)。

4)修改lr,然后刷新cache,返回到mmap2得到的堆上的函數(shù)進(jìn)行執(zhí)行

OAD:510A4010 01 1C MOVS R1, R0 ; r0 0x4fb83e3c
LOAD:510A4012 10 1C MOVS R0, R2 ; r2 0x4fb83bd8
LOAD:510A4014 BE 46 MOV LR, R7 ; r7 0x4fb83d91
LOAD:510A4014 ; 這里對(duì)lr寄存器進(jìn)行修改
LOAD:510A4016 C3 E7 B libexec___ARM_NR_cacheflush

5)再次調(diào)用mmap2,抹掉最初的“AA”、“BB”和“CC”三塊數(shù)據(jù),這樣,之前執(zhí)行的代碼和數(shù)據(jù)將都被清零。這三塊數(shù)據(jù)就被搬運(yùn)到第一次調(diào)用mmap2得到的地址上,數(shù)據(jù)在內(nèi)存重新做了排布。

6)解壓數(shù)據(jù):將數(shù)據(jù)解壓到第二次調(diào)用mmap2清零的地址上,偏移為0x6b0。數(shù)據(jù)的解壓使用的是lzma算法,解壓的代碼:

上圖中,r4指向的lzma的解壓函數(shù),r0是待解壓的數(shù)據(jù),r1是待解壓的長(zhǎng)度,r2是解壓到的地址,r3上是保存解壓到的地址空間的長(zhǎng)度的地址。Lzma的具體實(shí)現(xiàn):

7)修正指定數(shù)據(jù):解壓完的數(shù)據(jù)并不是最終的代碼,還需要進(jìn)行修正。修正代碼如下:

修正的方法是:解壓后的數(shù)據(jù)從末端開始往前,每四字節(jié)為單位,如果這四字的最高個(gè)字節(jié)是低四位是0xb,則進(jìn)行移位修復(fù)。(這種方法很像pe殼中解密代碼后,對(duì)0xe8、0xe9、0xff25等的修復(fù))修復(fù)代碼如下:

8)修改內(nèi)存屬性,代碼就完全解密,并可以運(yùn)行了。

解密前的導(dǎo)出函數(shù)代碼如下:

顯然上圖中的代碼沒法運(yùn)行,解碼后的代碼如下:

解碼后的代碼分析

解密后的代碼上邊已經(jīng)給出,r0傳遞的就是jnienv虛表指針,gdb中顯示相關(guān)api在libicuil8n.so模塊中,如下:

1)動(dòng)態(tài)注冊(cè)接口:解密后的代碼主要是動(dòng)態(tài)注冊(cè)用于java調(diào)用的接口,如下:

2)load的實(shí)現(xiàn)

3)run方法的實(shí)現(xiàn)

gdb調(diào)試的問題:

1)Gdb在遇到bxpc;nop指令,從thumb指令切換到arm指令時(shí),會(huì)出錯(cuò),調(diào)試無(wú)法繼續(xù)進(jìn)行。解決方法:

http://sourceware-org.1504.n7.nabble.com/Fix-ARM-stepping-over-Thumb-mode-quot-bx-pc-quot-or-quot-blx-pc-quot-td69213.html)

2)gdb遇到如下的拷貝指令也會(huì)退出調(diào)試,解決方法是跳過這段代碼的位置下斷點(diǎn)。

LOAD:510A3F88 loc_510A3F88 ; CODE XREF: libexec_memcopy+1Ej
LOAD:510A3F88 DC 13 B1 E8 LDMIA R1!, {R2-R4,R6-R9,R12}
LOAD:510A3F8C 01 50 55 E2 SUBS R5, R5, #1 ; 這里r5不斷的減少
LOAD:510A3F90 DC 13 A0 E8 STMIA R0!, {R2-R4,R6-R9,R12}
LOAD:510A3F94 FB FF FF 1A BNE loc_510A3F88
LOAD:510A3F98 D4 03 BD E8 LDMFD SP!, {R2,R4,R6-R9}



評(píng)論


技術(shù)專區(qū)

關(guān)閉