首先,frida
是啥,github目錄Awesome Frida這樣介紹frida
的:java
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.node
frida
是平臺原生app
的Greasemonkey
,說的專業一點,就是一種動態插樁工具,能夠插入一些代碼到原生app
的內存空間去,(動態地監視和修改其行爲),這些原平生臺能夠是Win
、Mac
、Linux
、Android
或者iOS
。並且frida
仍是開源的。Greasemonkey
可能你們不明白,它其實就是firefox
的一套插件體系,使用它編寫的腳本能夠直接改變firefox
對網頁的編排方式,實現想要的任何功能。並且這套插件仍是外掛的,很是靈活機動。frida
也是同樣的道理。python
動靜態修改內存實現做弊一直是剛需,好比金山遊俠,本質上frida
作的跟它是一件事情。原則上是能夠用frida
把金山遊俠,包括CheatEngine
等「外掛」作出來的。
固然,如今已經不是直接修改內存就能夠高枕無憂的年代了。你們也不要這樣作,作外掛但是違法行爲。
在逆向的工做上也是同樣的道理,使用frida
能夠「看到」平時看不到的東西。出於編譯型語言的特性,機器碼在CPU和內存上執行的過程當中,其內部數據的交互和跳轉,對用戶來說是看不見的。固然若是手上有源碼,甚至哪怕有帶調試符號的可執行文件包,也可使用gbd
、lldb
等調試器連上去看。
那若是沒有呢?若是是純黑盒呢?又要對app
進行逆向和動態調試、甚至自動化分析以及規模化收集信息的話,咱們須要的是細粒度的流程控制和代碼級的可定製體系,以及不斷對調試進行動態糾正和可編程調試的框架,這就是frida
。frida
使用的是python
、JavaScript
等「膠水語言」也是它火爆的一個緣由,能夠迅速將逆向過程自動化,以及整合到現有的架構和體系中去,爲大家發佈「威脅情報」、「數據平臺」甚至「AI風控」等產品打好基礎。
官宣屁屁踢甚至將其敏捷開發
和迅速適配到現有架構
的能力做爲其核心賣點。linux
主機:android
Host:Macbook Air CPU: i5 Memory:8GSystem:Kali Linux 2018.4 (Native,非虛擬機)git
客戶端:github
client:Nexus 6 shamu CPU:Snapdragon 805 Mem:3GSystem:lineage-15.1-20181123-NIGHTLY-shamu,android 8.1shell
用kali linux
的緣由是工具很全面,權限很單一,只有一個root
,做爲原型開發很好用,不然python
和node
的各類權限、環境和依賴實在是煩。用lineage
由於它有便利的網絡ADB調試
,能夠省掉一個usb
數據線鏈接的過程。(雖然真實的緣由是沒錢買新設備,Nexus 6
官方只支持到7.1.1
,想上8.1
只有lineage
一個選擇。)記得須要刷進去一個lineage
的 su
包,獲取root
權限,frida
是須要在root
權限下運行的。
首先到官網下載一個platform-tools
的linux版本——SDK Platform-Tools for Linux
,下載解壓以後能夠直接運行裏面的二進制文件,固然也能夠把路徑加到環境裏去。這樣adb
和fastboot
命令就有了。
而後再將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
便可,在kali
裏安裝frida
真是太簡單了,一句話命令便可,保證不出錯。(可能會須要先安裝pip
,也是一句話命令:curl [[https://bootstrap.pypa.io/get-pip.py](https://bootstrap.pypa.io/get-pip.py)]([https://bootstrap.pypa.io/get-pip.py](https://bootstrap.pypa.io/get-pip.py)) -o get-pip.py
)json
pip install frida-tools
而後用frida-ps -U
命令連上去,就能夠看到正在運行的進程了。
root@kali:~# frida-ps -U Waiting for USB device to appear... PID Name ---- ----------------------------------------------- 431 ATFWD-daemon 3148 adbd 391 adspd 2448 android.ext.services 358 android.hardware.cas@1.0-service 265 android.hardware.configstore@1.0-service 359 android.hardware.drm@1.0-service 360 android.hardware.dumpstate@1.0-service.shamu 361 android.hardware.gnss@1.0-service 266 android.hardware.graphics.allocator@2.0-service 357 android.hidl.allocator@1.0-service ... ...
先本身寫個app
:
package com.roysue.demo02; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { @Override protected 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
函數。
$ nano 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
。
$ nano 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
裏的內容也變了。
11-26 21:44:47.875 2420 2420 D Sum : 80 11-26 21:44:48.375 2420 2420 D Sum : 80 11-26 21:44:48.875 2420 2420 D Sum : 80 11-26 21:44:49.375 2420 2420 D Sum : 80 11-26 21:44:49.878 2420 2420 D Sum : 7 11-26 21:44:50.390 2420 2420 D Sum : 7 11-26 21:44:50.904 2420 2420 D Sum : 7 11-26 21:44:51.408 2420 2420 D Sum : 7 11-26 21:44:51.921 2420 2420 D Sum : 7 11-26 21:44:52.435 2420 2420 D Sum : 7 11-26 21:44:52.945 2420 2420 D Sum : 7 11-26 21:44:53.459 2420 2420 D Sum : 7 11-26 21:44:53.970 2420 2420 D Sum : 7 11-26 21:44:54.480 2420 2420 D Sum : 7
在kali
主機上能夠觀察到:
$ python loader.py Script loaded successfully Inside java perform function Java.Use.Successfully! original call: fun(50, 30) original call: fun(50, 30) original call: fun(50, 30) original call: fun(50, 30) original call: fun(50, 30) original call: fun(50, 30) original call: fun(50, 30) original call: fun(50, 30) original call: fun(50, 30)
說明腳本執行成功了,代碼也插到com.roysue.demo02
這個包裏去,而且成功執行了,s1.js
裏的代碼成功執行了,而且把交互結果傳回了kali
主機上。
咱們如今把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 = "@@@###@@@"; @Override protected 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
作一些處理。
def my_message_handler(message , payload): #定義錯誤處理 print message print payload ... script.on("message" , my_message_handler) #調用錯誤處理 script.load()
再運行$ 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基本數據類型,而是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)
這樣隱藏方法也被調用起來了。
上一小節中咱們在安卓機器上使用js
腳本調用了隱藏函數secret()
,它在app
內雖然沒有被任何地方調用,可是仍然被咱們的腳本「找到」而且「調用」了起來
這一小節咱們要實現的是,不只要在跑在安卓機上的js
腳本里調用這個函數,還要能夠在kali
主機上的py
腳本里,直接調用這個函數。
也就是使用frida
提供的RPC
功能(Remote Procedure Call)。
安卓app
不須要有任何修改,此次咱們要修改的是js
腳本和py
腳本。
$ nano 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 = { callsecretfunction: callSecretFun //把callSecretFun函數導出爲callsecretfunction符號,導出名不能夠有大寫字母或者下劃線 };
而後咱們能夠在kali
主機的py
腳本里直接調用該函數:
$ nano loader3.py
import time import frida def my_message_handler(message, payload): print message print payload device = frida.get_usb_device() pid = device.spawn(["com.roysue.demo02"]) device.resume(pid) time.sleep(1) session = device.attach(pid) with open("s3.js") as f: script = session.create_script(f.read()) script.on("message", my_message_handler) script.load() command = "" while 1 == 1: command = raw_input("Enter command:\n1: Exit\n2: Call secret function\nchoice:") if command == "1": break elif 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
內部的函數的能力。
最後咱們要實現的功能是,咱們不只僅能夠在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; @Override protected 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() { @Override public void onClick(View v) { if (username_et.getText().toString().compareTo("admin") == 0) { message_tv.setText("You cannot login as admin"); return; } //hook target message_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_data console.log("string_to_recv: " + string_to_recv); }).wait(); //收到數據以後,再執行下去 return this.setText(string_to_recv); } });
kali
主機端的流程就是,將接受到的JSON
數據解析,提取出其中的密碼部分,而後將用戶名替換成admin
,這樣就實現了將admin
和pw
發送給「服務器」的結果。
import time import frida def my_message_handler(message, payload): print message print payload if message["type"] == "send": print message["payload"] data = message["payload"].split(":")[1].strip() print 'message:', message data = data.decode("base64") user, pw = data.split(":") data = ("admin" + ":" + pw).encode("base64") print "encoded data:", data script.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 :YWFhYTpiYmJi message: {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==
動態修改輸入內容就這樣實現了。
短視頻、直播數據實時採集接口,請查看文檔: TiToData
免責聲明:本文檔僅供學習與參考,請勿用於非法用途!不然一切後果自負。