說到逆向APP,很多人首先想到的都是反編譯,但是單看反編譯出來的代碼很難得知某個函數在被調用時所傳入的參數和它返回的值,極大地增加了逆向時的複雜度,有沒有什麼辦法可以方便地知道被傳入的參數和返回值呢?
答案是有的,這個方法就是Hook,Hook的原理簡單地說就是用一個新的函數替代掉原來的函數,在這個新的函數中你想做什麼都可以,為所欲為。
本文中的Frida就是一個很常用的Hook工具,只需要編寫一段Javascript代碼就能輕鬆地對指定的函數進行Hook,而且它基本上可以算是全平臺的(主流平臺全覆蓋),除了Android以外,iOS和PC端的APP也可以用它來進行Hook,非常方便。
那麼怎麼使用呢?首先我們在Frida官方文檔中的Installation頁可以看到,我們需要有Python環境,並且用pip安裝一個叫frida-tools的庫,然後才可以開始使用。
Python環境相信大家都有了,直接打開命令行,執行一波pip install frida-tools吧。
安裝完畢以後,因為這一頁文檔的下半部分用於測試剛裝好的庫是否可用的話過於麻煩,我們這裡就直接使用frida-ps命令來測試吧。
看起來是沒問題了,然後我們怎麼Hook Android手機上的APP呢?別急,還需要在手機上做一些操作你才能這麼做。
我們需要有一臺已經Root了的Android手機,因為不同型號的手機Root方法存在差異,本文中就不指導如何對手機進行Root操作了,請自行通過搜尋引擎查找方法。實在沒有可以Root的Android手機的話可以選擇使用模擬器,推薦使用Genymotion之類系統較為原生的模擬器,並將Android版本選擇在6.0以上,否則可能會出現一些奇奇怪怪的問題。
手機準備好了之後,找到Frida文檔中Tutorials欄裡的Android頁,開始進行Frida的手機端準備工作。
文檔中能看到,Frida官方最近的大部分測試都是在運行著Android 9的Pixel 3上進行的,所以理論上來講如果你在使用中遇到任何奇怪的問題,都可能是你手機的系統導致,所以這裡再次建議,使用較為原生和偏高版本的系統(建議至少6.0)進行操作。
接著往下看,我們需要在手機上以Root權限運行一個叫frida-server的東西,這個東西需要在Frida官方的GitHub倉庫中下載,文檔中有連結可以直接跳轉。
打開GitHub之後你會發現,這裡有很多個不同的版本,應該下載哪一個呢?
可以看到這一排的文件中,末尾處都有個系統和CPU架構的標識,我們直接看Android的。這裡標了Android的一共有四個,然後X86的因為不是手機使用的CPU架構,可以直接pass掉,剩下的就是arm和arm64兩種了,那麼我們需要怎麼判斷自己的手機/模擬器屬於哪一種CPU架構的呢?
查CPU架構的方法很多,這裡介紹一個比較方便快捷的——使用一個名叫Device Info HW的APP。
安裝後打開它,在晶片欄中我們可以看到一個叫ABI的東西,右邊就是我們手機的CPU架構了,如下:
所以這裡我需要下載的是arm64版本的frida-server,下載後解壓出來一個沒後綴的文件,然後我們需要將這個文件放入手機中運行起來。先把這個文件的名字改成frida-server,然後在這個文件所在的目錄下打開命令行(Windows下為Shift+滑鼠右鍵,選擇「在此處打開CMD/PowerShell」),執行以下命令:
1adb root
2adb push frida-server /data/local/tmp/
3adb shell "chmod 755 /data/local/tmp/frida-server"
4adb shell "/data/local/tmp/frida-server &"
如果你的手機和我的一樣,直接這麼運行會提示權限不足的話,可以先進入adb shell,在執行su命令獲取Root權限後(手機端可能會彈出Root授權提示),再運行/data/local/tmp/frida-server &啟動frida-server。
啟動後,我們先照慣例來測試一下是否能正常使用了,和前面一樣,使用frida-ps命令,但在後面加一個-U參數,這個參數的意思是讓它對USB連接的設備操作,如果不出意外的話,你應該能看到與不加-U參數時截然不同的顯示。
至此,所有準備工作均已完成。
小提示:在手機重啟後需要重新運行一次frida-server,但可以不重新執行adb push操作,因為文件已經放進去了。
終於到了喜聞樂見的實戰環節了,就拿Frida官方文檔中的提到的CTF APP來開刀吧,找到文檔中Examples欄裡的Android頁,經過幾次跳轉後下載APP安裝、打開,會看到這樣的一個界面:
這個APP是一個石頭剪子布的遊戲,點擊下面三個按鈕分別選擇石頭、剪子、布,玩起來的時候是這麼一個效果(加號後面的是得分值,正常情況下連勝會每次在原來的基礎上+1):
我們先做個比較簡單的操作吧,讓我們的每次出招都必勝~先複製一下文檔中的代碼,建一個.py文件粘貼進去,將this.cnt.value = 999;這一條刪除或注釋掉,然後運行這個python腳本,在注入完成後,不管你怎麼點,你都是必定勝利的,如下圖:
註:圖中左下方顯示的是Hook時產生的日誌,其中value是得分值。
但是這樣子弄,如果我們需要讓分值達到很高的話,就需要點很多次了,怎麼讓它一次就加到999呢?很簡單,直接把得分值也給改了就好了,我們把前面去掉的this.cnt.value = 999;再改回來,然後重新運行一遍這個腳本。
正常情況下這個分值會是一個+999,這裡顯示成這樣是因為這個樣例APP太老了,不兼容新版本系統,導致出現這種情況,換舊版本系統可解,所以這裡不糾結這個問題。
單看這麼一通操作是不是覺得很懵?複製過來的代碼是幹啥的都不知道,如果換一個APP咋搞?不慌,我把這個代碼的意思一行一行地給你解釋一遍,你就知道怎麼用了。
首先import不用說了吧,大家都懂,直接看on_message這個函數。這個on_message的用途是接收下面Javascript代碼中調用send函數傳出的日誌,通常我們可以不用管它,直接複製出來用就行了,或者可以使用console.log打日誌,效果也是差不多的。
然後是jscode這個變量,這個變量其實建議使用一個單獨的.js文件代替,因為這樣的話可以使用各種編輯器、IDE的JavaScript代碼格式化、智能提示等功能,寫起來會舒服很多。如果你要替換掉的話,改成讀JS代碼文件之後read出內容字符串賦值給jscode就行了。
接著是JS代碼中的部分。
先看看Java.perform,在Frida官方文檔的javascript-api頁中可以看到,它的用途是確保當前線程已連接到VM用的,所以我們直接照著這麼用就行了;
然後看看Java.use這個函數,它的用途是獲取一個指向某個類的指針,參數中的com.example.seccon2015.rock_paper_scissors.MainActivity就是我們需要Hook的那個類;
接著就是真正執行Hook的部分了,這個代碼中使用了一個MainActivity.onClick.implementation,意思就是Hook前面獲取到的類中的onClick方法,後面跟著的賦值函數的部分,函數的參數為對應要Hook方法的參數,內部執行的部分就是在對應方法被調用時所執行的代碼,這裡它是先打了一個onClick日誌,然後調用了原始方法(如果不調用的話原始方法不會被執行),接著它將m、n、cnt(變量具體含義請自行反編譯APP後查看代碼)的值做了修改,最後,它又打了一個攜帶著cnt變量值的日誌。
最後是一些常規操作,frida.get_usb_device().attach()是獲取指定APP(參數為包名)當前運行著的進程,process.create_script(jscode)、script.load()是將前面的JS代碼注入進去,script.on('message', on_message)是設置消息傳出時的回調,最後的sys.stdin.read()是輸出日誌用的。
總之,除了JS代碼部分,其他的其實只是個殼子,核心的Hook操作邏輯全在JS代碼中,我們在使用時一般只改JS代碼部分和指定包名的部分就可以了。
看了這一篇文章後你應該會對使用Frida對Android手機上Java層的Hook有所了解了吧,如果覺得玩Frida官方文檔中的這個石頭剪子布APP不夠刺激,還可以看看我前面的《當你寫爬蟲遇到APP的請求有加密參數時該怎麼辦?【初級篇-秒殺模式】》這篇文章,裡面使用的DEMO APP是有SSL Pinning、且對代碼進行了混淆的,希望你能夠舉一反三,自己寫出一個幹掉這個SSL Pinning的腳本。(如果還不會的話可以看更前面的《當你寫爬蟲抓不到APP請求包的時候該怎麼辦?【高級篇-混淆導致通用Hook工具失效】》這篇文章)
發送消息「Frida Android初級篇」到我的公眾號【小周碼字】即可獲得代碼和APP的下載地址~
這個時代各種東西變化太快,而網絡上的垃圾信息又很多,你需要有一個良好的知識獲取渠道,很多時候早就是一種優勢,還不趕緊關注我的公眾號並置頂/星標一波~