前言
前幾篇介紹了的靜態調試,這篇文章著重講解一下使用IDA對指令,so文件的調試,在越來越成熟的階段,越來越多的app
本系列一共五篇:
《Smali基礎語法總結》
《靜態+動態分析程序(一)》
《JNI靜態注冊和動態注冊》(二)
《使用IDA對so文件進行動態調試與過反調試(三)》
《hook與注入(四)》
《文件保護技術(五)》
調試基礎什么是so文件
中的so文件是動態鏈接庫,是二進制文件,即ELF文件。多用于NDK開發中。
readelf –h xxx.so //查看elf的頭部信息
so文件的加載
so文件的加載有兩種方式:一種是load(),另一種是()
()
這是在 Java 層的調用,直接使用系統提供的接口:
System.loadLibrary(String libName)
.() 只需要傳入so在 .mk 中定義的 的值即可,系統會調用 . 把這個 轉化成對應平臺的so的全稱并嘗試去尋找這個so的加載。比如我們的so文件名為 .so ,加載動態庫只需要傳入 pass 即可:
System.loadLibrary("pass")
加載流程為:
load()
System.load(String fileName)
.load()為動態加載非apk打包期間內置的so文件提供了可能,也就是說可以使用這個方法來指定我們要加載的so文件的路徑來動態的加載so文件,比如在打包期間并不打包so文件,而是在應用運行時將當前設備適用的so文件從服務器上下載下來,放在 /data/data/
/mydir 下,然后在使用so時調用:
System.load("/data/data//mydir/libmath.so ");
即可成功加載這個so,開始調用本地方法了
其實和load最終都會調用 (name, , ) 方法,只是因為的參數傳入的僅僅是so的文件名,所以,需要首先找到這個文件的路徑,然后加載這個so文件。而load傳入的參數是一個文件路徑so動態調試取參數,所以它不需要去尋找這個文件路徑,而是直接通過這個路徑來加載so文件。
加載機制為:
IDA動態調試so時有哪三個層次?以及如何下斷點?
在so加載時候有這個過程:
.init -> .init array -> JNI_Onload -> java_com_xxx
在脫殼的過程中會在一些系統級的.so中下斷點比如:fopen,fget,等等
而 .init 以及 . 一般會作為殼的入口地方,那我們索性叫它外殼級的.so文件
這里歸納為三類:
應用級別的:;
外殼級別的:,.init,.;
系統級別的:fopen,fget,;
對于在應用級別的和系統級別的就不說了比較簡單容易理解,這里也是在實現篇中會重點說的,看到上面的.so的加載執行過程我們知道如果說反調試放在外殼級別的.so文件的話我們就會遇程序在應用級核心函數一下斷點就退出的尷尬,事實上多數的反調試會放在這,那么過反調試就必須要在這些地方下斷點,下面會重點的說如何在.和處理下斷點。
IDA下斷點調試的原理
由于下斷點有硬件斷點和軟件斷點,我們在這里只說IDA中的軟件斷點原理:
X86系列處理器提供了一條專門用來支持調試的指令,即INT 3,這條指令的目的就是使CPU中斷(break)到調試器,以供調試者對執行現場進行各種分析。
當我們在IDA中對代碼的某一行設置斷點時,即:F2,調試器會先把這里的本來指令的第一個字節保存起來,然后寫入一條INT 3指令,因為INT 3指令的機器碼為(0xCC)當運行到這的時候CPU會捕獲一條異常,轉去處理異常,CPU會保留上上下文環境,然后中斷到調試器,大多數調試器的做法是在被調試程序中斷到調試器時,會先將所有斷點位置被替換為INT 3的指令恢復成原來的指令,然后再把控制權交給用戶。這樣我們就可以愉快的開始調試了。
如下圖所示也是寫調試器的原理圖:
反調試與反附加的區別反調試
就是阻止你進行動態調試所采用的一種手段,在下文中會進行具體的講解反調試的手段,以及解決反調試的辦法。
反附加
在這塊重要的是說jdb的反附加,很多情況下jdb會附加不上,就是會出現“無法附加到目標的VM”這樣的問題那是因為在每個應用程序下,有這個:="true"才能調試。
IDA調試常用的快捷鍵
快捷鍵
功能
F2
在所在行下斷點
F5
可以將ARM指令轉化為可讀的C代碼,同時可以使用Y鍵,對指針做一個類型轉換,從而對JNI里經常使用的方法能夠識別
F7
單步進入調試
F8
按照順序一行一行,單步調試
F9
直接跳到下一個斷點處
Shift + F12
快速查看so文件中的字符串信息,分析過程中通過一些關鍵字符串能夠迅速定位到關鍵函數
Ctrl + s
有兩個用途,在IDA View頁面中可以查看文件so文件的所有段信息,在調試頁面可以查看程序中所有so文件映射到內存的基地址。tips:在進行so調試過程中,很有用的一個小技巧就是IDA雙開,一個用于進行靜態分析;一個用于動態調試。比如說調試過程中要找到一個函數的加載到內存中的位置,
Esc
回退鍵,能夠倒回上一部操作的視圖(只有在反匯編窗口才是這個作用,如果是在其他窗口按下esc,會關閉該窗口)
g
直接跳到某個地址
y
更改變量的類型
x
對著某個函數、變量按該快捷鍵,可以查看它的交叉引用
n
更改變量的名稱
p
創建函數
常見命名意義
名字
含義
sub
指令和字函數的起點
loc
指令
返回指令
off
包含偏移量
seg
包含段地址值
asc
ASCII字符
byte
字節(或字節數組)
word
16位數據(或字數組)
dword
32位數據(或雙字數組)
qword
64位數據(或4字數組)
flt
32位浮點數(或浮點數組)
dbl
64位浮點數(或雙精度數組)
tbyte
80位浮點數(或擴展精度浮點數)
stru
結構體或結構體數組
algn
對齊指示
unk
未處理字節
Jni-字符串與數組
1.新建Java字符串
在JNI中,如果需要使用一個Java字符串,可以采用如下方式新建對象。
jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len);
2.獲取Java字符串長度
通過以下方法我們能夠獲取到Java字符串的長度
jsize GetStringLength(JNIEnv *env, jstring string);
3.從Java字符串獲取字符數組
我們可以通過以下方法從Java字符串獲取字符數組,當使用完畢后,我們需要調用進行釋放。
const jchar * GetStringChars(JNIEnv *env, jstring string,jboolean *isCopy);
4.釋放從Java字符串中獲取的字符數組
void ReleaseStringChars(JNIEnv *env, jstring string,const jchar *chars);
5.新建UTF-8編碼字符串
jstring NewStringUTF(JNIEnv *env, const char *bytes);
6.獲取UTF-8編碼的Java字符串的
const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy);
7.釋放從UTF-8字符串中獲取的字符數組
void ReleaseStringUTFChars(JNIEnv *env, jstring string,const char *utf);
8.從Java字符串中截取一段字符
如果我們想要從字符串中獲取其中的一段內容,可以采用如下方式:
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
9.獲取數組長度
jsize GetArrayLength(JNIEnv *env, jarray array);
10.新建對象數組
使用如下方法可以創建一個對象數組。
jobjectArray NewObjectArray(JNIEnv *env, jsize length,jclass elementClass, jobject initialElement);
參考鏈接《JNI完全指南(五)——字符串與數組》
IDA對so文件調試
使用的是吾愛破解上apk的例子,吾愛破解動態調試參考文章:《教我兄弟學逆向09 IDA動態破解登陸驗證》
1. 使用Jadx反編譯apk,打開.xml查看程序入口 這里:="true"表示此apk可以動態調試,如果是false動態調試的時候需要改成true,否則不可被動態調試。
2. 找到入口類,通過靜態分析java代碼可知,用戶在輸入用戶名和密碼后程序會調用方法check來校驗用戶名和密碼是否正確。
可以看到加載了.so這個文件里面的check方法來校驗用戶輸入
重新打包安裝再試一下,點擊登錄后直接閃退了,說明在so層做了某種驗證。
3.解壓apk,找到 lib\\.so,并用IDA打開 找到check函數并分析此函數
可以發現代碼可讀性較差,這里對F5生成的偽c代碼進行優化。
(1)還原JNI函數方法名
一般JNI函數方法名首先是一個指針加上一個數字,然后將這個地址作為一個方法指針進行方法調用,并且第一個參數就是指針自己,比如上圖的v5+676(v5…)。這實際上就是我們在JNI里經常用到的方法。因為Ida并不會自動的對這些方法進行識別,所以當我們對so文件進行調試的時候經常會見到卻搞不清楚這個函數究竟在干什么,因為這個函數實在是太抽象了。解決方法非常簡單so動態調試取參數,只需要對指針做一個類型轉換即可,比如說上面提到 v5 指針。可以先選中 v5 變量,然后按 Y 鍵: