Frida是一款基于python + javascript 的hook框架,由于是基于脚本的交互,因此相比xposed和substrace cydia更加便捷。

Frida的官网为:http://www.frida.re/

pip换源

pip国内的一些镜像

  阿里云 http://mirrors.aliyun.com/pypi/simple/
  中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/
  豆瓣(douban) http://pypi.douban.com/simple/
  清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/
  中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/

修改源方法:

临时使用:
可以在使用pip的时候在后面加上-i参数,指定pip源
pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple

安装

安装frida

首先要保证你的android手机已经root。通过pip安装frida:

pip install frida

下载frida-server:

frida_server的下载地址:https://github.com/frida/frida/releases

到android手机上并且运行

adb push frida-server /data/local/tmp/
adb shell
su
cd /data/local/tmp/
chmod 777 frida-server
./frida-server

转发android TCP端口到本地:

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

测试frida环境,如果出现android手机的进程列表说明搭建成功:

frida-ps -U
frida-ps -R
PID Name


安装objection

1
2
pip install objection
objection -h

如果报错Running setup.py install for objection … error

更新工具即可

1
2
pip install --upgrade pip
pip install --upgrade setuptools

Frida基础

frida

下面是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
Usage: frida [options] target

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--debug enable the Node.js compatible script debugger
--enable-jit enable JIT
-l SCRIPT, --load=SCRIPT
load SCRIPT
-c CODESHARE_URI, --codeshare=CODESHARE_URI
load CODESHARE_URI
-e CODE, --eval=CODE evaluate CODE
-q quiet mode (no prompt) and quit after -l and -e
--no-pause automatically start main thread after startup
-o LOGFILE, --output=LOGFILE
output to log file
1
2
3
frida-ps -Ua	#查看正在运行的

frida-ps -Uai #查看所有

脚本注入

1
frida -U -l myhook.js com.xxx.xxxx

参数解释:

  • -U 指定对USB设备操作
  • -l 指定加载一个Javascript脚本
  • 最后指定一个进程名,如果想指定进程pid,用-p选项。正在运行的进程可以用frida-ps -U命令查看

重启进程并注入

1
frida -U -l myhook.js -f com.xxx.xxxx --no-pause

参数解释:

  • -f 指定一个进程,重启它并注入脚本
  • –no-pause 自动运行程序

这种注入脚本的方法,常用于hook在App就启动期就执行的函数。

frida运行过程中,执行%resume重新注入,执行%reload来重新加载脚本;执行exit结束脚本注入

Hook Java

载入类

Java.use方法用于加载一个Java类,相当于Java中的Class.forName()。比如要加载一个String类:

1
var StringClass = Java.use("java.lang.String");

加载内部类:

1
var MyClass_InnerClass = Java.use("com.luoyesiqiu.MyClass$InnerClass");

其中InnerClass是MyClass的内部类

修改函数

修改一个函数的实现是逆向调试中相当有用的。修改一个函数的实现后,如果这个函数被调用,我们的Javascript代码里的函数实现也会被调用。

函数类型

不同的参数类型都有自己的表示方法

  1. 对于基本类型,直接用它在Java中的表示方法就可以了,不用改变,例如:
  • int
  • short
  • char
  • byte
  • boolean
  • float
  • double
  • long
  1. 基本类型数组,用左中括号接上基本类型的缩写

基本类型缩写表示表:

基本类型 缩写
boolean Z
byte B
char C
double D
float F
int I
long J
short S

例如:int[]类型,在重载时要写成[I

  1. 任意类,直接写完整类名即可

例如:java.lang.String

  1. 对象数组,用左中括号接上完整类名再接上分号

例如:[java.lang.String;

构造函数

修改参数为byte[]类型的构造函数的实现

1
2
3
ClassName.$init.overload('[B').implementation=function(param){
//do something
}

注:ClassName是使用Java.use定义的类;param是可以在函数体中访问的参数

修改多参数的构造函数的实现

1
2
3
ClassName.$init.overload('[B','int','int').implementation=function(param1,param2,param3){
//do something
}

无参

1
2
3
ClassName.$init.overload().implementation=function(){
//do something
}

调用原构造函数

1
2
3
4
5
ClassName.$init.overload().implementation=function(){
//do something
this.$init();
//do something
}

注意:当构造函数(函数)有多种重载形式,比如一个类中有两个形式的func:void func()void func(int),要加上overload来对函数进行重载,否则可以省略overload

普通函数

修改函数名为func,参数为byte[]类型的函数的实现

1
2
3
4
ClassName.func.overload('[B').implementation=function(param){
//do something
//return ...
}

无参

1
2
3
ClassName.func.overload().implementation=function(){
//do something
}

注: 在修改函数实现时,如果原函数有返回值,那么我们在实现时也要返回合适的值

1
2
3
4
ClassName.func.overload().implementation=function(){
//do something
return this.func();
}

调用函数

和Java一样,创建类实例就是调用构造函数,而在这里用$new表示一个构造函数。

1
2
var ClassName=Java.use("com.luoye.test.ClassName");
var instance = ClassName.$new();

实例化以后调用其他函数

1
2
3
var ClassName=Java.use("com.luoye.test.ClassName");
var instance = ClassName.$new();
instance.func();

字段操作

字段赋值和读取要在字段名后加.value,假设有这样的一个类:

1
2
3
4
5
package com.luoyesiqiu.app;
public class Person{
private String name;
private int age;
}

写个脚本操作Person类的name字段和age字段:

1
2
3
4
5
6
7
8
9
var person_class = Java.use("com.luoyesiqiu.app.Person");
//实例化Person类
var person_class_instance = person_class.$new();
//给name字段赋值
person_class_instance.name.value = "luoyesiqiu";
//给age字段赋值
person_class_instance.age.value = 18;
//输出name字段和age字段的值
console.log("name = ",person_class_instance.name.value, "," ,"age = " ,person_class_instance.age.value);

输出:

1
name =  luoyesiqiu , age =  18

类型转换

Java.cast方法来对一个对象进行类型转换,如将variable转换成java.lang.String

1
2
var StringClass=Java.use("java.lang.String");
var NewTypeClass=Java.cast(variable,StringClass);

实例

修改返回值
demo

带message

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
package com.example.mytest;

import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.mytest.databinding.ActivityMainBinding;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private AppBarConfiguration appBarConfiguration;
private ActivityMainBinding binding;
private TextView td;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

setSupportActionBar(binding.toolbar);

NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);

binding.fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

Snackbar.make(view, "Replace with your own action:" + isExcellent(), Snackbar.LENGTH_LONG)
.setAction("Action", null).show();

}
});
}
public String isExcellent() {
return "222str";
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}

@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
}
脚本
1
2
3
4
5
6
7
8
9
10
11
if(Java.available){
Java.perform(function(){
var MainActivity = Java.use("com.example.mytest.MainActivity");
MainActivity.isExcellent.implementation=function(){
//send("123")
console.log(4556)
return "124876543";
}
});

}
效果
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
PS D:\WorkingTool\frida\frida-agent-example\agent> frida -U -l .\exp1.js -f com.example.mytest --no-pause
____
/ _ | Frida 15.1.1 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
Spawned `com.example.mytest`. Resuming main thread!
[M2007J17C::com.example.mytest]-> %resume
[M2007J17C::com.example.mytest]-> message: {'type': 'send', 'payload': '123'} data: None
message: {'type': 'send', 'payload': '123'} data: None
message: {'type': 'send', 'payload': '123'} data: None
message: {'type': 'send', 'payload': '123'} data: None
message: {'type': 'send', 'payload': '123'} data: None
message: {'type': 'send', 'payload': '123'} data: None
[M2007J17C::com.example.mytest]->
[M2007J17C::com.example.mytest]-> 456
456
456
456
456
456
456
显示身份信息

新建poject->login Activity

使用jeb

Mr.Hu-Image

使用脚本

1
2
3
4
5
6
7
8
9
if(Java.available){
Java.perform(function(){
var LoginActivity = Java.use("com.example.login.ui.login.LoginViewModel").login.overload('java.lang.String', 'java.lang.String').implementation = function (str, str2)
{
//console.log("username:"+str+" password:"+str2);
send("username:"+str+" password:"+str2)
}
});
}

输入账户密码,点击按钮

效果

1
2
3
4
5
6
7
8
9
10
11
12
PS D:\WorkingTool\frida\frida-agent-example\agent> frida -U -l .\exp2.js -f com.example.login --no-pause
____
/ _ | Frida 15.1.1 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
Spawned `com.example.login`. Resuming main thread!
[M2007J17C::com.example.login]-> message: {'type': 'send', 'payload': 'username:12345@qq.com password:12345678'} data: None

HooK Native

API讲解

  • Interceptor.attach(target, callbacks[, data]): 拦截呼叫以在 .这是一个本地指点,指定您要拦截呼叫的功能的地址。请注意,在 32 位 ARM 上,该地址必须将其最不重要的位设置为 0 用于 ARM 功能,1 用于拇指功能。弗里达照顾这个细节为你,如果你从弗里达API(例如模块.获取出口比名()的地址。target

    参数是包含以下一个或多个对象:callbacks

    • onEnter(args): 回调函数给出一个参数,可用于将参数作为一系列原点点对象读取或写入。[: #interceptor参赛者]args
    • onLeave(retval): 回调函数给出一个参数,即包含原始返回值的原点源性对象。您可以调用用整数替换返回值,或者用指点器替换。请注意,此对象在离开呼叫时被回收,因此不要将其存储并在回调之外使用。如果您需要存储包含的价值,请制作一份深拷贝,例如: .retval``retval.replace(1337)``1337``retval.replace(ptr("0x1234"))``ptr(retval.toString())

    如果挂钩函数非常热,并且可能是自位指值指向使用**CModule**编译的原生 C 函数。他们的签名是:onEnter``onLeave

    • void onEnter (GumInvocationContext * ic)
    • void onLeave (GumInvocationContext * ic)

    在这种情况下,第三个可选参数可能是通过访问本地指点。 data``gum_invocation_context_get_listener_function_data()

    您也可以通过传递一个函数而不是对象来拦截任意指令。此函数具有相同的签名,但传递给它的参数只会在截获的指令处于函数的开头或寄存器/堆栈尚未偏离该点时给出合理的值。callbacks``onEnter``args

    与上图一样,此功能也可以在 C 中通过指定"本地指点"而不是”功能”来实现。

    返回可以调用的听众对象。detach()

    请注意,这些功能将被调用与每调用(线程本地)对象,在那里你可以存储任意数据,这是有用的,如果你想阅读一个参数,并采取行动。this``onEnter``onLeave

    • 例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Interceptor.attach(Module.getExportByName('libc.so', 'read'), {
    onEnter(args) {
    this.fileDescriptor = args[0].toInt32();
    },
    onLeave(retval) {
    if (retval.toInt32() > 0) {
    /* do something with this.fileDescriptor */
    }
    }
    });
    • 此外,对象还包含一些有用的属性:

      • returnAddress: 返回地址作为**本地点**
      • context: 具有键的对象,以及分别指定 ia32/x64/臂的 EIP/RIP/PC 和 ESP/RSP/SP 的**原生指**点对象。其他处理器特定密钥也可用,例如,等。您也可以通过分配到这些密钥来更新注册值。pc``sp``eax``rax``r0``x0
      • errno: (UNIX) 当前额值(您可以替换它)
      • lastError: (Windows) 当前操作系统错误值(您可以替换它)
      • threadId: 操作系统线程 ID
      • depth: 相对于其他调用的呼叫深度

      例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Interceptor.attach(Module.getExportByName(null, 'read'), {
    onEnter(args) {
    console.log('Context information:');
    console.log('Context : ' + JSON.stringify(this.context));
    console.log('Return : ' + this.returnAddress);
    console.log('ThreadId : ' + this.threadId);
    console.log('Depth : ' + this.depth);
    console.log('Errornr : ' + this.err);

    // Save arguments for processing in onLeave.
    this.fd = args[0].toInt32();
    this.buf = args[1];
    this.count = args[2].toInt32();
    },
    onLeave(result) {
    console.log('----------')
    // Show argument 1 (buf), saved during onEnter.
    const numBytes = result.toInt32();
    if (numBytes > 0) {
    console.log(hexdump(this.buf, { length: numBytes, ansi: true }));
    }
    console.log('Result : ' + numBytes);
    }
    })

常用代码

免写参数

1
2
3
4
5
6
7
// 函数原型 encodeRequest(int i, String str, String str2, String str3, String str4, String str5, byte[] bArr, int i2, int i3, String str6, byte b, byte b2, byte[] bArr2, boolean z)
var CodecWarpper = Java.use("xx.CodecWarpper");
CodecWarpper.encodeRequest.implementation = function() {
var ret = this.encodeRequest.apply(this, arguments);
//这里可以打印参数和返回值
return ret;
}

枚举模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Java.perform(function(){
//枚举当前加载的模块
var process_Obj_Module_Arr = Process.enumerateModules();
for(var i = 0; i < process_Obj_Module_Arr.length; i++) {
//包含"lib"字符串的
if(process_Obj_Module_Arr[i].path.indexOf("lib")!=-1)
{
console.log("模块名称:",process_Obj_Module_Arr[i].name);
console.log("模块地址:",process_Obj_Module_Arr[i].base);
console.log("大小:",process_Obj_Module_Arr[i].size);
console.log("文件系统路径",process_Obj_Module_Arr[i].path);
}
}
});

枚举导出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 //枚举模块中所有中的所有导入表(Import)函数
function frida_Module_import() {
Java.perform(function () {
const hooks = Module.load('libc.so');
var Imports = hooks.enumerateImports();
for(var i = 0; i < Imports.length; i++) {
//函数类型
console.log("type:",Imports[i].type);
//函数名称
console.log("name:",Imports[i].name);
//属于的模块
console.log("module:",Imports[i].module);
//函数地址
console.log("address:",Imports[i].address);
}
});
}
setImmediate(frida_Module_import,0);

bytes2Hex

java中 byte范围 -128~127
16进制范围 0 ~ 255

1
2
3
4
5
6
7
8
9
10
11
12
13
function bytes2Hex(arr) {
var str = "[";
for (var i = 0; i < arr.length; i++) {
var z = parseInt(arr[i]);
if (z < 0) z = 255 + z;
var tmp = z.toString(16);
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str = str + " " + tmp;
}
return (str + " ]").toUpperCase();
}

获取方法名

1
2
3
4
5
6
7
8
9
10
function getMethodName() {
var ret;
Java.perform(function() {
var Thread = Java.use("java.lang.Thread")
ret = Thread.currentThread().getStackTrace()[2].getMethodName();
});
return ret;
}


打印堆栈

1
2
3
4
5
function showStacks() {
Java.perform(function() {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
}

输出类所有方法名

1
2
3
4
5
6
7
8
9
10
11
function enumMethods(targetClass) {
var ret;
Java.perform(function() {
var hook = Java.use(targetClass);
var ret = hook.class.getDeclaredMethods();
ret.forEach(function(s) {
console.log(s);
})
})
return ret;
}

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

Java.perform(
function x() {

var targetClass = "com.anchorfree.eliteapi.EliteApi";

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")));
console.log(decodeURIComponent(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1"))));
}

hookCls[decodeURIComponent("vpnConfig%24lambda-36%24lambda-34")]
.implementation = function (str1,str2) {
console.log("-------------------------start Hook-----------------------------");
console.log("参数1:",str1);
console.log("参数2:",str2);

}

}
)

hook 所有重载函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hookAllOverloads(targetClass, targetMethod) {
Java.perform(function () {
var targetClassMethod = targetClass + '.' + targetMethod;
var hook = Java.use(targetClass);
var overloadCount = hook[targetMethod].overloads.length;
for (var i = 0; i < overloadCount; i++) {
hook[targetMethod].overloads[i].implementation = function() {
var retval = this[targetMethod].apply(this, arguments);
//这里可以打印结果和参数
return retval;
}
}
});
}

输出 byte[] 等 java 对象

1
2
3
4
function jobj2Str(jobject) {
var ret = JSON.stringify(jobject);
return ret;
}

dump 地址

1
2
3
4
5
6
7
8
9
function dumpAddr(address, length) {
length = length || 1024;
console.log(hexdump(address, {
offset: 0,
length: length,
header: true,
ansi: false
}));
}

ArrayBuffer 转换

1
2
3
4
5
6
7
8
function ab2Hex(buffer) {
var arr = Array.prototype.map.call(new Uint8Array(buffer), function (x) {return ('00' + x.toString(16)).slice(-2)}).join(" ").toUpperCase();
return "[" + arr + "]";
}

function ab2Str(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}

基于python

HooK native函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import frida
import sys
rdev = frida.get_remote_device()
session = rdev.attach("com.tencent.mm")
scr = """
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
onEnter: function(args) {
send("open("+Memory.readCString(args[0])+","+args[1]+")");
},
onLeave:function(retval){

}
});
"""
script = session.create_script(scr)
def on_message(message ,data):
print message
script.on("message" , on_message)
script.load()
sys.stdin.read()

HooK Java层函数

如下代码为hook微信(测试版本为6.3.13,不同版本由于混淆名字的随机生成的原因或者代码改动导致类名不一样)
com.tencent.mm.sdk.platformtools.ay类的随机数生成函数,让微信猜拳随机(type=2),而摇色子总是为6点(type=5)

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
import frida
import sys
rdev = frida.get_remote_device()
session = rdev.attach("com.tencent.mm")

scr = """
Java.perform(function () {
var ay = Java.use("com.tencent.mm.sdk.platformtools.ay");
ay.pu.implementation = function(){
var type = arguments[0];
send("type="+type);
if (type == 2)
{
return this.pu(type);
}
else
{
return 5;
}
};

});
"""

script = session.create_script(scr)
def on_message(message ,data):
print message
script.on("message" , on_message)
script.load()
sys.stdin.read()

注入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
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
import frida, sys, optparse, re
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)

jscode = """
Java.perform(function () {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var context = currentApplication.getApplicationContext();
var pkgName = context.getPackageName();
var dexPath = "%s";
var entryClass = "%s";
Java.openClassFile(dexPath).load();
console.log("inject " + dexPath +" to " + pkgName + " successfully!")
Java.use(entryClass).%s("%s");
console.log("call entry successfully!")
});
"""

def checkRequiredArguments(opts, parser):
missing_options = []
for option in parser.option_list:
if re.match(r'^\[REQUIRED\]', option.help) and eval('opts.' + option.dest) == None:
missing_options.extend(option._long_opts)
if len(missing_options) > 0:
parser.error('Missing REQUIRED parameters: ' + str(missing_options))

if __name__ == "__main__":
usage = "usage: python %prog [options] arg\n\n" \
"example: python %prog -p com.android.launcher " \
"-f /data/local/tmp/test.apk " \
"-e com.parker.test.DexMain/main " \
"\"hello fridex!\""
parser = optparse.OptionParser(usage)
parser.add_option("-p", "--package", dest="pkg", type="string",
help="[REQUIRED]package name of the app to be injected.")
parser.add_option("-f", "--file", dest="dexPath", type="string",
help="[REQUIRED]path of the dex")
parser.add_option("-e", "--entry", dest="entry", type="string",
help="[REQUIRED]the entry function Name.")

(options, args) = parser.parse_args()
checkRequiredArguments(options, parser)
if len(args) == 0:
arg = ""
else:
arg = args[0]

pkgName = options.pkg
dexPath = options.dexPath
entry = options.entry.split("/")
if len(entry) > 1:
entryClass = entry[0]
entryFunction = entry[1]
else:
entryClass = entry[0]
entryFunction = "main"

process = frida.get_usb_device(1).attach(pkgName)
jscode = jscode%(dexPath, entryClass, entryFunction, arg)
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running fridex')
script.load()
sys.stdin.read()

Objection基础

基础指令

objection启动并注入内存

1
objection -d -g package_name explore

查看内存中加载的module

1
memory list modules

查看库的导出函数

1
memory list exports libssl.so

dump内存空间

memory dump all 文件名

1
memory dump from_base 起始地址 字节数 文件名
搜索内存空间
1
Usage: memory search "<pattern eg: 41 41 41 ?? 41>" (--string) (--offsets-only)

内存堆搜索实例

1
android heap search instances 类名

在堆上搜索类的实例

1
2
3
4
5
6
7
8
9
10
sakura@sakuradeMacBook-Pro:~$ objection -g myapplication.example.com.frida_demo explore
Using USB device `Google Pixel`
Agent injected and responds ok!

[usb] # android heap search instances myapplication.example.com.frida_demo
.MainActivity
Class instance enumeration complete for myapplication.example.com.frida_demo.MainActivity
Handle Class toString()
-------- ------------------------------------------------- ---------------------------------------------------------
0x2102 myapplication.example.com.frida_demo.MainActivity myapplication.example.com.frida_demo.MainActivity@5b1b0af

调用实例的方法 android heap execute 实例ID 实例方法

查看当前可用的activity或者service android hooking list activities/services

直接启动activity或者服务 android intent launch_activity/launch_service activity/服务

android intent launch_activity com.android.settings.DisplaySettings
这个命令比较有趣的是用在如果有些设计的不好,可能就直接绕过了密码锁屏等直接进去。

1
2
3
4
com.android.settings on (google: 8.1.0) [usb] # android hooking list services
com.android.settings.SettingsDumpService
com.android.settings.TetherService
com.android.settings.bluetooth.BluetoothPairingService

列出内存中所有的类

1
android hooking list classes

在内存中所有已加载的类中搜索包含特定关键词的类。 android hooking search classes display

1
2
3
4
5
6
7
com.android.settings on (google: 8.1.0) [usb] # android hooking search classes display
[Landroid.icu.text.DisplayContext$Type;
[Landroid.icu.text.DisplayContext;
[Landroid.view.Display$Mode;
android.hardware.display.DisplayManager
android.hardware.display.DisplayManager$DisplayListener
android.hardware.display.DisplayManagerGlobal

内存中搜索指定类的所有方法 android hooking list class_methods 类名

1
2
3
4
com.android.settings on (google: 8.1.0) [usb] # android hooking list class_methods java.nio.charset.Charset
private static java.nio.charset.Charset java.nio.charset.Charset.lookup(java.lang.String)
private static java.nio.charset.Charset java.nio.charset.Charset.lookup2(java.lang.String)
private static java.nio.charset.Charset java.nio.charset.Charset.lookupViaProviders(java.lang.String)

在内存中所有已加载的类的方法中搜索包含特定关键词的方法 android hooking search methods display

知道名字开始在内存里搜就很有用

1
2
3
4
5
6
7
com.android.settings on (google: 8.1.0) [usb] # android hooking search methods display
Warning, searching all classes may take some time and in some cases, crash the target application.
Continue? [y/N]: y
Found 5529 classes, searching methods (this may take some time)...
android.app.ActionBar.getDisplayOptions
android.app.ActionBar.setDefaultDisplayHomeAsUpEnabled
android.app.ActionBar.setDisplayHomeAsUpEnabled

hook类的方法

(hook类里的所有方法/具体某个方法)

android hooking watch class 类名
这样就可以hook这个类里面的所有方法,每次调用都会被log出来。

1
android hooking watch class 类名 --dump-args --dump-backtrace --dump-return

在上面的基础上,额外dump参数,栈回溯,返回值

1
android hooking watch class xxx.MainActivity --dump-args --dump-backtrace --dump-return
1
android hooking watch class_method 方法名
1
2
//可以直接hook到所有重载
android hooking watch class_method xxx.MainActivity.fun --dump-args --dump-backtrace --dump-return

grep trick和文件保存

objection log默认是不能用grep过滤的,但是可以通过objection run xxx | grep yyy的方式,从终端通过管道来过滤。
用法如下

1
2
3
4
5
6
sakura@sakuradeMacBook-Pro:~$ objection -g com.android.settings run memory list modules | grep libc
Warning: Output is not to a terminal (fd=1).
libcutils.so 0x7a94a1c000 81920 (80.0 KiB) /system/lib64/libcutils.so
libc++.so 0x7a9114e000 983040 (960.0 KiB) /system/lib64/libc++.so
libc.so 0x7a9249d000 892928 (872.0 KiB) /system/lib64/libc.so
libcrypto.so 0x7a92283000 1155072 (1.1 MiB) /system/lib64/libcrypto.so

有的命令后面可以通过--json logfile来直接保存结果到文件里。
有的可以通过查看.objection文件里的输出log来查看结果。

1
2
3
4
sakura@sakuradeMacBook-Pro:~/.objection$ cat *log | grep -i display
android.hardware.display.DisplayManager
android.hardware.display.DisplayManager$DisplayListener
android.hardware.display.DisplayManagerGlobal

Xposed基础

框架加载

Xposed Installer | Xposed Module Repository

XposedAPI相关介绍

1、在Zygote启动时调用,用于系统服务的Hook

​ IXposedHookZygoteInit

2、在资源布局初始化时会回被执行(inflate方法)

​ IXposedHookInitPackageResources

3、回调方法

​ handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParamresparam)

​ InitPackageResourcesParam包含两个参数,包名和XResource(资源相关)

​ 有了这个XResource对象,就可以拿到布局资源树了,通过重写hookLayout方法,

4、LayoutInflatedParam

​ 里面这个view就是布局资源树了,你可以拿到遍历,拿到某个特定控件,然后做一些骚操作。

5、XposeHelpers

​ 提供了一些辅助方法

6、在APP中调用特定方法

​ callMethod(Objectobj,StringmethodName,Object…args)

​ 参数依次是:调用方法的所在类,调用方法名,方法参数

7、findClass(StringclassName,ClassLoaderclassLoader)

获取class类实例参数依次是类名,类加载器

8、findMethodExact

​ 通过反射查找类的成员方法(可setAccessible(true)设置非私有)

9、findConstructorExact

​ 通过反射查找构造函数(同样可设置可访问下性)

10、findAndHookMethod

​ 查找并Hook

11、XposedBridge.log(“日志内容”):输入日志和写入到/data/xposed/debug.log

​ XposedInstaller日志那里可以看到!

Xposed模块编写

1、在新创建的android工程Project中创建一个lib目录,然后放入对应版本的XposedBridgeApi-xx.jar,这里用的模拟器是安卓5.1版本,所以放入54的版本进行测试,最后还需要修改gradle的compile为provided(编译的时候用到依赖相应的jar包 但是打包为apk的时候不会进行参与)

jar包链接:https://pan.baidu.com/s/1aMqFmPv1Ya4EOFBW9wE_qA
提取码:kikl

Mr.Hu-Image

2、然后再AndroidManifest文件中的application标签中加入如下内容

1
2
3
<meta-data android:name="xposedmodule" android:value="true"/> <!-- true会加载这个模块尝试hook -->
<meta-data android:name="xposeddescription" android:value="HookTest"/> <!-- 这是模块描述 -->
<meta-data android:name="xposedminversion" android:value="54"/> <!-- XposedBridgeApi版本号 -->
Mr.Hu-Image

3、在build.grade添加jar包路径compileOnly files(‘libs/XposedBridgeApi-54.jar’)

1
2
3
4
5
6
7
8
9
10
11
dependencies {
compileOnly files('libs/XposedBridgeApi-54.jar')
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.navigation:navigation-fragment:2.3.5'
implementation 'androidx.navigation:navigation-ui:2.3.5'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

然后再弹出来的窗口中点击sync now(这个很重要)

4、回到app/src/main/java/xxx.xxx.xxx(包名)/包名下创建一个类,类名随意(这里创建的类名为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 android.content.Context;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

public class Test implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
try{
if (loadPackageParam.packageName.equals("com.example.loginhooktest")){//要hook的包名
findAndHookMethod("com.example.loginhooktest.MainActivity",loadPackageParam.classLoader,"logintest", new XC_MethodHook(){
protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
}

protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
Class aClass = param.thisObject.getClass();
XposedBridge.log("要hook的方法所在的类:" + aClass.getName());
//通过反射
// Field field[] = aClass.getDeclaredFields();
// for(Field test:field){
// XposedBridge.log("存在的Field:" + test.toString());
// }
//
Field field= aClass.getDeclaredField("et2");
field.setAccessible(true);
EditText et2 = (EditText) field.get(param.thisObject);
String str = et2.getText().toString();
XposedBridge.log("劫持到的密码:" + str);
}
});
//要hook的类名以及方法名,如果有参数,在方法名后面写参数类型与参数个数,例如int.class或String.class或Context.class,有几个写几个,没有就不写。
}
} catch (Exception e){
e.printStackTrace();
}
}
}

5、在AS项目的app文件夹上点击右键,选择 NEW -> Folder -> Assets Folder,选择 main,点击ok

Mr.Hu-Image

6、来到app/src/main/Assets,右键Assets选择NEW,创建一个File,命名为xposed_init,里面的内容是hook模块的入口点,本例中的包名以及入口点是com.example.xposedtest.Test 所以这个文件里就写了这个,具体你们的包名是什么,类名是什么,你们自己更改

Mr.Hu-Image

7、然后打开Xposed选中自己刚才安装的Xposed编写的模块,还需要进行重启模块才会生效

Mr.Hu-Image

8、查看Xposed的日志,发现HOOK成功

注意:看过很多网上的方法,其中有一个就是把第三步替换成以下内容,

1
2
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'

安装包提取

1、查看所有包名

​ adb shell pm list package

2、找到对应的包名

adb shell pm path com.istone.xdf

3、提取apk

adb pull /data/app/xxxxx/base.apk

如果有root权限

1、切换权限

​ su

2、进入app目录

​ cd /data/app

3、找到对应的包目录进行提取

adb pull /data/app/xxxxx/base.apk

参考

frida的用法–Hook Java代码篇 - luoyesiqiu - 博客园 (cnblogs.com)

Frida入门学习笔记-hook native中的函数(1) - 简书 (jianshu.com)

Android逆向之hook框架frida篇 - 简书 (jianshu.com)

Frida Android hook | Sakuraのblog (eternalsakura13.com)