1. 程式人生 > >Apk脫殼聖戰之---脫掉“360加固”的殼

Apk脫殼聖戰之---脫掉“360加固”的殼

一、前言

現在主流的加固平臺有:梆梆加固,愛加密,360加固,騰訊加固,在之前的一篇文章中介紹了:如何脫掉“愛加密”的殼,現在這裡要脫掉另外一個平臺的殼:360加固,因為有了之前的脫殼經驗,很多基礎知識和準備工作這裡就不詳細介紹了,為了能夠脫掉他家的殼,用一個案例來去360平臺進行加固,然後進行脫殼。下面就來開始脫殼:

二、分析360加固的原理

首先拿到加固之後的apk,這裡為了方便檢視內部資訊,先不用dex2jar+jd-gui工具進行分析了,直接使用我們之前分析了原始碼的一個工具:Jadx,直接檢視:


其實現在的加固的常規套路都差不多,這裡看到和之前分析的愛加密加固的形式幾乎一樣,這裡的殼Application是StubApplication在attachBaseContext中做一些初始化操作,一般是將assets目錄中的so檔案拷貝到程式的沙盒目錄下:/data/data/xxx/files/..;然後再用System.load進行載入,通過檢視可以得知源程式apk已經被加密了,就是存放在這裡的so中,之前的文章也是分析了,一般源程式加密之後就存放在那幾個目錄下,一般是:dex檔案尾部,libs目錄,assets目錄。

下面再來看一下他的AndroidManifest.xml檔案:


找到了他的入口Activity了,但是這裡沒有android:debuggable="true",所以程式是不能被除錯的,所以我們需要新增這個屬性,然後在進行回編譯進行除錯,這時候就需要使用到apktool工具了:


好了,這裡看到,360加固為了防止apktool反編譯功能,添加了一個qihoo屬性,這個屬性apktool不認識就報錯了,但是我們之前的一篇文章已經介紹了:,我們有了apktool原始碼,可以直接進行修復的,然後進行反編譯:


反編譯成功了,檢視他的AndroidManifest.xml檔案內容:


的確,是有一個屬性qihoo,這個就是Android系統在解析apk檔案的時候,發現不存在的屬性直接略過,但是apktool工具卻不會,360加固就是利用這個漏洞來增加反編譯難度的,但是我們之前的一篇文章中介紹瞭如何修復,這裡修復很簡單了。所以說只要有了apktool原始碼,什麼都好做了。

然後我們在新增android:debuggable屬性:


然後回編譯:


這時候看到,在回編譯的時候也是報錯了,說找不到這個屬性,為了方便這裡直接把android:qihoo給幹掉,因為其實他沒有任何作用的,就是為了干擾反編譯工作的,所以直接去掉即可,然後在回編譯:


好了,回編譯成功,然後在進行簽名打包即可。這裡就不在介紹了。

那麼從上面我們可以看到,其實360加固為了防止反編譯,就利用了Android系統本身在解析apk的時候,遇到不認識的屬性直接略過,而apktool工具卻不會的漏洞來給AndroidManifest.xml中新增一個混淆反編譯的屬性:qihoo,幸好我們有原始碼,可以修復這個問題,在進行反編譯即可,這裡也希望apktool官網能夠及時修復這個漏洞。為了回編譯成功,我們可以直接把這個屬性刪除。不然回編譯也是會報錯的。這個屬性只是360為了混淆反編譯工作,所以刪除對程式邏輯沒有任何影響的。

三、開啟系統的除錯總開關

這裡就要開始介紹本文的第一個重點了:如何在不需要反編譯的情況下,新增android:debuggable屬性,就可以進行除錯。

這個現在已經有很多工具可以做了,先來說說具體的原理吧:

其實Android中有一些常用的配置資訊都是存放在一個檔案中,比如裝置的系統,版本號,cpu型號等資訊,而這個檔案位置在:

/system/build.prop


我們檢視檔案的內容,可以看到很多裝置的資訊,而且這些ro開頭的表示這些屬性值是隻讀的,不能進行修改的。

同時Android中提供了兩個命令來操作這些資訊:getprop和setprop命令:


檢視系統的sdk版本號


設定系統的sdk版本號為22,可是這裡並沒有修改成功,原因就是因為ro開頭的屬性是不允許後期修改的,改也是可以修改的,需要重新編譯系統映象檔案boot.img,但是這裡並不是本人介紹的重點了。

既然Android中的一些系統屬性值存放在一個檔案中的,而且這些值是隻讀的,當然不僅可以通過getprop命令讀取,有一個api也是可以直接讀取的,就是:System.getProperty("ro.build.version.sdk");其實這個方法是native層實現的,具體就不分析了。

那麼這個檔案是儲存這些屬性值的,那麼是誰來進行解析載入到記憶體中,能夠給每個app都能訪問到呢?

這個工作就是init.rc程序操作的,我們應該瞭解了系統啟動的時候第一步就是解析init.rc檔案,這個檔案是在系統的根目錄下,這裡會做很多初始化操作,這裡不詳細分析了,後面再分析Android中系統啟動流程的時候在詳細分析。這裡同時會做屬性檔案的解析工作,所以,Android 屬性系統通過系統服務提供系統配置和狀態的管理。為了讓執行中的所有程序共享系統執行時所需要的各種設定值,系統會開闢一個屬性儲存區域,並提供訪問該記憶體區域的 API。所有程序都可以訪問屬性值,但是隻有 init 程序可以修改屬性值,其他程序若想修改屬性值,需要向 init 程序發出請求,最終由 init 程序負責修改屬性值。

那麼上面說到的是system/build.prop檔案。裡面主要是系統的配置資訊,其實還有一個重要檔案在根目錄下面:default.prop:


這裡有一個重要屬性:ro.debuggable,對這裡就是關係到系統中每個應用是否能夠被除錯的關鍵。其實在Android系統中一個應用能否被除錯是這麼判斷的:

當Dalvik虛擬機器從android應用框架中啟動時,系統屬性ro.debuggable為1,如果該值被置1,系統中所有的程式都是可以除錯的。如果系統中的 ro.debuggable 為0,則會判斷程式的AndroidManifest.xml中application標籤中的 android:debuggable元素是否為true,如果為true則開啟除錯支援。

好了到這裡,我們可以總結一下了:

Android系統中有一個可以除錯所有裝置中的應用的開關,在根目錄中的default.prop檔案中的ro.debuggable屬性值,如果把這個值設定成1的話,那麼裝置中所有應用都可以被除錯,即使在AndroidManifest.xml中沒有android:debuggable=true,還是可以除錯的。而這些系統屬性的檔案system/build.prop和default.prop,都是init程序來進行解析的,系統啟動的時候就會去解析init.rc檔案,這個檔案中有配置關於系統屬性的解析工作資訊。然後會把這些系統屬性資訊解析到記憶體中,提供給所有app進行訪問,這塊資訊也是記憶體共享的。但是這些ro開頭的屬性資訊只能init程序進行修改。下面來分析一下修改這個屬性值的三種方式:

第一種:直接修改default.prop檔案中的值,然後重啟裝置

那麼現在如果按照上面的目的:就是不需要反編譯apk,新增android:debuggable屬性的話,直接修改default.prop檔案,把ro.debuggable屬性改成1即可,但是通過上面的分析,修改完成之後肯定需要重啟裝置的,因為需要讓init程序重新解析屬性檔案,把屬性資訊載入記憶體中方可起作用的。但是並沒有那麼順利,在實踐的過程中,修改了這個屬性,結果出現的結果就是裝置宕機了,其實想想也是正常的,如果屬效能夠通過這些檔案來修改的話,那就感覺系統會出現各種問題了,感覺系統是不會讓修改這些檔案的內容的。

第二種:改寫系統檔案,重新編譯系統映象檔案,然後刷入到裝置中

那麼上面修改default.prop檔案,結果導致宕機,最終也是沒有修改成功,我們還有什麼辦法呢?其實上面已經提到過一次了,就是這些屬性檔案其實是在系統映象檔案boot.img在系統啟動的時候,釋放到具體目錄中的,也就是說如果我們能夠直接修改boot.img中的這個屬性即可,那麼這個操作是可以進行的,但是困難那是不一般的順利,至少我沒成功過,修改系統檔案,然後重新編譯映象檔案,最後在刷到裝置中。這個過程我嘗試過是失敗了,不過理論上是可以的。而且這種方式如果成功了,那麼這個裝置就是永遠可以進行各種應用的除錯了。

第三種:注入init程序,修改記憶體中的屬性值

那麼上面直接重新編譯boot.img,然後在刷到裝置中的工作是失敗的,那麼還有其他方法嗎?肯定是有的,我們其實在上面分析了,init程序會解析這個屬性檔案,然後把這些屬性資訊解析到記憶體中,給所有app進行訪問使用,所以在init程序的記憶體塊中是存在這些屬性值的,那麼這時候就好辦了,有一個技術可以做到了,就是程序注入技術,我們可以使用ptrace注入到init程序,然後修改記憶體中的這些屬性值,只要init程序不重啟的話,那麼這些屬性值就會起效。好了,這個方法可以嘗試,但是這個方法有一個弊端,就是如果init程序掛了重啟的話,那麼設定就沒有任何效果了,必須重新操作了,所以有效期不是很長,但是一般情況下只要保證裝置不重啟的話,init程序會一直存在的,而且如果發生了init程序掛掉的情況,那麼裝置肯定會重啟的。到時候在重新操作一下即可。

好了上面分析了三種方式去設定系統中的除錯屬性總開關,那麼最後一種方式是最靠譜的。

而且思路也很簡單,但是我們不會重新去寫這個程式碼邏輯的,因為已經有大神做了這件事,具體工具後面會給出下載地址:

這個工具用法很簡單,首先把可執行檔案mprop拷貝到裝置中的目錄下,然後執行命令:

./mprop ro.debuggable 1


這個工具可以修改記憶體中所有的屬性值,包括機型資訊。

這裡修改完成之後,使用getprop命令在檢視值,發現修改成功了,但是需要注意的是,我們修改的是記憶體的值,而不是檔案中的值。所以default.prop檔案中的內容是沒有發生變化的。


這時候,我們可以使用Eclipse的DDMS來檢視可以除錯的應用列表:


當然也可以使用adb jdwp命令來檢視可以除錯的程序id:


但是可惜的是,發現還是沒有展示裝置中所有的應用,其實這裡是有一個細節問題了,因為我們雖然修改了記憶體值,但是有一個程序我們需要重啟一下,哪個程序呢?那就是adbd這個程序,這個程序是adb的守護程序,就是裝置連線資訊傳輸後臺程序,所以想看到可以除錯的程序資訊的話,那麼需要重啟這個程序,這樣連線資訊才會更新。

重啟這個程序很簡單:直接使用stop;start命令即可


其實這是兩個命令,用分號隔開,首先是幹掉程序,然後在重啟。

執行完命令之後,再去看DDMS視窗資訊:


這時候所有的應用程序都是可以除錯的了,這時候我們在使用dumpsys package命令檢視一個應用的包資訊:


這裡可以看到,這個應用的flags標誌中並沒有debuggable屬性值,但是這個應用是可以除錯的。所以看到ro.debuggable這個是總開關,只要他為1,開啟的話,即使沒有android:debuggable也是可以的了。

好了到這裡,我們來總結一下:

1、我們的目的是怎麼在不需要反編譯apk包,新增android:debuggable屬性,就可以進行apk的除錯?

2、我們通過分析系統屬性檔案和系統啟動流程以及解析系統屬性檔案的流程,知道了裝置中關於除錯有一個總開關屬性值:ro.debuggable,預設是0,不開啟的。那麼這時候我們就可以猜想有這幾種方式可以去修改。

3、分析了三種方式去修改這個屬性值:

第一種方式:直接修改default.prop檔案中的這個欄位值,但是可惜的是修改失敗,在修改的過程中出現宕機,重啟裝置之後,屬性值還是0。

第二種方式:修改系統原始碼的編譯指令碼,直接修改屬性值,然後重新編譯映象檔案boot.img,然後刷入到裝置中,但是在實踐的過程中並沒有成功,所以放棄了,而且這種方式有一個好處就是一旦修改了,只要不在重新刷系統,那麼這個欄位將永遠有效。

第三種方式:注入到init程序,修改記憶體中的這些系統屬性值,這種方式實現是最簡單的,但是有一個問題,就是一旦裝置重啟,init程序重新解析default.prop檔案的話,那麼ro.debuggable值將又重新被清空,需要再次注入修改。

4、最後採用了第三種方式,不過網上已經有人寫了這樣的工具,用法也很簡單:./mprop ro.debuggable 1;但是修改完成之後,一定要記得重新啟動adbd程序,這樣才能夠獲取到可以除錯應用資訊。

5、使用工具修改完成之後,在Eclipse中的DDMS視窗發現,裝置中的所有應用都處於可以除錯狀態了。也就是說我們的操作成功了。

那麼上面的這個過程成功之後的意義還是很大的:標誌著我們以後如果是單純的想讓一個apk能夠被除錯,去反編譯在新增屬性值的話,其實這種方式很高效的。可以讓任意一個apk出於被除錯狀態。

四、開始脫殼

講完了上面的一個重點之後,下面我們就開始來講解本文的另外一個重點,開始脫殼了。

第一步:開啟android_server


第二步:埠轉發


第三步:啟動應用

adb shell am start -D -n com.CMapp/com.e4a.runtime.android.mainActivity


第四步:開啟IDA,附加程序


第五步:設定Debugger Option選項


第六步:執行jdb除錯等待

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=10265


注意:這裡需要注意了,因為我們改了系統的ro.debuggable屬性,裝置中所有的應用都處於可調式狀態,基本埠8700已經被佔用了,那麼這時候需要使用被除錯程式的獨有埠了,可以在DDMS視窗進行檢視。

第七步:關鍵函式下斷點


首先找到mmap函式的記憶體地址,這裡可以直接使用G鍵,通過函式名來跳轉:


注意:這裡和之前的脫愛加密的殼方法可能不一樣了,還記得之前脫愛加密的殼的時候,給fopen和fgets函式下斷點,因為如果有反除錯的話,肯定是讀取/proc/pid/status檔案中的TracerPid欄位值的,然後修改TracerPid值為0即可,但是這個方法對360加固的不好使了,因為360加固的反除錯是通過mmap函式來讀取/proc/pid/status,所以這裡需要給mmap函式下斷點了,而且後面還會看到給dvmDexFileOpenPartial這個函式下斷點也不好使了,原因是360加固自己在底層實現瞭解析dex的函式來替代了這個dvmDexFileOpenPartial函式。但是不管是他自己實現dex解析載入,最終都是需要把dex檔案載入到記憶體中,還是得用mmap函式來進行操作。所以在脫360加固的殼的時候mmap函式是重點。

好了給mmap函式下了斷點,下面就F9執行程式吧:


進入到了mmap的斷點處,這裡因為mmap函式程式碼比較長,為了節省時間,我們可以在mmap函式的結束處下一個斷點,然後直接F9執行到函式的結尾處,因為系統中有很多個so需要載入到記憶體中,所以mmap函式會執行多次,但是其實我們最關心的是載入我們自己的so檔案,即libjiagu.so檔案,因為這個才是我們的native層程式碼,所以等出現如下介面:


這時候,說明這個so檔案被載入到記憶體中了,也就是程式的native層程式碼開始執行了,注意不能在F9了,而是使用F8單步除錯:


F8單步執行到這裡的時候,遇到一個問題,就是F8了很多次,始終在這個地方執行,後來分析了arm指令之後,發現原來這裡是一個迴圈,初始值是0,儲存在R11中,然後逐步加1,和R3中儲存的閾值作比較,通過檢視暫存器的值,發現R3暫存器中是A7,所以這裡得去修改暫存器R11的值了,不然我們得單步A7次,這裡直接把R11值修改為A6:


修改暫存器也是很容易的,直接右擊暫存器:


點選Modify value:


點選OK,之後再來看看R11的暫存器的值:


修改成功了,這時候在單步F8,兩次之後就執行完了迴圈了,從這裡也可以看到,這個地方也算是為了防止被除錯,加大除錯成本的一種方式。繼續往下走:


到這裡,執行完BL之後就退出除錯介面了,嘗試多次都一樣,所以猜想反除錯肯定在這裡,可以F7跟進去看看:


到BLX這裡,每次之前完也是退出除錯介面,所以這裡還得F7單步進入看看:


這裡看到了一行重要的arm指令:CMP比較指令,而且是和0比較,很可能這裡就是比較TracerPid的值是否為0,如果不為0就退出,可以檢視R0暫存器的內容:


然後在檢視被除錯程序的TracerPid的值:


果然R0儲存的是TracerPid的值,為了驗證正確性,這裡繼續:


果然,執行到了自殺的地方,一直單步執行:


退出程式了。

那麼上面就知道了反除錯的地方,就好辦了,直接修改暫存器R0的值為0即可:


然後繼續單步F8執行,後面還有一個CMP和0進行比較的地方,我們一樣進行置零操作,再次單步F8,當執行到此處的時候:


看到memcpy函式的時候,這時候可以直接執行F9,又會執行到mmap那裡,然後依次F9,還是執行到了上面的那個迴圈,這樣依次類推,在這個過程中我運行了7次迴圈,改了R0值改了9次,所以這個地方會執行多次是正常的,但是這裡在我多次除錯之後總了一個好的方法,就是看到多次執行的路線都差不多:

mmap函式=》迴圈=》(MOV R0,R8)BL=》(MOV LR,R4)BLX=》CMP R0,#0=》mmap....

這個過程中,其實為了簡便我們可以

1》在mmap函式的開始處,結束處下一個斷點,這兩個斷點是為了後面載入記憶體的dex檔案做準備

2》在迴圈處下一個斷點,這個斷點是為了修改迴圈值,節省時間

3》在BL處下個斷點,是為了進入BLX

4》在BLX處下個斷點,是為了進入比較TracerPid處

5》在CMP下斷點,是為了修改TracerPid的值

同時在這個過程中,需要使用F9,直接跳轉到下一個斷點,高效,只有在到達了CMP處的時候,要用F8單步除錯,而且這個地方一定要小心,不能按錯了,不然又得從頭再來,我吃了很多次虧,也重來了很多次。只要當看到了memcpy函式的時候,再次F9到下一個斷點處。更需要注意的是:每次到達mmap斷點處的時候,一定要看當前棧資訊的檢視視窗,看看是否出現了classes.dex的字樣,因為最終都是使用mmap來把解密之後的dex載入到記憶體中的,所以這裡一定要注意,是本次除錯的核心。

當然這個只是個人的除錯思路,每個人都有自己的思路,只要能成功都可以。

就這樣來回搞了幾次之後,終於看到了曙光:


當再次來到了mmap函式處的時候,終於看到了classes.dex字樣了,說明這裡開始解密dex然後進行載入到記憶體了,這時候不能在F9跳轉了,而是F8單步執行,然後檢視R0暫存器的值:


每次都是執行完__mmap2這個函式之後,R0就有值了,每次看到R0中有值的時候,可以到Hex View視窗中使用G鍵開始地址跳轉,檢視是否為dex內容:


如果發現不是,就還是單步F8,知道mmap函式結束,然後再次F9,到達mmap函式開始處,時刻看緊Hex View,棧視窗,R0暫存器這三個地方的值:


在多次嘗試之後,終於成功了,這裡看到了熟悉的dex檔案的頭資訊,關於dex檔案的頭部資訊可以看這篇文章:Dex檔案格式解析


所以這裡在頭部資訊的第33個位元組然後連續4個位元組就是dex的長度了,那麼現在有了dex在記憶體中的其實位置,長度大小,下面就可以使用Shirt+F2開啟指令碼執行視窗,dump出記憶體中的dex資料:

static main(void)
{
  auto fp, begin, end, dexbyte;
  fp = fopen("E:\\dump.dex", "wb");
  begin = 0x755A9000;
  //偏移0x20處,取4位元組為dex檔案大小
  end = 0x755A9000 + 0x0004BC38;  
  for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
      fputc(Byte(dexbyte), fp);
}


儲存到E:\dump.dex,然後在使用Jadx工具進行檢視:


這裡可以檢視到原始碼了,而且類名,方法名,變數名都是用中文來命名的,感覺好不習慣,但是Java中是支援這麼幹的,因為Java採用的是Unicode編碼的。

五、脫殼總結

好了到這裡,我們就成功了脫掉了360加固的殼了,下面來總結一下他的殼的特點和除錯需要注意的點:

1、首先360加固依然是外部套一個Application殼:StubApplication,源程式加密存放在libjiagu.so,放在了assets目錄下,在Application啟動的時候,釋放到應用的沙盒目錄files下面,然後在使用System.load方法進行載入,這個和愛加密的方式是一樣的

2、關於360加固的反除錯,依然使用的是讀取/proc/[pid]/status中的TracerPid欄位值,判斷是否為0,但是這裡和愛加密不一樣的是,在讀取這個檔案的時候不是用的fopen系統函式,而是mmap系統函式,所以在解決反除錯的時候需要給這個函式下斷點。

3、360加固底層不是採用dvmDexFileOpenPartial這個系統函式來解析dex然後載入到記憶體中的,而是自己實現了一個函式,所以給這個函式下斷點,然後獲取引數值來dump記憶體中的dex資料是行不通的,但是有一個思路就是不管他用哪個函式去解析dex載入到記憶體,最終都得使用mmap這個系統函式來操作,所以還得給這個函式下斷點,所以這裡在除錯的時候需要時刻注意的是當斷點到達了mmap函式處的時候,需要觀察Stack View棧視窗中是否出現了classes.dex字樣,如果出現了,說明開始解密dex檔案,準備載入到記憶體中了,那麼這時候需要觀察R0暫存器的值,然後在Hex View中跳轉到指定記憶體地址,可以觀察到是否為dex記憶體資料

4、在觀察是否為記憶體資料的時候,需要注意dex檔案是有自己的檔案格式的,那麼頭資訊就是個根據,所以我們可以檢視開頭為:dex.35 這樣的內容來判斷此處為dex資料,因為dex頭部資訊中也有dex的檔案大小,那麼這時候就可以使用指令碼dump處記憶體中的dex資料了。

5、在除錯的過程中,會發現很多斷點多次執行,特別是有一個迴圈,需要我們修改暫存器的值來快速結束迴圈,而且在關鍵處下斷點,也是加快除錯效率的。

六、技術概要

1、本文開始的時候介紹了通過注入系統init程序,修改記憶體中的系統屬性值:ro.debuggable,讓裝置中所有的應用都可以被除錯,這個功能將對後續逆向破解有重大意義,也會省去了反編譯的工作。所以這個方式還是很具有里程碑意義的。

2、在脫愛加密的殼的時候,學習到了給fopen和fgets這兩個系統函式下斷點來解決反除錯,在這裡我們又多了一個下斷點的好去處就是給mmap下斷點,當發現給fopen函式下斷點不好使的時候,在嘗試給mmap下個斷點吧。

3、在脫愛加密的殼的時候,給dvmDexFileOpenPartial函式下斷點,來獲取dex在記憶體的起始地址和大小,從而dump處記憶體中的dex資料,但是360加固並沒有走這個函式,因為在給這個函式下斷點的時候,他壓根沒走到,所以斷定它內部使用了其他的函式去解析dex的,然後載入到記憶體中的,但是如果最後載入到記憶體中,那肯定要用到mmap函式,所以只要給mmap函式下斷點即可。

七、總結

本篇文章就介紹瞭如何脫掉360平臺加固的apk應用的殼,在結合之前的一篇脫掉愛加密家的殼的知識,看到現在在脫殼的時候其實就兩點,一點是找到關鍵處解決反除錯,一般都是fopen,fgets,mmap,open等系統函式下斷點,還有一點就是如何找到記憶體中dex的起始地址和dex的大小,這個一般現在就是dvmDexFileOpenPartial函式下斷點,還有就是給mmap函式下斷點。

《Android應用安全防護和逆向分析》

點選立即購買:京東  天貓

更多內容:點選這裡

關注微信公眾號,最新Android技術實時推送