百度加固免費版分析及VMP修復


背景

本來計划年后跑路的,不知道是我太菜,還是疫情原因,投簡歷都沒人搭理我。現在又不能出門,只好自己找點事干了。

本文基於Android8.1分析。如果不想看分析過程,可以直接跳到最后的總結。

加固和簡單分析

         自己隨便寫個app,上傳到百度開發者平台去加固。

 

 

 

  加固后反編譯看下。包名com.example.test下原來的類都沒了,多了個com.baidu.protect,assets下面多了幾個文件,lib下面多了一個so。猜測是通過libbaiduprotect.so將assets下的文件解密出dex,然后加載。

 

詳細分析

  通過AndroidManifest.xml知道最先執行的class為com.baidu.protect.StubApplication

 

 

 

 

  找到attachBaseContext方法,可以看到先通過Debug.isDebuggerConnected檢測是否被調試,如果被調試就不會加載so,直接進行application替換,這樣肯定是不行的,因為原來的class都沒有被加載進來,程序直接就會崩潰,所以要調試的話需要把這里過掉。

 

 

 

  然后看loadLibrary方法,可以看到直接System.loadLibrary(AppInfo.LIBNAME)加載so,AppInfo.LIBNAME就是baiduprotect。

 

 

 

 

  將libbaiduprotect.so用ida打開。可以看到有.init_array,這個數組包含好幾個函數,而其他很多函數,包括JNI_OnLoad都被加密了。

 

 

 

 

 

 

 

 

  現在詳細分析.init_array中的每一個函數。

       先看第一個函數sub_88060,一看就是被混淆過的。

 

 

 

  先跳過第一個函數,看下后面的。進到函數sub_6FC4,看下c代碼。沒有被加密,也沒有被混淆,但是其中調用的loc_3E73C被加密了。

 

 

 

 

 

  現在只能回去分析第一個函數sub_88060了,把垃圾代碼刪除之后,分析出調用流程,發現全是一些字符操作,應該是在解密,沒有發現有反調試的地方。

 

 

 

  所以直接動態調試,當第一個函數sub_88060執行完后,dump解密后的so。將其用ida打開,可以看到函數都已經被解密了。

 

 

 

  繼續分析.init_array中的函數,通過分析,從sub_6FC4到sub_7578這8個函數都在做一件事情,就是向一個列表添加函數,每個函數添加的時候會指定一個數字(索引),將其通過索引排序插入列表。后面的過程中會按順序調用列表中所有函數。

       這8個函數這里就分析其中的sub_6FC4,其余的都類似,有興趣的可以自己去看。

  為了防止靜態分析,so中所有字符串都被加密的,每個字符串的解密函數都不一樣,但是原理都是一樣的,只是解密用的key不一樣。這里sub_28C28就是一個將字符串解密的函數,然后通過函數sub_3E590初始化一個調用對象,這里的第三個參數(1)就是索引,off_BB5E0是一個指針,里面存着調用的函數地址,qword_BE7B8就是函數列表,函數sub_3E73C用於將剛剛構造的調用對象添加到列表中。

 

 

 

  最后分析得到一個索引(即調用順序)和函數的映射關系。

 

 

 

  .init_array中最后兩個函數sub_76BC和sub_7744是初始化tls和mutex相關的,就不看了。

 

  .init_array分析完了,就該JNI_OnLoad了。可以看到它也被混淆了。

 

 

 

  將垃圾代碼刪除后,得到真實流程。開始我以為會通過時間來判斷是否被調試,其實這里獲取時間只是統計信息上報。

 

 

 

  這里的函數sub_3E628就是調用隊列中的所有函數,它的第2個參數會被作為參數傳到函數中,判斷函數是否該執行。

 

 

 

  所以,首先用1作為參數調用隊列中的所有函數,通過分析只有sub_B3B4會執行。該函數通過dlsym將libc中的符號獲取保存下來,之后調用這些函數都通過指針調用。

 

 

 

  繼續回到JNI_OnLoad,執行完函數隊列中的函數,就該執行函數sub_91E4了,該函數是注冊com.baidu.protect.A中的部分本地函數,n001,n002,n003,分別對應的函數為sub_9318,sub_94E4,sub_9564

 

 

 

 

 

  繼續回到JNI_OnLoad,注冊函數后,失敗就直接返回了,成功則以2作為參數再次調用隊列中的所有函數。通過分析,只有sub_3E29C會執行。可以看到,該函數可以接受2和3,現在我們只看參數為2的情況。

 

 

 

  現在進入sub_3E36C,其主要就是調用sub_3E3F0,而sub_3E3F0就是通過讀取/proc/self/maps文件,通過加載的虛擬機文件,判斷虛擬機類型。

 

 

 

  至此,libbaiduprotect.so的加載流程就執行完了。

  現在回到java層,調用com.baidu.protect.A.n001方法,通過前面分析可以,該方法對應的本地函數為sub_9318

 

 

 

  sub_9318中將參數保存起來,然后最主要就是調用了sub_781C,

 

 

   

  sub_781C也是被混淆的,但是代碼很少,稍微看下,就知道只做了一件事,就是用3作為參數調用函數隊列中的所有函數。

 

 

 

  通過分析,會有sub_3E29C、sub_40CF8、sub_3DFC4、sub_11F5C、sub_45964這幾個函數執行。

  先看sub_3E29C,這個函數之前執行過參數為2的部分,現在來看參數為3的部分。先執行sub_13880,通過分析,該函數是獲取apk包的簽名,然后計算簽名的MD5。然后sub_66064將簽名的MD5值進行擴展,變為176字節。然后將簽名的MD5和擴展后的內容存放在qword_BE690。

 

 

 

  再看sub_40CF8,該函數可以接受3和4作為參數,現在先看3,

  調用sub_409E0,它先注冊com.baidu.protect.CrashHandler的本地函數,然后調用init方法。然后用sigaction設置信號4、6、7、8、11的回調sub_40BD0。

 

 

 

  再看sub_3DFC4,這個函數最主要就是sub_3D6AC,通過解析apk中assets,生成各種路徑,然后通過qword_BE2F0生成目錄,qword_BE2F0就是之前從libc中獲取的mkdir的指針。

 

 

 

  再看sub_11F5C,該函數只是調用了sub_BC60,sub_BC60也被混淆了,刪除垃圾代碼后,流程如下。因為我用的手機是8.1的,所以只看了sdk大於26的,

 

 

 

  先看sub_188AC,該函數也是被混淆的,刪除垃圾代碼后流程如下,

 

 

   

  sub_4029C讀取/proc/self/maps文件,查找對應的so,修改內存頁屬性

 

 

 

  sub_3FF9C是對函數進行hook,通過動態節,找到重定位節和got,然后替換指定標簽的地址。這里分別hook了__android_log_print和mmap,__android_log_print被替換為sub_1B044,這是個空函數,禁用log。      mmap被替換為sub_1B070,在加載dex的時候有用,稍后分析。

 

  再看sub_11AB0,最主要的是下面的這個循環,將assets下所有的jar解密為dex,然后通過InMemoryDexClassLoader加載,然后提取DexPathList$Element添加到源classloader中。

 

 

 

  對於每個assets/baiduprotect*.jar,它由以下幾部分組成,

 

 

 

  先看sub_3BA90,該函數用於解密dex,其中sub_9B2C用於獲取apk包中的所有文件目錄,sub_A104用於檢查apk包中是否存在assets/baiduprotect*.jar,sub_A23C用於獲取該文件的信息(壓縮前后大小,時間,crc等),sub_A60C用於將文件解壓出來,sub_65980用之前的簽名信息將前0x1000字節解密,sub_1C43C將附加數據1復制到附加數據頭中指定的位置,用簽名信息解密,再用附加數據2修復class_data_off_,然后重新計算校驗和。

  由此可知,如果重新打包,簽名不正確的話就會解密失敗。

  dex脫殼:當sub_3BA90執行返回就可以dump解密的dex了,如果不想動態調試手動搞,也可以寫個xposed模塊,在后面一步InMemoryDexClassLoader加載dex的時候獲取。

 

 

 

  sub_18880保存當前dex的大小,后面加載dex的時候要用到。

  sub_12100用InMemoryDexClassLoader類調用NewObjectV加載dex,InMemoryDexClassLoader內部會使用mmap分配內存存放dex,通過前面分析,我們知道mmap被hook替換為sub_1B070,所以現在來看下sub_1B070,可以看到,先將原始方法調用了一遍,然后檢查是否為剛才加載dex所需的那塊內存,是則將其保存。

 

 

 

  回到剛才加載dex的調用之后,通過sub_1217C分配一個新的DexPathList$Element數組,將原來系統的類加載器和剛才的InMemoryDexClassLoader中的classLoader.pathList.dexElements合並成一個數組,然后替換原來系統中的類加載器的dexElements。sub_1889C獲取剛才mmap的hook函數保存的dex地址。sub_3DDC8解析出dex中的各種數組指針

 

  最后一個函數sub_45964,主要就兩步。

 

 

  

  第一步調用sub_42C08注冊com.baidu.protect.A中剩余的本地函數,用於vmp代理,可以看到,一共10個代理,每個對應一個返回值類型。每個函數對應的本地函數都是一樣的,均為sub_42598。

 

 

 

  第二步就是對每個dex調用sub_42D8C,去解析附加數據3,通過分析,其數據結構如下,其中有用的字段為方法數組和指令替換表。

 

 

 

  其中方法的數據結構如下,

 

 

 

  至此,com.baidu.protect.A.n001方法的調用過程就分析完了。

  現在,回到attachBaseContext,剩下的就只是替換程序原來的application了。

  然后就是onCreate,它調用了com.baidu.protect.A.n002

 

 

 

  通過前面分析可以,該方法對應的本地函數為sub_94E4,該函數主要就是用4作為參數調用函數隊列中的所有函數。

 

 

 

  通過分析,會有sub_ 40CF8、sub_ 3E96C、sub_ 42388這幾個函數執行。這部分就不詳細寫了,通過分析知sub_ 40CF8調用CrashHandler.asynRun方法,向https://apkprotect.baidu.com/apklog發送統計信息。sub_ 3E96C assets/baiduprotect.m檢查dex的完整性,該文件中存有加密的dex MD5。sub_ 42388注冊com.baidu.xshield.jni.Asc和com.baidu.xshield.utility.KeyUtil的本地函數,調用com.baidu.xshield.ac.XH.init方法。

  至此啟動流程分析完畢。

 

  接下來看看dump出來的dex文件。可以看到onCreate方法被改了,調用了沒有返回值那個代理函數。推測0xAB000000是其id,暫且稱為vmp_method_id

 

 

 

  由前面分析可知,所有代理綁定的本地函數都為sub_42598,該函數也被混淆了,刪除垃圾代碼后如下,由此可知,vmp_method_id值的最高字節沒有用,第16至24位為dex編號,通過該值找到對應dex解析后的信息。

 

 

 

  sub_4A458通過分析,可知,vmp_method_id的低16位為附加數據3中method的索引,通過該id找到對應的method結構,然后分配寄存器空間。將參數值放入寄存器中,然后檢查指令對應的函數數組有沒有初始化,沒有初始化則通過附加數據3最后的指令替換表,將原始的指令數組映射。然后開始通過解釋器執行指令。

  隨便找個vmp化后的方法指令,然后和未加固前的指令對比,可以看出只是將指令第一個字節替換了(第一個字節表示哪條指令,后面的都是操作數)。還有就是有些id因為重新編譯后變了。所以我們只需要把指令根據替換表再改回來就行了。

 

 

  指令替換函數去掉混淆后的垃圾代碼如下

 

 

 

修復vmp方法

思路:

1.將附加數據3解析出來,構造成DexCode添加到dex文件的后面,然后將class_data中的code_off修改為新構造的DexCode。

2.因為code_off是uleb128類型的值,所以新的值和舊的值占用空間可能不一樣,所以當空間占用相同的情況下,則在原來的地方直接修改,不相同的話還得重新構造一個class_data放在所有添加的DexCode之后,然后將class_def中的class_data_off更新。

3.如何判斷dex中的方法和附加數據中方法的關系?通過dex中方法id(暫且稱為dex_method_id)和調用vmp代理時使用的vmp_method_id進行關聯,如下圖所示。

4.當method_ids_map為空的時候,修復程序將所有方法和它的id輸出到文件,然后手動在文件中去找到對應方法dex_method_id,添加到method_ids_map中,再次運行修復程序就會將map所有指定的方法修復

 

 

 

修復過程:

首先將dex讀取進內存。

 

 

 

當map為空的時候,將文件添加個后綴,然后將所有方法信息寫入。

 

 

 

然后通過搜索找到方法dex_method_id,然后將方法dex_method_id和vmp調用時的vmp_method_id添加到map中。

 

 

 

 

 

再次執行,現在因為map不為空,開始修復。

首先,解析附加數據3構造出所有的DexCode。

 

 

 

然后遍歷class的方法,修復code_off

 

 

 

 

 

 

將修復后的內容寫入文件。

 

 

 

修復完成后,將dex反編譯,可以看到已經能夠看到原來的代碼了。

 

 

 

總結

1.直接在InMemoryDexClassLoader構造函數處獲取dex。

2.反編譯獲取到的dex,找到所有vmp方法的vmp_method_id,如下圖所示。

 

 

3.第一次運行修復程序,將str_dex_path改為要修復的文件路徑,method_ids_map內容置空,如下圖所示。

 

 

4.第一次運行完后,在dex同目錄下,有一個同名.methods.txt后綴的文件,打開找到對應方法的dex_method_id,如下圖所示。

 

5.第二次運行修復程序,將方法對應的兩個id添加到method_ids_map,如下圖所示。注意順序不要搞反了。

 

6. 第二次運行完畢后,在dex同目錄下,有一個同名.new.dex后綴的文件,反編譯就能看到修復后的代碼了。

 

說明

本文的數據結構和修復程序只對當前分析的版本有效。

 

附件

分析所用的apk和修復代碼【baiduprotect.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM