2020-10-26(安卓逆向开篇)
學習逆向的初衷是想系統學習Android下的hook技術和工具, 想系統學習Android的hook技術和工具是因為Android移動性能實戰這本書. 這本書里用hook技術hook一些關鍵函數來計算關鍵函數的調用參數和調用時長, 從而確定性能問題發生的位置和原因. 但目前沒有比較系統的講解hook的書籍, 所以就系統的了解下逆向分析.
在讀了姜維的Android應用安全防護和逆向分析和豐生強的Android軟件安全與逆向分析后, 準備分享下這方面知識. 在寫文章時發現, 這兩本書缺少對最新的逆向工具和加固工具的描述. 在查閱相關文獻后補充了這一部分.
本文從五個維度來講解Android逆向, 每個維度盡量分’原理’, ‘工具’, ‘實例’三個方面.
反編譯
靜態分析
動態分析
重編譯
Docker
1.反編譯
1.1 原理
Android App正向編譯
說到反編譯, 先來看下正向編譯, 如上圖, 正向編譯是
java -> class -> dex -> apk
反編譯和正向編譯稍有不同, 反編譯可以分成兩類:
java <- smali <- dex <- apk
這種方法是將dex文件轉為smali, smali是Dalvik虛擬機的匯編語言, 可以用來動態調試程序.
java <- class/jar <- dex <- apk
這種方法中是將Dalvik字節碼轉化為等價的Java字節碼, 然后用豐富的java分析工具分析源碼.
如何反反編譯(即對抗反編譯):
閱讀反編譯工具源碼查找缺陷
壓力測試找反編譯工具bug(下載很多apk, 寫個腳本調用ApkTool反編譯這些apk, ApkTool因為某些bug無法反編譯某個apk, 這時我們就通過壓力測試找到了ApkTool的bug, 將發現的這個應用到我們的apk中, 即可保護我們的apk免受ApkTool反編譯)
如何反反反編譯呢:
閱讀反編譯源碼修復缺陷
1.2 工具
反編譯工具-dex-class
上圖的反編譯工具走的java <- class/jar <- dex <- apk路線, 即先把apk里的dex找到, 然后使用Enjarify/dex2jar/classyshark/jadx反編譯得到jar包, 然后使用jd-gui/CFR/Procyon閱讀jar包里的java源碼. 這些工具各有優缺點, 我們一般選擇dex2jar+jd-gui, 相比其他工具, jd-gui雖然很久不更新了, 但是支持跳轉, 方便查看代碼. 特別說明下Bytecode-Viewer, 其是Procyon的一個前端, 同時集成了很多其他工具, 功能強大.
反編譯工具-dex-smali
看下上圖, 這些工具走的是java <- class/jar <- dex <- apk路線. 將dex文件轉化為smali匯編, 然后直接閱讀smali匯編語言, 或者smali再轉為java(這里沒有強大的工具, 可能經常無法成功轉化).
最常用反編譯工具
從上圖可以看到有很多反編譯工具, 我們平時最常用的是dex2jar+jd-gui和ApkTool.
jd-gui不僅有不錯的界面, 最關鍵的是支持類之間的跳轉, 在混淆后的代碼中跳轉可以大大方便我們查看.
ApkTool隱隱有無冕之王的聲勢, 可以反編譯代碼和資源, 修改后可以重編譯成apk, 在Android Studio下使用smalidea插件還可以完成無源碼調試, 十分強大.
工具地址:
https://github.com/Storyyeller/enjarify
https://github.com/pxb1988/dex2jar
https://github.com/google/android-classyshark
https://github.com/skylot/jadx
https://github.com/java-decompiler/jd-gui
http://www.benf.org/other/cfr/
https://bitbucket.org/mstrobel/procyon/wiki/Java%20Decompiler
https://github.com/Konloch/bytecode-viewer
https://github.com/deathmarine/Luyten
http://www.secureteam.net/d4j
https://github.com/iBotPeaches/Apktool
https://github.com/demitsuri/smali2java
https://www.pnfsoftware.com/
1.3 實例
這里以一個實例說明下反反編譯和反反反編譯:
使用早期ApkTool反編譯apk時,可能會遇到反編譯失敗, 出現如下問題:
Exception in thread "main" brut.androlib.AndrolibException: Multiple res specs: attr/name at brut.androlib.res.data.ResTypeSpec.addResSpec(ResTypeSpec.java:78) at brut.androlib.res.decoder.ARSCDecoder.readEntry(ARSCDecoder.java:248) at brut.androlib.res.decoder.ARSCDecoder.readTableType(ARSCDecoder.java:212) at brut.androlib.res.decoder.ARSCDecoder.readTableTypeSpec(ARSCDecoder.java:154) at brut.androlib.res.decoder.ARSCDecoder.readTablePackage(ARSCDecoder.java:116) at brut.androlib.res.decoder.ARSCDecoder.readTableHeader(ARSCDecoder.java:78) at brut.androlib.res.decoder.ARSCDecoder.decode(ARSCDecoder.java:47) at brut.androlib.res.AndrolibResources.getResPackagesFromApk(AndrolibResources.java:544) at brut.androlib.res.AndrolibResources.loadMainPkg(AndrolibResources.java:63) at brut.androlib.res.AndrolibResources.getResTable(AndrolibResources.java:55) at brut.androlib.Androlib.getResTable(Androlib.java:66) at brut.androlib.ApkDecoder.setTargetSdkVersion(ApkDecoder.java:198) at brut.androlib.ApkDecoder.decode(ApkDecoder.java:96) at brut.apktool.Main.cmdDecode(Main.java:165) at brut.apktool.Main.main(Main.java:81)查看ApkTool代碼發現, 是Apk利用了ApkTool的一個bug, Apk做了混淆,在編譯時存入了重復id值,導致ApkTool crash.
針對這個問題, 解決辦法是create fake names to prevent abuse from duplicate key
names, 其github提交如下:
create fake names to prevent abuse from duplicate key names
實例地址:
https://github.com/iBotPeaches/Apktool/commit/567907b187ad2f78b3564d0a0405e3b207832e17
2.靜態分析
2.1 原理
什么是靜態分析?
不運行代碼,采用反編譯工具生成程序的反編譯代碼,然后閱讀反編譯代碼來掌握程序功能.
Android靜態分析步驟:
反編譯apk程序
查看Application類(在Activity啟動之前, 一般加固/授權放在這里)
查看MainActivity類
找關鍵代碼
反靜態分析:
代碼混淆(ProGuard等)
使用NDK+STL編寫
手動注冊native函數()
默認情況, 使用javah, com.example.k12 -> java_完整包名類名方法名.
但可以使用函數映射表 static JNINativeMethod methods[] = {
{“dynamicGenerateKey”, “(Ljava/lang/String;)Ljava/lang/String;”, (void
) native_dynamic_key}}; RegisterNatives(jclass clazz, const
JNINativeMethod methods,jint nMethods)來注冊native函數名,
提高破解難度.
加固(dex/so加殼,指令抽取等)
反反靜態分析:
定位關鍵代碼技巧
信息反饋法(點擊界面, 出現注冊失敗, 那么檢查代碼里哪里使用到了注冊失敗)
特征函數法/關鍵系統調用(一般情況下, 最終都會調用到系統函數. 為了提升難度, 可以自制和系統函數功能相同的函數, 這樣難以下斷點)
Log代碼注入法/棧跟蹤法(動靜分析結合, 在合適位置注入log, 編譯運行時可以打印當前上下文信息和堆棧信息)
IDA分析匯編(asm->c, 雖然很多函數還沒重定位, 但是c比匯編的表達力更強, 更便于分析)
脫殼
IDA脫殼(dvm:dvmDexFileOpenPartial, art:openDexFileNative, 無論如何, 最終都是要調用系統API加載dex, 在這里加斷點, 然后dump出內存中的dex文件[現在一些加固工具都是自己寫加載dex的函數, 這樣簡單在上述方法上加斷點是無法命中的])
Xposed/VirtualXposed
Dex文件格式
這里不詳細介紹,
感興趣參考”https://blog.csdn.net/jiangwei0910410003/article/details/50668549”
Dalvik指令集
空指令 寄存器數據操作指令 返回指令 數據定義指令 鎖指令 實例操作指令
數組/字段操作指令 異常指令 跳轉指令 比較指令 方法調用指令 數據轉換指令
數據運算指令
.field private isFlag:z 定義變量
.method 方法
.parameter 方法參數
.prologue 方法開始
.line 12 此方法位于第12行
return-void 函數返回void
.end method 函數結束
new-instance 創建實例
iput-object 對象賦值
iget-object 調用對象
invoke-static 調用靜態函數條件跳轉分支:
invoke-super 調用父函數
invoke-direct 調用函數
“if-eq vA, vB, :cond_” 如果vA等于vB則跳轉到:cond_
“if-ne vA, vB, :cond_” 如果vA不等于vB則跳轉到:cond_
“if-lt vA, vB, :cond_” 如果vA小于vB則跳轉到:cond_
“if-ge vA, vB, :cond_” 如果vA大于等于vB則跳轉到:cond_
“if-gt vA, vB, :cond_” 如果vA大于vB則跳轉到:cond_
“if-le vA, vB, :cond_” 如果vA小于等于vB則跳轉到:cond_
“if-eqz vA, :cond_” 如果vA等于0則跳轉到:cond_
“if-nez vA, :cond_” 如果vA不等于0則跳轉到:cond_
“if-ltz vA, :cond_” 如果vA小于0則跳轉到:cond_
“if-gez vA, :cond_” 如果vA大于等于0則跳轉到:cond_
“if-gtz vA, :cond_” 如果vA大于0則跳轉到:cond_
“if-lez vA, :cond_” 如果vA小于等于0則跳轉到:cond_
這里主要關注跳轉指令, 因為我們逆向Apk時, 一般只關注特殊的幾點邏輯,
注意跳轉語句跳轉到了哪些特殊函數.
ELF文件格式和尋址方式
這里不詳細介紹,
感興趣的同學可以參考”https://blog.csdn.net/jiangwei0910410003/article/details/49336613”
Arm匯編語法
跳轉指令 存儲器訪問指令 數據處理指令(加減乘除)
空操作 軟中斷
arm匯編里我們主要關注如下函數調用語句:
BL 執行函數調用
BLX執行函數調用, 可以在ARM和Thumb指令集間切換
這里解釋下ARM和Thumb指令集的區別:
Thumb是ARM體系結構中一種指令集。
Thumb指令只有16bit,可以減小代碼量。
Thumb指令功能并不完整,必要時仍需要使用ARM指令集。
擴展下NEON/VFP知識點:
VFP是一種浮點硬件加速器。
NEON是一個SIMD(單指令多數據)協處理器。
以加法指令為例,單指令單數據(SISD)的CPU對加法指令譯碼后,執行部件先訪問內存,取得第一個操作數;之后再一次訪問內存,取得第二個操作數;隨后才能進行求和運算。而在SIMD型的CPU中,指令譯碼后幾個執行部件同時訪問內存,一次性獲得所有操作數進行運算。這個特點使SIMD特別適合于多媒體應用等數據密集型運算。
加固技術:
第一代加固技術——混淆技術;
第二代加固技術——加殼技術(落地與不落地脫殼);
第三代加固技術——指令抽離;
第四代加固技術——指令轉換,即VMP(虛擬軟件保護)加固技術。
二代加固:
加殼是指給可執行文件加個外衣, 這個外衣就是殼程序. 殼程序先取得程序的控制權, 之后把加密的可執行程序在內存中解開為真正的程序并運行.
可執行文件加固示意圖
三代加固:
抽取dex文件中DexCode的部分結構,即虛擬機操作碼。在虛擬機加載到此類的時候對DexCode結構進行還原。
指令抽取-未抽取時
比如此圖中的getPwd方法很重要,需要抽取. 那么生成Dex文件后, 找到Dex文件中的getPwd的方法體, 將對應的方法體抽取出來放到so文件或者特定位置. 然后Hook住系統的FindClass方法, 當系統查找CoreUtils類時, 找到getPwd在內存中的位置, 然后將抽取出來的方法重新寫入. 這樣即使被破解拿到Dex, 這個Dex也是殘缺的, 沒有關鍵的函數.這時候如果我們查看Dex, 會發現getPwd的方法是個空方法.
指令抽取-抽取完成
指令抽取-hook-findClass
該方法的流程如下:
指令抽取流程
四代加固VMP技術:
基于三代加固技術,把原本可執行文件中的機器指令代碼轉換成了它自己虛擬機的指令,而且還插入了大量的垃圾代碼。
這種方法將核心代碼轉化為虛擬機自己的指令, 破解apk的難度和破解虛擬機指令的難度一致. PC上存在類似的VMProtect, 號稱無人一定能破.
VMP加固原理
從難度方面來說, 二代加固一般還有破解思路, 但到了四代加固這里, 一般的逆向脫殼技術全部失效, 你面對的是如何破解這個虛擬機.
https://blog.csdn.net/jiangwei0910410003/article/details/78070610
https://www.leiphone.com/news/201712/TABfBNU8x0lZIPoT.html
https://bbs.pediy.com/thread-224921.htm
2.2 實例
apk加殼實例:
apk加殼示例
apk加殼實例可以用上圖來說明, 我們把要加固的myapk.apk放到一個dex尾部. 這個dex有脫殼邏輯, 程序運行時, 首先運行這個脫殼dex, 脫殼dex從dex尾部獲取到要加密的apk的大小, 然后從自己的dex中拷貝出這個myapk.apk, 最后調用Android系統API運行myapk.apk. 這樣就算用ApkTool等逆向工具, 也無法直接獲得我們加固的myapk.apk. 為了增大逆向難度, 我們可以把脫殼邏輯用c實現放到so文件中, 同時把加密的myapk.apk分段放到so文件中. 為了防止特征破解, 我們可以改寫apk魔數. 這樣下來, 一個簡單的加固工具就完成了.
這里提供一個demo, 只有最簡單的把myapk.apk放到脫殼dex尾部的功能, git地址:
https://github.com/oncealong/apk_dex_shell
demo分為三個項目:
DexReinforcingTools
給Apk加殼的工具, 可以用java或者cpp或者任何其他語言寫成.
MyApk
需要加固的Apk
ShellingMyApk
脫殼Apk, 實際安裝到用戶手機上的是該Apk, 其在Application的attachBaseContext 時會解壓得到實際的apk文件, 然后運行實際的Apk.
這里再說下, 這種二代加殼是現在最簡單的加殼方式, 也是最基本的加殼方式.
參考文檔:
https://blog.csdn.net/jiangwei0910410003/article/details/48415225
3.動態分析
3.1 原理
動態分析主要基于下面兩個工具:
JPDA(Java Platform Debugger Architecture)
JPDA原理圖
JPDA分為三層, 分別是JVMTI,JDWP,JDI.
JVMTI(Java Virtual Machine Tool Interface)是一套由虛擬機直接提供的 native接口,通過這些接口,開發人員不僅調試在該虛擬機上運行的 Java程序,還能查看它們運行的狀態,設置回調函數,控制某些環境變量,從而優化程序性能。
JDWP(Java Debug Wire Protocol)是一個為 Java調試而設計的一個通訊交互協議,它定義了調試器和被調試程序之間傳遞的信息的格式。
JDI(Java Debug Interface)提供 Java API 來遠程控制被調試虛擬機
JPDA-JVM
Android調試模型是一種JPDA框架的具體實現
有兩點主要區別:
JVM TI適配了Android設備特有的Dalvik虛擬機/ART虛擬機
JDWP的實現支持ADB和Socket兩種通信方式
JPDA-Android
ptrace(process trace)
ptrace原理
ptrace()
提供了跟蹤和調試的功能。它允許一個進程(跟蹤進程tracer)去控制另外一個進程(被跟蹤進程tracee)。
tracer可以觀察和控制tracee的運行,可以查看和改變tracee的內存和寄存器。它主要用來實現斷點調試和系統調用跟蹤。
tracer流程一般如下:
tracer流程圖
其中PTRACE_ATTACH/PTRACE_GETREGS/PTRACE_POKETEXT/PTRACE_SETREGS/PTRACE_DETACH定義如下:
PTRACE_ATTACH,表示附加到指定遠程進程;
PTRACE_DETACH,表示從指定遠程進程分離
PTRACE_GETREGS,表示讀取遠程進程當前寄存器環境
PTRACE_SETREGS,表示設置遠程進程的寄存器環境
PTRACE_CONT,表示使遠程進程繼續運行
PTRACE_PEEKTEXT,從遠程進程指定內存地址讀取一個word大小的數據
PTRACE_POKETEXT,往遠程進程指定內存地址寫入一個word大小的數據
ptrace是*nix系統上最常用的系統調用之一, 常見的gdb調試也是通過它實現的.
gdb流程圖
檢測ptrace
當我們使用ptrace方式跟蹤一個進程時,目標進程會記錄自己被誰跟蹤,可以查看/proc/pid/status來確認. 所以apk里為了防止被逆向, 一般都會新開一個線程, 對status做檢測, 如果TracerPid不為0, 立刻退出apk.
/proc/pid/status
正常情況
被ptrace時的status狀態
被ptrace時
反動態分析:
檢查是否有調試
Debug.isDebuggerConnected();
針對ptrace, 檢查TracerPid是否為0
檢測是否在模擬器
getprop不同(虛擬機和真機的環境變量不同,
比如虛擬機的ro.kernel.qemu=1而真機沒有這個屬性)
反反動態分析:
對抗反調試
java層:smali代碼注釋掉
native層 (nop掉so文件或內存中指令, 斷點fopen/fget并修改內存)
Android程序是否可調試:
Android程序是否可調試
開啟調試:
1.下載mprop, 注入init進程, 修改內存中屬性值
./mprop ro.debuggable 1
2.重啟adbd
stop;start
tip:
說到android:debuggable這個屬性, 想到另一個屬性android:allowBackup.
android:allowBackup默認為true, 一定要顯式設置android:allowBackup=false.
否則adb backup/adb restore備份恢復數據
微信6.0以前未設置此屬性,可以備份恢復數據
參考地址:
https://tech.meituan.com/android-remote-debug.html
http://burningcodes.net/%E7%90%86%E8%A7%A3ptrace%E8%B0%83%E8%AF%95%E5%8F%8A%E5%8F%8D%E8%B0%83%E8%AF%95/
https://ops.tips/gists/using-c-to-inspect-linux-syscalls/
https://www.nevermoe.com/?p=854
https://github.com/wpvsyou/mprop
3.2 工具
動態分析工具
這里特別推薦下VirtualXposed, 其基于VirtualApp和epic, 將Xposed安裝到VirtualApp中, 可以不用root權限就使用Xposed, 而且安裝插件后重啟極快.
Frida是一個DBI工具, 使用其進行動態分析時, 被分析進程的TracerPid仍為0. 下圖是Frida原理, 其最初建立連接時通過ptrace向相關進程注入代碼, 其后使用其特有的通道來通信, 如下圖. Frida-Gadget支持Android下非root和iOS下非越獄的逆向.
Frida原理
IDA家喻戶曉, 其支持dex和so的動態分析, 尤其是asm->c的轉化, 可以大大方便分析.
radare是一個比IDA還要強大的工具, 其起源是調查取證, 不過目前支持數不勝數的功能. 但是其學習曲線比Vim還要陡峭
工具地址:
https://forum.xda-developers.com/showthread.php?t=3034811
https://github.com/android-hacker/VirtualXposed
https://github.com/frida/frida
https://www.hex-rays.com/products/ida/
https://github.com/radare/radare2
http://rada.re/r/cmp.html
https://www.megabeets.net/a-journey-into-radare-2-part-1/
3.3 實例
無源碼動態調試smali代碼
可以將apk用ApkTool反編譯后, 使用AndroidStudio+smalidea插件來調試apk.
這里來張圖感受下無源碼調試的強大.
AndroidStudio+smalidea無源碼調試
分享一個小tip, 如何讓程序暫停在啟動界面.
因為反逆向代碼一般在Application的onCreate或更早就執行, 如果等到程序運行到MainActivity再attach進程, 時機就太晚了.
可以用如下命令讓app停在等待debug界面:
等待debug一次: adb shell am set-debug-app -w com.oncealong.sample
一次debug不一定能解決問題,多次調試則在所難免,如果每次調試都執行上述語句, 稍顯啰嗦, 那么此時可以執行下述語句:
一直等待debug: adb shell am set-debug-app -w --persistent com.oncealong.sample
待debug完畢, 使用下述語句取消打開app時的等待.
取消等待debug: adb shell am clear-debug-app
這里的示例不在展開, 只說明這種方法和其效果, 對其感興趣可以看下述鏈接.
參考地址:
http://www.cnblogs.com/goodhacker/p/5592313.html
https://droidyue.com/blog/2017/05/14/a-little-but-useful-debug-skill_for_android/
IDA動態調試
IDA動態調試可以獲得內存中的信息, 比如在dvmDexFileOpenPartial函數上加斷點, 然后執行IDA腳本直接把內存中的dex拷貝出來以脫殼. 詳情見Android應用安全防護和逆向分析相關章節. 這里也不做詳細介紹,
只用下圖展示IDA的強大.
IDA動態調試
參考地址:
https://blog.csdn.net/jltxgcy/article/details/50600241
https://blog.csdn.net/qq1084283172/article/details/46872937
VirtualXposed hook java
VirtualXposed可以hook java, 相比Xposed安裝插件需要重啟手機, VirtualXposed只用重啟下Xposed程序, 如果前者重啟手機耗時1min, 后者重啟Xposed程序只用1s不到. 對于一些簡單的hook或者逆向, 或者驗證Xposed插件邏輯, 這里強烈推薦VirtualXposed. 不過Xposed只支持hook java層, 如果需要hook native層, 可以使用下一個工具Frida.
VirtualXposed hook 構造函數
VirtualXposed hook 方法
參考地址:
https://github.com/android-hacker/VirtualXposed
https://github.com/ac-pm/Inspeckage
http://www.cnblogs.com/lkislam/p/4859959.html
Frida
Frida支持java/native層的hook. 而且Frida支持腳本, 這樣可以更方便的復現結果.
比如Frida的這個Android示例. 將下面的代碼放到一個py腳本中, 隨時運行都可以獲得結果. 不像IDA還需要恢復現場.
Frida-hook
參考地址:
https://github.com/frida/frida/releases
https://github.com/dweinstein/awesome-Frida
https://www.anquanke.com/post/id/85758
https://www.anquanke.com/post/id/85759
https://koz.io/using-frida-on-android-without-root/
https://software.intel.com/en-us/articles/pin-a-dynamic-binary-instrumentation-tool
http://blog.mengy.org/how-valgrind-work/
http://www.ninoishere.com/frida-learn-by-example/
https://www.frida.re/docs/presentations/osdc-2015-the-engineering-behind-the-reverse-engineering.pdf
http://dogewatch.github.io/2017/05/15/Hook-Native-Function-Use-Frida/
4.重編譯
4.1 原理
反重編譯:
運行時檢查簽名(signatures比較長,hash后比較)
運行時校驗保護(校驗classes.dex的md5)
反反重編譯:
查關鍵函數, 注釋掉或nop掉
如果到這一步, 光靠本地的檢測基本無效, 可以考慮在http請求時加入對apk簽名的檢查, 如果不合法就不返回數據. 但是這樣無法阻止app被非法本地運行, 逆向者也可以通過抓包正常apk的請求來模擬正常請求. 不過這樣可以進一步提高破解門檻.
5.Docker
5.1 原理
與逆向工具高內聚,與外界系統低耦合
在Linux下, Docker性能不錯, 還可以使用VNC連接桌面.
# pull imagedocker pull cryptax/android-re:latest# run locally interactivedocker run -it --rm -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix cryptax/android-re:latest /bin/bash# run through ssh or VNCdocker run -d -p SSH_PORT:22 -p VNC_PORT:5900 cryptax/android-re## sample: docker run -d --privileged -p 5900:5900 -p 5022:22 cryptax/android-ressh -X -p SSH_PORT root@127.0.0.1## sample: ssh -p 5022 -X root@127.0.0.1 #password: rootpassvncviewer HOST::VNC_PORT##vncviewer 127.0.0.1::5900工具地址:
https://github.com/cryptax/androidre/
總結
以上是生活随笔為你收集整理的2020-10-26(安卓逆向开篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020-10-25(极客pyc题目)
- 下一篇: 2020-10-26(对Dex文件的理解