1. 程式人生 > >Android 逆向apk的.so動態庫

Android 逆向apk的.so動態庫

那麼我們上篇文章中提及了安全性問題, Android apk如何加固防止被破解(防止逆向編譯),那麼本篇文章提及一點,so動態庫的安全性與重要性。

首先我們要知道, .so動態庫是做什麼用的,它不像.smail檔案可修改,它是屬於組合語言,如果直接去修改,檔案會發生錯亂。早上有人來問我,遊戲打入渠道sdk之後發生錯誤,且只有armeabi裡發生錯誤,這種情況可以斷定.so動態庫中有了相容的衝突。

通常我們會看到libs下面有這麼幾個資料夾,mips、armeabi、armeabi-v7a和x86,其實是代表著不同的CPU型別,那麼在arm下有不同的指令,想要了解的可以參考這篇文章 《Android ARM常用的彙編指令合集》

實現步驟:

  1. 使用apktool命令編譯出來apk的目錄
  2. 然後用IDA開啟.so檔案,在apk根目錄的lib資料夾下
  3. 選單中有個Search,可以用text作為入口
  4. 找到入口,一般為init方法
  5. 使用動態除錯,給libdvm.so中的函式:dvmDexFileOpenPartial 下斷點,然後得到dex檔案在記憶體中的起始地址和大小,然後dump處dex資料即可
  6. 分析底層載入dex原始碼,知道有一個函式:dvmDexFileOpenPartial 這個函式有兩個重要引數,一個是dex的其實地址,一個是dex的大小,而且知道這個函式是在libdvm.so中的。所以我們可以使用IDA進行動態除錯獲取資訊
  7. 雙開IDA開始獲取記憶體中的dex內容,雙開IDA,走之前的動態破解so方式來給dvmDexFileOpenPartial函式下斷點,獲取兩個引數的值,然後使用一段指令碼,將記憶體中的dex資料儲存到本地磁碟中。
  8. 分析獲取到的dex內容,得到了記憶體中的dex之後,我們在使用dex2jar工具去檢視原始碼,但是發現儲存,以為是dump出來的dex格式有問題,但是最後使用baksmali工具進行處理,得到smali原始碼是可以的,然後我們就開始分析smali原始碼。

Tips:

  • debugger模式
  • 通過dump出記憶體中的dex資料,其實不管apk如何加固,最後都是會載入到記憶體中的
  • 可以嘗試呼叫so中的native方法,在知道了這個方法的定義之後 adb shell input text 命令來輔助我們的輸入

我們以 趣頭條.apk 為例,反向思維來看下這個包是如何加密的,首先我們來看目錄結構:

深圳市米奇雲科技有限公司



我們可以看到,smail裡面是沒多少程式碼的,主要的是so動態庫和兩個jar包,開啟jar包來看看:(bdxadsdk.jar 和 gdtadv2.jar )

深圳市米奇雲科技有限公司

深圳市米奇雲科技有限公司



什麼都沒有,可以肯定的是,這個是加了殼的,加密方式是怎麼樣的呢,我們來參考網上一張圖:
深圳市米奇雲科技有限公司



我們先去看下 smail檔案裡面有什麼線索沒

深圳市米奇雲科技有限公司


“libjiagu” 顧名思義,加固,我們發現 ,這個包還加固了。來,繼續往下走

“DexOptJobService_DexOptimization” 動態載入了Dex
深圳市米奇雲科技有限公司



那麼動態載入dex技術是如何處理的?引用一張圖來看看,順便過下這個知識點

深圳市米奇雲科技有限公司

  1. 關於PathClassLoader,API中提及: Android uses this class for its system class loader and for its application class loader(s) —->> Android應用就是用它來載入;

  2. DexClass可以載入apk,jar,及dex檔案,但PathClassLoader只能載入已安裝到系統中(即/data/app目錄下)的apk檔案。

子節點都是從繼承BaseDexClassLoader中來的,那我們去看看原始碼:

@Override  
protected Class<?> findClass(String name) throws ClassNotFoundException {  
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();  
    Class c = pathList.findClass(name, suppressedExceptions);  
    if (c == null) {  
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);  
        for (Throwable t : suppressedExceptions) {  
            cnfe.addSuppressed(t);  
       }  
        throw cnfe;  
    }  
     return c;  
}

從程式碼中我們發現,當我們需要去找class時,是從pathList中的findClass方法中讀取,檢視原始碼,可以發現pathList是DexPathList類的一個例項,我們接著來看findClass(name, suppressedExceptions);

public Class findClass(String name, List<Throwable> suppressed) {  
    for (Element element : dexElements) {  
        DexFile dex = element.dexFile;  
        if (dex != null) {  
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);  
            if (clazz != null) {  
                return clazz;  
            }  
        }  
   }  
    if (dexElementsSuppressedExceptions != null) {  
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));  
    }  
    return null;  
}

看完之後我們可以發現,它是遍歷一個裝在dex檔案(每個dex檔案實際上是一個DexFile物件)的陣列(Element陣列,Element是一個內部類),然後依次去載入所需要的class檔案,直到找到為止。

public String inject(String libPath) {  
    boolean hasBaseDexClassLoader = true;  
    try {  
        Class.forName("dalvik.system.BaseDexClassLoader");  
    } catch (ClassNotFoundException e) {  
        hasBaseDexClassLoader = false;  
    }  
    if (hasBaseDexClassLoader) {  
        PathClassLoader pathClassLoader = (PathClassLoader)sApplication.getClassLoader();  
        DexClassLoader dexClassLoader = new DexClassLoader(libPath, sApplication.getDir("dex", 0).getAbsolutePath(), libPath, sApplication.getClassLoader());  
        try {  
            Object dexElements = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(dexClassLoader)));  
            Object pathList = getPathList(pathClassLoader);  
            setField(pathList, pathList.getClass(), "dexElements", dexElements);  
            return "SUCCESS";  
        } catch (Throwable e) {  
            e.printStackTrace();  
            return android.util.Log.getStackTraceString(e);  
        }  
    }  
    return "SUCCESS";  
}

看到這裡,注入的解決方案也就浮出水面,假如我們將第二個dex檔案放入Element陣列中,那麼在載入第二個dex包中的類時,應該可以直接找到。

OK,這個知識點到底結束,我們知道了這個apk的加固與載入dex方式,那如何逆向分析這個.so動態庫?

等我稍後整理下圖片,先發布了,持續更新。