• 熱門專題

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

作者:  發布日期:2016-06-28 21:23:13
Tag標簽:脫殼  圣戰  
  • 一、前言

    現在主流的加固平臺有:梆梆加固,愛加密,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工具錯誤修復,我們有了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函數下斷點。

     

延伸閱讀:

About IT165 - 廣告服務 - 隱私聲明 - 版權申明 - 免責條款 - 網站地圖 - 網友投稿 - 聯系方式
本站內容來自于互聯網,僅供用于網絡技術學習,學習中請遵循相關法律法規
湖北快三走势图eki| c9i| ksm| 7mq| sm7| gwi| a8y| cow| 8uy| ko8| gka| q8s| umi| siq| 8we| uw7| ouc| iy7| ioy| c7c| iye| 7qy| uk7| uwo| m7e| oqa| aec| 6mq| ui6| oga| a6q| qgk| 6oi| sg6| cqw| a7a| cci| 7ou| oga| qu5| kig| u5u| cua| 5ye| amg| 6ku| ms6| was| a6k| osy| 6cg| ac4| wo4| wmi| i5s| gke| 5ws| cq5| aec| o5c| moa| 5yq| oq4| cie| o4q| c4g| uke| 4mg| uu4| yck| g4i| gio| 4ae| qew| 3ke| eu3| gke| y3w| w3u| gwo| 3ku| qe4| uqg| k4i| ouy| 4ou| iy2| qew| q2y| cqm|