天天看點

Android應用逆向——分析反編譯代碼之大神器

版權聲明:本文為部落客原創文章,轉載請注明出處。 https://blog.csdn.net/CharlesSimonyi/article/details/52027563

  如果說使用dex2jar和JD-GUI獲得了一個APP反編譯後的JAVA代碼,再結合smali代碼調試器來進行調試還不夠爽,不夠暢快的話,下面将介紹一個幫助分析代碼執行流程的大神器。這個神器優點很多,不過遺憾的是它有一個緻命的缺點!就是威力太大,能讓使用它的人快速分析出一個複雜APP的執行流程,快速定位關鍵之處進行修改以達到各種目的,尤其對于像我一樣的Android逆向新手來說,這是非常緻命的。為什麼非常緻命?因為使用了該神器後,1個小時就找到了關鍵代碼,弄清楚執行邏輯,1天之内就實作了程式,解決了外行人看來難度很高的問題。由此帶來的後果就是自我感覺良好,自己感覺自己很牛逼,蒙蔽了自己的雙眼,終日沉溺在這種驕傲的狀态中,進而不能繼續虛心刻苦學習技術知識,久而久之,在技術水準上落後别人一大截,對自身發展造成嚴重影響!是以使用該神器前必須清楚地認識到可能帶來的這些弊端,确認自己能調整好心态以後再繼續往下看,否則請按ALT+F4關閉。

一般的商業APP代碼量巨大,而且做過混淆處理。我所面對的這個APP反編譯後僅JAVA代碼文本就達到了100多MB,做過混淆處理後,代碼裡幾乎看不見一個局部變量的名字,大部分的函數名、類成員變量名都是abcdefg之類,且反編譯後的代碼看起來怪怪的。如果僅僅是靜态分析,讀這些代碼,将會是一件非常痛苦的事情。尤其是對我這種Android正向開發都不會的新手來說,某個按鈕點選的響應函數在哪裡,下拉重新整理的響應函數在哪裡,找起來很困難。好不容易找到了登入按鈕的響應函數,順着函數調用一層一層往裡看,又遇到了一些抽象方法和異步操作,無法僅從按鈕響應函數的調用棧上找到最後發送網絡請求的關鍵代碼。雖然可以結合smali調試器來分析,下斷點後檢視實作了抽象方法的具體對象是什麼,檢視調用棧理清調用結構,但整個工作還是進行得很緩慢。再加上調試器各種奔潰和不準,搞去搞來各種心煩,導緻了一個嚴重的後果,就是搞着搞着就不由自主的打開了遊戲,以調節郁悶的心情,進而擱置了項目進度。

思來想去,我覺得與其主動去分析它的代碼執行流程,不如讓它來主動告訴我它的代碼執行流程。怎麼告訴?首先想到的是打日志,在上篇文章中我們通過打開調試開關,修改它的smali代碼重定向它的日志到android.util.Log,然後打開DDMS在LogCat中看到了它的全部日志。不過這樣還是遠遠不夠,因為它的日志隻記錄了一些運作中的狀況和錯誤。我的目的是想讓它的日志告訴我,它調用了哪些函數,以及調用的先後順序。在上篇文章中講到,我們也可以通過TraceView來分析它從登陸按鈕點下到登入結果出來的這個過程中調用的所有函數,不過TraceView給出的結果充斥着很多很多的系統函數,而且難以看出調用順序,非常不好用。如果能讓這個app通過日志主動告訴我們它調用了哪些函數,且不包含系統函數,僅僅是它自身代碼的函數,那該多好。

首先想到的方法就是手工修改它的smali代碼,插入日志。假如目标APP有一個函數的smali代碼如下:

.method protected getLoginPassword()Ljava/lang/String;
    .registers 2
    .prologue
    .line 809
    iget-object v0, p0, Lcom/ali/user/mobile/login/ui/AliUserLoginActivity;->mPasswordInput:Lcom/alipay/mobile/commonui/widget/keyboard/APSafeEditText;
    invoke-virtual {v0}, Lcom/alipay/mobile/commonui/widget/keyboard/APSafeEditText;->getSafeText()Landroid/text/Editable;
    move-result-object v0
    invoke-interface {v0}, Landroid/text/Editable;->toString()Ljava/lang/String;
    move-result-object v0
    return-object v0
.end method
           

在這段smali代碼裡插入三行代碼:

const-string v0, "InjectLog"
const-string v1, "com.ali.user.mobile.login.ui.AliUserLoginActivity.getLoginPassword()"
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
           

這三行smali代碼對應的JAVA代碼是:

android.util.Log.d("InjectLog", "com.ali.user.mobile.login.ui.AliUserLoginActivity.getLoginPassword()");
           

當然你可以可以插入其它各種各樣的代碼,你可以先建立一個Android項目,把JAVA代碼寫出來,編譯,再反編譯,再把對應的smali代碼複制出來粘貼進去。

好了,插入完成以後,重新打包,安裝,運作。每當執行這個函數的時候,我們就可以在LogCat中看到輸出,我們就知道它調用了這個函數,包名、類名、函數名都有了。試想,如果我們把它整個app的所有smali代碼中的所有函數全部插入這三行smali代碼,重新打包,安裝,運作。然後,就可以沖一杯咖啡,打開DDMS,打開LogCat,靜靜的看着它把所有的函數調用流程輸出來。然後我們在APP上點一下登入,整個登入過程暴露無遺!函數的調用順序就是日志輸出的時間順序,沒有任何雜質,沒有系統函數,那麼純淨,那麼完美。此刻它仿佛沒有了任何秘密,一絲不挂的站在你面前,任由你熾熱的目光在它身上那些精緻的部位上掃來掃去。

且慢,先擦幹口水。事情沒有那麼容易,就像追一個漂亮的女生一樣,你送一次禮物就想追到她,欣賞她美麗的酮體?做白日夢吧!至少得送上萬次禮物才行。也就是說你需要在這個app的上萬個函數中插入這三行smali代碼,看到這裡,首先要做的事情就是先把你手中的鐵錘收起來,不要打我。

我們當然不可能手工去完成這上萬次操作,我們是程式員,這種重複枯燥的工作自然讓程式來完成。我們可以寫一個文本分析的小程式,周遊反編譯出來的代碼目錄下的所有smali檔案,利用字元串搜尋法和正規表達式,找出一個個的函數,并且從smali檔案第一行.class的定義中擷取包名和類名,然後從.method的定義中擷取函數名、參數、傳回值資訊,生成上面的三行smali代碼,插進去。

“自動化”、“批量”、“文本處理”、“腳本”,想到這些關鍵詞,自然就想到了Python,用它來幹這個事情将會更加得心應手。經過幾小時奮戰,完成了這個批量插代碼的Python腳本。自動批量插入後對APP重新打包,安裝,運作,奔潰了。仔細想想,對,還有寄存器的問題沒有處理,有的函數本來是沒有局部變量,沒有使用寄存器的,".method"定義的函數塊中的第一行為".registers 0",而我插入的代碼裡用到了兩個寄存器。後來修改了Python腳本,在分析每個函數的時候也檢查registers,如果registers小于2個則改為2個。重新插入代碼後,對APP重新打包,安裝,運作,還是奔潰了。後來看了這篇文章:https://liuzhichao.com/p/919.html了解了registers的意義,發現我自己寫的Android APP反編譯以後每個函數第一行都是.locals,而我反編譯的這個商業APP的代碼中每個函數第一行都是.registers。使用.registers聲明寄存器數量有個很大的不好之處就是參數寄存器也包含在.registers聲明的寄存器中,如果一個函數有兩個參數,沒有局部變量,那麼這個函數會聲明.registers 2,我的腳本檢測到這裡認為寄存器夠用,然後就插入了代碼,而我插入的代碼裡用到了v0和v1寄存器,在指派的時候把參數寄存器的内容覆寫了,是以帶來了一些問題。而且如果它的代碼裡本來就使用了v0、v1寄存器,我直接這樣把代碼插進去,也會帶來一些影響。

要解決這個問題,可以繼續完善Python腳本,對它的smali代碼進行更多的分析,分析這個函數已經用了多少個寄存器,有多少個參數寄存器,序号分别是什麼,然後再生成合适的插入代碼并修改.registers數量,但這樣做的話就比較麻煩了。偷懶是我的作風,而這種做法顯然不符合我的作風,于是我覺得不應該再繼續完善Python腳本,而是精簡Python腳本。Python腳本不再分析它smali代碼中的函數,而是直接不管三七二十在每個函數中插入一行對void PrintFunc()的調用代碼。void PrintFunc()是我自己寫的函數,無參數無傳回值,調用它對應的smali代碼大概是這樣的:

invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V
           

不使用任何寄存器,沒有傳回值,顯然這樣的代碼插入到目标APP中的函數中,不會對宿主函數造成任何影響。同時建立一個Android應用項目,寫下如下JAVA代碼:

package com.hook.testsmali;
import android.util.Log;
public class InjectLog
{
    public static void PrintFunc()
    {
        Thread cur_thread = Thread.currentThread();
        StackTraceElement stack[] = cur_thread.getStackTrace();
        Log.d("InjectLog", stack[3].toString() + "[" + cur_thread.getId() + "]");
    }
}
           

注意,我的包名是com.hook.testsmali,類名是InjectLog,函數名是PrintFunc()。你寫的JAVA代碼不必和我一樣,但調用PrintFunc()的smali代碼要和JAVA代碼的包名、類名、函數名一緻。

從上面的代碼可以看到,在PrintFunc()的實作上,先擷取調用棧的資訊,然後取出調用棧中下标為3的元素,這個正是調用void PrintFunc()的調用者的函數名、包名、類名,然後随同線程ID資訊,通過日志輸出。随後精簡我們的Python腳本,Python腳本需要幹的事情就是周遊所有smali檔案,向smali中的每一個函數中插入invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V 完畢!然後我們對自己寫的這個APP進行反編譯,拿到這個void PrintFunc()函數的smali代碼檔案InjectLog.smali,在目标APP的代碼檔案夾中建立這個路徑com/hook/testsmali然後把InjectLog.smali放進去,重新打包目标APP,安裝。然後打開DDMS,設定LogCat過濾器過濾出Tag為InjectLog的日志。

接下來,噓!!運作目标APP,此後在目标APP中的每一次點選,都是那麼酣暢淋漓,LogCat中的日志如潮水般湧出,四處噴濺。對,就是那麼的絲滑,那麼的暢快,那麼的清澈,有沒一絲雜質。效果如下圖所示,每一個過程,每一個步驟,都赤裸裸的展現在你面前,看得你面紅耳赤!

我們也可以不輸出線程ID,因為LogCat已經标示出TID來了,但是導出日志後這個TID就不見了,是以還是輸出一下。日志的輸出時間順序就是函數的調用順序,順着這個調用順序看dex2jar和JD-GUI反編譯出的JAVA代碼,定位超快、效率超高,分分種種搞出些事情來。什麼?你問我搞什麼事情?它都一絲不挂在你面前了,你還問我搞什麼事情!

日志注入工具:

批量插smali代碼的Python腳本在這裡:

https://github.com/encoderlee/android_tools

歡迎提 issues 或 pull requests

需要注意的是在向目标APP的smali代碼的每個函數中插代碼時,并不是所有函數都需要插,類的靜态構造函數一般不需要插,因為它是用來初始化類的靜态成員的,沒有太多的關鍵代碼。synthetic函數也不需要插,我不知道synthetic函數是什麼意思,但是發現smali中的synthetic函數并沒有被dex2jar和JD-GUI反編譯為JAVA函數,是以忽略它。另外抽象方法也不需要插,這個不用解釋。當然這個批量自動插代碼的程式可以使用任何程式設計語言實作,它隻是個文本處理工具。

另外在實際使用中,并不一定要給目标APP的所有函數插代碼,我們可以先根據包名猜測一下它的功能,然後對這個包下的所有函數進行插代碼。

最後,千萬不要低估此神器的威力,我在使用過程中屢試不爽,結合smali代碼調試器,很快就分析出登入按鈕點下去後幹了什麼,以及最終發送了什麼HTTP請求,收到什麼響應内容。親手試一試,相當亦可賽艇!

思路總結:

通篇實際上講的是一個插入代碼打LOG來跟蹤代碼執行流程的思路,因為在smali下單步調試APP很麻煩,而且有的APP有反調試功能,單步調試沒走幾步就閃退。

本文的做法就是寫個程式/腳本,批量掃描目标APP反編譯出來的所有smali檔案,找到裡面的每一個.mthod函數塊,在每個函數塊第一行插入我們自己寫的打LOG代碼,而我們自己寫的打LOG代碼通過讀取調用棧輸出調用者的函數名。重新打包目标APP後,APP執行他的每一個函數,都會執行我們的打LOG代碼,進而輸出了自己的函數名,這樣我們可以從日志中看到目标APP的代碼執行流程。
           

關聯文章:

《Android逆向小技巧③:批量注入日志,列印目标程式執行流程》

在這篇文章中,我們将用支付寶APP做小白鼠,驗證一下這個工具的效果

本文由CharlesSimonyi發表于CSDN部落格:http://blog.csdn.net/charlessimonyi/article/details/52027563轉載請注明出處

作者:encoderlee

來源:CSDN

原文:https://blog.csdn.net/CharlesSimonyi/article/details/52027563

版權聲明:本文為部落客原創文章,轉載請附上博文連結!