[TOC]

Frida all in one

努力ALL IN ONE,部分代码可配合AndroidReversePractice/FridaTestApp at main · Forgo7ten/AndroidReversePractice (github.com)试验。

JavaScript API | Frida • A world-class dynamic instrumentation framework

Frida环境与基础使用

多python frida版本切换

pyenv安装

1
2
curl -L https://gitee.com/forgo7ten-mirrors/pyenv-installer/raw/master/bin/pyenv-installer | bash
cd ~/.pyenv && src/configure && make -C src

.bashrc添加

1
2
3
4
5
6
7
8
9
# 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

pyenv使用

1
2
3
4
5
6
7
8
9
10
11
# 下载python版本3.8.2
pyenv install 3.8.2

# 查看pyenv版本
pyenv versions

# 切换某个python版本
pyenv local 3.8.2

# 切换到系统python版本
python local system

Frida环境安装

需前往frida · PyPI查看支持python版本(如frida 15.0.13不支持python3.7)

1
pip install frida-tools

安装特定版本(使用pip默认源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 使用虚拟环境
python 3.8.5 frida_12_8_0
pyenv local frida_12_8_0
pip install wheel
pip install frida==12.8.0
pip install frida-tools==5.3.0
pip install objection==1.8.4

python 3.8.6 frida_14_2_18
pyenv local frida_14_2_18
pip install frida==14.2.18
pip install frida-tools==9.2.4
pip install objection==1.11.0

python 3.8.8 frida_15_1_28
pyenv local frida_15_1_28
pip install frida==15.1.28
pip install frida-tools==10.8.0
pip install objection==1.11.0

Release Frida 12.8.0 · frida/frida (github.com)

手机环境配置

前往Releases · frida/frida (github.com)下载对应的frida-server文件,frida-server版本要和frida版本一致

push到设备目录,赋予执行权限

监听

设置端口号为8888

1
./frida -l 0.0.0.0:8888

Objection环境安装

1
pip install objection

Frida开发环境搭建

全局获得代码提示(在用户根目录使用)

1
2
npm install --save @types/frida-gum
# --save到当前目录

或者ts编译

1
2
3
git clone https://gitee.com/Forgo7ten/frida-agent-example.git
cd frida-agent-example/
npm install

agent目录下编写

1
2
3
4
5
6
7
/** test.js **/
function main() {
Java.perform(function x() {
console.log("Hello Frida")
})
}
setImmediate(main)

控制台执行

1
2
3
4
5
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脚本执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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基本使用

objection启动并注入内存

1
2
3
4
5
6
objection -N -h [hostname] -p [port] -d -g [packagename] explore
# -N 指定网络连接
# -h 指定主机名
# -p 指定端口
# -d 启用debug模式
# -g 打开的app包名

memory

1
2
3
4
5
6
7
8
# 查看内存中加载的库
memory list modules

# 查看库的导出函数
memory list exports [modulename]

# 结果保存到文件(以json形式)
memory list exports [modulename] --json [filename]
1
2
3
4
5
# 提取整个内存
memory dump all [filename]

# 提取部分内存
memory dump from_base [start_addr] [length] [file_name]

android

1
2
# 搜索类的实例
android heap search instances [class_name]
1
2
3
4
5
6
# 主动调用实例方法
android heap execute [instance_handle] [method_name]

# 在实例上执行js代码
android heap evaluate [instance_handle] {Enter}
## 输入js代码 ## (clazz代表当前类 )

启动activity或service

1
2
# 启动activity
android intent launch_activity [activity]
1
2
# 启动service
android intent launch_service [service]

hooking

1
2
3
4
5
6
7
8
9
10
11
# 查看内存中的activity
android hooking list activities

# 查看内存中的service
android hooking list services

# 查看内存中所有的类
android hooking list classes

# 列出某个类的所有方法
android hooking list class_methods [class]
1
2
3
4
5
# 搜索包含特定关键词的类
android hooking search classes [string]

# 内存中搜索包含特定关键词的方法
android hooking search methods [string]
1
2
3
4
5
6
7
8
# 生成该类所有方法的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

1
2
3
4
5
# 查看jobs列表(当有hook任务时)
jobs list

# 删除任务 JobID通过list得到
jobs kill [JobID]

objection插件

插件使用

1
plugin load [FileUrl]

Wallbreaker

FRIDA-DEXDump

objection高级使用

objection spawn进程(hook某些一启动就执行的app)

1
objection -d -g [packageName] explore --startup-command 'android hooking watch class xxx'
1
objection -d -g [packageName] explore -c commands.txt

Frida Java层应用

hook方法

hook静态方法与实例方法

Frida对静态方法与实例方法没有区分,直接hook即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);

hook构造方法

构造方法为$init,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
};
})
}

对方法重载的hook

使用.overload()来确定hook哪个重载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);

hook指定方法的所有重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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";
hookMethodAllOverloads(className, methodName);
}

hook内部类与匿名类中的方法

内部类获取时className使用$拼接,如:

1
var InnerClazz = Java.use("com.forgotten.fridatestapp.HookedObject$innerClass");

匿名类也同样使用$拼接,但其后的要使用smali或者objection内存搜索来查看

1
var clazz = Java.use("com.forgotten.fridatestapp.MainActivity$1");

不可见函数名hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
}
)

内存搜索

查找实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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包装器以便于以后使用。

1
2
3
4
5
6
7
8
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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指定方法的所有重载来hook所有的方法

使用Java.enumerateLoadedClasses()API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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());
}
}
}
});
}

hook动态加载dex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

内存搜索接口

枚举类的所有接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function findAllInterfaces(packageName){
Java.perform(function(){
Java.enumerateLoadedClasses({
onMatch:function(class_name){
//
if(class_name.indexOf("fridatestapp.DynamicDex")>=0){
return;
}
//
if(class_name.indexOf(packageName)<0){
return;
}else{
var clazz = Java.use(class_name);
var interfaces = clazz.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("findAllInterfaces end");
}
})
})
}

function main() {
var packageName = "com.forgotten.fridatestapp";
findAllInterfaces(packageName);
}

寻找接口的实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function findImpByInterface(packageName,interfaceName){
Java.perform(function(){
Java.enumerateLoadedClasses({
onMatch:function(class_name){
//
if(class_name.indexOf("fridatestapp.DynamicDex")>=0){
return;
}
//
if(class_name.indexOf(packageName)<0){
return;
}else{
var clazz = Java.use(class_name);
var interfaces = clazz.class.getInterfaces();
if(interfaces.length>0){
for(var i in interfaces){
if(interfaces[i].toString().indexOf(interfaceName)>=0){
console.log(class_name,":",interfaces[i].toString())
}
}
}
}
},
onComplete:function(){
console.log("findImpByInterface end");
}
})
})
}

function main() {
var packageName = "com.forgotten.fridatestapp";
var interfaceName = "android.view.View$OnClickListener";
findInterface(packageName, interfaceName);
}

定位抽象类的实现类

打印所有类的父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function findAllSuperclasses(packageName){
Java.perform(function(){
Java.enumerateLoadedClasses({
onMatch:function(class_name){
//
if(class_name.indexOf("fridatestapp.DynamicDex")>=0){
return;
}
//
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("findAllSuperclasses end");
}
})
})
}

寻找继承某类的子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function findChildBySuper(packageName,superClassName) {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (class_name) {
if(class_name.indexOf("DynamicDex")>=0){
return;
}
// 对搜索范围进行限定
if (class_name.indexOf(packageName) < 0) {
return;
} else {
var hook_cls = Java.use(class_name);
var superClass = hook_cls.class.getSuperclass();
while(superClass!=null){
if(superClass.toString().indexOf(superClassName)>=0){
console.log("Found:",class_name,superClass);
}
superClass = superClass.getSuperclass();
}
}
},
onComplete: function () {
console.log("findChildBySuper end");
},
});
});
}

打印参数

加载dex使用gson打印

1
2
3
4
5
6
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类型强转来指定类型。

1
2
3
4
5
6
7
8
// 通过反射查看当前实例类型(所属类)
.getClass().getName().toString()

// 子类强制转换父类
Java.cast()
var WaterHandle = Java.cast(JuiceHandle,Java.use("com.r0ysue.a0526printout.Water"))

/** 父类不能强制转换子类 **/
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 根据实例对象找到其类名
*/
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));
}

Array数组

hook array数组
1
2
3
4
// char[] [C
Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){}

// 同理byte[] 为[B
打印 array数组
  1. 使用gson打印
  2. 使用java.util.Arrays.toString()打印
  3. 使用JSON.stringify()打印
  4. 遍历
    1. 如果Frida直接打印可以显示为数组,可通过.length属性进行for循环遍历
    2. 复杂类型的,如Frida直接打印显示为[[C[B的,可以使用Java的Array类。Array.getLength()获得长度、Array.get()获得对应元素,Array.getChar()获得char元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
};
});
}
byte[]打印
1
2
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log(ByteString.of(result).hex());

或者使用格式化打印十六进制数据

枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
console.log("venum.ordinal():", venum.ordinal());
},
onComplete: function () {
console.log("venum: search completed");
},
});
});

HashMap类型

使用iterator迭代打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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");
},
});
});
}

构造参数

构造Object[]

1
result = Java.array("Ljava.lang.Object;",[Java.use("java.lang.Integer").$new(9),Java.use("java.lang.String").$new("nihao")])
构造 array数组
1
2
3
4
5
// 构造char[]数组
Java.array('char', [ '烟','村','四','五','家']);

// 构造String[]数组
Java.array('java.lang.String', [ '烟','村','四','五','家']);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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));
});
}

Others

Frida构造多线程(新建类)

新建线程(即实现类时实现Runnable接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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();
});
}

获取context

1
2
3
4
5
6
7
8
9
10
11
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());
})
}

强制主线程运行

1
2
3
4
5
6
7
8
9
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();
})
})

java层代码模板复用

打印调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}
});
}
1
2
3
4
5
6
7
8
function printStack(name) {
Java.perform(function () {
var throwable = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
console.log("=============================" + name + " Stack strat=======================");
console.log(throwable);
console.log("=============================" + name + " Stack end=======================\r\n");
});
}
1
2
3
// sample 2
var exception = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
console.log(exception);

格式化打印十六进制数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 十六进制打印数组
* @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 }));
}

事件

hook Toast提示 定位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hook_toast() {
function printStack(name) {
Java.perform(function () {
var throwable = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
console.log("=============================" + name + " Stack strat=======================");
console.log(throwable);
console.log("=============================" + name + " Stack end=======================\r\n");
});
}
Java.perform(function () {
var Toast = Java.use("android.widget.Toast");
Toast.show.implementation = function () {
printStack("SHOW Toast");
return this.show();
};
});
}

setImmediate(hook_toast);

hook findViewById 定位组件

hook StartActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}
})

hook onClick事件 定位

hook Keystore:客户端证书校验

Frida RPC(Remote procedure call)

  • Frida(rpc)开到公网:

    通过vps安装NPS来将公网的流量转发到手机相应端口

  • 利用flask开启网址服务

    利用python flask库,将主动调用函数映射为网址服务,只要访问即可调用app中方法

RPC主动调用

rpc.exports导出名不可以有大写字母或者下划线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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,
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

rpc动态修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
};
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

More

frida-python/examples at main · frida/frida-python (github.com)

Frida native层应用

Frida实现的Env:frida-java-bridge/env.js at main · frida/frida-java-bridge (github.com)

判断Thumb OR arm

判断Thumb OR arm模式:

  • IDA查看指令机器码长度,都为4字节为arm指令
  • push指令为thumb指令特有

hook用户函数

attach静态注册(或导出)函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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);
},
});
});
}

replace静态注册(或导出)函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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找到函数偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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);
},
});
});
}

模块相关操作

枚举出所有模块的所有导出符号

1
2
3
4
5
6
7
8
9
10
11
12
/* 枚举出所有模块的所有导出符号 */
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));
}
}

遍历某模块符号/导出/导入

1
2
3
4
5
6
7
8
9
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");

JNI框架层的利用

JNI框架hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 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());
},
});
}

JNI框架replace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* 对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"]
)
);
}

native层代码模板复用

Frida写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 写文件
* @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);
}

遍历module的导出表与符号表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* 遍历导出表与符号表 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);

读写 std::string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
}

Frida打印String[]

1
2
3
4
5
6
7
8
9
10
11
const len = Java.vm.getEnv().getArrayLength(jstringArr);
console.log("len", len);
for(var i=0;i<len;i++){
var obj = Java.vm.getEnv().getObjectArrayElement (jstringArr,i);
// console.log(obj);
// 方式一:
// var element = Java.cast(obj, Java.use("java.lang.String"));
// 方式二:
var element = Java.vm.getEnv().getStringUtfChars(obj, null).readCString();
console.log("第"+i+"个:"+ element)
}

相关脚本使用

Unpackers

FRIDA-DEXDump

hluwa/frida-dexdump: A frida tool to dump dex in memory to support security engineers analyzing malware. (github.com)

frida_fart

hanbinglengyue/FART: ART环境下自动化脱壳方案 (github.com)

frida_dump

lasting-yang/frida_dump: frida dump dex, frida dump so (github.com)

NetWork

r0capture

r0ysue/r0capture: 安卓应用层抓包通杀脚本 (github.com)

OkHttpLogger-Frida

siyujie/OkHttpLogger-Frida: Frida 实现拦截okhttp的脚本 (github.com)

frida_ssl_logger

BigFaceCat2017/frida_ssl_logger: ssl_logger based on frida (github.com)

okhttp-sslunpinning

bxl0608/okhttp-sslunpinning (github.com)

Trace

jnitrace

chame1eon/jnitrace: A Frida based tool that traces usage of the JNI API in Android apps. (github.com)

frida_hook_libart

lasting-yang/frida_hook_libart: Frida hook some jni functions (github.com)

frida-trace

frida-trace | Frida • A world-class dynamic instrumentation framework

Memory

Wallbreaker

hluwa/Wallbreaker: 🔨 Break Java Reverse Engineering form Memory World! (github.com)

Or