android平臺arm指令學(xué)習(xí)和調(diào)試
一、Ndk下內(nèi)聯(lián)匯編
跟vc下一樣,ndk編譯環(huán)境下也能使用內(nèi)聯(lián)匯編,如下:
本文引用地址:http://butianyuan.cn/article/201611/317575.htminclude
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進行編譯
1)編譯一個可執(zhí)行程序,Android.mk文件的內(nèi)容如下:
LOCAL_PATH:=$(callmy-dir)
include$(CLEAR_VARS)
LOCAL_MODULE:=helloa
LOCAL_SRC_FILES:=testHello.c
include$(BUILD_EXECUTABLE)
2)編譯一個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)必須有一個jni目錄,目錄結(jié)構(gòu):
4)這樣之后,就可以在cmd窗口中,切換到代碼目錄,使用ndk-build命令進行編譯。
三、調(diào)試可執(zhí)行程序
1)Push編譯好的elf到樣機中
2)使用gdbserver啟動該文件
“/data/local/gdbserver:12345/data/local/helloa”
3)adbforwardtcp:12345tcp:12345
4)啟動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顯示斷點,而delete刪除斷點disable禁用斷點
10)Disas0xAAAA,+20(20字節(jié)的數(shù)據(jù))顯示反匯編
11)調(diào)試比較
Gdbarm和ida反匯編比較:
四、調(diào)試so
以愛加密為例,進行調(diào)試,范例是看雪求助帖給出的,鏈接如下:
http://bbs.pediy.com/showthread.php?t=184375
調(diào)試方法
1)So中實現(xiàn)的方法,程序跑起來再附加的話,該方法可能已經(jīng)執(zhí)行完??囱┥辖o出方法步驟較多,應(yīng)用和底層都調(diào)試,有點復(fù)雜如下:
我試出來的方法,對so文件進行修改,讓其在入口處直接調(diào)用它本身。需要注意的是,必須在so的入口函數(shù)處修改,如果在內(nèi)部調(diào)用函數(shù)修改,app會自動退出。
2)gdb的調(diào)試,使用ndk下自帶的gdbserver和gdb進行調(diào)試
3)將修改的so導(dǎo)入到手機該應(yīng)用的lib目錄下,覆蓋之前的so文件。然后運行app,出現(xiàn)只有邊界的黑框。Gdbserver附加,使用set命令,將bl調(diào)用自身的命令改回去,則可以進行正常調(diào)試了。
4)模塊基地址的獲取cat/proc/pid/maps找到
5)Dump內(nèi)存的命令式dumpbinarymemoryc:xxxstartAdendAd
6)小細(xì)節(jié)要注意的是:.init_proc的地址是0x31cb9,ida直接點過去,是一堆數(shù)據(jù),摁c無法轉(zhuǎn)成代碼
因為arm指令和thumb指令是2字節(jié)或者4字節(jié)對齊的,所以,改地址加1應(yīng)該是真正的地址。將該地址修改為調(diào)用自身,然后gdb附加,成功斷下來,然后輸入“ir$lr”查看返回值,如下
該地址恰巧是在linker模塊中,
此處的在linker中的代碼如下:
7)至于為什么系統(tǒng)的linker會調(diào)用.init_proc,不得而知。該so的oep為空,沒有section表,但是有dynamicsection表。自己寫一個so,并沒有函數(shù)“init_proc”的導(dǎo)出,需要再研究下elf文件格式。
自解碼
本身的so是經(jīng)過加密的,解密的代碼是在.init_proc+1的位置,字節(jié)碼的代碼較長,部分代碼如下:
1)windows下逃避斷點的方法,就是利用“codeshadow”的方式,在申請內(nèi)存,然后將代碼拷貝到指定的申請的空間上執(zhí)行,這樣,直接斷原代碼是斷不到的。但是,直接搜索二進制就可以找到這樣的鏡像代碼。
如下,之前應(yīng)該運行的代碼:
拷貝到其它地方執(zhí)行的代碼:
2)計算指定函數(shù)的匯編指令長度:代碼要進行拷貝執(zhí)行,必須計算拷貝代碼的長度。愛加密計算拷貝代碼的長度方式是通過call來計算的。函數(shù)實現(xiàn)的排布如下:
__asmcalltargetFunc
xxxxxx
xxxxxx
xxxxxx
xxxxxx
xxxxxx
xxxxxx
VoidtargetFunc
而call的指令是:
00f0是指令opcode,c0與跳轉(zhuǎn)的距離有關(guān),計算公式是(targetAddr–curAddr)/2–2=opdataLen。那么知道c0就知道中間xxx代碼的長度了。
3)愛加密處理的數(shù)據(jù)有三塊,第一塊未知,第二塊是壓縮過的指令數(shù)據(jù),第三塊是需要拷貝到堆上的代碼,這三塊數(shù)據(jù)總長度為0x29e3c,使用mmap2進行申請映射內(nèi)存,如下:
然后將三塊數(shù)據(jù)拷貝到映射的內(nèi)存上,
在此之后,數(shù)據(jù)內(nèi)存排布:
AAAAAAAAA
BBBBBBBBBB
CCCCCCCCCC
愛加密還原代碼前,處理了三塊數(shù)據(jù),如上。其中,“AAAAAAAAAA”數(shù)據(jù)是一組未使用過的數(shù)據(jù),具體功能未知,長度為0x6b0;“BBBBBBBBBB”代表的是lzma壓縮過的數(shù)據(jù);“CCCCCCCCCC”代表的是拷貝函數(shù)的數(shù)據(jù)(一共拷貝三個函數(shù)的代碼)。
4)修改lr,然后刷新cache,返回到mmap2得到的堆上的函數(shù)進行執(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 ; 這里對lr寄存器進行修改
LOAD:510A4016 C3 E7 B libexec___ARM_NR_cacheflush
5)再次調(diào)用mmap2,抹掉最初的“AA”、“BB”和“CC”三塊數(shù)據(jù),這樣,之前執(zhí)行的代碼和數(shù)據(jù)將都被清零。這三塊數(shù)據(jù)就被搬運到第一次調(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是待解壓的長度,r2是解壓到的地址,r3上是保存解壓到的地址空間的長度的地址。Lzma的具體實現(xiàn):
7)修正指定數(shù)據(jù):解壓完的數(shù)據(jù)并不是最終的代碼,還需要進行修正。修正代碼如下:
修正的方法是:解壓后的數(shù)據(jù)從末端開始往前,每四字節(jié)為單位,如果這四字的最高個字節(jié)是低四位是0xb,則進行移位修復(fù)。(這種方法很像pe殼中解密代碼后,對0xe8、0xe9、0xff25等的修復(fù))修復(fù)代碼如下:
8)修改內(nèi)存屬性,代碼就完全解密,并可以運行了。
解密前的導(dǎo)出函數(shù)代碼如下:
顯然上圖中的代碼沒法運行,解碼后的代碼如下:
解碼后的代碼分析
解密后的代碼上邊已經(jīng)給出,r0傳遞的就是jnienv虛表指針,gdb中顯示相關(guān)api在libicuil8n.so模塊中,如下:
1)動態(tài)注冊接口:解密后的代碼主要是動態(tài)注冊用于java調(diào)用的接口,如下:
2)load的實現(xiàn)
3)run方法的實現(xiàn)
gdb調(diào)試的問題:
1)Gdb在遇到bxpc;nop指令,從thumb指令切換到arm指令時,會出錯,調(diào)試無法繼續(xù)進行。解決方法:
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遇到如下的拷貝指令也會退出調(diào)試,解決方法是跳過這段代碼的位置下斷點。
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}
評論