最近學(xué)習(xí)了一下安卓應(yīng)用的調(diào)試方法薪者,覺(jué)得可能對(duì)于部分初學(xué)者而言會(huì)有一定幫助彩扔,所以在這里把整個(gè)調(diào)試過(guò)程記錄下來(lái)址晕。
調(diào)試環(huán)境
android 7.1(userdebug版本痹愚,可以在不重打包的條件下調(diào)試應(yīng)用憎瘸。ps. 使用sdk下載的鏡像默認(rèn)就是這種版本)
IDA 6.8(沒(méi)有的同學(xué)可以到52pojie上下載)
測(cè)試對(duì)象
whctf2017的安卓逆向題loopcrypto
程序分析
反調(diào)試
在init_array里面定義了一個(gè)函數(shù)sub_83DC
通過(guò)反編譯不難發(fā)現(xiàn)入篮,這是一個(gè)反調(diào)試函數(shù),需要patch后才能調(diào)試
PS. 不知道調(diào)用fork函數(shù)算不算一種反調(diào)試方法幌甘,但是總會(huì)遇到fork之后斷開(kāi)調(diào)試的情況潮售。這種反調(diào)試很容易解決,因?yàn)槲覀儾捎玫氖莿?dòng)態(tài)patch锅风,所以直接修改pc到return的指令上就可以了酥诽。解決這個(gè)反調(diào)試后就可以正常的調(diào)試了。
函數(shù)注冊(cè)
IDA在進(jìn)行分析的過(guò)程中并沒(méi)有吧JNI_OnLoad函數(shù)識(shí)別出來(lái)皱埠,但我們可以在export視圖里面找到它肮帐。
通過(guò)分析,不難發(fā)現(xiàn)JNI_Onload注冊(cè)的check函數(shù)對(duì)應(yīng)的真實(shí)函數(shù)為sub_87FC(為了方便分析jni函數(shù)調(diào)用边器,我們可以導(dǎo)入jni.h函數(shù)定義训枢,快鍵鍵Ctrl+F9)
調(diào)試過(guò)程
因?yàn)閼?yīng)用加載so庫(kù)的過(guò)程有反調(diào)試代碼,因此需要在應(yīng)用加載so庫(kù)之前attach到應(yīng)用程序上忘巧,并patch相關(guān)反調(diào)試代碼恒界。這里我們采用實(shí)時(shí)patch的方法,這樣的話可以省去重打包的麻煩砚嘴,同時(shí)也可以不用處理java層的簽名檢測(cè)十酣。
第一步
以調(diào)試模式啟動(dòng)應(yīng)用(調(diào)試模式啟動(dòng)可以使得應(yīng)用程序停止在程序入口點(diǎn))
adb shell am start -D -n com.a.sample.loopcrypto/.MainActivity
第二步
使用IDA attach到目標(biāo)應(yīng)用。在此之前我們可能需要先push一個(gè)android_server(android7.1上需要使用pie編譯的版本)到手機(jī)內(nèi)部
λ file android_server
android_server: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, stripped
λ adb push android_server /data/local/tmp
android_server: 1 file pushed...MB/s (523480 bytes in 0.108s)
λ adb shell chmod 777 /data/local/tmp/android_server
使用root權(quán)限啟動(dòng)android_server
λ adb shell
angler:/ # id
uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:su:s0
angler:/ # cd /data/local/tmp
/android_server < IDA Android 32-bit remote debug server(ST) v1.19. Hex-Rays (c) 2004-2015
Listening on port #23946...
開(kāi)啟端口轉(zhuǎn)發(fā)
adb forward tcp:23946 tcp:23946
準(zhǔn)備工作完成之后就可以開(kāi)心的調(diào)試了际长。首先選擇Android debugger
然后attach目標(biāo)應(yīng)用
設(shè)置調(diào)試選項(xiàng)為加載動(dòng)態(tài)庫(kù)時(shí)掛起耸采,然后恢復(fù)運(yùn)行。
事實(shí)上這個(gè)時(shí)候我們還不能調(diào)試也颤,因?yàn)閖ava層代碼還處于等待調(diào)試狀態(tài)洋幻,需要使用jdb恢復(fù)運(yùn)行,但命令行的方式不是特別好用翅娶,所以我們這里使用Android Studio來(lái)解決這個(gè)問(wèn)題(需要?jiǎng)?chuàng)建一個(gè)應(yīng)用)文留。
這個(gè)時(shí)候我們就會(huì)發(fā)現(xiàn)IDA停在了加載so庫(kù)的位置好唯,只不過(guò)不是加載libcheck.so。
恢復(fù)運(yùn)行燥翅,程序再次會(huì)再次斷下來(lái)骑篙,通過(guò)output window可以看到即將加載libcheck.so。
這個(gè)時(shí)候我們可以給init_array里面定義的函數(shù)下斷點(diǎn)(Ctrl+S查看內(nèi)存map)森书。
恢復(fù)運(yùn)行靶端,程序會(huì)在剛下斷點(diǎn)的地方停下來(lái)。因?yàn)檎麄€(gè)函數(shù)除了反調(diào)之外沒(méi)有實(shí)際功能凛膏,所以我們可以修改pc讓其直接返回(為了保證棧平衡杨名,我們需要完整的執(zhí)行一對(duì)push和pop指令,所以執(zhí)行完13DC處的push指令后再修改pc使其指向下圖中標(biāo)識(shí)的指令)
到這兒猖毫,關(guān)于這個(gè)函數(shù)的反調(diào)試就已經(jīng)結(jié)束了台谍。這個(gè)時(shí)候我們就可以給check函數(shù)下斷點(diǎn)了
恢復(fù)運(yùn)行,在手機(jī)上隨便輸入一個(gè)flag吁断,程序就是停在check函數(shù)入口處
到這里的話安卓應(yīng)用調(diào)試方法就已經(jīng)介紹完了趁蕊,如果大家對(duì)于調(diào)試java代碼感興趣的話可以參考這篇文章。對(duì)于這道題后續(xù)的分析的話就沒(méi)有什么難度了仔役,所以不再贅述掷伙。
總結(jié)
本文簡(jiǎn)要地介紹了安卓應(yīng)用調(diào)試方法,但為了方便大家分析又兵,整個(gè)操作過(guò)程采用手動(dòng)完成任柜。事實(shí)上,IDAPython提供了豐富的調(diào)試接口寒波,這部分工作完全可以放到腳本里面自動(dòng)化完成乘盼。
補(bǔ)充
室友寫(xiě)的IDAPython腳本,懶得重寫(xiě)俄烁,直接粘過(guò)來(lái)了
from idaapi import *
from idc import *
from idautils import *
class DbgHook(DBG_Hooks):
def dbg_library_load(self, pid, tid, ea, modinfo_name, modinfo_base, modinfo_size):
print "%#x %s %#x" %(ea, modinfo_name, modinfo_base)
if "libcheck.so" in modinfo_name:
self.libcheck = modinfo_base
print "libcheck addr is 0x%08x" % self.libcheck
self.init_pop = self.libcheck + 0x8440
self.init_addr = self.libcheck + 0x83e0
add_bpt(self.init_addr,0,BPT_SOFT)
#enable_bpt(self.init_addr,True)
SetBptAttr( self.init_addr, BPTATTR_FLAGS, BPT_ENABLED|BPT_TRACE)
self.check_ptrace = self.libcheck + 0x86f6
self.check_fork = self.libcheck + 0x86b0
add_bpt(self.check_fork,0,BPT_SOFT)
#enable_bpt(self.check_fork,True)
SetBptAttr( self.check_fork, BPTATTR_FLAGS, BPT_ENABLED|BPT_TRACE)
self.key_addr = self.libcheck + 0x8728
add_bpt(self.key_addr,0,BPT_SOFT)
enable_bpt(self.key_addr,True)
return
def dbg_bpt(self, tid, bptea):
print "[*] Hit: 0x%08x" % bptea
if bptea == self.init_addr:
print "Set Reg Value:"
SetRegValue(self.init_pop,"PC")
elif bptea == self.check_fork:
print "Set Reg Value:"
SetRegValue(self.check_ptrace,"PC")
return
def dbg_step_over(self):
eip = GetRegValue("PC")
print("MyDbgHook : 0x%x %s" % (eip, GetDisasm(eip)))
return
num_breakpoints = GetBptQty()
print "[*] Set %d breakpoints." % num_breakpoints
debugger = DbgHook()
debugger.hook()