Frida 基础操作2
1.FRIDA 初級(jí)
1.1.Frida遠(yuǎn)程過程調(diào)用(rpc)
有時(shí)候分析一個(gè)應(yīng)用的算法時(shí)找到了算法的實(shí)現(xiàn)方法但方法的邏輯特別復(fù)雜或被混淆了怎么辦?
這時(shí)候我們可以選擇一個(gè)簡(jiǎn)單而粗暴的方式,直接剛,啊呸,直接調(diào)。
frida支持在js代碼中使用rpc.exports關(guān)鍵字對(duì)js函數(shù)進(jìn)行導(dǎo)出,導(dǎo)出方法可在python代碼中通過script.exports.導(dǎo)出符號(hào)()進(jìn)行調(diào)用。配合之前說過的frida的方法主動(dòng)調(diào)用,可以很好的完成對(duì)應(yīng)用中的算法或核心方法進(jìn)行主動(dòng)單獨(dú)調(diào)用。
python代碼
# -*- coding: UTF-8 -*- import frida import sys# 目標(biāo)包名 appPacknName = "cn.gemini.k.fridatest" scriptFile = "hook_script.js"# 輸出日志的回調(diào)方法 def on_message(message, data):if message['type'] == 'send':print("[*] {0}".format(message['payload']))else:print(message)device = frida.get_usb_device() # spawn模式,找到目標(biāo)包名并重啟,在啟動(dòng)前注入腳本 pid = device.spawn([appPacknName]) session = device.attach(pid) # 注意這里需要將device.attach(pid)這句代碼寫在前面,這樣執(zhí)行才符合預(yù)期(啟動(dòng)時(shí)程序白屏,等待下面這行代碼來恢復(fù)執(zhí)行) # 其實(shí)在https://www.jianshu.com/p/b833fba1bffe這篇文章中有提到 device.resume(pid)# 方式一: 通過js文件創(chuàng)建hook代碼 with open(scriptFile, encoding='UTF-8') as f:script = session.create_script(f.read()) # 方式二: 直接將hook代碼寫在python文件中 # script = session.create_script(js_code)script.on("message", on_message) script.load() # 把js代碼注入到目標(biāo)應(yīng)用中 # 避免結(jié)束 # sys.stdin.read()# frida RPC測(cè)試 script.exports.exporthook10() # exportmain為js文件中的導(dǎo)出符號(hào)1.2.Frida手動(dòng)加載dex
破解應(yīng)用時(shí)如果想在應(yīng)用中執(zhí)行我們自己寫的Java類代碼通過frida怎么實(shí)現(xiàn)?
可以將自定義的Java類編譯成一個(gè)dex文件,懶得單獨(dú)編譯也可以直接去apk文件中獲取,需要注意的是有些apk可能存在多個(gè)dex文件,這里需要找一下一定要是含有我們Java類的那個(gè)dex。
dex文件準(zhǔn)備好后使用adb工具push到設(shè)備的"/data/local/tmp/“目錄下方便我們的frida代碼加載,之后通過Java.openClassFile(”/data/local/tmp/my.dex").load()對(duì)目標(biāo)dex進(jìn)行加載。這樣就可以通過Java.use調(diào)用我們直接寫的方法了。
1.3.Frida Hook動(dòng)態(tài)加載的dex
如果需要hook的類所在dex是應(yīng)用在運(yùn)行過程中動(dòng)態(tài)加載的怎么hook?
首先怎么判斷是否為動(dòng)態(tài)加載:內(nèi)存中確實(shí)存在該類,但apk中的dex卻找不到該類,那么可能就是動(dòng)態(tài)加載的。
這種情況下通過frida的一般hook流程是hook不上這個(gè)類的,因?yàn)閒rida使用的默認(rèn)classloader與加載該類的classloader是不一樣,此時(shí)就需要通過frida的enumerateClassLoaders方法來枚舉當(dāng)前進(jìn)程的classloader,再通過loader.findClass方法在每個(gè)枚舉到的classloader中尋找是否存在我們想要的類,找到后再通過Java.classFactory.loader=loader來切換一下當(dāng)前frida使用的classloader,切換完成后就可以通過Java.use進(jìn)行類的查找了。
1.4.Frida?;厮?/h3>
一般在定位應(yīng)用的某個(gè)功能點(diǎn)時(shí)用的較多,常見用法是對(duì)一些系統(tǒng)API進(jìn)行hook,比如系統(tǒng)加解密API、接受用戶輸入的系統(tǒng)API等,當(dāng)程序執(zhí)行到被hook的方法時(shí)再通過對(duì)當(dāng)時(shí)的堆棧進(jìn)行打印并分析上下棧信息即可得知該功能的代碼實(shí)現(xiàn)所在位置。
function PrintJavaStacks1(){var thread = Java.use("java.lang.Thread");var tOBJ = thread.$new();var stack = tOBJ.currentThread().getStackTrace();var at = ""for(var i = 0; i < stack.length; i++){at += stack[i].toString()+"\n";}console.log(at); }1.5.Frida Hook時(shí)機(jī)
有些場(chǎng)景下的hook要求我們?cè)贏PP應(yīng)用啟動(dòng)前就要進(jìn)行hook,比如分析被加過固的應(yīng)用又或是需要hook的方法是一個(gè)靜態(tài)方法,在程序啟動(dòng)時(shí)就被初始化,這種情況該如何控制frida的hook時(shí)機(jī)呢?
前面說過frida支持兩種注入模式,一種是直接對(duì)已啟動(dòng)的目標(biāo)應(yīng)用進(jìn)行附加注入。另一種是以掛起的方式重啟目標(biāo)應(yīng)用,應(yīng)用啟動(dòng)時(shí)會(huì)先進(jìn)入等待模式,觀察設(shè)備發(fā)現(xiàn)應(yīng)用是處于白屏狀態(tài)的,實(shí)際上是在等待我們喚醒主線程來繼續(xù)完成啟動(dòng)。
對(duì)于需要在APP應(yīng)用啟動(dòng)前就hook的場(chǎng)景我們就需要選擇第二種注入模式。
以掛起的方式啟動(dòng)應(yīng)用可以使用"frida -U -f com.xxx.xxx -l hook.js “來實(shí)現(xiàn),之后需要我們通過手動(dòng)輸入%resume的方式來讓主線程開始運(yùn)行。
該命令沒有加”–no-pause"參數(shù),如果加上該參數(shù)程序啟動(dòng)時(shí)就不會(huì)進(jìn)入等待而是直接啟動(dòng)。加與不加的區(qū)別就是啟動(dòng)時(shí)不等待與等待。
參數(shù)"-f"表示,需要重啟這個(gè)應(yīng)用并且attach上去,與之對(duì)應(yīng)的是"-n"或"-p"參數(shù),分別是通過指定進(jìn)程名或進(jìn)程id來attach到正在運(yùn)行的應(yīng)用。
參數(shù)"-l"表示本次需要加載的js注入代碼。
如果是在python代碼中要以掛起的方式啟動(dòng)則可以使用spawn()方法,喚醒的方法是resume(),需要指定pid。
1.6.Frida程序與App數(shù)據(jù)交互
主要涉及到兩個(gè)方法send()/recv();
send:向Frida應(yīng)用程序發(fā)送JSON可序列化消息
recv:接收來自Frida應(yīng)用程序發(fā)送的一條消息
wait:直到消息已經(jīng)被recv接收,并且recv的回調(diào)方法已經(jīng)執(zhí)行完畢并返回
js注入程序通過send方法將應(yīng)用的數(shù)據(jù)發(fā)送給PC端的frida應(yīng)用程序,然后frida應(yīng)用程序?qū)邮盏降臄?shù)據(jù)進(jìn)行各種處理修改后再發(fā)送給js注入程序,這樣就可以完成python對(duì)app數(shù)據(jù)動(dòng)態(tài)修改的效果了。可能有些小伙伴會(huì)問,為什么要傳到PC端去處理,之后又回傳給js代碼而不直接通過js代碼處理呢?
主要是考慮到手機(jī)性能的問題,如果需要處理的數(shù)據(jù)比較復(fù)雜,那么使用PC去跑則效果更好。
python客戶端處理代碼
注入的js代碼
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;console.log("send"+string_to_send);send(string_to_send); // 將數(shù)據(jù)發(fā)送給PC端recv(function (received_json_object) {string_to_recv = received_json_object.my_dataconsole.log("string_to_recv: " + string_to_recv);}).wait(); //收到數(shù)據(jù)之后,再執(zhí)行下去var string = Java.use("java.lang.String");string_to_recv = string.$new(string_to_recv);//將收到的數(shù)據(jù)轉(zhuǎn)化為String類型return this.setText(string_to_recv);} });1.7.Frida類型轉(zhuǎn)換、參數(shù)構(gòu)造
1.7.1.類型轉(zhuǎn)換
我們?cè)趆ook某個(gè)方法分析其參數(shù)值時(shí),如果參數(shù)不是string類型,那么打印出來的很可能就是[object Object],這種情況我們就需要對(duì)參數(shù)做一些轉(zhuǎn)換處理才能打印出來真實(shí)的值。
- 打印HashMap或Map類型的參數(shù)x
frida給我們提供了類型強(qiáng)轉(zhuǎn)方法:Java.cast()
var map = Java.use("java.util.HashMap"); var args_x = Java.cast(x,map); //將參數(shù)x轉(zhuǎn)換為HashMap類型 console.log(args_x.toString()); //調(diào)用HashMap的toString方法- 打印char[]類型的參數(shù)x
- 打印byte[]類型的參數(shù)x
1.7.2.參數(shù)構(gòu)造
frida獲取應(yīng)用context
我們?cè)谥鲃?dòng)調(diào)用app的某個(gè)方法時(shí)經(jīng)常會(huì)遇到參數(shù)是一個(gè)context對(duì)象,此時(shí)我們就可以通過下面的方式獲取context對(duì)象。
frida構(gòu)造任意類型的數(shù)組
frida還給我們提供了構(gòu)造任意類型數(shù)組的方法java.array()
用法格式:Java.array(‘type’,[value1,value2,…])
使用構(gòu)造char[]類型:
使用構(gòu)造Object[]類型:
var objectclass = Java.use("java.lang.Object"); var objectArray = Java.array('char',[objectclass.class]);1.8.打印non-ascii
應(yīng)用場(chǎng)景一般是apk被混淆了,混淆后的方法名都是一些亂碼或不顯示的非ASCII字符,這時(shí)候因?yàn)槲覀儧]法確定方法名所以就沒法指定需要hook的方法,這時(shí)候可以通過先編碼打印出來,再用編碼后的字符串去hook即可解決方法名亂碼的問題。
https://api-caller.com/2019/03/30/frida-note/#non-ascii
示例方法:
js代碼:
Java.perform(function x() {var cls = Java.use("com.example.hooktest.MainActivity");var methods = cls.class.getDeclaredMethods(); // getDeclaredMethods方法會(huì)獲取該類的所有方法,包括私有的,公有的,保護(hù)的for (var i in methods) { // 遍歷獲取到的所有方法console.log(methods[i].toString()); // 打印原始方法名console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1"))); // 過濾處理并編碼}// %D6%8F == ?cls[decodeURIComponent("%D6%8F")].implementation = function (x) {console.log("original call: fun(" + x + ")");var result = this[decodeURIComponent("%D6%8F")](900);return result;}} )1.9.收集的一些工具方法
string轉(zhuǎn)byte[]
frida讀寫std::string
function readStdString(str){const isTiny = (str.readU8()&1) === 0;if (isTiny)return str.add(1).readUtf8String();return str.add(2*Provess.pointerSize).readPointer().readUtf8String(); }function writeStdString(str, content){const isTiny = (str.readU8()&1) === 0;if (isTiny)str.add(1).writeUtf8String(content);elsestr.add(2*Process.pointerSize).readPointer().writeUtf8String(content); }總結(jié)
以上是生活随笔為你收集整理的Frida 基础操作2的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Frida之安装和使用教程
- 下一篇: 逆向工具清单