Android逆向-Frida初级学习笔记
1.FRIDA 初级
接着上篇⽂章"",这次记录⼀些⽇常⼯作中分析app时会⽤到的⼀些frida⼩技巧。
1.1.Frida远程过程调⽤(rpc)
有时候分析⼀个应⽤的算法时到了算法的实现⽅法但⽅法的逻辑特别复杂或被混淆了怎么办?
这时候我们可以选择⼀个简单⽽粗暴的⽅式,直接刚,啊呸,直接调。
frida⽀持在js代码中使⽤ports关键字对js函数进⾏导出,导出⽅法可在python代码中通过ports.导出符号()进⾏调⽤。配合之前说过的frida的⽅法主动调⽤,可以很好的完成对应⽤中的算法或核⼼⽅法进⾏主动单独调⽤。
function Hook10(){
Java.perform(function(){
console.log("Frida Test Hook10");
// 主动调⽤类静态⽅法
var clszz = Java.use("cn.gemini.k.fridatest.FridaHook1");
console.log("func3_verify_static  ret:"+clszz.func3_verify_static(">>>pwd<<<"));
});
}
exporthook10: Hook10 //将⽅法导出为exporthook10符号,这⾥要注意导出名千万不能有⼤写字母或下划线
};
python代码
# -*- coding: UTF-8 -*-
import frida
import sys
# ⽬标包名
appPacknName ="cn.gemini.k.fridatest"
scriptFile ="hook_script.js"
# 输出⽇志的回调⽅法
def on_message(message, data):
if message['type']=='send':
print("[*] {0}".format(message['payload']))
else:
print(message)
device = _usb_device()
# spawn模式,到⽬标包名并重启,在启动前注⼊脚本
pid = device.spawn([appPacknName])
session = device.attach(pid)
# 注意这⾥需要将device.attach(pid)这句代码写在前⾯,这样执⾏才符合预期(启动时程序⽩屏,等待下⾯这⾏代码来恢复执⾏)
# 其实在www.jianshu/p/b833fba1bffe这篇⽂章中有提到
# ⽅式⼀: 通过js⽂件创建hook代码
with open(scriptFile, encoding='UTF-8')as f:
script = ate_ad())
# ⽅式⼆: 直接将hook代码写在python⽂件中
# script = ate_script(js_code)
<("message", on_message)
script.load()# 把js代码注⼊到⽬标应⽤中
# 避免结束
# ad()
# frida RPC测试
1.2.Frida⼿动加载dex
破解应⽤时如果想在应⽤中执⾏我们⾃⼰写的Java类代码通过frida怎么实现?
可以将⾃定义的Java类编译成⼀个dex⽂件,懒得单独编译也可以直接去apk⽂件中获取,需要注意的是有些apk可能存在多个dex⽂件,这⾥需要⼀下⼀定要是含有我们Java类的那个dex。
dex⽂件准备好后使⽤adb⼯具push到设备的"/data/local/tmp/“⽬录下⽅便我们的frida代码加载,之后通过
Java.openClassFile(”/data/local/tmp/my.dex").load()对⽬标dex进⾏加载。这样就可以通过Java.use调⽤我们直接写的⽅法了。
function hook(){
Java.perform(function(){
Java.openClassFile("/data/local/tmp/my.dex").load();
var cls = Java.use("xxxxxxxx");// ⾃定义的Java类
console.log(cls);
var obj = cls.$new();// 实例化⾃定义的Java类
console.log(obj);
});
}
1.3.Frida Hook动态加载的dex
如果需要hook的类所在dex是应⽤在运⾏过程中动态加载的怎么hook?
⾸先怎么判断是否为动态加载:内存中确实存在该类,但apk中的dex却不到该类,那么可能就是动态加载的。
这种情况下通过frida的⼀般hook流程是hook不上这个类的,因为frida使⽤的默认classloader与加载该类的classloader是不⼀样,此时就需要通过frida的enumerateClassLoaders⽅法来枚举当前进程的classloader,再通过loader.findClass⽅法在每个枚举到的classloader中寻是否存在我们想要的类,到后再通过Java.classFactory.loader=loader来切换⼀下当前frida使⽤的classloader,切换完成后就可以通过Java.use进⾏类的查了。
// hook动态加载的dex
function Hook11(){
Java.perform(function(){
console.log("Frida Test Hook11");
// 遍历当前所有的classloader
onMatch:function(loader){
try{
// 这⾥需要注意的是如果遍历到的loader中没有到⽬标类则会报错,所以需要添加try catch
if(loader.loadClass("")){
Java.classFactory.loader = loader;
var Dynamic = Java.use("");
console.log(Dynamic);
Dynamic.HHH.implementation=function(){
return"hook 11";
}
}
}catch(error){
}
},
onComplete:function(){
}
})
});
}
1.4.Frida栈回溯
⼀般在定位应⽤的某个功能点时⽤的较多,常见⽤法是对⼀些系统API进⾏hook,⽐如系统加解密API、
接受⽤户输⼊的系统API等,当程序执⾏到被hook的⽅法时再通过对当时的堆栈进⾏打印并分析上下栈信息即可得知该功能的代码实现所在位置。
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时机
有些场景下的hook要求我们在APP应⽤启动前就要进⾏hook,⽐如分析被加过固的应⽤⼜或是需要hook的⽅法是⼀个静态⽅法,在程序启动时就被初始化,这种情况该如何控制frida的hook时机呢?
前⾯说过frida⽀持两种注⼊模式,⼀种是直接对已启动的⽬标应⽤进⾏附加注⼊。另⼀种是以挂起的⽅式重启⽬标应⽤,应⽤启动时会先进⼊等待模式,观察设备发现应⽤是处于⽩屏状态的,实际上是在等待我们唤醒主线程来继续完成启动。
对于需要在APP应⽤启动前就hook的场景我们就需要选择第⼆种注⼊模式。
以挂起的⽅式启动应⽤可以使⽤"frida -U -x -l hook.js “来实现,之后需要我们通过⼿动输⼊%resume的⽅式来让主线程开始运⾏。
该命令没有加”–no-pause"参数,如果加上该参数程序启动时就不会进⼊等待⽽是直接启动。加与不加的区别就是启动时不等待与等待。参数"-f"表⽰,需要重启这个应⽤并且attach上去,与之对应的是"-n"或"-p"参数,分别是通过指定进程名或进程id来attach到正在运⾏的应⽤。
参数"-l"表⽰本次需要加载的js注⼊代码。
如果是在python代码中要以挂起的⽅式启动则可以使⽤spawn()⽅法,唤醒的⽅法是resume(),需要指定pid。
1.6.Frida程序与App数据交互
主要涉及到两个⽅法send()/recv();
send:向Frida应⽤程序发送JSON可序列化消息
recv:接收来⾃Frida应⽤程序发送的⼀条消息
wait:直到消息已经被recv接收,并且recv的回调⽅法已经执⾏完毕并返回
js注⼊程序通过send⽅法将应⽤的数据发送给PC端的frida应⽤程序,然后frida应⽤程序对接收到的数据进⾏各种处理修改后再发送给js注⼊程序,这样就可以完成python对app数据动态修改的效果了。可能有些⼩伙伴会问,为什么要传到PC端去处理,之后⼜回传给js代码⽽不直接通过js代码处理呢?
主要是考虑到⼿机性能的问题,如果需要处理的数据⽐较复杂,那么使⽤PC去跑则效果更好。
python客户端处理代码
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)
print("burning data:"+data)
data =str(base64.b64decode(data),"utf-8")# 解码
user, pw = data.split(":")# 提取⽤户名和密码
data  =str(base64.b64encode(("admin"+":"+ pw).encode("utf-8")),"utf-8")# 组成新的组合并编码,这是使⽤admin登陆print("encoded data:", data)
script.post({"my_data": data})# 将JSON对象发送回去
print("Modified data sent")
注⼊的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);// 将数据发送给PC端
recv(function(received_json_object){
string_to_recv = received__data
console.log("string_to_recv: "+ string_to_recv);
}).wait();//收到数据之后,再执⾏下去
var string = Java.use("java.lang.String");
string_to_recv = string.$new(string_to_recv);//将收到的数据转化为String类型
return this.setText(string_to_recv);
}
});
1.7.Frida类型转换、参数构造
1.7.1.类型转换
我们在hook某个⽅法分析其参数值时,如果参数不是string类型,那么打印出来的很可能就是[object Object],这种情况我们就需要对参数做⼀些转换处理才能打印出来真实的值。
打印HashMap或Map类型的参数x
frida给我们提供了类型强转⽅法:Java.cast()
var map = Java.use("java.util.HashMap");
var args_x = Java.cast(x,map);//将参数x转换为HashMap类型
console.log(String());//调⽤HashMap的toString⽅法
打印char[]类型的参数x
var arr = Java.use("java.util.Arrays");
console.log("参数对应数组:"+ String(x));
打印byte[]类型的参数x
var arr = Java.use("java.util,Arrays")
var JavaString = Java.use("java.lang.String")
console.log("参数对应数组:"+ String(x));//调⽤Arrays类的toString⽅法将byte[]类型参数x转为string类型
console.log("参数对应字符串:"+ JavaString.$new(x));//将byte[]类型参数x做为String类构造参数
1.7.
2.参数构造
frida获取应⽤context
我们在主动调⽤app的某个⽅法时经常会遇到参数是⼀个context对象,此时我们就可以通过下⾯的⽅式获取context对象。
var currentApplication= Java.use("android.app.ActivityThread").currentApplication();
var context = ApplicationContext();
frida构造任意类型的数组
frida还给我们提供了构造任意类型数组的⽅法java.array()
⽤法格式:Java.array(‘type’,[value1,value2,…])
使⽤构造char[]类型:
var charArray = Java.array('char',['你','好']);
使⽤构造Object[]类型:
var objectclass = Java.use("java.lang.Object");
var objectArray = Java.array('char',[objectclass.class]);
1.8.打印non-asciiandroid学习教程
int  (int x){
return x +100;
}
js代码:
Java.perform(function x(){
var cls = Java.use("ample.hooktest.MainActivity");
var methods = DeclaredMethods();// getDeclaredMethods⽅法会获取该类的所有⽅法,包括私有的,公有的,保护的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转byte[]

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。