參考: http://www.itdadao.com/articles/c15a190757p0.html
一. 為什么要調試init_array
init_array的用途
1. 一些全局變量的初始化 (我這里試過, 一些全局變量的初始化,會統一用一個init_array表項來完成初始化)
2. 通過__attribute__ ((constructor)) 聲明的函數 (可以定義n個)
通過so加載流程來看,init_array是我們程序代碼可以控制的最早的時機了, 其次才加載Jni_onload
所以有些樣本會在init_array做一些反調試和相關環境檢測的活, 所以我們需要在init_array中和對方兵戎相見
當然網上已有很多教我們如何在init_array下斷的函數, 但是卻都只教了方法, 沒有細說原理, 最后我們可能只學會了幾個快捷鍵, 空有招式卻無內功, 知其然卻不知其所以然, 下面我們就姿勢和知識這2方面來進行討論
二. 斷init_array的姿勢
1. 定位調試進程中linker的dlopen函數地址
把調試機器中的linker拷貝出來, 路徑為/system/bin/linker, 然后開一個IDA分析
在Shift+F12在字符串窗口中查找"dlopen", 跟蹤引用到一個函數, 如下圖
得到其文件偏移為0xF30
附加上調試器后, 我們得到linker加載到內存的起始地址為400BD000
所以我們在代碼窗口Go過去看看400BD000 + F30 = 400BDF30
發現全部是DCB形式的代碼(代碼沒有解析出來), 這個時候我們需要對linker進行分析, 操作如下: 右鍵->Analyze Module
go過去我們發現和靜態分析中的一樣, 在函數頭部下一個斷點
2. 定位到calling相關代碼
同樣在拷貝出來的ida搜索字符串calling
同樣定位到代碼,得到文件偏移 2720
那么我們內存中的地址就是 400BD000 + 2720 = 400BF720
同樣在調試的ida中下好斷點, 第2個斷點就是調用.init_array數組的代碼
然后按F9,注意觀察寄存器窗口, 當有顯示調試的是你想要斷的so的時候開始注意
當斷點斷在BLX R4的時候,下一步就是調用init_array數組了, 所以F7跟進去
在直接把我們想要分析的so拖到ida分析進行驗證, 代碼一樣, 說明我們成功的斷點在了init_array數組
二. 斷init_array的知識
通過上面的操作我們學會了招式, 內功心法卻不見修習, 下面我們通過Android的系統源碼來一探究竟
環境介紹
源碼環境: Android 6.0.1
沒有下載源碼的同學可以去androidxref在線看源碼也很方面
1. 回到源頭看問題
我們都知道我們要在apk中要加載一個so我們可以通過
System.loadLibrary("libname");
System.load("lib_path");
這2者區別如下:
(1). System.load參數必須為庫文件的絕對路徑,可以是任意路徑;
(2). System.loadLibrary參數為庫文件名,不包含庫文件的擴展名,必須是在JVM屬性Java.library.path所指向的路徑中,路徑可以通過System.getProperty('java.library.path')
2. java層到native層的過程
我們把android_source\libcore\luni部分的源碼作為單獨的部分丟進Source Insight進行分析
定位到android_source\libcore\luni\src\main\java\java\lang\System.java, 搜索loadLibrary, 就可以開始分析了
java層代碼主要是一些路徑, 和標記值的初始化
最后比較關鍵的函數是JavaVMExt.LoadNativeLibrary, 該函數主要完成如下事情
1. 調用linker的dlopen完成加載
2. 調用dlsym獲取目標so的JniOnload地址並調用
3. 初始化SharedLibrary對象並添加到表中, 下次加載相同的so則不在重復加載
linker之前的函數調用流程圖如下:
3. linker的dlopen簡易分析
android系統通過調用linker的dlopen來完成so的轉載
把aosp\bionic目錄添加到source insight中進行分析
配合AndroidXref站點我們找到, dlopen定義在dlfcn.cpp中
dlopen函數定義如下, 只是簡單的調用了dlopen_ext

do_dlopen簡單的判斷了一下參數, 然后調用find_library進行轉載鏈接so文件
加載成功后,返回soinfo對象指針,同時調用soinfo的成員函數call_constructors來調用so中的init_array
call_constructors先完成其他模塊的加載,然后調用call_array()來調用init_array數組的調用


由於篇幅問題,大致介紹下linker的調用流程, 函數調用流程如下:

1. 在do_dlopen中通過find_library進行加載so
在加載完so后通過call_constructors完成init_array的加載
2. find_library最后調用load_libray完成so的轉載
3. 最后通過load_library的elf_reader.load完成so的裝載
四.總結
由於android是開源的操作系統, android中的很多問題我們都可以通過分析源碼來了解細節, 解決問題, 並知其所以然
同時我們還可以通過編譯源碼來定制我們想要的功能, 達到我們想要的目的