一篇文章带你领悟 Frida 的精髓(基于安卓8.1)
轉載(一篇文章帶你領悟Frida的精髓(基于安卓8.1)):https://www.freebuf.com/articles/system/190565.html
《Frida操作手冊》:https://github.com/hookmaster/frida-all-in-one
Frida github 地址:https://github.com/dweinstein/awesome-frida
前言
受?《Xposed模塊編寫的那些事》:https://www.freebuf.com/articles/terminal/114910.html?影響,感覺有必要寫一篇文章來回饋 freebuf 社區?,F在最火爆的又是 frida,該框架從 Java 層 hook 到 Native 層 hook 無所不能,雖然持久化還是要依靠 Xposed 和 hookzz 等開發框架,但是 frida 的動態和靈活對逆向以及自動化逆向的幫助非常巨大。
frida 是啥?
首先,frida?是啥,github目錄 Awesome Frida(?https://github.com/dweinstein/awesome-frida )這樣介紹?frida?的:
Frida is Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript into native apps that run on Windows, Mac, Linux, iOS and Android. Frida is an open source software.
frida 是平臺原生 app 的 Greasemonkey,說的專業一點,就是一種動態插樁工具,可以插入一些代碼到原生 app 的內存空間去,(動態地監視和修改其行為),這些原生平臺可以是 Win、Mac、Linux、Android 或者 iOS。而且 frida 還是開源的。
Greasemonkey 可能大家不明白,它其實就是 firefox 的一套插件體系,使用它編寫的腳本可以直接改變 firefox 對網頁的編排方式,實現想要的任何功能。而且這套插件還是外掛的,非常靈活機動。
frida 也是一樣的道理。
frida 為什么這么火?
動靜態修改內存實現作弊一直是剛需,比如 金山游俠,本質上 frida 做的跟它是一件事情。原則上是可以用 frida 把金山游俠,包括 CheatEngine 等 "外掛"?做出來的。
當然,現在已經不是直接修改內存就可以高枕無憂的年代了。大家也不要這樣做,做外掛可是違法行為。
在逆向的工作上也是一樣的道理,使用 frida 可以 "看到"?平時看不到的東西。出于編譯型語言的特性,機器碼在 CPU 和內存上執行的過程中,其內部數據的交互和跳轉,對用戶來講是看不見的。當然如果手上有源碼,甚至哪怕有帶調試符號的可執行文件包,也可以使用 gbd、lldb 等調試器連上去看。
那如果沒有呢?如果是純黑盒呢?又要對 app 進行逆向和動態調試、甚至自動化分析以及規?;占畔⒌脑?#xff0c;我們需要的是細粒度的流程控制和代碼級的可定制體系,以及不斷對調試進行動態糾正和可編程調試的框架,這就是 frida。
frida 使用的是 python、JavaScript 等 "膠水語言"?也是它火爆的一個原因,可以迅速將逆向過程自動化,以及整合到現有的架構和體系中去,為你們發布 "威脅情報"、"數據平臺"?甚至 "AI風控"?等產品打好基礎。
frida 實操環境
frida 分 客戶端環境 和 服務端環境。
- 客戶端:編寫 Python 代碼,用于連接遠程設備,提交要注入的代碼到遠程,接受服務端的發來的消息等。可以把 客戶端 理解成控制端。
- 服務端:需要用 Javascript 代碼注入到目標進程,操作內存數據,給客戶端發送消息等操作。可以把 服務端 理解成被控端。
假如我們要用 PC 來對 Android 設備上的某個進程進行操作,那么 PC 就是客戶端,而 Android 設備就是服務端。
準備 frida 服務端環境
服務端在 Android 平臺測試。服務端環境準備步驟如下:
根據自己的平臺下載 frida 服務端并解壓:https://github.com/frida/frida/releases
執行以下命令將服務端推到手機的 /data/local/tmp 目錄:
adb push frida-server /data/local/tmp/frida-server執行以下命令修改 frida-server 文件權限:
adb shell chmod 777 /data/local/tmp/frida-server注:Windows 系統執行命令可以在 CMD 中進行;Linux 和 MacOS 執行命令可以在終端中進行。adb 是 Android 一個調試工具,具體安裝方法不是本文的重點。
準備 客戶端環境
在 PC 上安裝 Python 的運行環境,安裝完成后執行下面的命令安裝 frida:pip install frida
frida 命令幫助:
kali@kali:~$ frida -h Usage: frida [options] targetOptions:--version show program's version number and exit-h, --help show this help message and exit-D ID, --device=ID connect to device with the given ID-U, --usb connect to USB device-R, --remote connect to remote frida-server-H HOST, --host=HOST connect to remote frida-server on HOST-f FILE, --file=FILE spawn FILE-F, --attach-frontmostattach to frontmost application-n NAME, --attach-name=NAMEattach to NAME-p PID, --attach-pid=PIDattach to PID--stdio=inherit|pipe stdio behavior when spawning (defaults to “inherit”)--aux=option set aux option when spawning, such as “uid=(int)42”(supported types are: string, bool, int)--runtime=duk|v8 script runtime to use--debug enable the Node.js compatible script debugger--squelch-crash if enabled, will not dump crash report to console-O FILE, --options-file=FILEtext file containing additional command line options-l SCRIPT, --load=SCRIPTload SCRIPT-P PARAMETERS_JSON, --parameters=PARAMETERS_JSONparameters as JSON, same as Gadget-C CMODULE, --cmodule=CMODULEload CMODULE-c CODESHARE_URI, --codeshare=CODESHARE_URIload CODESHARE_URI-e CODE, --eval=CODE evaluate CODE-q quiet mode (no prompt) and quit after -l and -e--no-pause automatically start main thread after startup-o LOGFILE, --output=LOGFILEoutput to log file--exit-on-error exit with code 1 after encountering any exception inthe SCRIPT kali@kali:~$如何將一個腳本注入到 Android 目標進程:frida -U -l myhook.js com.xxx.xxxx
參數解釋:
- -U? ? 指定對 USB 設備操作
- -l? ? 指定加載一個 Javascript 腳本
- 最后指定一個進程名,如果想指定進程 pid,用 -p 選項。
查看正在運行的進程可以用 frida-ps -U 命令,frida 運行過程中,執行?%resume?重新注入,執行?%reload?來重新加載腳本;執行?exit?結束腳本注入
用 kali linux 的原因是工具很全面,否則 python 和 node 的各種權限、環境和依賴實在是煩。
主機:
Host:Macbook Air CPU: i5 Memory:8GSystem:Kali Linux 2018.4 (Native,非虛擬機)
客戶端:
client:Nexus 6 shamu CPU:Snapdragon 805 Mem:3GSystem:lineage-15.1-20181123-NIGHTLY-shamu,android 8.1
安裝 "安卓sdk",然后添加環境變量 ,這樣 adb 和 fastboot 命令就有了。下載?frida-server(下載地址:?https://github.com/frida/frida/releases )并把對應的 frida-server 拷貝到安卓機器里去,使用?root用戶跑起來,保持?adb?的連接不要斷開。
$ ./adb root # might be required $ ./adb push frida-server /data/local/tmp/ $ ./adb shell "chmod 755 /data/local/tmp/frida-server" $ ./adb shell "/data/local/tmp/frida-server &"最后在 kali linux 里安裝好 frida 即可,一句話命令即可,保證不出錯。(可能會需要先安裝 pip,a安裝命令:curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
pip install frida-tools然后用?frida-ps -U?命令連上去,就可以看到正在運行的進程了。
基本能力 1:hook參數、修改結果
先自己寫個?app:
package com.roysue.demo02;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}fun(50,30);}}void fun(int x , int y ){Log.d("Sum" , String.valueOf(x+y));}}原理上很簡單,就是間隔一秒在控制臺輸出一下?fun(50,30)?函數的結果,fun()?這個函數的作用是求和。那最終結果在控制臺如下所示。
$ adb logcat |grep Sum 11-26 21:26:23.234 3245 3245 D Sum : 80 11-26 21:26:24.234 3245 3245 D Sum : 80 11-26 21:26:25.235 3245 3245 D Sum : 80 11-26 21:26:26.235 3245 3245 D Sum : 80 11-26 21:26:27.236 3245 3245 D Sum : 80 11-26 21:26:28.237 3245 3245 D Sum : 80 11-26 21:26:29.237 3245 3245 D Sum : 80圖示:
現在我們來寫一段js代碼,并用 frida-server 將這段代碼加載到 com.roysue.demo02 中去,執行其中的 hook 函數。
s1.js 代碼:
console.log("Script loaded successfully "); Java.perform(function x() {console.log("Inside java perform function");//定位類var my_class = Java.use("com.roysue.demo02.MainActivity");console.log("Java.Use.Successfully!");//定位類成功!//在這里更改類的方法的實現(implementation)my_class.fun.implementation = function(x,y){//打印替換前的參數console.log( "original call: fun("+ x + ", " + y + ")");//把參數替換成2和5,依舊調用原函數var ret_value = this.fun(2, 5);return ret_value;} });然后我們在 kali 主機上使用一段 python 腳本,將這段 js 腳本 "傳遞"?給安卓系統里正在運行的frida-server。
loader.py 代碼:
import time import frida# 連接安卓機上的frida-server device = frida.get_usb_device() # 啟動`demo02`這個app pid = device.spawn(["com.roysue.demo02"]) device.resume(pid) time.sleep(1) session = device.attach(pid) # 加載s1.js腳本 with open("s1.js") as f:script = session.create_script(f.read()) script.load()# 腳本會持續運行等待輸入 raw_input()然后就是保證 frida-server 一直處于運行。在 kali 主機輸入frida-ps -U 命令,如果安卓機上的進程出現了,則 frida-server 運行良好。
還需要保證 selinux 是關閉的狀態,可以在 adb shell 里,su - 獲得 root 權限之后,輸入 setenforce 0 命令來獲得,在 Settings→About Phone→SELinux status 里看到 Permissive,說明selinux 關閉成功。
然后在 kali 主機上輸入 python loader.js,可以觀察到安卓機上 com.roysue.demo02 這個 app 馬上重啟了。然后 $ adb logcat|grep Sum 里的內容也變了。
說明腳本執行成功了,代碼也插到 com.roysue.demo02 這個包里去,并且成功執行了,s1.js 里的代碼成功執行了,并且把交互結果傳回了 kali 主機上。
基本能力 2:參數構造、方法重載、隱藏函數的處理
我們現在把 app 的代碼稍微寫復雜一點點:
package com.roysue.demo02;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log;public class MainActivity extends AppCompatActivity {private String total = "@@@###@@@";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}fun(50,30);Log.d("ROYSUE.string" , fun("LoWeRcAsE Me!!!!!!!!!"));}}void fun(int x , int y ){Log.d("ROYSUE.Sum" , String.valueOf(x+y));}String fun(String x){total +=x;return x.toLowerCase();}String secret(){return total;} }app 運行起來后在使用 logcat 打印出來的日志如下:
$ adb logcat |grep ROYSUE 11-26 22:22:35.689 3051 3051 D ROYSUE.Sum: 80 11-26 22:22:35.689 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:36.695 3051 3051 D ROYSUE.Sum: 80 11-26 22:22:36.696 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:37.696 3051 3051 D ROYSUE.Sum: 80 11-26 22:22:37.696 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:38.697 3051 3051 D ROYSUE.Sum: 80 11-26 22:22:38.697 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:39.697 3051 3051 D ROYSUE.Sum: 80 11-26 22:22:39.698 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!!圖示:
可以看到?fun() 方法有了重載,在參數是兩個?int?的情況下,返回兩個 int 之和。在參數為 String 類型之下,則返回字符串的小寫形式。另外,secret() 函數為隱藏方法,在 app 里沒有被直接調用。
這時候如果我們直接使用上面的 js 腳本和 loader.js 來加載的話,肯定會崩潰。為了看到崩潰的信息,我們對 loader.js 做一些處理。
再運行$ python loader.py的話,就會看到如下的錯誤信息返回:
$ python loader.py Script loaded successfully Inside java perform function Java.Use.Successfully! {u'columnNumber': 1, u'description': u"Error: fun(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload('java.lang.String')\n\t.overload('int', 'int')", u'fileName': u'frida/node_modules/frida-java/lib/class-factory.js', u'lineNumber': 2233, u'type': u'error', u'stack': u"Error: fun(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload('java.lang.String')\n\t.overload('int', 'int')\n at throwOverloadError (frida/node_modules/frida-java/lib/class-factory.js:2233)\n at frida/node_modules/frida-java/lib/class-factory.js:1468\n at x (/script1.js:14)\n at frida/node_modules/frida-java/lib/vm.js:43\n at M (frida/node_modules/frida-java/index.js:347)\n at frida/node_modules/frida-java/index.js:299\n at frida/node_modules/frida-java/lib/vm.js:43\n at frida/node_modules/frida-java/index.js:279\n at /script1.js:15"} None可以看出是一個 throwOverloadError,這時候就是因為我們沒有處理重載,造成的重載處理錯誤。這個時候就需要我們來處理重載了,在 js 腳本中處理重載是這樣寫的:
my_class.fun.overload("int" , "int").implementation = function(x,y){//... } my_class.fun.overload("java.lang.String").implementation = function(x){//... }其中參數均為兩個 int 的情況下,上一節已經講過了。參數為 String類的時候,由于 String類 不是Java 基本數據類型(?https://www.runoob.com/java/java-basic-datatypes.html ),而是 java.lang.String 類型,所以在替換參數的構造上,需要花點心思。
var string_class = Java.use("java.lang.String"); //獲取String類型my_class.fun.overload("java.lang.String").implementation = function(x){console.log("*************************************");var my_string = string_class.$new("My TeSt String#####"); //new一個新字符串console.log("Original arg: " + x);var ret = this.fun(my_string); // 用新的參數替換舊的參數,然后調用原函數獲取結果console.log("Return value: "+ ret);console.log("*************************************");return ret; };這樣我們對于重載函數的處理就算是 ok 了。我們到實驗里來看下:
$ python loader.py Script loaded successfully Inside java perform function original call: fun(50, 30) ************************************* Original arg: LoWeRcAsE Me!!!!!!!!! Return value: my test string##### ************************************* original call: fun(50, 30) ************************************* Original arg: LoWeRcAsE Me!!!!!!!!! Return value: my test string##### ************************************* original call: fun(50, 30) ************************************* Original arg: LoWeRcAsE Me!!!!!!!!! Return value: my test string##### *************************************然后?logcat?打出來的結果也變了。
$ adb logcat |grep ROYSUE 11-26 22:23:29.597 3244 3244 D ROYSUE.Sum: 7 11-26 22:23:29.673 3244 3244 D ROYSUE.string: my test string##### 11-26 22:23:30.689 3244 3244 D ROYSUE.Sum: 7 11-26 22:23:30.730 3244 3244 D ROYSUE.string: my test string##### 11-26 22:23:31.740 3244 3244 D ROYSUE.Sum: 7 11-26 22:23:31.789 3244 3244 D ROYSUE.string: my test string##### 11-26 22:23:32.797 3244 3244 D ROYSUE.Sum: 7 11-26 22:23:32.833 3244 3244 D ROYSUE.string: my test string#####最后再說一下隱藏方法的調用,frida?對其的處理辦法跟?Xposed?是非常像的,Xposed?使用的是XposedHelpers.findClass("com.example.inner_class_demo.demo", lpparam.classLoader); 方法,直接?findClass,
其實?frida?也非常類似,也是使用的直接到內存里去尋找的方法,也就是?Java.choose(className, callbacks) 函數,通過類名觸發回掉函數。
Java.choose("com.roysue.demo02.MainActivity" , {onMatch : function(instance){ //該類有多少個實例,該回調就會被觸發多少次console.log("Found instance: "+instance);console.log("Result of secret func: " + instance.secret());},onComplete:function(){} });最終運行效果如下:
$ python loader.py Script loaded successfully Inside java perform function Found instance: com.roysue.demo02.MainActivity@92d5deb Result of secret func: @@@###@@@ original call: fun(50, 30) ************************************* Original arg: LoWeRcAsE Me!!!!!!!!! Return value: my test string##### ************************************* original call: fun(50, 30) ************************************* Original arg: LoWeRcAsE Me!!!!!!!!! Return value: my test string##### ************************************* original call: fun(50, 30)這樣隱藏方法也被調用起來了。
示例代碼:
import sys import frida"""java 層 Hook"""''' https://blog.csdn.net/weixin_33862188/article/details/93088768 Hook 普通方法 ''' js_code = ''' Java.perform(function(){var utils = Java.use('com.example.fridaapp.Utils');utils.getCalc.implementation = function(a, b){console.log("Hook Start ...");send(arguments[0]);send(arguments[1]);var ret_val = this.getCalc(arguments[0], arguments[1]); send(ret_val.toString());return 12345;} }); '''''' https://blog.csdn.net/weixin_33690963/article/details/93088770 hook 構造函數 和 普通函數 是有區別的,構造函數要用 $init 這種形式, 并且要 return this.$init(arg1,arg2) 調用原始的函數實現 ''' js_code = ''' Java.perform(function() { var money = Java.use('com.example.fridaapp.Money');money.$init.implementation = function (a, b) {console.log("Hook Start...");send(arguments[0]);send(b);send("Success!");return this.$init(10000, "美元");} }); '''''' http://www.mamicode.com/info-detail-2684127.html hook重載函數的幾種寫法:https://blog.csdn.net/universsky2015/article/details/77965614 hook 重載方法 ''' js_code_test = ''' Java.perform(function () {var utils = Java.use('com.example.fridaapp.Utils');utils.test.overload("int").implementation = function (a) {console.log("Hook Start...");send(arguments[0]);send("有參數!");return "有參數";}utils.test.overload().implementation = function (a) {console.log("Hook Start...");send(arguments[0]);send("沒有參數!");return "沒有參數";} }); '''''' http://www.mamicode.com/info-detail-2684162.html Hook 對象參數函數 ''' js_code = ''' Java.perform(function () {var utils = Java.use('com.xiaojianbang.app.Utils');var money = Java.use('com.xiaojianbang.app.Money');utils.test.overload('com.xiaojianbang.app.Money').implementation = function (obj) {send("Hook Start...");send(obj.getInfo());var mon = money.$new(2000,‘港幣‘);send(mon.getInfo());return this.test(mon);} }); '''js_code = """ Java.perform(function () {// Function to hook is defined herevar MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');// Whenever button is clickedMainActivity.onClick.implementation = function (v) {// Show a message to know that the function got calledsend('onClick');// Call the original onClick handlerthis.onClick(v);// Set our values after running the original onClick handlerthis.m.value = 0;this.n.value = 1;this.cnt.value = 999;// Log to the console that it's done, and we should have the flag!//console.log('Done:' + JSON.stringify(this.cnt));}} ); """def js_callback_func(msg, data):if msg['type'] == 'send':print(f'[*] {msg["payload"]}')else:print(msg)if __name__ == '__main__':# get_remote_device 獲取遠程設備 (get_usb_device) attach 附加進程process = frida.get_remote_device().attach('com.example.fridaapp')script = process.create_script(js_code_test)script.on('message', js_callback_func) # 綁定一個事件script.load()sys.stdin.read()pass中級能力:遠程調用( RPC )
上面我們在安卓機器上使用 js 腳本調用了隱藏函數 secret(),它在 app 內雖然沒有被任何地方調用,但是仍然被我們的腳本 "找到"?并且 "調用" 了起來
這一小節我們要實現的是,不僅要在跑在安卓機上的 js 腳本里調用這個函數,還要可以在 kali 主機上的 py 腳本里,直接調用這個函數。
也就是使用?frida?提供的?RPC?功能( Remote Procedure Call )。
安卓 app 不需要有任何修改,這次我們要修改的是 js 腳本和 py 腳本。
s3.js 代碼:
console.log("Script loaded successfully ");function callSecretFun() { //定義導出函數Java.perform(function () { //找到隱藏函數并且調用Java.choose("com.roysue.demo02.MainActivity", {onMatch: function (instance) {console.log("Found instance: " + instance);console.log("Result of secret func: " + instance.secret());},onComplete: function () { }});}); } rpc.exports = {// 把callSecretFun函數導出為callsecretfunction符號,// 導出名不可以有大寫字母或者下劃線callsecretfunction: callSecretFun };然后我們可以在 kali 主機的 py 腳本里直接調用該函數:
loader3.py 代碼:
import time import fridadef my_message_handler(message, payload):print(message)print(payload)device = frida.get_usb_device() pid = device.spawn(["com.example.demo2"]) device.resume(pid) time.sleep(1) session = device.attach(pid) with open("s3.js", encoding='utf-8') as f:script = session.create_script(f.read()) script.on("message", my_message_handler) script.load()command = "" while 1 == 1:command = input("Enter command:\n\t1: Exit\n\t2: Call secret function\nchoice:")if command == "1":breakelif command == "2": # 在這里調用script.exports.callsecretfunction()然后在 kali 主機上我們就可以看到以下的輸出:
$ python loader3.py Script loaded successfully Enter command: 1: Exit 2: Call secret function choice:2 Found instance: com.roysue.demo02.MainActivity@2eacd80 Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!! Enter command: 1: Exit 2: Call secret function choice:2 Found instance: com.roysue.demo02.MainActivity@2eacd80 Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!! Enter command: 1: Exit 2: Call secret function choice:2 Found instance: com.roysue.demo02.MainActivity@2eacd80 Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!! Enter command: 1: Exit 2: Call secret function choice:1這樣我們就實現了在 kali 主機上直接調用安卓 app 內部的函數的能力。
frida rpc 與常規 hook 的區別:
- rpc 能主動調用要 Hook 的代碼
- 常規 Hook 是被動,Hook 的函數/方法要被動等待觸發,不能主動調用要 Hook 的代碼
rpc 寫法:
rpc.exports = {var sig = "";get_hello: function (str) {Java.perfrom(function () {var some = Java.use('XXXXX')sig = some.get_sig()})}return sig; };常規 hook 寫法:
Java.perfrom(function(){var some = Java.use('XXXXX')some.get_sig.implemtation = function(){//do some thing}} )示例:
# -*- coding: utf-8 -*- # @Author : 佛祖保佑, 永無 bug # @Date : # @File : rpc.py # @Software: PyCharm # @description : XXXimport fridarpc_js_code = ''' /// 方式 1 // function getas111(arg_str) {send('hello');Java.perform(function () {// 拿到 context 上下文var current_application = Java.use('android.app.ActivityThread').currentApplication();var context = current_application.getApplicationContext();var AuthUtils = Java.use('com.coolapk.market.util.AuthUtils');var sig = AuthUtils.getAS(context, arg_str);send(sig);}); } rpc.exports = {callgetas111:getas111 }; /// 方式 2 // // rpc.exports = { // callgetas222:function (arg_str) { // send('hello'); // Java.perform(function () { // // 拿到 context 上下文 // var current_application = Java.use('android.app.ActivityThread').currentApplication(); // var context = current_application.getApplicationContext(); // // var AuthUtils = Java.use('com.coolapk.market.util.AuthUtils'); // var sig = AuthUtils.getAS(context, arg_str); // send(sig); // }); // } // }; / '''def js_callback_func(msg, data):# js中執行send函數后要回調的函數if 'send' == msg['type']:print(f'[*] {msg["payload"]}')elif 'error' == msg['type']:print(msg['stack'])else:print(msg)process = frida.get_remote_device().attach('com.coolapk.market') script = process.create_script(rpc_js_code) script.on('message', js_callback_func) script.load()# ######### 導出名不可以有大寫字母,或者下劃線 ######### script.exports.callgetas111('hdfashfkdsh') script.exports.callgetas222('hdfashfkdsh')運行結果:
高級能力:互聯互通、動態修改
最后我們要實現的功能是,我們不僅僅可以在 kali 主機上調用安卓 app 里的函數。我們還可以把數據從安卓 app 里傳遞到 kali 主機上,在主機上進行修改,再傳遞回安卓 app 里面去。
我們編寫這樣一個 app,其中最核心的地方在于判斷用戶是否為 admin,如果是,則直接返回錯誤,禁止登陸。如果不是,則把用戶和密碼上傳到服務器上進行驗證。
package com.roysue.demo04;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Base64; import android.view.View; import android.widget.EditText; import android.widget.TextView;public class MainActivity extends AppCompatActivity {EditText username_et;EditText password_et;TextView message_tv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);password_et = (EditText) this.findViewById(R.id.editText2);username_et = (EditText) this.findViewById(R.id.editText);message_tv = ((TextView) findViewById(R.id.textView));this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (username_et.getText().toString().compareTo("admin") == 0) {message_tv.setText("You cannot login as admin");return;}//hook targetmessage_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT));}});} }最終跑起來之后,效果就是這樣。
我們的目標就是在 kali 主機上 "得到"?輸入框輸入的內容,并且修改其輸入的內容,并且 "傳輸"?給安卓機器,使其通過驗證。也就是說,我們哪怕輸入admin 的賬戶和密碼,也可以繞過本地校驗,進行登陸的操作。
所以最終安卓端的 js 代碼的邏輯就是,截取輸入,傳輸給 kali 主機,暫停執行,得到 kali 主機傳回的數據之后,繼續執行。形成代碼如下:
Java.perform(function () {var tv_class = Java.use("android.widget.TextView");tv_class.setText.overload("java.lang.CharSequence").implementation = function (x) {var string_to_send = x.toString();var string_to_recv;send(string_to_send); // 將數據發送給kali主機的python代碼recv(function (received_json_object) {string_to_recv = received_json_object.my_dataconsole.log("string_to_recv: " + string_to_recv);}).wait(); //收到數據之后,再執行下去return this.setText(string_to_recv);} });kali 主機端的流程就是,將接受到的 JSON 數據解析,提取出其中的密碼部分,然后將用戶名替換成 admin,這樣就實現了將 admin 和 pw 發送給 "服務器"?的結果。
import time import fridadef my_message_handler(message, payload):print messageprint payloadif message["type"] == "send":print message["payload"]data = message["payload"].split(":")[1].strip()print 'message:', messagedata = data.decode("base64")user, pw = data.split(":")data = ("admin" + ":" + pw).encode("base64")print "encoded data:", datascript.post({"my_data": data}) # 將JSON對象發送回去print "Modified data sent"device = frida.get_usb_device() pid = device.spawn(["com.roysue.demo04"]) device.resume(pid) time.sleep(1) session = device.attach(pid) with open("s4.js") as f:script = session.create_script(f.read()) script.on("message", my_message_handler) # 注冊消息處理函數 script.load() raw_input()我們只要輸入任意用戶名(非admin)+密碼,非admin的用戶名可以繞過compareTo校驗,然后frida會幫助我們將用戶名改成admin,最終就是?admin:pw?的組合發送到服務器。
$ python loader4.py Script loaded successfully {u'type': u'send', u'payload': u'Sending to the server :YWFhYTpiYmJi\n'} None Sending to the server :YWFhYTpiYmJimessage: {u'type': u'send', u'payload': u'Sending to the server :YWFhYTpiYmJi\n'} data: aaaa:bbbb pw: bbbb encoded data: YWRtaW46YmJiYg==Modified data sent string_to_recv: YWRtaW46YmJiYg==動態修改輸入內容就這樣實現了。
總結
以上是生活随笔為你收集整理的一篇文章带你领悟 Frida 的精髓(基于安卓8.1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 mitmproxy + pytho
- 下一篇: 小甲鱼 OllyDbg 教程系列 (一)