App調(diào)試和UI定位
工具
Android Studio,后面的內(nèi)容簡稱AS.
Android Studio 是谷歌推出的一個Android集成開發(fā)工具,基于IntelliJ IDEA. 類似 Eclipse ADT,Android Studio 提供了集成的 Android 開發(fā)工具用于開發(fā)和調(diào)試。
系統(tǒng)app調(diào)試
開發(fā)系統(tǒng)app的時候,大多數(shù)基于makefile的,并且簽名是platform的,因此不能直接通過源碼進行調(diào)試。本文不打算拿系統(tǒng)app來講解如何調(diào)試,不過會使用這里的調(diào)式技巧,那么調(diào)試系統(tǒng)app也很簡單了(系統(tǒng)app的源碼大都涉密,不方便拿來舉例,AOSP的源碼例外)。
這里主要是Java層的調(diào)試,native層的調(diào)試在后續(xù)文章中會講解。
應用程序
源碼很簡單,主要包括一個MainActivity,部分代碼如下:
? ?@Override
? ?protected void onCreate(Bundle savedInstanceState) {
? ? ? ?super.onCreate(savedInstanceState);
? ? ? ?setContentView(R.layout.activity_main);
? ? ? ?password_et = (EditText) this.findViewById(R.id.password);
? ? ? ?username_et = (EditText) this.findViewById(R.id.username);
? ? ? ?message_tv = ((TextView) findViewById(R.id.textView));
? ? ? ?registerReceiver(new MyReceiver(), new IntentFilter("send"));
? ? ? ?...
}
通過objection查看安裝目錄,關(guān)于objection的使用在Frida進階之內(nèi)存漫游及簡單抓包有所介紹。通過env命令可以查看安裝目錄。

使用adb pull?導出apk.
使用AS進行debug
點擊File->Profile or Debug APK,選擇導出的apk。

在右上角有Attach Kotlin/Java Source...,選擇源代碼路徑,這時候相應的smali就編程源代碼了。注意:在debug配置的時候選擇Java Only。

選擇Attach debugger to Android Process,選擇相應的進程,在需要的地方下斷點就可以正常調(diào)式了。

這就和和正常的app的調(diào)試是一樣的了。
當然也可以通過Run->Debug來調(diào)試,這種和普通app的調(diào)試就一樣了。
JDB調(diào)試Android程序
在App動態(tài)調(diào)試(1)-Radare2和lldb?中對JDB調(diào)試進行了簡單的介紹,通過jdb調(diào)試來跟蹤指定的動態(tài)庫加載完成。
JDWP 協(xié)議
首先讓我們認識一下什么是 JDWP(Java Debug Wire Protocol),說白了就是 JVM 或者類 JVM 的虛擬機都支持一種協(xié)議,通過該協(xié)議,Debugger 端可以和目標 VM 通信,可以獲取目標 VM 的包括類、對象、線程等信息。
在調(diào)試 Android 應用程序這一場景,Debugger 一般是指你的 develop machine 的某一支持 JDWP 協(xié)議的工具例如 Android Studio 或者 JDB,而?Target JVM 是指運行在你 mobile 設備當中的各個 App(因為它們都是一個個虛擬機 Dalvik 或者 ART)。
JDWP Agent一般負責監(jiān)聽某一個端口,當有 Debugger 向這一個端口發(fā)起請求的時候,Agent 就轉(zhuǎn)發(fā)該請求給 Target JVM 并最終由該 JVM 來處理請求,并把 reply 信息返回給 Debugger 端。

針對Android設備,可參考下面這個圖, JDWP Agent 在 Android 手機上應該是指 adbd 進程。

JDWP 協(xié)議的報文格式,JDWP 協(xié)議中主要有兩種報文:Command packet 和 Reply packet,command packet 就是我們上面所說的請求報文,reply 自然就是對 command 的回答。
如果想深入了解JDWP協(xié)議,可參考https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/introclientissues005.html。
關(guān)于JDWP曾經(jīng)存在過遠程命令執(zhí)行漏洞,有興趣可參考https://blog.csdn.net/wanzt123/article/details/82793023。
JDB命令列表
這個除了step、stop,其他的用的不太多,除非要自己開發(fā)調(diào)試器。 命令 | 作用 -------- | ----- connectors | 列出此 VM 中可用的連接器和傳輸 run [class [args]] | 開始執(zhí)行應用程序的主類 threads [threadgroup] | 列出線程 suspend [thread id(s)] | 掛起線程 (默認值: all) resume [thread id(s)] | 恢復線程 (默認值: all) where [ | all] | 轉(zhuǎn)儲線程的堆棧 wherei [ | all] | 轉(zhuǎn)儲線程的堆棧, 以及 pc 信息 up [n frames] | 上移線程的堆棧 down [n frames] | 下移線程的堆棧 kill | 終止具有給定的異常錯誤對象的線程 interrupt | 中斷線程 print | 輸出表達式的值 dump | 輸出所有對象信息 eval | 對表達式求值 (與 print 相同) set | 向字段/變量/數(shù)組元素分配新值 locals | 輸出當前堆棧幀中的所有本地變量 classes | 列出當前已知的類 class | 顯示已命名類的詳細資料 methods | 列出類的方法 fields | 列出類的字段 threadgroups | 列出線程組 threadgroup | 設置當前線程組 stop in .[(argument_type,...)] | 在方法中設置斷點 stop at : | 在行中設置斷點 clear .[(argument_type,...)] | 清除方法中的斷點 clear : | 清除行中的斷點 clear | 列出斷點 catch [uncaught caught all] | 出現(xiàn)指定的異常錯誤時中斷 ignore [uncaught caught all] | 對于指定的異常錯誤, 取消 ‘catch’ watch [access all] . | 監(jiān)視對字段的訪問/修改 unwatch [access all] . | 停止監(jiān)視對字段的訪問/修改 trace [go] methods [thread] | 跟蹤方法進入和退出。 | 除非指定 ‘go’, 否則掛起所有線程 trace [go] method exit exits [thread] | 跟蹤當前方法的退出, 或者所有方法的退出 | 除非指定 ‘go’, 否則掛起所有線程 untrace [methods] | 停止跟蹤方法進入和/或退出 step | 執(zhí)行當前行 step up | 一直執(zhí)行, 直到當前方法返回到其調(diào)用方 stepi | 執(zhí)行當前指令 cont | 從斷點處繼續(xù)執(zhí)行 list [line number method] | 輸出源代碼 use (或 sourcepath) [source file path] | 顯示或更改源路徑 exclude [, ... | "none"] | 對于指定的類, 不報告步驟或方法事件 classpath | 從目標 VM 輸出類路徑信息 monitor | 每次程序停止時執(zhí)行命令 monitor | 列出監(jiān)視器 unmonitor <monitor#> | 刪除監(jiān)視器 read | 讀取并執(zhí)行命令文件 lock | 輸出對象的鎖信息 threadlocks [thread id] | 輸出線程的鎖信息 pop | 通過當前幀出棧, 且包含當前幀 reenter | 與 pop 相同, 但重新進入當前幀 redefine | 重新定義類的代碼 disablegc | 禁止對象的垃圾收集 enablegc | 允許對象的垃圾收集
調(diào)試程序
使用JDB調(diào)試上面的apk程序。 (1) 查看進程 adb shell ps | grep com.example.myapplication (2) 端口轉(zhuǎn)發(fā) adb forward tcp:12345 jdwp:15513(進程PID) (3)JDB和app之間建立聯(lián)系 jdb -attach localhost:12345

(4)設置斷點 stop in com.example.myapplication.MainActivity.onCreate(android.os.Bundle)
由于這個這個方法已經(jīng)運行了,因此這里是不能夠出發(fā)斷點的,如果在改方法調(diào)用之前能夠suspend進程就能解決這個問題了。在App動態(tài)調(diào)試(1)-Radare2和lldb?通過R2frida的spawan模式使得進程suspend的。
進入Debug模式
添加這段代碼android.os.Debug.waitForDebugger() 是能夠?qū)崿F(xiàn)的。
UI定位
記錄AS中的一個錯誤
記錄AS中的一個錯誤,由于移除插件導致再次啟動AS的時候出現(xiàn)了下面的錯誤:
missing essential plugin org.jetbrains.android please reinstall android studio from scratch
這個錯誤是和用戶有關(guān)的,因此可以通過用戶切換來解決。
在Ubuntu上的解決辦法:
rm -rf ./.config/Google/AndroidStudio4.1/disabled_plugins.txt
在其他平臺也類似,找到用戶目錄,搜索disabled_plugins.txt,然后刪除就OK了。
布局探測
以某多多為例,根據(jù)這個關(guān)鍵字就能基本確定代碼的位置了。

寫在最后
Android中涉及的調(diào)試有很多,包括framework層的調(diào)試以及native層的調(diào)試,后續(xù)會持續(xù)更新調(diào)試相關(guān)的文章。熟練使用調(diào)試對閱讀代碼和定位問題都有很大的幫助。
公眾號
更多內(nèi)容,歡迎關(guān)注我的微信公眾號: 無情劍客。
