一篇文章帶你領悟Frida的精髓(基於安卓8.1)
前言
前陣子受 ofollow,noindex" target="_blank">《Xposed模組編寫的那些事》 這篇文章的幫助很大,感覺有必要寫一篇文章來回饋freebuf社群。現在最火爆的又是frida,該框架從Java層hook到Native層hook無所不能,雖然持久化還是要依靠Xposed和hookzz等開發框架,但是frida的動態和靈活對逆向以及自動化逆向的幫助非常巨大。
frida是啥?
首先, frida
是啥,github目錄 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
進行逆向和動態除錯、甚至自動化分析以及規模化收集資訊的話,我們需要的是細粒度的流程控制和程式碼級的可定製體系,以及不斷對除錯進行動態糾正和可程式設計除錯的框架,這就是 frida
。
frida
使用的是 python
、 JavaScript
等“膠水語言”也是它火爆的一個原因,可以迅速將逆向過程自動化,以及整合到現有的架構和體系中去,為你們釋出“威脅情報”、“資料平臺”甚至“AI風控”等產品打好基礎。
官宣屁屁踢甚至將其 敏捷開發
和 迅速適配到現有架構
的能力作為其核心賣點。
frida實操環境
主機:
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
用 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) -o get-pip.py
)
pip install frida-tools
然後用 frida-ps -U
命令連上去,就可以看到正在執行的程序了。
root@kali:~# frida-ps -U Waiting for USB device to appear... PIDName --------------------------------------------------- 431ATFWD-daemon 3148adbd 391adspd 2448android.ext.services 358android.hardware.cas@1.0-service 265android.hardware.configstore@1.0-service 359android.hardware.drm@1.0-service 360android.hardware.dumpstate@1.0-service.shamu 361android.hardware.gnss@1.0-service 266android.hardware.graphics.allocator@2.0-service 357android.hidl.allocator@1.0-service ... ...
基本能力Ⅰ: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 { @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.23432453245 D Sum: 80 11-26 21:26:24.23432453245 D Sum: 80 11-26 21:26:25.23532453245 D Sum: 80 11-26 21:26:26.23532453245 D Sum: 80 11-26 21:26:27.23632453245 D Sum: 80 11-26 21:26:28.23732453245 D Sum: 80 11-26 21:26:29.23732453245 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.87524202420 D Sum: 80 11-26 21:44:48.37524202420 D Sum: 80 11-26 21:44:48.87524202420 D Sum: 80 11-26 21:44:49.37524202420 D Sum: 80 11-26 21:44:49.87824202420 D Sum: 7 11-26 21:44:50.39024202420 D Sum: 7 11-26 21:44:50.90424202420 D Sum: 7 11-26 21:44:51.40824202420 D Sum: 7 11-26 21:44:51.92124202420 D Sum: 7 11-26 21:44:52.43524202420 D Sum: 7 11-26 21:44:52.94524202420 D Sum: 7 11-26 21:44:53.45924202420 D Sum: 7 11-26 21:44:53.97024202420 D Sum: 7 11-26 21:44:54.48024202420 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.68930513051 D ROYSUE.Sum: 80 11-26 22:22:35.68930513051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:36.69530513051 D ROYSUE.Sum: 80 11-26 22:22:36.69630513051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:37.69630513051 D ROYSUE.Sum: 80 11-26 22:22:37.69630513051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:38.69730513051 D ROYSUE.Sum: 80 11-26 22:22:38.69730513051 D ROYSUE.string: lowercase me!!!!!!!!! 11-26 22:22:39.69730513051 D ROYSUE.Sum: 80 11-26 22:22:39.69830513051 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')\nat throwOverloadError (frida/node_modules/frida-java/lib/class-factory.js:2233)\nat frida/node_modules/frida-java/lib/class-factory.js:1468\nat x (/script1.js:14)\nat frida/node_modules/frida-java/lib/vm.js:43\nat M (frida/node_modules/frida-java/index.js:347)\nat frida/node_modules/frida-java/index.js:299\nat frida/node_modules/frida-java/lib/vm.js:43\nat frida/node_modules/frida-java/index.js:279\nat /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.59732443244 D ROYSUE.Sum: 7 11-26 22:23:29.67332443244 D ROYSUE.string: my test string##### 11-26 22:23:30.68932443244 D ROYSUE.Sum: 7 11-26 22:23:30.73032443244 D ROYSUE.string: my test string##### 11-26 22:23:31.74032443244 D ROYSUE.Sum: 7 11-26 22:23:31.78932443244 D ROYSUE.string: my test string##### 11-26 22:23:32.79732443244 D ROYSUE.Sum: 7 11-26 22:23:32.83332443244 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==
動態修改輸入內容就這樣實現了。
打算做個成套的教程、目錄已經想好了
frida『葵花寶典』
第一章.各種環境安裝(包括Win、Mac、Ubuntu、ARM機器下的各種環境安裝)第二章.基本案例上手(安卓、iOS、Win、Mac為物件的各種插樁方法)第三章.frida-tools(frida原生提供的各種工具的使用)第四章.frida-scripts(各種frida指令碼的介紹、使用和總結)第五章.frida高階應用(安卓hook引數模型的總結、SSL-unpinning模型、iOS應用重打包動態修改等等)第六章.二次開發基礎(frida-API基本使用方法、基於frida的二次開發模型)第七章.二次開發案例(Fridump、r2frida、brida、Appmon等原始碼解析和解讀)
當然還在醞釀中,大家有想法可以跟我溝通,想要原始碼的也可以留言。
謝謝大家。
參考資料
nluug-2015-frida-putting-the-open-back-into-closed-software
*本文作者:Roy_Chen,轉載請註明來自FreeBuf.COM