說到逆向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”),執行以下命令:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n26" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">adb root
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"</pre>
如果你的手機和我的一樣,直接這么運行會提示權限不足的話,可以先進入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的下載地址~
這個時代各種東西變化太快,而網絡上的垃圾信息又很多,你需要有一個良好的知識獲取渠道,很多時候早就是一種優勢,還不趕緊關注我的公眾號并置頂/星標一波~