close

前言

大家好,窩又來寫文章了,咱們現在在這篇文章中,我們來對其官方的一些非常常用的API進行學習。所謂工欲善其事,必先利其器。想要好好學習FRIDA我們就必須對FRIDA API深入的學習以對其有更深的了解和使用,通常大部分核心原理也在官方API中寫着,我們學會來使用一些案例來結合API的使用。

注意,運行以下任何代碼時都需要提前啟動手機中的frida-server文件。

系列文章目錄搬新「家」了,地址:https://github.com/r0ysue/AndroidSecurityStudy ,接下來窩會努力寫更多喔 ~

1.1 FRIDA輸出打印1.1.1 console輸出

不論是什麼語言都好,第一個要學習總是如何輸出和打印,那我們就來學習在FRIDA打印值。在官方API有兩種打印的方式,分別是console、send,我們先來學習非常的簡單的console,這裡我創建一個js文件,代碼示例如下。

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文件。

roysue@ubuntu:~$ adb shellsailfish:/ $ susailfish:/ $ ./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 代碼示例

可以到終點已經成功注入了腳本並且打印了hello,但是顏色不同,這是log的級別的原因,在FRIDA的console中有三個級別分別是log、warn、error。

級別

含義

log

正常

warn

警告

error

錯誤

1.1.2 console之hexdump

error級別最為嚴重其次warn,但是一般在使用中我們只會使用log來輸出想看的值;然後我們繼續學習console的好兄弟,hexdump,其含義:打印內存中的地址,target參數可以是ArrayBuffer或者NativePointer,而options參數則是自定義輸出格式可以填這幾個參數offset、lengt、header、ansi。

hexdump代碼示例以及執行效果如下。

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 0123456789ABCDEF00000000 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...

1.1.3 send

send是在python層定義的on_message回調函數,jscode內所有的信息都被監控script.on('message', on_message),當輸出信息的時候on_message函數會拿到其數據再通過format轉換, 其最重要的功能也是最核心的是能夠直接將數據以json格式輸出,當然數據是二進制的時候也依然是可以使用send,十分方便,我們來看代碼1-2示例以及執行效果。

# -*- coding: utf-8 -*-import fridaimport sysdef 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的好處啦~

1.2 FRIDA變量類型

學完輸出之後我們來學習如何聲明變量類型。

索引

API

含義

1

new Int64(v)

定義一個有符號Int64類型的變量值為v,參數v可以是字符串或者以0x開頭的的十六進制值

2

new UInt64(v)

定義一個無符號Int64類型的變量值為v,參數v可以是字符串或者以0x開頭的的十六進制值

3

new NativePointer(s)

定義一個指針,指針地址為s

4

ptr(「0」)

同上

代碼示例以及效果

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

含義

1

add(rhs)、sub(rhs)、and(rhs)、or(rhs)、xor(rhs)

加、減、邏輯運算

2

shr(N)、shl(n)

向右/向左移位n位生成新的Int64

3

Compare(Rhs)

返回整數比較結果

4

toNumber()

轉換為數字

5

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.3 RPC遠程調用

可以替換或插入的空對象,以向應用程序公開RPC樣式的API。該鍵指定方法名稱,該值是導出的函數。此函數可以返回一個純值以立即返回給調用方,或者承諾異步返回。也就是說可以通過rpc的導出的功能使用在python層,使python層與js交互,官方示例代碼有Node.js版本與python版本,我們在這裡使用python版本,代碼如下。

1.3.1 遠程調用代碼示例

import fridadef 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()

1.3.2 遠程調用代碼示例詳解

官方源碼示例是附加在目標進程為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.4 Process對象

我們現在來介紹以及使用一些Process對象中比較常用的api~

1.4.1 Process.id

Process.id:返回附加目標進程的PID

1.4.2 Process.isDebuggerAttached()

Process.isDebuggerAttached():檢測當前是否對目標程序已經附加

1.4.3 Process.enumerateModules()

枚舉當前加載的模塊,返回模塊對象的數組。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.4 Process.enumerateThreads()

Process.enumerateThreads():枚舉當前所有的線程,返回包含以下屬性的對象數組:

索引

屬性

含義

1

id

線程id

2

state

當前運行狀態有running, stopped, waiting, uninterruptible or halted

3

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.5 Process.getCurrentThreadId()

Process.getCurrentThreadId():獲取此線程的操作系統特定ID作為數字

1.5 Module對象

3.4章節中Process.EnumererateModules()方法返回了就是一個Module對象,咱們這裡來詳細說說Module對象,先來瞧瞧它都有哪些屬性。

1.5.1 Module對象的屬性

索引

屬性

含義

1

name

模塊名稱

2

base

模塊地址,其變量類型為NativePointer

3

size

大小

4

path

完整文件系統路徑

除了屬性我們再來看看它有什麼方法。

1.5.2 Module對象的API

索引

API

含義

1

Module.load()

加載指定so文件,返回一個Module對象

2

enumerateImports()

枚舉所有Import庫函數,返回Module數組對象

3

enumerateExports()

枚舉所有Export庫函數,返回Module數組對象

4

enumerateSymbols()

枚舉所有Symbol庫函數,返回Module數組對象

5

Module.findExportByName(exportName)、Module.getExportByName(exportName)

尋找指定so中export庫中的函數地址

6

Module.findBaseAddress(name)、Module.getBaseAddress(name)

返回so的基地址

1.5.3 Module.load()

在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

1.5.4 Process.EnumererateModules()

咱們這一小章節就來使用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。

1.5.5 enumerateImports()

該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: functionname: __cxa_atexitmodule: /system/lib/libc.soaddress: 0xf58f4521type: functionname: __cxa_finalizemodule: /system/lib/libc.soaddress: 0xf58f462d type: functionname: __stack_chk_failmodule: /system/lib/libc.soaddress: 0xf58e2681...

1.5.6 enumerateExports()

該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: functionname: Java_com_roysue_roysueapplication_hellojni_getSumaddress: 0xdf2d411btype: functionname: unw_save_vfp_as_Xaddress: 0xdf2d4c43type: functionaddress: 0xdf2d4209type: function...

1.5.7 enumerateSymbols()

代碼示例如下。

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: truetype: functionsection: {"id":"13.text","protection":"r-x"}name: _Unwind_GetRegionStartaddress: 0xf591c798isGlobal: truetype: functionsection: {"id":"13.text","protection":"r-x"}name: _Unwind_GetTextRelBaseaddress: 0xf591c7cc...

1.5.8 Module.findExportByName(exportName), Module.getExportByName(exportName)

返回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: 0xdf2d413dJava_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d

1.5.9 Module.findBaseAddress(name)、Module.getBaseAddress(name)

返回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: 0xdf2d3000so address: 0xdf2d3000

1.6 Memory對象

Memory的一些API通常是對內存處理,譬如Memory.copy()複製內存,又如writeByteArray寫入字節到指定內存中,那我們這章中就是學習使用Memory API向內存中寫入數據、讀取數據。

1.6.1 Memory.scan搜索內存數據

其主要功能是搜索內存中以address地址開始,搜索長度為size,需要搜是條件是pattern,callbacks搜索之後的回調函數;此函數相當於搜索內存的功能。

我們來直接看例子,然後結合例子講解,如下圖1-5。

如果我想搜索在內存中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-5中112A地址,其實很簡單。so的基址是0xdf2d3000,而搜到的地址是0xdf2d412a,我們只要df2d412a-df2d3000=112A。就是說我們已經搜索到了!

1.6.2 搜索內存數據Memory.scanSync

功能與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}]

1.6.3 內存分配Memory.alloc

在目標進程中的堆上申請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字節的空間~我們來看執行腳本的效果圖1-7。

可以看到在0xdfe4cd40處申請了10個字節內存空間~

也可以使用:Memory.allocUtf8String(str)分配utf字符串Memory.allocUtf16String分配utf16字符串Memory.allocAnsiString分配ansi字符串

1.6.4 內存複製Memory.copy

如同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 0123456789ABCDEFe8142070 7f 45 4c 46 01 01 01 00 00 00 .ELF......

從module.base中複製10個字節的內存到新年申請的r內

1.6.6 寫入內存Memory.writeByteArray

將字節數組寫入一個指定內存,代碼示例如下:

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 0123456789ABCDEF00000000 72 6f 79 73 75 65 roysue

1.6.7 讀取內存Memory.readByteArray

將一個指定地址的數據,代碼示例如下:

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 0123456789ABCDEF00000000 72 6f 79 73 75 65 roysue

結語

在這篇中我們學會了在FRIDACLI中如何輸出想要輸出格式,也學會如何聲明變量,一步步的學習。在逐步的學習的過程,總是會遇到不同的問題。歌曲<奇蹟再現>我相信你一定聽過吧~,新的風暴已經出現,怎麼能夠停止不前..遇到問題不要怕,總會解決的。

咱們會在下一篇中來學會如何使用FRIDA中的Java對象、Interceptor對象、NativePointer對象NativeFunction對象、NativeCallback對象咱們拭目以待吧~


- 結尾 -
精彩推薦
【技術分享】【CTF攻略】第三屆XCTF——鄭州站ZCTF第一名戰隊Writeup
【技術分享】淺析xml之xinclude & xslt
【技術分享】從crash到getshell 0ctf2019_plang 詳解

戳「閱讀原文」查看更多內容
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()