推荐肉丝r0ysue课程(包含安卓逆向与js逆向):https://img-blog.csdnimg.cn/7fa698312c304ab7bb0a03e2c866990f.png
努力ALL IN ONE,部分代码可配合AndroidReversePractice/FridaTestApp at main · Forgo7ten/AndroidReversePractice (github.com)试验。
JavaScript API | Frida • A world-class dynamic instrumentation framework
curl -L https://gitee.com/ibopo//pyenv-installer/raw/master/bin/pyenv-installer | bash
cd ~/.pyenv && src/configure && make -C src
.bashrc添加
# pyenv start
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
export PATH="$PYENV_ROOT/shims:$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init -)"
fi
eval "$(pyenv virtualenv-init -)"
# pyenv end
# 下载python版本3.8.2
pyenv install 3.8.2
# 查看pyenv版本
pyenv versions
# 切换某个python版本
pyenv local 3.8.2
# 切换到系统python版本
python local system
需前往frida · PyPI查看支持python版本(如frida 15.0.13不支持python3.7)
pip install frida-tools
安装特定版本
### Python3.8.2 ###
pyenv local
pip install frida==12.8.0
pip install frida-tools==5.3.0
pip install objection==1.8.4
前往Releases · frida/frida (github.com)下载对应的frida-server
文件,frida-server版本要和frida版本一致
push到设备目录,赋予执行权限
设置端口号为8888
./friandx86 -l 0.0.0.0:8888
用原版源
pip install objection
全局获得代码提示
npm install --save @types/frida-gum # -g到全局, --save到当前目录
git clone https://gitee.com/Forgo7ten/frida-agent-example.git
cd frida-agent-example/
npm install
在agent
目录下编写
/** test.js **/
function main() {
Java.perform(function x() {
console.log("Hello Frida")
})
}
setImmediate(main)
控制台执行
frida -H 192.168.0.106:8888 -f com.android.settings -l test.js --no-pause
-H:设置主机和端口号
-f:启动某个应用(packagename)
-l:执行的js文件
npm run watch
会监控代码修改自动编译生成js文件或者python脚本执行
# loader.py
import time
import frida
# 获取指定 UID/远程 设备
device = frida.get_device_manager().add_remote_device("192.168.0.103:8888")
# 寻找某apk包名
pid = device.spawn("com.android.settings")
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
with open("test.js") as f:
# 创建js脚本
script = session.create_script(f.read())
# 加载js脚本
script.load()
objection启动并注入内存
objection -N -h [hostname] -p [port] -d -g [packagename] explore
# -N 指定网络连接
# -h 指定主机名
# -p 指定端口
# -d 启用debug模式
# -g 打开的app包名
memory
# 查看内存中加载的库
memory list modules
# 查看库的导出函数
memory list exports [modulename]
# 结果保存到文件(以json形式)
memory list exports [modulename] --json [filename]
# 提取整个内存
memory dump all [filename]
# 提取部分内存
memory dump from_base [start_addr] [length] [file_name]
android
# 搜索类的实例
android heap search instances [class_name]
# 主动调用实例方法
android heap execute [instance_handle] [method_name]
# 在实例上执行js代码
android heap evaluate [instance_handle] {Enter}
## 输入js代码 ## (clazz代表当前类 )
启动activity或service
# 启动activity
android intent launch_activity [activity]
# 启动service
android intent launch_service [service]
hooking
# 查看内存中的activity
android hooking list activities
# 查看内存中的service
android hooking list services
# 查看内存中所有的类
android hooking list classes
# 列出某个类的所有方法
android hooking list class_methods [class]
# 搜索包含特定关键词的类
android hooking search classes [string]
# 内存中搜索包含特定关键词的方法
android hooking search methods [string]
# 生成该类所有方法的hook,每次调用都会被log
android hooking generate simple [class]
# 附带参数:额外打印参数,调用栈,返回值
android hooking watch class [class_method] --dump-args --dump-backtrace --dump-return
# hook某方法(同时会hook所有重载)
android hooking watch [class_method] [method_name]
objection jobs
# 查看jobs列表(当有hook任务时)
jobs list
# 删除任务 JobID通过list得到
jobs kill [JobID]
插件使用
plugin load [FileUrl]
objection spawn进程(hook某些一启动就执行的app)
objection -d -g [packageName] explore --startup-command 'android hooking watch class xxx'
objection -d -g [packageName] explore -c commands.txt
Frida对静态方法与实例方法没有区分,直接hook即可。
function main() {
Java.perform(function () {
// 先查找HookedObject类,然后hook其的stringTwo方法
Java.use("com.forgotten.fridatestapp.HookedObject").stringTwo.implementation = function (arg) {
// this为当前实例,获得原方法执行的结果
var result = this.stringTwo(arg);
// 打印参数和原方法结果
console.log("stringTwo arg,result: ", arg, result);
// 对方法的结果进行修改(相当于重写了该方法)
return Java.use("java.lang.String").$new("hhello");
};
});
}
// Frida一附加上,就执行函数
setImmediate(main);
构造方法为$init
,例如
function main2() {
Java.perform(function(){
// hook有重载的无参方法时,overload()填空
Java.use("com.forgotten.fridatestapp.HookedObject").$init.overload().implementation = function () {
// this为当前实例,获得原方法执行的结果
var result = this.$init();
// 可以打印参数和原方法结果
console.log("call $init");
// 返回原方法执行的结果
return result;
};
})
}
使用.overload()
来确定hook哪个重载方法
function main() {
Java.perform(function () {
// hook addNumber方法 function参数列表可以什么都不填
Java.use("com.forgotten.fridatestapp.HookedObject").addNumber.overload("int", "int").implementation = function () {
// 内置有变量[argument],为方法的参数列表
for (var i = 0; i < arguments.length; i++) {
console.log("addNumber arguments[" + i + "]=" + arguments[i]);
}
var result = this.addNumber(arguments[0], arguments[1]);
console.log("addNumber arg,result: ", arguments, result);
return 99;
};
});
}
// Frida一附加上,就执行函数
setImmediate(main);
function hookMethodAllOverloads(className, methodName) {
Java.perform(function () {
// hook 指定方法的所有重载
var clazz = Java.use(className);
// Object.toString同Object["toString"]
var overloadsLength = clazz[methodName].overloads.length;
for (var i = 0; i < overloadsLength; i++) {
clazz[methodName].overloads[i].implementation = function () {
// 主动调用原方法获得结果
var result = this[methodName].apply(this, arguments);
var paramStr = "";
// 遍历arguments
for (var j = 0; j < arguments.length; j++) {
if (j == arguments.length - 1) {
paramStr += arguments[j];
} else {
paramStr += arguments[j] + ",";
}
}
// 打印参数以及结果
console.log("Called", className + "." + methodName + "(" + paramStr + ") :", result);
// 调用原方法
return result;
};
}
console.log("[" + overloadsLength + "]", className + "." + methodName, "Hooked!");
});
}
function main() {
var className = "com.forgotten.fridatestapp.HookedObject";
var methodName = "addNumber";
hookAllOverloads(className, methodName);
}
内部类获取时className
使用$
拼接,如:
var InnerClazz = Java.use("com.forgotten.fridatestapp.HookedObject$innerClass");
匿名类也同样使用$
拼接,但其后的要使用smali或者objection内存搜索来查看
var clazz = Java.use("com.forgotten.fridatestapp.MainActivity$1");
function main() {
Java.perform(function () {
// 要查找的对象类名
var className = "com.forgotten.fridatestapp.MainActivity";
Java.choose(className, {
onMatch: function (instance) {
console.log("Found it!!", instance);
// something to do...
},
onComplete: function () {
console.log("Search complete!");
},
});
});
}
保存对象在外部方法使用
Java.retain(obj)
:复制一份obj
的java包装器以便于以后使用。
Java.perform(() => {
const Activity = Java.use("android.app.Activity");
let lastActivity = null;
Activity.onResume.implementation = function () {
lastActivity = Java.retain(this);
this.onResume();
};
});
主动调用方法
Java.use
获得类后直接调用Java.choose
查找到类实例后进行调用$init
.overload().apply()
手动选择选择要调用的重载方法静态/非静态变量
[field].value
[field_name].value = [value]
,其他方面和函数一样。_
,如_[field_name].value = [value]
function invoke() {
Java.perform(function () {
// 在内存中搜索类的实例
Java.choose("com.forgotten.fridatestapp.HookedObject", {
// 如果匹配上执行回调,参数为类实例
onMatch: function (instance) {
console.log("Found `HookedObject` instance:", instance);
// 打印私有成员变量的值,需要[field].value
console.log("instance.score =", instance.score.value);
// 打印静态成员变量的值
console.log("HookedObject.msg =", Java.use("com.forgotten.fridatestapp.HookedObject").msg.value);
console.log("instance.msg =", instance.msg.value);
// 修改成员变量的值
instance.score.value = Java.use("java.lang.Integer").parseInt("-900");
// 打印修改之后的值
console.log("instance.score =", instance.score.value);
// 与方法同名的成员变量,需前面加_
console.log("instance.stringTwo =", instance._stringTwo.value);
// 主动调用方法,根据传入的参数自动选择重载
console.log(instance.addNumber(1, 2));
console.log(instance.addNumber(4, 5, 6));
// 调用指定方法重载:apply参数一为调用的对象,参数二为参数列表
console.log(instance.addNumber.overload("int", "int").apply(instance,[8,9]));
},
// 搜索完成执行回调
onComplete: function () {
console.log("Found Completed");
},
});
});
}
配合hook指定方法的所有重载来hook所有的方法
使用Java.enumerateLoadedClasses()
API
function main(){
Java.perform(function(){
Java.enumerateLoadedClasses({
// name为加载的类名字符串
onMatch: function(name,handle){
// 可以通过包名限定需要处理的类名
if (name.indexOf("com.forgotten.fridatestapp") != -1){
console.log(name,handle);
// 利用反射 获取类中的所有方法
var TargetClass = Java.use(name);
// return Method Object List
var methodsList = TargetClass.class.getDeclaredMethods();
for (var i = 0; i < methodsList.length; i++){
// 打印其中方法的名字
console.log(methodsList[i].getName());
// 可以hook该类中的所有方法
hookMethodAllOverloads(name,methodsList[i].getName());
}
}
},
onComplete: function(){
console.log("enumerateLoadedClasses complete!!!")
}
})
})
}
使用Java.enumerateLoadedClassesSync()
API
function main() {
Java.perform(function () {
// return String[] class name
var classList = Java.enumerateLoadedClassesSync();
for (var i = 0; i < classList.length; i++) {
var targetClass = classList[i];
if (targetClass.indexOf("com.forgotten.fridatestapp") != -1) {
console.log("hook the class: ", targetClass);
var TargetClass = Java.use(targetClass);
// 利用反射获取类中的所有方法
var methodsList = TargetClass.class.getDeclaredMethods();
for (var j = 0; j < methodsList.length; j++) {
console.log(methodsList[j].getName());
hookMethodAllOverloads(targetClass,methodsList[j].getName());
}
}
}
});
}
function findClassLoader(className) {
Java.perform(function () {
// 枚举内存中的 类加载器
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
// 如果找到的类加载器 能加载的类有[class_name]
if (loader.findClass(className)) {
console.log("Successfully found loader");
console.log(loader);
// 设置 java默认的classloader
Java.classFactory.loader = loader;
}
} catch (error) {
console.log("find error:" + error);
}
},
onComplete: function () {
console.log("End");
},
});
// 再 使用该类
Java.use("[class_name]");
});
}
或使用Java.enumerateClassLoadersSync()
API
function findAllInterfaces(packageName) {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (class_name) {
// 对搜索范围进行限定
if (class_name.indexOf(packageName) < 0) {
return;
} else {
var hook_cls = Java.use(class_name);
var interfaces = hook_cls.class.getInterfaces();
if (interfaces.length > 0) {
console.log(class_name + ": ");
for (var i in interfaces) {
console.log("\t", interfaces[i].toString());
}
}
}
},
onComplete: function () {
console.log("end");
},
});
});
}
function main() {
var packageName = "com.forgotten.fridatestapp";
findAllInterfaces(packageName);
}
function findInterface(packageName, interfaceName) {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (class_name) {
// 对搜索范围进行限定
if (class_name.indexOf(packageName) < 0) {
return;
} else {
var hook_cls = Java.use(class_name);
var interfaces = hook_cls.class.getInterfaces();
if (interfaces.length > 0) {
for (var i in interfaces) {
if (interfaces[i].toString().indexOf(interfaceName) >= 0) {
console.log("Found:", class_name);
break;
}
}
}
}
},
onComplete: function () {
console.log("end");
},
});
});
}
function main() {
var packageName = "com.forgotten.fridatestapp";
var interfaceName = "android.view.View$OnClickListener";
findInterface(packageName, interfaceName);
}
function findAllSuperclasses(packageName) {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (class_name) {
// 对搜索范围进行限定
if (class_name.indexOf(packageName) < 0) {
return;
} else {
var hook_cls = Java.use(class_name);
var superClass = hook_cls.class.getSuperclass();
console.log(class_name,":")
while(superClass!=null){
console.log("\t",superClass.toString());
superClass = superClass.getSuperclass();
}
}
},
onComplete: function () {
console.log("end");
},
});
});
}
function main() {
var packageName = "com.forgotten.fridatestapp";
findAllSuperclasses(packageName);
}
Java.openClassFile("xxx.dex").load();
// 示例
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');
gson.$new().toJson( object );
对于hook时,找不到相关属性(Frida不清楚类型的),可以使用Cast类型强转来指定类型。
// 通过反射查看当前实例类型(所属类)
.getClass().getName().toString()
// 子类强制转换父类
Java.cast()
var WaterHandle = Java.cast(JuiceHandle,Java.use("com.r0ysue.a0526printout.Water"))
/** 父类不能强制转换子类 **/
/**
* 根据实例对象找到其类名
*/
function getObjClassName(obj) {
if (!jclazz) {
var jclazz = Java.use("java.lang.Class");
}
if (!jobj) {
var jobj = Java.use("java.lang.Object");
}
return jclazz.getName.call(jobj.getClass.call(obj));
}
// char[] [C
Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){}
// 同理byte[] 为[B
// 构造char[]数组
Java.array('char', [ '烟','村','四','五','家']);
// 构造String[]数组
Java.array('java.lang.String', [ '烟','村','四','五','家']);
function main() {
Java.perform(function () {
var string1 = Java.use("java.lang.String").$new("123");
var string2 = Java.use("java.lang.String").$new("");
// var strarr = Java.array("java.lang.String", [string1, string2]);
// 创建String数组,并添加值
var Ref_arr = Java.use("java.lang.reflect.Array");
var stringClass = Java.use("java.lang.String").class;
var arr = Ref_arr.newInstance(stringClass, 2);
Ref_arr.set(arr, 0, string1);
Ref_arr.set(arr, 1, string2);
var obj1 = Java.use("java.lang.String").$new("24717361");
var obj2 = Java.use("java.lang.Integer").$new(19);
var obj3 = Java.use("java.lang.String").$new("");
// 创建Object数组
var objarr = Java.array("java.lang.Object", [arr, obj1, obj2, obj3]);
console.log(JSON.stringify(objarr))
console.log(Java.use("java.util.Arrays").toString(objarr));
});
}
java.util.Arrays.toString()
打印JSON.stringify()
打印function main(){
Java.perform(function () {
// Hook Arrays.toString方法 重载char[]
Java.use("java.util.Arrays").toString.overload("[C").implementation = function () {
// 打印参数
console.log("arg = ", arguments[0]);
// 可正确打印方法1
// console.log("arg = ", this.toString(arguments[0]));
console.log("arg = ", Java.use("java.util.Arrays").toString(arguments[0]));
// 可正确打印方法2
console.log("arg = ", JSON.stringify(arguments[0]));
/* 手动构造一个Java array:参数一为类型,参数二为数组 */
var arg = Java.array("char", ["上", "山", "打", "老", "虎"]);
var result = this.toString(arg);
console.log("[NEW]arg,result = ", arg, result);
return result;
};
});
}
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log(ByteString.of(result).hex());
或者使用格式化打印十六进制数据
Java.perform(function () {
Java.choose("com.forgotten.fridatestapp.construct.ConstructoredObject", {
onMatch: function (instance) {
// 找到后获取实例field map的值,尝试转为自定义Enum Signal类型
var venum = Java.cast(instance.color.value, Java.use("com.forgotten.fridatestapp.construct.ConstructoredObject$Signal"));
console.log("venum:", venum);
// 调用Enum的方法
console.log("venum.name():", venum.name());
},
onComplete: function () {
console.log("venum: search completed");
},
});
});
function main() {
Java.perform(function () {
// 在内存中查找ConstructoredObject类的实例
Java.choose("com.forgotten.fridatestapp.construct.ConstructoredObject", {
onMatch: function (instance) {
// 找到后获取实例field map的值,尝试转为HashMap类型
var vmap = Java.cast(instance.map.value, Java.use("java.util.HashMap"));
console.log("vmap:", vmap);
// 1.
var key_iterator = vmap.keySet().iterator();
while (key_iterator.hasNext()) {
var key = key_iterator.next().toString();
var value = vmap.get(key).toString();
console.log(key + ": " + value);
}
// 2.
var entry_iterator = vmap.entrySet().iterator();
while (entry_iterator.hasNext()) {
var entry = Java.cast(entry_iterator.next(),Java.use("java.util.HashMap$Node"));
console.log("entry", entry);
console.log(entry.getKey(), entry.getValue());
}
// 3.
console.log("vmap.toString():", vmap.toString());
},
onComplete: function () {
console.log("vmap: search completed");
},
});
});
}
Java.perform(
function x() {
// 定义目标类
var targetClass = "com.example.hooktest.MainActivity";
var hookCls = Java.use(targetClass);
// 获得目标类的所有方法
var methods = hookCls.class.getDeclaredMethods();
// 遍历所有方法名
for (var i in methods) {
console.log(methods[i].toString());
console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));
}
// 如果有等于不可见字符类的
hookCls[decodeURIComponent("%D6%8F")]
.implementation = function (x) {
console.log("original call: fun(" + x + ")");
var result = this[decodeURIComponent("%D6%8F")](900);
return result;
}
}
)
新建线程(即实现类时实现Runnable接口)
function newThread() {
Java.perform(function () {
var Runnable = Java.use("java.lang.Runnable");
var Thread = Java.use("java.lang.Thread");
var MyRunnable = Java.registerClass({
name: "com.forgotten.thread",
implements: [Runnable],
fields: {
// 成员变量名,与类型
num: "int",
str: "java.lang.String"
},
methods: {
$init: [
{
returnType: "void",
argumentTypes: ["int","java.lang.String"],
implementation: function (num,str) {
this.num.value = num;
this.str.value = str;
},
},
],
run: function () {
var i = 0;
for (i = 0; i < 10; i++) {
console.log(this.num.value,this.str.value);
this.num.value = this.num.value + 1;
Thread.sleep(5);
}
},
},
});
var myRunnable = MyRunnable.$new(10,"hello");
var myThread = Thread.$new(myRunnable);
myThread.start();
});
}
function getContext(){
Java.perform(function(){
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
console.log(currentApplication);
var context = currentApplication.getApplicationContext();
console.log(context);
var packageName = context.getPackageName();
console.log(packageName);
console.log(currentApplication.getPackageName());
})
}
Java.perform(function() {
var Toast = Java.use('android.widget.Toast');
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();
Java.scheduleOnMainThread(function() {
Toast.makeText(context, "Hello World", Toast.LENGTH_LONG.value).show();
})
})
function printStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\\n");
console.log("=============================" + name + " Stack strat=======================");
console.log(replaceStr);
console.log("=============================" + name + " Stack end=======================\r\n");
Exception.$dispose();
}
});
}
// sample 1
var throwable = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
console.log(throwable);
// sample 2
var exception = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
console.log(exception);
/**
* 十六进制打印数组
* @param {*} array 数组
* @param {*} off 偏移
* @param {*} len 长度
*/
function jhexdump(array, off, len) {
off = off || 0;
len = len || 0;
var llen = len == 0 ? array.length : len;
var ptr = Memory.alloc(llen);
for (var i = 0; i < llen; ++i) Memory.writeS8(ptr.add(i), array[i]);
//console.log(hexdump(ptr, { offset: off, length: len, header: false, ansi: false }));
console.log(hexdump(ptr, { offset: off == 0 ? 0 : off, length: llen, header: false, ansi: false }));
}
Java.perform(function () {
var Activity = Java.use("android.app.Activity");
//console.log(Object.getOwnPropertyNames(Activity));
Activity.startActivity.overload('android.content.Intent').implementation=function(p1){
console.log("Hooking android.app.Activity.startActivity(p1) successfully,p1="+p1);
//console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(decodeURIComponent(p1.toUri(256)));
this.startActivity(p1);
}
Activity.startActivity.overload('android.content.Intent', 'android.os.Bundle').implementation=function(p1,p2){
console.log("Hooking android.app.Activity.startActivity(p1,p2) successfully,p1="+p1+",p2="+p2);
//console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(decodeURIComponent(p1.toUri(256)));
this.startActivity(p1,p2);
}
Activity.startService.overload('android.content.Intent').implementation=function(p1){
console.log("Hooking android.app.Activity.startService(p1) successfully,p1="+p1);
//console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(decodeURIComponent(p1.toUri(256)));
this.startService(p1);
}
})
Frida(rpc)开到公网:
通过vps安装NPS来将公网的流量转发到手机相应端口
利用flask
开启网址服务
利用python flask
库,将主动调用函数映射为网址服务,只要访问即可调用app中方法
rpc.exports导出名不可以有大写字母或者下划线
function invoke() {
Java.perform(function () {
// 搜索HookedObject类实例
Java.choose("com.forgotten.fridatestapp.HookedObject", {
onMatch: function (instance) {
console.log("found HookedObject:", instance);
// 查找到实例后主动调用方法
console.log("ho.getPasswd():", instance.getPasswd("123 ABC"));
},
onComplete: function () {
console.log("HookedObject: search complete.");
},
});
});
}
/* 测试函数 */
function test() {
console.log("I'm Frida_rpc.js!");
}
/* 导出函数列表(可供py调用的) py函数映射和实际函数名 */
rpc.exports = {
invokefunc: invoke,
testfunc: test,
};
import time
import frida
## handler | script脚本信息交互函数
def my_message_handler(message,payload):
print(message)
print(payload)
# 通过Usb连接设备
# device = frida.get_usb_device()
# 通过ip:port 连接设备
device = frida.get_device_manager().add_remote_device("192.168.0.104:8888")
################ 通过spawn方式启动 ###########################
pid = device.spawn(["com.forgotten.fridatestapp"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
##### <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# session = device.attach("com.forgotten.fridatestapp")
################ 通过attach现有进程方式启动 ###################
with open("./frida_rpc.js") as f:
# 创建一个新脚本
script = session.create_script(f.read())
# 加载信息交互handler函数
script.on("message",my_message_handler)
# 加载脚本
script.load()
command = ""
while True:
command = input("Enter Command(y/t/n): ")
if command=="y":
script.exports.invokefunc()
elif command=="t":
script.exports.testfunc()
elif command=="n":
break
Java.perform(function () {
Java.use("com.forgotten.fridatestapp.HookedObject").getPasswd.implementation = function () {
// 需要发送给python的字符串:由函数的参数和结果拼接而成
var string_to_send = arguments[0] + ":" + this.getPasswd(arguments[0]);
var string_to_recv;
// 发送到python程序
send(string_to_send);
// 同时调用.wait()来 阻塞运行,等待接收消息
recv(function (received_json_objection) {
// 接收来的json字符串
console.log("recv in js:",JSON.stringify(received_json_objection))
// 打印json的`my_data`,json串来自python
string_to_recv = received_json_objection.my_data;
console.log("string_to_recv:", string_to_recv);
}).wait();
// 将接收到的字符串当作被hook函数的结果返回回去
var result = Java.use("java.lang.String").$new(string_to_recv);
return result;
};
});
import time
import frida
## handler | script脚本信息交互函数
def my_message_handler(message, payload):
print(message) # 打印得到的信息
print(payload) # 输出的为`none`?
# 如果`type`字段为"send" 则是js发来的消息
if message["type"] == "send":
# 打印json的`payload`内容(js发送过来的内容)
print(message["payload"])
# 向script发送消息,格式为字典
script.post({"my_data": "Hello"})
# 通过Usb连接设备
# device = frida.get_usb_device()
# 通过ip:port 连接设备
device = frida.get_device_manager().add_remote_device("192.168.0.104:8888")
################ 通过spawn方式启动 ###########################
pid = device.spawn(["com.forgotten.fridatestapp"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
##### <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# session = device.attach("com.forgotten.fridatestapp")
################ 通过attach现有进程方式启动 ###################
with open("./frida_rpc.js") as f:
# 创建一个新脚本
script = session.create_script(f.read())
# 加载信息交互handler函数
script.on("message", my_message_handler)
# 加载脚本
script.load()
command = ""
while True:
command = input("Enter `n` for leave: ")
if command == "n":
break
frida-python/examples at main · frida/frida-python (github.com)
Frida实现的Env:frida-java-bridge/env.js at main · frida/frida-java-bridge (github.com)
判断Thumb OR arm模式:
push
指令为thumb指令特有function main0() {
/* hook 可导出的native函数 */
Java.perform(function () {
// 寻找模块so的地址
var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so");
console.log("native_lib_addr -> ", lib_fridatestapp_addr);
// 寻找导出函数的地址
var staticString_addr = Module.findExportByName("libfridatestapp.so", "Java_com_forgotten_fridatestapp_MainActivity_staticString");
console.log("staticString() addr -> ", staticString_addr);
// 对函数进行attach
Interceptor.attach(staticString_addr, {
// 函数进入时,参数为函数的参数
onEnter: function (args) {
/* 打印native函数调用栈,有Backtracer.ACCURATE和Backtracer.FUZZY两种模式切换 */
console.log("CCCryptorCreate called from:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n") + "\n");
// 打印三个参数地址
console.log("Interceptor.attach staticString() args:", args[0], args[1], args[2]);
// 将 参数三传进去的jstring字符串,转换为char*再用readCString()得到JavaScript字符串来输出
console.log("jstring is", Java.vm.getEnv().getStringUtfChars(args[2], null).readCString());
// 可以对参数进行修改
var new_arg2 = Java.vm.getEnv().newStringUtf("new arg2 from Frida");
args[2] = new_arg2;
},
// 函数执行完的时候,参数为函数的返回值
onLeave: function (reval) {
console.log("Interceptor.attach staticString() retval", reval);
console.log("Interceptor.attach staticString() retval", Java.vm.getEnv().getStringUtfChars(reval, null).readCString());
// 对函数的返回值进行替换
var new_reval = Java.vm.getEnv().newStringUtf("HaHa Frida!!!");
reval.replace(new_reval);
},
});
});
}
注册native函数:new NativeFunction(address, returnType, argTypes[, abi])
function main1() {
/* 主动调用 可导出的native函数 */
Java.perform(function invoke_justAdd_func() {
// 寻找模块so的地址
var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so");
console.log("native_lib_addr -> ", lib_fridatestapp_addr);
// 寻找导出函数的地址
var justAdd_addr = Module.findExportByName("libfridatestapp.so", "_Z7justAddii");
console.log("justAdd() addr -> ", justAdd_addr);
// 新建一个Native函数,参数分别为 已存在函数地址,函数返回值类型,函数参数列表
var justAdd_func = new NativeFunction(justAdd_addr, "int", ["int", "int"]);
// 执行函数,获得函数返回值
var justAdd_result = justAdd_func(10, 2);
console.log("invoke justAdd(10,2) result-> ", justAdd_result);
});
Java.perform(function invoke_nativeString_func() {
/* 大部分代码同 hook函数中的 */
// 寻找模块so的地址
var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so");
console.log("native_lib_addr -> ", lib_fridatestapp_addr);
// 寻找导出函数的地址
var staticString_addr = Module.findExportByName("libfridatestapp.so", "Java_com_forgotten_fridatestapp_MainActivity_staticString");
console.log("staticString() addr -> ", staticString_addr);
/* 声明该native函数,返回值和参数env、jobject等都是"pointer" */
var nativeString_func = new NativeFunction(staticString_addr, "pointer", ["pointer", "pointer", "pointer"]);
// 对函数进行attach
Interceptor.attach(staticString_addr, {
// 函数进入时,参数为函数的参数
onEnter: function (args) {
// 打印三个参数地址
console.log("Interceptor.attach staticString() args:", args[0], args[1], args[2]);
// 将 参数三传进去的jstring字符串,转换为char*再用readCString()得到JavaScript字符串来输出
console.log("jstring is", Java.vm.getEnv().getStringUtfChars(args[2], null).readCString());
/* 主动调用方法,打印函数结果 */
console.log("==> invoke stringfunc(): ", Java.vm.getEnv().getStringUtfChars(nativeString_func(args[0], args[1], args[2]), null).readCString());
// 可以对参数进行修改
var new_arg2 = Java.vm.getEnv().newStringUtf("new arg2 from Frida");
args[2] = new_arg2;
},
// 函数执行完的时候,参数为函数的返回值
onLeave: function (reval) {
console.log("Interceptor.attach staticString() retval", reval);
console.log("Interceptor.attach staticString() retval", Java.vm.getEnv().getStringUtfChars(reval, null).readCString());
// 对函数的返回值进行替换
var new_reval = Java.vm.getEnv().newStringUtf("HaHa Frida!!!");
reval.replace(new_reval);
},
});
});
}
function main2() {
/* 替换 justAdd函数 */
Java.perform(function replace_func() {
// 寻找模块so的地址
var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so");
console.log("native_lib_addr -> ", lib_fridatestapp_addr);
// 寻找导出函数的地址
var justAdd_addr = Module.findExportByName("libfridatestapp.so", "_Z7justAddii");
console.log("justAdd() addr -> ", justAdd_addr);
// 对原native函数进行替换,参数1为替换的地址,参数2为一个NativeCallback
Interceptor.replace(
justAdd_addr,
new NativeCallback(
// 参数分别为,替换执行的函数,返回值类型,参数类型列表
function (a, b) {
console.log("justAdd args: ", a, b);
var result = a * (b + 5);
console.log("new Func Result: ", result);
return result;
},
"int",
["int", "int"]
)
);
});
}
通过ida找到函数偏移
function main3() {
/* 靠地址偏移hook未导出函数 */
Java.perform(function () {
// 寻找模块so的地址
var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so");
console.log("native_lib_addr -> ", lib_fridatestapp_addr);
// 通过函数偏移+模块的地址,得到函数的地址
var dynamicString_addr = lib_fridatestapp_addr.add(0xa48);
console.log("dynamicString() addr -> ", dynamicString_addr);
// 对函数进行attach
Interceptor.attach(dynamicString_addr, {
// 函数进入时,参数为函数的参数
onEnter: function (args) {
/* 打印native函数调用栈,有Backtracer.ACCURATE和Backtracer.FUZZY两种模式切换 */
// console.log("CCCryptorCreate called from:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n") + "\n");
// 打印三个参数地址
console.log("Interceptor.attach dynamicString() args:", args[0], args[1], args[2]);
// 将 参数三传进去的jstring字符串,转换为char*再用readCString()得到JavaScript字符串来输出
// console.log("jstring is", Java.vm.getEnv().getStringUtfChars(args[2], null).readCString());
// 可以对参数进行修改
var new_arg2 = Java.vm.getEnv().newStringUtf("new arg2 from Frida");
args[2] = new_arg2;
},
// 函数执行完的时候,参数为函数的返回值
onLeave: function (reval) {
console.log("Interceptor.attach dynamicString() retval", reval);
console.log("Interceptor.attach dynamicString() retval", Java.vm.getEnv().getStringUtfChars(reval, null).readCString());
// 对函数的返回值进行替换
var new_reval = Java.vm.getEnv().newStringUtf("HaHa Frida!!!");
// reval.replace(new_reval);
},
});
});
}
/* 枚举出所有模块的所有导出符号 */
function EnumerateAllExports() {
var modules = Process.enumerateModules();
//print all modules
//console.log("Process.enumerateModules->",JSON.stringify(modules));
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var module_name = modules[i].name;
var exports = module.enumerateExports();
console.log("module.enumerateeExports", JSON.stringify(exports));
}
}
function look_module(module_name){
// 根据模块名称寻找地址;根据地址找到模块返回Module对象
var native_lib_addr = Process.findModuleByAddress(Module.findBaseAddress(module_name));
console.log("native_lib_addr => ",JSON.stringify(native_lib_addr));
// 遍历模块的所有Symbols
console.log("enumerateImports=>",JSON.stringify(native_lib_addr.enumerateSymbols()));
}
look_module("linker64");
/* hook jni函数GetStringUTFChars */
function hook_getStringUTFChars_func() {
var GetStringUTFChars_addr = null;
// 该函数在这个so里面,遍历里面的所有符号
var symbools = Process.findModuleByName("libart.so").enumerateSymbols();
//console.log(JSON.stringify(symbool));
for (var i = 0; i < symbools.length; i++) {
// 取到符号的name
var symbol = symbools[i].name;
// 过滤一下,因为还有一个checkjni类中有该函数
if (symbol.indexOf("CheckJNI") == -1 && symbol.indexOf("JNI") >= 0) {
if (symbol.indexOf("GetStringUTFChars") >= 0) {
console.log("finally found GetStringUTFChars name:", symbol);
// 保存该函数的地址
GetStringUTFChars_addr = symbools[i].address;
console.log("finally found GetStringUTFChars address :", GetStringUTFChars_addr);
}
}
}
/* 开始附加该函数 */
Interceptor.attach(GetStringUTFChars_addr, {
onEnter: function (args) {
console.log("art::JNI::GetStringUTFChars(_JNIEnv*,_jstring*,unsigned char*)->", args[0], Java.vm.getEnv().getStringUtfChars(args[1], null).readCString(), args[2]);
// 打印栈回溯
// console.log("CCCryptoCreate called from:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n") + "\n");
},
onLeave: function (retval) {
// 打印返回值,为c字符串
console.log("retval is->", retval.readCString());
},
});
}
/* 对NewStringUTF函数进行replace操作 */
function replace_NewStringUTF_func() {
/* 同上 */
var NewStringUTF_addr = null;
// 该函数在这个so里面,遍历里面的所有符号
var symbools = Process.findModuleByName("libart.so").enumerateSymbols();
//console.log(JSON.stringify(symbool));
for (var i = 0; i < symbools.length; i++) {
// 取到符号的name
var symbol = symbools[i].name;
// 过滤一下,因为还有一个checkjni类中有该函数
if (symbol.indexOf("CheckJNI") == -1 && symbol.indexOf("JNI") >= 0) {
if (symbol.indexOf("NewStringUTF") >= 0) {
console.log("finally found NewStringUTF_name:", symbol);
// 保存该函数的地址
NewStringUTF_addr = symbools[i].address;
console.log("finally found NewStringUTF_address :", NewStringUTF_addr);
}
}
}
// new一个NewStringUTF的NativeFunction
/* static jstring NewStringUTF(JNIEnv* env, const char* utf) */
var NewStringUTF = new NativeFunction(NewStringUTF_addr, "pointer", ["pointer", "pointer"]);
// 然后执行替换
Interceptor.replace(
NewStringUTF_addr,
new NativeCallback(
function (arg1, arg2) {
// 打印原本的参数
console.log("NewStringUTF arg1,arg2->", arg1, arg2.readCString());
// new一个char*字符串
var newARG2 = Memory.allocUtf8String("newPARG2");
/* 将参数替换,然后执行原函数并返回结果
var result=NewStringUTF(arg1,newARG2); // 不能随意修改,会导致崩溃*/
var result = NewStringUTF(arg1, arg2);
return result;
},
"pointer",
["pointer", "pointer"]
)
);
}
/**
* 写文件
* @param {*} path 写文件的路径
* @param {*} contents 写文件的内容
*/
function writeSomething(path, contents) {
var fopen_addr = Module.findExportByName("libc.so", "fopen");
var fputs_addr = Module.findExportByName("libc.so", "fputs");
var fclose_addr = Module.findExportByName("libc.so", "fclose");
//console.log("fopen=>",fopen_addr," fputs=>",fputs_addr," fclose=>",fclose_addr);
var fopen = new NativeFunction(fopen_addr, "pointer", ["pointer", "pointer"]);
var fputs = new NativeFunction(fputs_addr, "int", ["pointer", "pointer"]);
var fclose = new NativeFunction(fclose_addr, "int", ["pointer"]);
//console.log(path,contents)
var fileName = Memory.allocUtf8String(path);
var mode = Memory.allocUtf8String("a+");
var fp = fopen(fileName, mode);
var contentHello = Memory.allocUtf8String(contents);
var ret = fputs(contentHello, fp);
fclose(fp);
}
/**
* 遍历导出表与符号表 Example
*/
/**
* 写文件
* @param {*} path 写文件的路径
* @param {*} contents 写文件的内容
*/
function writeSomething(path, contents) {
var fopen_addr = Module.findExportByName("libc.so", "fopen");
var fputs_addr = Module.findExportByName("libc.so", "fputs");
var fclose_addr = Module.findExportByName("libc.so", "fclose");
//console.log("fopen=>",fopen_addr," fputs=>",fputs_addr," fclose=>",fclose_addr);
var fopen = new NativeFunction(fopen_addr, "pointer", ["pointer", "pointer"]);
var fputs = new NativeFunction(fputs_addr, "int", ["pointer", "pointer"]);
var fclose = new NativeFunction(fclose_addr, "int", ["pointer"]);
//console.log(path,contents)
var fileName = Memory.allocUtf8String(path);
var mode = Memory.allocUtf8String("a+");
var fp = fopen(fileName, mode);
var contentHello = Memory.allocUtf8String(contents);
var ret = fputs(contentHello, fp);
fclose(fp);
}
/** 对指定函数进行attachHOOK **/
function attach(name, address) {
console.log("attaching ", name);
Interceptor.attach(address, {
onEnter: function (args) {
console.log("Entering => ", name);
},
onLeave: function (retval) {
//console.log("retval is => ",retval)
},
});
}
var app_packagename = "com.forgotten.learntest";
/* 遍历moudles的exports */
function traceNativeExport() {
var modules = Process.enumerateModules();
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
if (module.name.indexOf("libssl.so") < 0) {
continue;
}
var path = "/data/data/" + app_packagename + "/cache/" + module.name + "_exports.txt";
var exports = module.enumerateExports();
for (var j = 0; j < exports.length; j++) {
console.log("module name is =>", module.name, " symbol name is =>", exports[j].name);
writeSomething(path, "type: " + exports[j].type + " function name :" + exports[j].name + " address : " + exports[j].address + " offset => 0x" + exports[j].address.sub(modules[i].base) + "\n");
if (exports[j].name.indexOf("SSL_write") >= 0) {
attach(exports[j].name, exports[j].address);
}
}
}
}
/* 遍历moudles的symbols */
function traceNativeSymbol() {
var modules = Process.enumerateModules();
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
// console.log(JSON.stringify(module));
/*可以对指定module进行过滤*/
if (module.name.indexOf("linker64") < 0) {
continue;
}
var path = "/data/data/" + app_packagename + "/cache/" + module.name + "_symbols.txt";
var exports = module.enumerateSymbols();
// console.log(JSON.stringify(exports))
for (var j = 0; j < exports.length; j++) {
if (exports[j] == null) {
continue;
}
console.log("module name is =>", module.name, " symbol name is =>", exports[j].name);
writeSomething(path, "type: " + exports[j].type + " function name :" + exports[j].name + " address : " + exports[j].address + " offset => 0x" + exports[j].address.sub(modules[i].base) + "\n");
}
}
}
function main() {
console.log("Entering main");
traceNativeExport();
traceNativeSymbol();
}
setImmediate(main);
function readStdString(str) {
var isTiny = (str.readU8 & 1) === 0;
if (isTiny) {
return str.add(1).readUtf8String();
}
return str
.add(2 * Process.pointerSize)
.readPointer()
.readUtf8String();
}
function writeStdString(str, content) {
var isTiny = (str.readU8() & 1) === 0;
if (isTiny) {
str.add(1).writeUtf8String(content);
} else {
str.add(2 * Process.pointerSize)
.readPointer()
.writeUtf8String(content);
}
}
hanbinglengyue/FART: ART环境下自动化脱壳方案 (github.com)
lasting-yang/frida_dump: frida dump dex, frida dump so (github.com)
r0ysue/r0capture: 安卓应用层抓包通杀脚本 (github.com)
siyujie/OkHttpLogger-Frida: Frida 实现拦截okhttp的脚本 (github.com)
BigFaceCat2017/frida_ssl_logger: ssl_logger based on frida (github.com)
bxl0608/okhttp-sslunpinning (github.com)
lasting-yang/frida_hook_libart: Frida hook some jni functions (github.com)
frida-trace | Frida • A world-class dynamic instrumentation framework
hluwa/Wallbreaker: Break Java Reverse Engineering form Memory World! (github.com)