你們好,窩又來寫文章了,我們如今在這篇文章中,咱們來對其官方的一些很是經常使用的API
進行學習。所謂工欲善其事,必先利其器。想要好好學習FRIDA
咱們就必須對FRIDA API
深刻的學習以對其有更深的瞭解和使用,一般大部分核心原理也在官方API
中寫着,咱們學會來使用一些案例來結合API
的使用。
注意,運行如下任何代碼時都須要提早啓動手機中的frida-server
文件。
python
不管是什麼語言都好,第一個要學習老是如何輸出和打印,那咱們就來學習在FRIDA
打印值。在官方API有兩種打印的方式,分別是console
、send
,咱們先來學習很是的簡單的console
,這裏我建立一個js
文件,代碼示例以下。android
function hello_printf() { Java.perform(function () { console.log(""); console.log("hello-log"); console.warn("hello-warn"); console.error("hello-error"); }); } setImmediate(hello_printf,0);
當文件建立好以後,咱們須要運行在手機中安裝的frida-server
文件,在上一章咱們學過了如何安裝在android
手機安裝frida-server
,如今來使用它,咱們在ubuntu
中開啓一個終端,運行如下代碼,啓動咱們安裝好的frida-server
文件。shell
roysue@ubuntu:~$ adb shell sailfish:/ $ su sailfish:/ $ ./data/local/tmp/frida-server
而後執行如下代碼,對目標應用app
的進程com.roysue.roysueapplication
使用-l
命令注入Chap03.js
中的代碼1-1
以及執行腳本以後的效果圖1-1
!frida -U com.roysue.roysueapplication -l Chap03.js
代碼1-1 代碼示例
圖1-1 終端執行
能夠到終點已經成功注入了腳本而且打印了hello
,可是顏色不一樣,這是log
的級別的緣由,在FRIDA
的console
中有三個級別分別是log、warn、error
。json
級別 | 含義 |
---|---|
log | 正常 |
warn | 警告 |
error | 錯誤 |
error
級別最爲嚴重其次warn
,可是通常在使用中咱們只會使用log
來輸出想看的值;而後咱們繼續學習console
的好兄弟,hexdump
,其含義:打印內存中的地址,target
參數能夠是ArrayBuffer
或者NativePointer
,而options
參數則是自定義輸出格式能夠填這幾個參數offset、lengt、header、ansi
。hexdump
代碼示例以及執行效果以下。ubuntu
var libc = Module.findBaseAddress('libc.so'); console.log(hexdump(libc, { offset: 0, length: 64, header: true, ansi: true })); 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............ 00000010 03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00 ..(.........4... 00000020 34 a8 04 00 00 00 00 05 34 00 20 00 08 00 28 00 4.......4. ...(. 00000030 1e 00 1d 00 06 00 00 00 34 00 00 00 34 00 00 00 ........4...4...
send
是在python
層定義的on_message
回調函數,jscode
內全部的信息都被監控script.on('message', on_message)
,當輸出信息的時候on_message
函數會拿到其數據再經過format
轉換, 其最重要的功能也是最核心的是可以直接將數據以json
格式輸出,固然數據是二進制的時候也依然是可使用send
,十分方便,咱們來看代碼1-2
示例以及執行效果。api
# -*- coding: utf-8 -*- import frida import sys def on_message(message, data): if message['type'] == 'send': print("[*] {0}".format(message['payload'])) else: print(message) jscode = """ Java.perform(function () { var jni_env = Java.vm.getEnv(); console.log(jni_env); send(jni_env); }); """ process = frida.get_usb_device().attach('com.roysue.roysueapplication') script = process.create_script(jscode) script.on('message', on_message) script.load() sys.stdin.read() 運行腳本效果以下: roysue@ubuntu:~/Desktop/Chap09$ python Chap03.py [object Object] [*] {'handle': '0xdf4f8000', 'vm': {}}
能夠看出這裏兩種方式輸出的不一樣的效果,console
直接輸出了[object Object]
,沒法輸出其正常的內容,由於jni_env
其實是一個對象,可是使用send
的時候會自動將對象轉json
格式輸出。經過對比,咱們就知道send
的好處啦~
數組
學完輸出以後咱們來學習如何聲明變量類型。session
API | 含義 |
---|---|
new Int64(v) | 定義一個有符號Int64類型的變量值爲v,參數v能夠是字符串或者以0x開頭的的十六進制值 |
new UInt64(v) | 定義一個無符號Int64類型的變量值爲v,參數v能夠是字符串或者以0x開頭的的十六進制值 |
new NativePointer(s) | 定義一個指針,指針地址爲s |
ptr(「0」) | 同上 |
代碼示例以及效果app
Java.perform(function () { console.log(""); console.log("new Int64(1):"+new Int64(1)); console.log("new UInt64(1):"+new UInt64(1)); console.log("new NativePointer(0xEC644071):"+new NativePointer(0xEC644071)); console.log("new ptr('0xEC644071'):"+new ptr(0xEC644071)); }); 輸出效果以下: new Int64(1):1 new UInt64(1):1 new NativePointer(0xEC644071):0xec644071 new ptr('0xEC644071'):0xec644071
frida
也爲Int64(v)
提供了一些相關的API:異步
API | 含義 |
---|---|
add(rhs)、sub(rhs)、and(rhs)、or(rhs)、xor(rhs) | 加、減、邏輯運算 |
shr(N)、shl(n) | 向右/向左移位n位生成新的Int64 |
Compare(Rhs) | 返回整數比較結果 |
toNumber() | 轉換爲數字 |
toString([radix=10]) | 轉換爲可選基數的字符串(默認爲10) |
我也寫了一些使用案例,代碼以下。
function hello_type() { Java.perform(function () { console.log(""); //8888 + 1 = 8889 console.log("8888 + 1:"+new Int64("8888").add(1)); //8888 - 1 = 8887 console.log("8888 - 1:"+new Int64("8888").sub(1)); //8888 << 1 = 4444 console.log("8888 << 1:"+new Int64("8888").shr(1)); //8888 == 22 = 1 1是false console.log("8888 == 22:"+new Int64("8888").compare(22)); //轉string console.log("8888 toString:"+new Int64("8888").toString()); }); }
代碼執行效果如圖1-2。
圖1-2 Int64 API
能夠替換或插入的空對象,以嚮應用程序公開RPC
樣式的API
。該鍵指定方法名稱,該值是導出的函數。此函數能夠返回一個純值以當即返回給調用方,或者承諾異步返回。也就是說能夠經過rpc的導出的功能使用在python
層,使python
層與js
交互,官方示例代碼有Node.js
版本與python
版本,咱們在這裏使用python
版本,代碼以下。
import frida def on_message(message, data): if message['type'] == 'send': print(message['payload']) elif message['type'] == 'error': print(message['stack']) session = frida.get_usb_device().attach('com.roysue.roysueapplication') source = """ rpc.exports = { add: function (a, b) { return a + b; }, sub: function (a, b) { return new Promise(function (resolve) { setTimeout(function () { resolve(a - b); }, 100); }); } }; """ script = session.create_script(source) script.on('message', on_message) script.load() print(script.exports.add(2, 3)) print(script.exports.sub(5, 3)) session.detach()
官方源碼示例是附加在目標進程爲iTunes
,再經過將rpc
的./agent.js
文件讀取到source
,進行使用。我這裏修改了附加的目標的進程以及直接將rpc
的代碼定義在source
中。咱們來看看這段是咋運行的,仍然先對目標進程附加,而後在寫js
中代碼,也是source
變量,經過rpc.exports
關鍵字定義須要導出的兩個函數,上面定義了add
函數和sub
函數,兩個的函數寫做方式不同,你們之後寫按照add
方法寫就行了,sub
稍微有點複雜。聲明完函數以後建立了一個腳本而且注入進程,加載了腳本以後能夠到print(script.exports.add(2, 3))
以及print(script.exports.sub(5, 3)),
在python
層直接調用。add
的返回的結果爲5
,sub
則是2
,下見下圖1-3
。
圖1-3 執行python腳本
咱們如今來介紹以及使用一些Process
對象中比較經常使用的api
~
Process.id
:返回附加目標進程的PID
Process.isDebuggerAttached()
:檢測當前是否對目標程序已經附加
枚舉當前加載的模塊,返回模塊對象的數組。Process.enumerateModules()
會枚舉當前全部已加載的so
模塊,而且返回了數組Module
對象,Module
對象下一節咱們來詳細說,在這裏咱們暫時只使用Module
對象的name
屬性。
function frida_Process() { Java.perform(function () { var process_Obj_Module_Arr = Process.enumerateModules(); for(var i = 0; i < process_Obj_Module_Arr.length; i++) { console.log("",process_Obj_Module_Arr[i].name); } }); } setImmediate(frida_Process,0);
我來們開看看這段js
代碼寫了啥:在js
中可以直接使用Process
對象的全部api
,調用了Process.enumerateModules()
方法以後會返回一個數組,數組中存儲N個叫Module的對象,既然已經知道返回了的是一個數組,很簡單咱們就來for
循環它即是,這裏我使用下標的方式調用了Module
對象的name
屬性,name
是so
模塊的名稱。見下圖1-4
。
圖1-4 終端輸出了全部已加載的so
Process.enumerateThreads()
:枚舉當前全部的線程,返回包含如下屬性的對象數組:
屬性 | 含義 |
---|---|
id | 線程id |
state | 當前運行狀態有running, stopped, waiting, uninterruptible or halted |
context | 帶有鍵pc和sp的對象,它們是分別爲ia32/x64/arm指定EIP/RIP/PC和ESP/RSP/SP的NativePointer對象。也可使用其餘處理器特定的密鑰,例如eax、rax、r0、x0等。 |
使用代碼示例以下:
function frida_Process() { Java.perform(function () { var enumerateThreads = Process.enumerateThreads(); for(var i = 0; i < enumerateThreads.length; i++) { console.log(""); console.log("id:",enumerateThreads[i].id); console.log("state:",enumerateThreads[i].state); console.log("context:",JSON.stringify(enumerateThreads[i].context)); } }); } setImmediate(frida_Process,0);
獲取當前是全部線程以後返回了一個數組,而後循環輸出它的值,以下圖1-5
。
圖1-4 終端執行
Process.getCurrentThreadId()
:獲取此線程的操做系統特定 ID
做爲數字
3.4章節中Process.EnumererateModules()
方法返回了就是一個Module
對象,我們這裏來詳細說說Module
對象,先來瞧瞧它都有哪些屬性。
屬性 | 含義 |
---|---|
name | 模塊名稱 |
base | 模塊地址,其變量類型爲NativePointer |
size | 大小 |
path | 完整文件系統路徑 |
除了屬性咱們再來看看它有什麼方法。
API | 含義 |
---|---|
Module.load() | 加載指定so文件,返回一個Module對象 |
enumerateImports() | 枚舉全部Import庫函數,返回Module數組對象 |
enumerateExports() | 枚舉全部Export庫函數,返回Module數組對象 |
enumerateSymbols() | 枚舉全部Symbol庫函數,返回Module數組對象 |
Module.findExportByName(exportName)、Module.getExportByName(exportName) | 尋找指定so中export庫中的函數地址 |
Module.findBaseAddress(name)、Module.getBaseAddress(name) | 返回so的基地址 |
在frida-12-5
版本中更新了該API
,主要用於加載指定so
文件,返回一個Module
對象。
使用代碼示例以下:
function frida_Module() { Java.perform(function () { //參數爲so的名稱 返回一個Module對象 const hooks = Module.load('libhello.so'); //輸出 console.log("模塊名稱:",hooks.name); console.log("模塊地址:",hooks.base); console.log("大小:",hooks.size); console.log("文件系統路徑",hooks.path); }); } setImmediate(frida_Module,0); 輸出以下: 模塊名稱: libhello.so 模塊地址: 0xdf2d3000 大小: 24576 文件系統路徑 /data/app/com.roysue.roysueapplication-7adQZoYIyp5t3G5Ef5wevQ==/lib/arm/libhello.so
我們這一小章節就來使用Module
對象,把上章的Process.EnumererateModules()
對象輸出給它補全了,代碼以下。
function frida_Module() { Java.perform(function () { var process_Obj_Module_Arr = Process.enumerateModules(); for(var i = 0; i < process_Obj_Module_Arr.length; i++) { if(process_Obj_Module_Arr[i].path.indexOf("hello")!=-1) { console.log("模塊名稱:",process_Obj_Module_Arr[i].name); console.log("模塊地址:",process_Obj_Module_Arr[i].base); console.log("大小:",process_Obj_Module_Arr[i].size); console.log("文件系統路徑",process_Obj_Module_Arr[i].path); } } }); } setImmediate(frida_Module,0); 輸出以下: 模塊名稱: libhello.so 模塊地址: 0xdf2d3000 大小: 24576 文件系統路徑 /data/app/com.roysue.roysueapplication-7adQZoYIyp5t3G5Ef5wevQ==/lib/arm/libhello.so
這邊若是去除判斷的話會打印全部加載的so
的信息,這裏咱們就知道了哪些方法返回了Module
對象了,而後咱們再繼續深刻學習Module
對象自帶的API
。
該API會枚舉模塊中全部中的全部Import函數,示例代碼以下。
function frida_Module() { Java.perform(function () { const hooks = Module.load('libhello.so'); var Imports = hooks.enumerateImports(); for(var i = 0; i < Imports.length; i++) { //函數類型 console.log("type:",Imports[i].type); //函數名稱 console.log("name:",Imports[i].name); //屬於的模塊 console.log("module:",Imports[i].module); //函數地址 console.log("address:",Imports[i].address); } }); } setImmediate(frida_Module,0); 輸出以下: [Google Pixel::com.roysue.roysueapplication]-> type: function name: __cxa_atexit module: /system/lib/libc.so address: 0xf58f4521 type: function name: __cxa_finalize module: /system/lib/libc.so address: 0xf58f462d type: function name: __stack_chk_fail module: /system/lib/libc.so address: 0xf58e2681 ...
該API會枚舉模塊中全部中的全部Export
函數,示例代碼以下。
function frida_Module() { Java.perform(function () { const hooks = Module.load('libhello.so'); var Exports = hooks.enumerateExports(); for(var i = 0; i < Exports.length; i++) { //函數類型 console.log("type:",Exports[i].type); //函數名稱 console.log("name:",Exports[i].name); //函數地址 console.log("address:",Exports[i].address); } }); } setImmediate(frida_Module,0); 輸出以下: [Google Pixel::com.roysue.roysueapplication]-> type: function name: Java_com_roysue_roysueapplication_hellojni_getSum address: 0xdf2d411b type: function name: unw_save_vfp_as_X address: 0xdf2d4c43 type: function address: 0xdf2d4209 type: function ...
代碼示例以下。
function frida_Module() { Java.perform(function () { const hooks = Module.load('libc.so'); var Symbol = hooks.enumerateSymbols(); for(var i = 0; i < Symbol.length; i++) { console.log("isGlobal:",Symbol[i].isGlobal); console.log("type:",Symbol[i].type); console.log("section:",JSON.stringify(Symbol[i].section)); console.log("name:",Symbol[i].name); console.log("address:",Symbol[i].address); } }); } setImmediate(frida_Module,0); 輸出以下: isGlobal: true type: function section: {"id":"13.text","protection":"r-x"} name: _Unwind_GetRegionStart address: 0xf591c798 isGlobal: true type: function section: {"id":"13.text","protection":"r-x"} name: _Unwind_GetTextRelBase address: 0xf591c7cc ...
返回so
文件中Export
函數庫中函數名稱爲exportName
函數的絕對地址。
代碼示例以下。
function frida_Module() { Java.perform(function () { Module.getExportByName('libhello.so', 'c_getStr') console.log("Java_com_roysue_roysueapplication_hellojni_getStr address:",Module.findExportByName('libhello.so', 'Java_com_roysue_roysueapplication_hellojni_getStr')); console.log("Java_com_roysue_roysueapplication_hellojni_getStr address:",Module.getExportByName('libhello.so', 'Java_com_roysue_roysueapplication_hellojni_getStr')); }); } setImmediate(frida_Module,0); 輸出以下: Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d
返回name
模塊的基地址。
代碼示例以下。
function frida_Module() { Java.perform(function () { var name = "libhello.so"; console.log("so address:",Module.findBaseAddress(name)); console.log("so address:",Module.getBaseAddress(name)); }); } setImmediate(frida_Module,0); 輸出以下: so address: 0xdf2d3000 so address: 0xdf2d3000
Memory
的一些API
一般是對內存處理,譬如Memory.copy()
複製內存,又如writeByteArray
寫入字節到指定內存中,那咱們這章中就是學習使用Memory API
向內存中寫入數據、讀取數據。
其主要功能是搜索內存中以address
地址開始,搜索長度爲size
,須要搜是條件是pattern,callbacks
搜索以後的回調函數;此函數至關於搜索內存的功能。
咱們來直接看例子,而後結合例子講解,以下圖1-5
。
圖1-5 IDA中so文件某處數據
若是我想搜索在內存中112A
地址的起始數據要怎麼作,代碼示例以下。
function frida_Memory() { Java.perform(function () { //先獲取so的module對象 var module = Process.findModuleByName("libhello.so"); //??是通配符 var pattern = "03 49 ?? 50 20 44"; //基址 console.log("base:"+module.base) //從so的基址開始搜索,搜索大小爲so文件的大小,搜指定條件03 49 ?? 50 20 44的數據 var res = Memory.scan(module.base, module.size, pattern, { onMatch: function(address, size){ //搜索成功 console.log('搜索到 ' +pattern +" 地址是:"+ address.toString()); }, onError: function(reason){ //搜索失敗 console.log('搜索失敗'); }, onComplete: function() { //搜索完畢 console.log("搜索完畢") } }); }); } setImmediate(frida_Memory,0);
先來看看回調函數的含義,onMatch:function(address,size)
:使用包含做爲NativePointer
的實例地址的address
和指定大小爲數字的size
調用,此函數可能會返回字符串STOP
以提早取消內存掃描。onError:Function(Reason)
:當掃描時出現內存訪問錯誤時使用緣由調用。onComplete:function()
:當內存範圍已徹底掃描時調用。
咱們來來講上面這段代碼作了什麼事情:搜索libhello.so
文件在內存中的數據,搜索以pattern
條件的在內存中能匹配的數據。搜索到以後根據回調函數返回數據。
咱們來看看執行以後的效果圖1-6
。
圖1-6 終端執行
咱們要如何驗證搜索究竟是不是圖1-5
中112A
地址,其實很簡單。so
的基址是0xdf2d3000
,而搜到的地址是0xdf2d412a
,咱們只要df2d412a-df2d3000=112A
。就是說咱們已經搜索到了!
功能與Memory.scan
同樣,只不過它是返回多個匹配到條件的數據。
代碼示例以下。
function frida_Memory() { Java.perform(function () { var module = Process.findModuleByName("libhello.so"); var pattern = "03 49 ?? 50 20 44"; var scanSync = Memory.scanSync(module.base, module.size, pattern); console.log("scanSync:"+JSON.stringify(scanSync)); }); } setImmediate(frida_Memory,0); 輸出以下,能夠看到地址搜索出來是同樣的 scanSync:[{"address":"0xdf2d412a","size":6}]
在目標進程中的堆上申請size
大小的內存,而且會按照Process.pageSize
對齊,返回一個NativePointer
,而且申請的內存若是在JavaScript
裏面沒有對這個內存的使用的時候會自動釋放的。也就是說,若是你不想要這個內存被釋放,你須要本身保存一份對這個內存塊的引用。
使用案例以下
function frida_Memory() { Java.perform(function () { const r = Memory.alloc(10); console.log(hexdump(r, { offset: 0, length: 10, header: true, ansi: false })); }); } setImmediate(frida_Memory,0);
以上代碼在目標進程中申請了10
字節的空間<br>能夠看到在`0xdfe4cd40`處申請了`10`個字節內存空間
也可使用:Memory.allocUtf8String(str)
分配utf字符串Memory.allocUtf16String
分配utf16字符串Memory.allocAnsiString
分配ansi字符串
如同c api memcp
同樣調用,使用案例以下。
function frida_Memory() { Java.perform(function () { //獲取so模塊的Module對象 var module = Process.findModuleByName("libhello.so"); //條件 var pattern = "03 49 ?? 50 20 44"; //搜字符串 只是爲了將so的內存數據複製出來 方便演示~ var scanSync = Memory.scanSync(module.base, module.size, pattern); //申請一個內存空間大小爲10個字節 const r = Memory.alloc(10); //複製以module.base地址開始的10個字節 那確定會是7F 45 4C 46...由於一個ELF文件的Magic屬性如此。 Memory.copy(r,module.base,10); console.log(hexdump(r, { offset: 0, length: 10, header: true, ansi: false })); }); } setImmediate(frida_Memory,0); 輸出以下。 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF e8142070 7f 45 4c 46 01 01 01 00 00 00 .ELF......
從module.base
中複製10
個字節的內存到新年申請的r
內
將字節數組寫入一個指定內存,代碼示例以下:
function frida_Memory() { Java.perform(function () { //定義須要寫入的字節數組 這個字節數組是字符串"roysue"的十六進制 var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65]; //申請一個新的內存空間 返回指針 大小是arr.length const r = Memory.alloc(arr.length); //將arr數組寫入R地址中 Memory.writeByteArray(r,arr); //輸出 console.log(hexdump(r, { offset: 0, length: arr.length, header: true, ansi: false })); }); } setImmediate(frida_Memory,0); 輸出以下。 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 72 6f 79 73 75 65 roysue
將一個指定地址的數據,代碼示例以下:
function frida_Memory() { Java.perform(function () { //定義須要寫入的字節數組 這個字節數組是字符串"roysue"的十六進制 var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65]; //申請一個新的內存空間 返回指針 大小是arr.length const r = Memory.alloc(arr.length); //將arr數組寫入R地址中 Memory.writeByteArray(r,arr); //讀取r指針,長度是arr.length 也就是會打印上面同樣的值 var buffer = Memory.readByteArray(r, arr.length); //輸出 console.log("Memory.readByteArray:"); console.log(hexdump(buffer, { offset: 0, length: arr.length, header: true, ansi: false })); }); }); } setImmediate(frida_Memory,0); 輸出以下。 [Google Pixel::com.roysue.roysueapplication]-> Memory.readByteArray: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 72 6f 79 73 75 65 roysue
在這篇中咱們學會了在FRIDACLI中如何輸出想要輸出格式,也學會如何聲明變量,一步步的學習。在逐步的學習的過程,老是會遇到不一樣的問題。歌曲<奇蹟再現>我相信你必定聽過吧~,新的風暴已經出現,怎麼可以中止不前..遇到問題不要怕,總會解決的。
短視頻、直播數據實時採集接口,請查看文檔: TiToData
免責聲明:本文檔僅供學習與參考,請勿用於非法用途!不然一切後果自負。