0.寫在前面
本文是筆者自學筆記,以破解某目標apk的方式進行學習,中間輔以原理性知識,方便面試需求。
參考文章的原文鏈接會附在相應流程位置,方便閱讀學習。
1.獲取目標apk
第一步是拿到目標安裝包文件,這一步很簡單,可以在主流的第三方市場內獲取,但是主流的第三方市場都要求應用加固,為了之后方便,筆者常常去app的官網下載,或者去一些小市場下載,運氣好的話能下載到他們的未加固版本。
2.確定逆向目標
這個就看各自需求,比如有破解內購,分析反逆向邏輯,crackme題目,查看競品實現方式,確定好目標再下手。
3.拖動目標文件到集成工具
直接拖apk到集成工具,如改之理,比如jadx,這里是為了檢查有沒有加固。
附上一些必要的開發工具下載地址
http://www.lxweimin.com/p/b33eb2f5efa0
4.加固
這里判斷很好判斷,形如有StubShell的包,都是被加固了的。
那接下來就是要脫殼了。
4.1加固原理
脫殼之前,了解殼是什么(加固原理)。
http://www.lxweimin.com/p/cec7ef861ace
加殼
https://mp.weixin.qq.com/s/KELi6e6x4-svGP6Ef6r4jQ
殼的加載
https://mp.weixin.qq.com/s/AYZ7k75IlKLDbAIJZ2BelA
so的加載
https://mp.weixin.qq.com/s/BhGxnJrRnrYAEWJ7zDZKmQ
還可能涉及到,so指令膨脹(加花),指令抽取等防護。
4.2脫殼
接下來才是脫殼
脫殼還可能涉及到指令修復
殼的指令修復
https://mp.weixin.qq.com/s/-vrDvp3rbTKNIX-pAlamVw
4.2.1手脫
手脫的麻煩點就在于過各種檢測,過完所有的檢測最終就是找到dex的內存地址,最后運行代碼進行dump
鏈接是收集各種手工脫殼的帖子
http://www.lxweimin.com/p/3f6bcf364197
4.2.2機脫
實際上就是各種脫殼機,原理就是
無論什么殼,最終都是要加載到內存中,等apk已經加載到內存后,脫殼機把dex dump出來就行了,也就省去了手工脫殼的麻煩。
這里的原理可參考
Android中apk加固完善篇之內存加載dex方案實現原理(不落地方式加載)
http://www.520monkey.com/archives/629
原理主要討論了兩個問題
如果解密后有一個未加密的apk做中間產物,那就是落地方式,這個方式已經被淘汰了,因為不安全。
如果不落地則可能出現加載兩次到內存的效率問題。
這里不深研,只提供一個脫殼機,fdex2
看雪原帖(也說了原理)
https://bbs.pediy.com/thread-224105.htm
52破解帖
https://www.52pojie.cn/forum.php?mod=viewthread&tid=758726&fromguid=hot
推薦另一個機脫dumpDex
https://github.com/WrBug/dumpDex
核心代碼,有空可以看
package com.ppma.xposed;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class MainHook implements IXposedHookLoadPackage {
XSharedPreferences xsp;
Class Dex;
Method Dex_getBytes;
Method getDex;
String packagename;
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
xsp = new XSharedPreferences("com.ppma.appinfo", "User");
xsp.makeWorldReadable();
xsp.reload();
initRefect();
packagename = xsp.getString("packagename", null);
XposedBridge.log("設定包名:"+packagename);
if ((!lpparam.packageName.equals(packagename))||packagename==null) {
XposedBridge.log("當前程序包名與設定不一致或者包名為空");
return;
}
XposedBridge.log("目標包名:"+lpparam.packageName);
String str = "java.lang.ClassLoader";
String str2 = "loadClass";
XposedHelpers.findAndHookMethod(str, lpparam.classLoader, str2, String.class, Boolean.TYPE, new XC_MethodHook() {
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Class cls = (Class) param.getResult();
if (cls == null) {
//XposedBridge.log("cls == null");
return;
}
String name = cls.getName();
XposedBridge.log("當前類名:" + name);
byte[] bArr = (byte[]) Dex_getBytes.invoke(getDex.invoke(cls, new Object[0]), new Object[0]);
if (bArr == null) {
XposedBridge.log("數據為空:返回");
return;
}
XposedBridge.log("開始寫數據");
String dex_path = "/data/data/" + packagename + "/" + packagename + "_" + bArr.length + ".dex";
XposedBridge.log(dex_path);
File file = new File(dex_path);
if (file.exists()) return;
writeByte(bArr, file.getAbsolutePath());
}
} );
}
public void initRefect() {
try {
Dex = Class.forName("com.android.dex.Dex");
Dex_getBytes = Dex.getDeclaredMethod("getBytes", new Class[0]);
getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex", new Class[0]);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void writeByte(byte[] bArr, String str) {
try {
OutputStream outputStream = new FileOutputStream(str);
outputStream.write(bArr);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
XposedBridge.log("文件寫出失敗");
}
}
}
4.3得到.dex文件
dex文件是什么
java,dex,smali,關系/區別又是什么
http://www.lxweimin.com/p/fb9aec070c0a
延展閱讀
dex直接得到smali,插入log代碼再回編譯dex
https://blog.csdn.net/jiangwei0910410003/article/details/79820040
涉及到兩個語句
java -jar baksmali.jar -o classes classes.dex 其中classes是反編譯dex之后的smali文件夾目錄
java -jar smali.jar classes -o classes.dex 其中classes是反編譯的smali目錄,classes.dex是回編譯之后的dex
5.集成工具分析
其實就是可以把剛才拿到的.dex拖進來分析了
6.減少混淆干擾
apk在打包時會被混淆,文件名被替換成影響無意義的字母甚至漢字。
jdax通過以下設置來減少混淆干擾
https://segmentfault.com/a/1190000012180752
發散:proguard的源碼閱讀
http://www.lxweimin.com/p/734424a14eff
7.定位目標
借助第三方工具
比如adb工具
https://blog.csdn.net/halibobo1998/article/details/50623929
或者有一部已經root的機器(可以減少很多麻煩),使用layoutInspector去查布局找id
簡單的就下一個第三方app 當前activity
還可以通過搜索界面里的特殊字串來定目標文件。
因為資源字串無論怎么混淆,反編譯出來的public.xml一定有對應的int值
8.分析業務邏輯
9.確定逆向方法
比如想通過xposed框架去hook某個結果,還是直接nop掉某個返回值。
10.使用apktool進行反編譯
執行代碼
apktool d -f 目標apk路徑
這里可能遇到殼利用apktool漏洞對其進行的干擾
http://www.520monkey.com/archives/808
11.得到.smali
smali語法
http://www.lxweimin.com/p/54e893ae28ea
空類的smali結構
http://www.lxweimin.com/p/758bccaaa0a6
12.源碼調試
靜態調試
http://www.lxweimin.com/p/4359598a2c9b
動態調試
http://www.lxweimin.com/p/90f495191a6a
動態調試so
http://www.lxweimin.com/p/5617220cbb02
13.回編譯 app
執行命令
apktool b -d 資源路徑 -o 輸出.apk
可能會遇到回編譯失敗的情況,比如低版本的apktool不認識高Android版本的屬性,可以選擇升級apktool或者刪除該屬性值。
14.簽名
執行命令
apksigner sign --ks testKey.jks --ks-key-alias testkey --ks-pass pass:123456 --key-pass pass:123456 --out output_sign.apk source.apk
參考
Android逆向分析筆記
https://lichao890427.github.io/wiki/android%20reverse%20engineering/#%E6%A6%82%E8%BF%B0
adb指令
http://www.lxweimin.com/p/85373d89bc81
bugly出的一篇反調總結
大部分方法都寫過,少部分如文件節點,inotify,so hash檢測可以加以了解(就是我沒寫過)
https://mp.weixin.qq.com/s/uvrkAvbfWuDYf7SWX_dJBA
todo
還有smali,baksmali沒寫上去,還有教我兄弟學逆向,臥槽還有好多。。