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編碼的。

案例下載:http://download.csdn.net/detail/jiangwei0910410003/9561416

 

五、脫殼總結

好了到這裏,我們就成功了脫掉了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應用安全防護和逆向分析》

點擊立即購買:京東  天貓  

更多內容:點擊這裏

關注微信公衆號,最新技術乾貨實時推送

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章