启动

1
2
3
4
5
6
7
8
9
adb push xxx.apk /sdcard/xxx.apk
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
adb shell
su
cd /data/local/tmp
./frida-server

frida -U -l .\t1.js -f demo2.jni.com.myapplication

调试

add 调试参数

在xml里面加上

1
android:debuggable="true"

img

调试init_array和JNI_Onload

用frida spawn,加pause参数(16版本就–pause,14及以前的版本就不用加)。然后用ida attach后运行,frida命令行输入%resume后点击same就可以调试上,且可以调试init_array和JNI_Onload这些位置的函数

QBDI

1
setenforce 0

问题大全

logcat中文乱码

1
chcp 65001

kill frida-server

1
2
netstat -tunlp
kill -9 {pid}

frida检测

  1. 遍历运行的进程列表从而检查fridaserver是否在运行
  2. 遍历data/local/tmp目录查看有无frida相关文件
  3. 遍历映射库,检测frida相关so
  4. 检查27042这个默认端口
  5. 内存中扫描frida特征字符串 “LIBFRIDA”
  6. frida使用D-Bus协议通信,我们为每个开放的端口发送D-Bus的认证消息
  7. 检测有无frida特征的线程名,如:gum-js-loop

简单绕过检测

  • frida端口改成其他的数,比如1234
1
2
adb shell su -c "/data/local/tmp/hs1604 -l 0.0.0.0:1234 &"
frida -H 127.0.0.1:1234 -f xxx -l xxx.js
  • android_server同样将端口改成别的
1
./as64 -p 12345
  • frida-server和android_server的名字不要直接写本名,改成as,fs这种
  • 用hluwa_server代替frida_server
  • server位置不要放在data/local/tmp,可以放在data/目录下

脚本

python脚本

加载脚本

1
2
3
4
5
6
7
8
9
10
11
import time
import frida
device = frida.get_remote_device()
pid = device.spawn(["com.kanxue.pediy1"])#程序名
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
with open("s1.js") as f:
script = session.create_script(f.read())
script.load()
input()

获取最前端进程

1
2
3
4
import frida
rdev = frida.get_remote_device()
front_app = rdev.get_frontmost_application()
print(front_app)

基础js脚本

枚举所有的类

enumerateLoadedClasses

这个函数拥有两个回调函数onMatch和onComplete,其中onMatch的参数就是类的信息。这个函数会遍历所有类,所以我们如果再onMatch处输出其参数就可以获得所有类的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
setTimeout(function (){
Java.perform(function (){
console.log("n[*] enumerating classes...");
Java.enumerateLoadedClasses({
onMatch: function(_className){
console.log("[*] found instance of '"+_className+"'");
},
onComplete: function(){
console.log("[*] class enuemration complete");
}
});
});
});

Java.enumerateLoadedClassesSync()也能实现这个功能,不过它返回的是数组

显示安卓系统版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function frida_Java() {
Java.perform(function () {
//作为判断用
if(Java.available)
{
//注入的逻辑代码
console.log("",Java.androidVersion);
}else{
//未能正常加载JAVA VM
console.log("error");
}
});
}
setImmediate(frida_Java,0);

枚举类加载器

Java.enumerateClassLoaders

该api枚举的是java vm中的类加载器,也是俩回调函数,onMatch和onComplete,其中onMatch的参数是loader,因为是枚举类加载器嘛。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function frida_Java() {
Java.perform(function () {
if(Java.available)
{
//枚举当前加载的Java VM类加载器
Java.enumerateClassLoaders({
//回调函数,参数loader是类加载的信息
onMatch: function (loader)
{
console.log("",loader);
},
//枚举完毕所有类加载器之后的回调函数
onComplete: function ()
{
console.log("end");
}
});
}else{
console.log("error");
}
});
}
setImmediate(frida_Java,0);

image-20221226000125286

附加调用Java.perform

该api很重要,java.perform(fn)主要用于当前线程附加到java vm并调用fn方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function frida_Java() {
//运行当前js脚本时会对当前线程附加到Java VM虚拟机,并且执行function方法
Java.perform(function () {
//判断是否Java VM正常运行
if(Java.available)
{
//如不意外会直接输出 hello
console.log("hello");
}else{
console.log("error");
}
});
}
setImmediate(frida_Java,0);

也有个兄弟Java.performNow(fn)

获取类Java.use

Java.use(classname),可以动态获取classname的定义,然后对其调用$new()可以调用构造函数,来从中实例化对象,想要回收类的时候可以调用$Dispose()方法显式释放

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function () {
//获取android.app.Activity类
var Activity = Java.use('android.app.Activity');
//获取java.lang.Exception类
var Exception = Java.use('java.lang.Exception');
//拦截Activity类的onResume方法
Activity.onResume.implementation = function () {
//调用onResume方法的时候,会在此处被拦截并且调用以下代码抛出异常!
throw Exception.$new('Oh noes!');
};
});

扫描实例类Java.choose

堆上找实例化的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
Java.perform(function () {
//查找android.view.View类在堆上的实例化对象
Java.choose("android.view.View", {
//枚举时调用
onMatch:function(instance){
//打印实例
console.log(instance);
},
//枚举完成后调用
onComplete:function() {
console.log("end")
}});
});

类型转换器Java.cast

将指定变量转换成需要的类型。通常在拦截so层时会用此函数将jstring,jarray转换后查看其值

定义数组Java.array

js中定义java数组的api

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function () {
//定义一个int数组、值是1003, 1005, 1007
var intarr = Java.array('int', [ 1003, 1005, 1007 ]);
//定义一个byte数组、值是0x48, 0x65, 0x69
var bytearr = Java.array('byte', [ 0x48, 0x65, 0x69 ]);
for(var i=0;i<bytearr.length;i++)
{
//输出每个byte元素
console.log(bytearr[i])
}
});

注册类Java.registerClass(spec)

Java.registerClass:创建一个新的Java类并返回一个包装器,其中规范是一个包含:
name:指定类名称的字符串。
superClass:(可选)父类。要从 java.lang.Object 继承的省略。
implements:(可选)由此类实现的接口数组。
fields:(可选)对象,指定要公开的每个字段的名称和类型。
methods:(可选)对象,指定要实现的方法。

1
2
3
4
5
6
7
Java.perform(function () {
//注册一个目标进程中的类,返回的是一个类对象
var hellojni = Java.registerClass({
name: 'com.roysue.roysueapplication.hellojni'
});
console.log(hellojni.addInt(1,2));
});

官方操作

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
//获取目标进程的SomeBaseClass类
var SomeBaseClass = Java.use('com.example.SomeBaseClass');
//获取目标进程的X509TrustManager类
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');

var MyWeirdTrustManager = Java.registerClass({
//注册一个类是进程中的MyWeirdTrustManager类
name: 'com.example.MyWeirdTrustManager',
//父类是SomeBaseClass类
superClass: SomeBaseClass,
//实现了MyWeirdTrustManager接口类
implements: [X509TrustManager],
//类中的属性
fields: {
description: 'java.lang.String',
limit: 'int',
},
//定义的方法
methods: {
//类的构造函数
$init: function () {
console.log('Constructor called');
},
//X509TrustManager接口中方法之一,该方法作用是检查客户端的证书
checkClientTrusted: function (chain, authType) {
console.log('checkClientTrusted');
},
//该方法检查服务器的证书,不信任时。在这里通过自己实现该方法,可以使之信任我们指定的任何证书。在实现该方法时,也可以简单的不做任何处理,即一个空的函数体,由于不会抛出异常,它就会信任任何证书。
checkServerTrusted: [{
//返回值类型
returnType: 'void',
//参数列表
argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
//实现方法
implementation: function (chain, authType) {
//输出
console.log('checkServerTrusted A');
}
}, {
returnType: 'java.util.List',
argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
implementation: function (chain, authType, host) {
console.log('checkServerTrusted B');
//返回null会信任所有证书
return null;
}
}],
// 返回受信任的X509证书数组。
getAcceptedIssuers: function () {
console.log('getAcceptedIssuers');
return [];
},
}
});

实现了证书类的javax.net.ssl.X509TrustManager类,,这里就是相当于自己在目标进程中重新创建了一个类,实现了自己想要实现的类构造,重构造了其中的三个接口函数、从而绕过证书校验

Java.vm对象

可以拿到JNI层的JNIEnv对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function frida_Java() {     
Java.perform(function () {
//拦截getStr函数
Interceptor.attach(Module.findExportByName("libhello.so" , "Java_com_roysue_roysueapplication_hellojni_getStr"), {
onEnter: function(args) {
console.log("getStr");
},
onLeave:function(retval){
//它的返回值的是retval 在jni层getStr的返回值的jstring
//我们在这里做的事情就是替换掉结果
//先获取一个Env对象
var env = Java.vm.getEnv();
//通过newStringUtf方法构建一个jstirng字符串
var jstring = env.newStringUtf('roysue');
//replace替换掉结果
retval.replace(jstring);
console.log("getSum方法返回值为:roysue")
}
});
}
setImmediate(frida_Java,0);

Interceptor.attach

该对象功能十分强大,函数原型是Interceptor.attach(target, callbacks):参数target是需要拦截的位置的函数地址,也就是填某个so层函数的地址即可对其拦截,target是一个NativePointer参数,用来指定你想要拦截的函数的地址,NativePointer我们也学过是一个指针。需要注意的是对于Thumb函数需要对函数地址+1callbacks则是它的回调函数,分别是以下两个回调函数:

onEnter:函数(args):回调函数,给定一个参数args,可用于读取或写入参数作为 NativePointer 对象的数组。

onLeave:函数(retval):回调函数给定一个参数 retval,该参数是包含原始返回值的 NativePointer 派生对象。可以调用 retval.replace(1337) 以整数 1337 替换返回值,或者调用 retval.replace(ptr("0x1234"))以替换为指针。请注意,此对象在 OnLeave 调用中回收,因此不要将其存储在回调之外并使用它。如果需要存储包含的值,请制作深副本,例如:ptr(retval.toString())

1
2
3
4
5
6
7
8
9
10
11
12
13
//使用Module对象getExportByNameAPI直接获取libc.so中的导出函数read的地址,对read函数进行附加拦截
Interceptor.attach(Module.getExportByName('libc.so', 'read'), {
//每次read函数调用的时候会执行onEnter回调函数
onEnter: function (args) {
this.fileDescriptor = args[0].toInt32();
},
//read函数执行完成之后会执行onLeave回调函数
onLeave: function (retval) {
if (retval.toInt32() > 0) {
/* do something with this.fileDescriptor */
}
}
});

通过我们对Interceptor.attach函数有一些基本了解了~它还包含一些属性。

索引 属性 含义
1 returnAddress 返回地址,类型是NativePointer
2 context 上下文:具有键pcsp的对象,它们是分别为ia32/x64/arm指定EIP/RIP/PCESP/RSP/SP的NativePointer对象。其他处理器特定的键也可用,例如eax、rax、r0、x0等。也可以通过分配给这些键来更新寄存器值。
3 errno 当前errno
4 lastError 当前操作系统错误值
5 threadId 操作系统线程ID
6 depth 相对于其他调用的调用深度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function frida_Interceptor() {
Java.perform(function () {
//对So层的导出函数getSum进行拦截
Interceptor.attach(Module.findExportByName("libhello.so" , "Java_com_roysue_roysueapplication_hellojni_getSum"), {
onEnter: function(args) {
//输出
console.log('Context information:');
//输出上下文因其是一个Objection对象,需要它进行接送、转换才能正常看到值
console.log('Context : ' + JSON.stringify(this.context));
//输出返回地址
console.log('Return : ' + this.returnAddress);
//输出线程id
console.log('ThreadId : ' + this.threadId);
console.log('Depth : ' + this.depth);
console.log('Errornr : ' + this.err);
},
onLeave:function(retval){
}
});
});
}
setImmediate(frida_Interceptor,0);

img

Interceptor.detachAll

让attach附加拦截的回调函数失效

Interceptor.replace

替换自己实现的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function frida_Interceptor() {
Java.perform(function () {
//这个c_getSum方法有两个int参数、返回结果为两个参数相加
//这里用NativeFunction函数自己定义了一个c_getSum函数
var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'),
'int',['int','int']);
//输出结果 那结果肯定就是 3
console.log("result:",add_method(1,2));
//这里对原函数的功能进行替换实现
Interceptor.replace(add_method, new NativeCallback(function (a, b) {
//h不论是什么参数都返回123
return 123;
}, 'int', ['int', 'int']));
//再次调用 则返回123
console.log("result:",add_method(1,2));
});
}

new NativePointer(s)

nativePointer对象相当于指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function frida_NativePointer() {
Java.perform(function () {
//第一种字符串定义方式 十进制的100 输出为十六进制0x64
const ptr1 = new NativePointer("100");
console.log("ptr1:",ptr1);
//第二种字符串定义方式 直接定义0x64 同等与定义十六进制的64
const ptr2 = new NativePointer("0x64");
console.log("ptr2:",ptr2);
//第三种定数值义方式 定义数字int类型 十进制的100 是0x64
const ptr3 = new NativePointer(100);
console.log("ptr3:",ptr3);
});
}
setImmediate(frida_NativePointer,0);

输出如下,都会自动转为十六进制的0x64
ptr1: 0x64
ptr2: 0x64
ptr3: 0x64

运算符和指针读写api

img

img

下脚本是readByteArray示例

1
2
3
4
5
6
7
8
9
10
function frida_NativePointer() {
Java.perform(function () {
console.log("");
//拿到libc.so在内存中的地址
var pointer = Process.findModuleByName("libc.so").base;
//读取从pointer地址开始的16个字节
console.log(pointer.readByteArray(0x10));
});
}
setImmediate(frida_NativePointer,0);

输出就是libc.so的前16个字节

readPointer()

定义是从此内存位置读取NativePointer

1
2
3
4
5
var pointer = Process.findModuleByName("libc.so").base;
console.log(pointer.readByteArray(0x10));
console.log("readPointer():"+pointer.readPointer());
输出如下。
readPointer():0x464c457f

writePointer(ptr)

读取ptr指针地址到当前指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        //先打印pointer指针地址
console.log("pointer :"+pointer);
//分配四个字节的空间地址
const r = Memory.alloc(4);
//将pointer指针写入刚刚申请的r内
r.writePointer(pointer);
//读取r指针的数据
var buffer = Memory.readByteArray(r, 4);
//r指针内放的pointer指针地址
console.log(buffer);

输出如下。
//console.log("pointer :"+pointer); 这句打印的地址 也就是libc的地址
pointer :0xf588f000
//console.log(buffer); 输出buffer 0xf588f000在内存数据会以00 f0 88 f5方式显示
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 00 f0 88 f5 ....

readS32()、readU32()

从该内存位置读取有符号或无符号8/16/32/etc或浮点数/双精度值,并将其作为数字返回。这里拿readS32()、readU32()作为演示.

1
2
3
4
5
6
7
8
9
10
11
    //从pointer地址读4个字节 有符号
console.log(pointer.readS32());
//从pointer地址读4个字节 无符号
console.log(pointer.readU32());


输出如下。
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
1179403647 == 0x464c457f
1179403647 == 0x464c457f

writeS32()、writeU32()

将有符号或无符号8/16/32/等或浮点数/双精度值写入此内存位置。

1
2
3
4
5
6
7
8
9
10
    //申请四个字节的内存空间
const r = Memory.alloc(4);
//将0x12345678写入r地址中
r.writeS32(0x12345678);
//输出
console.log(r.readByteArray(0x10));
// writeS32()、writeU32()输出的也是一样的,只是区别是有符号和无符号
输出如下。
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 78 56 34 12 00 00 00 00 00 00 00 00 00 00 00 00 xV4.............

readByteArray(length))、writeByteArray(bytes)

readByteArray(length))连续读取内存length个字节,、writeByteArray连续写入内存bytes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
       //先定义一个需要写入的字节数组
var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
//这里申请以arr大小的内存空间
const r = Memory.alloc(arr.length);
//将arr数组字节写入r
Memory.writeByteArray(r,arr);
//读取arr.length大小的数组
var buffer = Memory.readByteArray(r, arr.length);
console.log("Memory.readByteArray:");
console.log(hexdump(buffer, {
offset: 0,
length: arr.length,
header: true,
ansi: false
}));

输出如下。
Memory.readByteArray:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 72 6f 79 73 75 65 roysue

readCString([size = -1])、writeUtf8String(str)

readCString功能是读取指针地址位置的字节字符串,对应的writeUtf8String是写入指针地址位置的字符串处。(这里的r是接着上面的代码的变量)。

1
2
3
4
5
6
//在这里直接使用readCString读取会把上面的'roysue'字符串读取出来
console.log("readCString():"+r.readCString());
//这里是写入字符串 也就是 roysue起始位置开始被替换为haha
const newPtrstr = r.writeUtf8String("haha");
//替换完了之后再继续输出 必然是haha
console.log("readCString():"+newPtrstr.readCString());

NativeFunction

创建新的NativeFunction以调用address处的函数(用NativePointer指定),其中return Type指定返回类型,argTypes数组指定参数类型。如果不是系统默认值,还可以选择指定ABI。对于可变函数,添加一个‘.’固定参数和可变参数之间的argTypes条目,我们来看看官方的例子。

1
2
3
4
5
6
7
8
// LargeObject HandyClass::friendlyFunctionName();
//创建friendlyFunctionPtr地址的函数
var friendlyFunctionName = new NativeFunction(friendlyFunctionPtr,
'void', ['pointer', 'pointer']);
//申请内存空间
var returnValue = Memory.alloc(sizeOfLargeObject);
//调用friendlyFunctionName函数
friendlyFunctionName(returnValue, thisPtr);

函数定义格式为new NativeFunction(address, returnType, argTypes[, options]),参照这个格式能够创建函数并且调用!returnType和argTypes[,]分别可以填void、pointer、int、uint、long、ulong、char、uchar、float、double、int8、uint8、int16、uint16、int32、uint32、int64、uint64这些类型,根据函数的所需要的type来定义即可。

在定义的时候必须要将参数类型个数和参数类型以及返回值完全匹配,假设有三个参数都是int,则new NativeFunction(address, returnType, ['int', 'int', 'int']),而返回值是intnew NativeFunction(address, 'int', argTypes[, options]),必须要全部匹配,并且第一个参数一定要是函数地址指针。

NativeCallback对象

new NativeCallback(func,rereturn Type,argTypes[,ABI]):创建一个由JavaScript函数func实现的新NativeCallback,其中return Type指定返回类型,argTypes数组指定参数类型。您还可以指定ABI(如果不是系统默认值)。有关支持的类型和Abis的详细信息,请参见NativeFunction。注意,返回的对象也是一个NativePointer,因此可以传递给Interceptor#replace。当将产生的回调与Interceptor.replace()一起使用时,将调用func,并将其绑定到具有一些有用属性的对象,就像Interceptor.Attach()中的那样。我们来看一个例子。如下,利用NativeCallback做一个函数替换。

1
2
3
4
5
6
7
8
9
10
Java.perform(function () {
var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'),
'int',['int','int']);
console.log("result:",add_method(1,2));
//在这里new一个新的函数,但是参数的个数和返回值必须对应
Interceptor.replace(add_method, new NativeCallback(function (a, b) {
return 123;
}, 'int', ['int', 'int']));
console.log("result:",add_method(1,2));
});

string2byte

用于将jdk的string转成byte形式

1
2
3
4
function stringToBytes(str) {
var javaString = Java.use('java.lang.String');
return javaString.$new(str).getBytes();
}

context参数调用

用于hook时参数类型有context的情况

1
Java.use('android.app.ActivityThread').currentApplication();

js脚本收集

java层主动调用

如果方法不是静态,可以通过下方的new或者用choose先获得内存中类实例然后调用类方法

1
2
3
4
5
6
7
8
9
function hook_decrypt(){
Java.perform(function (){
var a = new Int8Array([114,514])
var verify = Java.use({class_name}).$new();
var bignum = Java.use("java.math.BigInteger").$new(114514);
//通过verify主动调用函数
verify.decrypt(a, bignum).overload('[B', 'java.math.BigInteger');
})
}

如果是静态,不用new

1
2
var verify = Java.use("{class_name}");
verify.verifyPassword(a, b);

ssl unpinning(okhttp4.9.1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// okhttp4
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');

CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(str) {
writeFile('! Intercepted okhttp4 in [check()]: ' + str);
return;
};

try {//.overload('java.lang.String', 'kotlin.jvm.functions.Function0')
CertificatePinner.check$okhttp.implementation = function(str, _) {
writeFile('! Intercepted okhttp4 in [check$okhttp]: ' + str);
return;
};
} catch (ex) {
writeFile("is this Okhttp3 ?!");
}
writeFile('* Setup okhttp4 pinning')
} catch (err) {
writeFile('* Unable to hook into okhttp4 pinner')
writeFile(err);
}

hook dlopen

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 hook_dlopen() {
var dlopenAddr = Module.findExportByName(null, "dlopen");
if (dlopenAddr == null) {
dlopenAddr = Module.findExportByName(null, "android_dlopen_ext");
}

Interceptor.attach(dlopenAddr, {
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("[dlopen]", path);
this.can_hook_lib = false;
this.can_hook_libmono = false;
if (path.indexOf("libil2cpp.so") >= 0) {
this.can_hook_lib = true;
}
}
},
onLeave: function (retval) {
if (this.can_hook_lib) {
var libso_address = Module.findBaseAddress("libil2cpp.so")
hook_input(libso_address)
}
}
})
}

hook exit

1
2
3
4
var sysexit = Java.use("java.lang.System");
sysexit.exit.implementation = function(a0){
console.log("bypass exit");
}

hook pthread_create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function pthread_hook(){
//int pthread_create(pthread_t *newthread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
var pthread_create_origin = Module.findExportByName("libc.so","pthread_create");
var pthread_create_replace = new NativeFunction(pthread_create_origin,"int",["pointer","pointer","pointer","pointer"]);
Interceptor.replace(pthread_create_origin,new NativeCallback(function(ptr0,ptr1,ptr2,ptr3){
var retval = ptr(0);//?why
if(ptr1.isNull() && ptr3.isNull()){
send("bypass the pthread");
}
else{
retval = pthread_create_replace(ptr0,ptr1,ptr2,ptr3);
}
send("pthread execute next is print_secert");
print_secert();
return retval;
},"int",["pointer","pointer","pointer","pointer"]));
}
//本代码是判断第二个和第四个参数为0的时候hook,用的时候看自己的需求

stalker简单使用

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
Interceptor.attach(tatgetAddr, {
onEnter: function(args){
console.log("enter tatgetAddr");

this.pid = Process.getCurrentThreadId();
Stalker.follow(this.pid,{
events:{

call:false,//不打印这些汇编
ret:false,
exec:false,

block:false,
compile:false
},
onReceive:function(events){
},

transform: function (iterator) {//主要是transform
var instruction = iterator.next();
const startAddress = instruction.address;
if(count <= 100){
do{
console.log(instruction.address.sub(base_addr) + "\t:\t" + instruction);
iterator.keep();
} while ((instruction = iterator.next()) !== null);
}
count += 1;
},

onCallSummary:function(summary){

}
});
},onLeave: function(retval){
Stalker.unfollow(this.pid);
console.log("leave tatgetAddr");
sleep(5000000000000).then(()=>{})
}
});

打印调用栈的log

1
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

可以输出调用栈

找interface的实现

image-20230118140334167

hook onclick

可以加上上方的打印调用栈的代码

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
var jclazz = null;
var jobj = null;

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));
}

function watch(obj, mtdName) {
var listener_name = getObjClassName(obj);
var target = Java.use(listener_name);
if (!target || !mtdName in target) {
return;
}
// send("[WatchEvent] hooking " + mtdName + ": " + listener_name);
target[mtdName].overloads.forEach(function (overload) {
overload.implementation = function () {
//send("[WatchEvent] " + mtdName + ": " + getObjClassName(this));
console.log("[WatchEvent] " + mtdName + ": " + getObjClassName(this))
return this[mtdName].apply(this, arguments);
};
})
}

function OnClickListener() {
Java.perform(function () {

Java.use("android.view.View").setOnClickListener.implementation = function (listener) {
if (listener != null) {
watch(listener, 'onClick');
}
return this.setOnClickListener(listener);
};

Java.choose("android.view.View$ListenerInfo", {
onMatch: function (instance) {
instance = instance.mOnClickListener.value;
if (instance) {
console.log("mOnClickListener name is :" + getObjClassName(instance));
watch(instance, 'onClick');
}
},
onComplete: function () {
}
})
})
}
setImmediate(OnClickListener);

hook linker

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

var mprotect_cnt = 0
//frida -U --no-pause -f com.testlinker.ty -l hook.js
function sleep(delay) {
var start = (new Date()).getTime();
while ((new Date()).getTime() - start < delay) {
continue;
}
}

function hook_svc_mprotect() {
let base_svc_mprotect = Module.findBaseAddress("libSecShell.so");
if (base_svc_mprotect != null) {
console.log("base_svc_mprotect : " + base_svc_mprotect)
}else{
return ;
}
let svc_mprotect = base_svc_mprotect.add(0xC0778);//32位
Interceptor.attach(svc_mprotect, {
onEnter: function(args) {
console.log("==========================================")

console.log("svc_mprotect: start = " + args[0] + " , len = " + args[1] + " , ATTRIBUTES = " + args[2])
mprotect_cnt += 1
console.log(hexdump(base_svc_mprotect.add(0x281B4)))
},
onLeave: function(){
console.log("svc_mprotect leave")
console.log("==========================================")
}
})
}
function dis(address, number) {
for (var i = 0; i < number; i++) {
var ins = Instruction.parse(address);
console.log("address:" + address + "--dis:" + ins.toString());
address = ins.next;
}
}
//libc->strstr() 从linker里面找到call_function的地址
function hook() {
//call_function("DT_INIT", init_func_, get_realpath());
var linkermodule
if (Process.pointerSize == 4) {
linkermodule = Process.findModuleByName("linker");
}else if (Process.pointerSize == 8) {
linkermodule = Process.findModuleByName("linker64");
}
// var linkermodule = Process.getModuleByName("linker");
var call_function_addr = null;
var symbols = linkermodule.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//LogPrint(linkername + "->" + symbol.name + "---" + symbol.address);
if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) {
call_function_addr = symbol.address;
//LogPrint("linker->" + symbol.name + "---" + symbol.address)
}
}
Interceptor.attach(call_function_addr, {
onEnter: function (args) {
var type = ptr(args[0]).readUtf8String();
var address = args[1];
var sopath = ptr(args[2]).readUtf8String();
console.log("loadso:" + sopath + "--addr:" + address + "--type:" + type);
if (sopath.indexOf("libSecShell.so") != -1) {
var libnativemodule = Process.getModuleByName("libSecShell.so");//call_function正在加载目标so,这时就拦截下来
var base = libnativemodule.base;
hook_svc_mprotect()
}
}
})
}
function main() {
hook();
}
setImmediate(main)

frida指令收集

adb指令

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
# 查看当前连接设备
adb devices

# 多个设备时指定设备
adb -s 设备号 其他指令
# adb -s 127.0.0.1:21503 shell
# adb -s FA6AE0309067 shell
# 127.0.0.1:5555 蓝叠
# 127.0.0.1:7555 MUMU模拟器
# 127.0.0.1:62001 夜游神模拟器
# 127.0.0.1:21503 逍遥模拟器

# 查看Android处理器架构
adb shell getprop ro.product.cpu.abi

# 安装APP
adb install xxx.apk

# 安装APP,已经存在,覆盖安装
adb install -r xxx.apk

# 卸载APP
adb uninstall app包名

# 卸载APP,保留数据
adb uninstall -k app包名

# 往手机传递文件
adb push 文件名 手机路径

# 从手机端获取文件
adb pull 手机路径/文件名

# 移动文件
mv /sdcard/Download/frida-server-12.8.0 /data/local/tmp/

# 修改文件权限
chmod 777 /data/local/tmp/frida-server-12.8.0

# 查看日志
adb logcat

# 清日志
adb logcat -c

# 手机端安装的所有app包名
adb shell pm list packages

# 查看当前包名和主Activity
adb shell dumpsys window | findstr mCurrentFocus

# 启动APP
adb shell am start 包名/主Activity
# adb shell am start com.autonavi.minimap/com.autonavi.map.activity.NewMapActivity

# 关闭App
adb shell am force-stop 包名
# adb shell am force-stop com.autonavi.minimap

# 屏幕截图
adb shell screencap 手机路径/xxx.png

# 录制视频
adb shell screenrecord 手机路径/xxx.mp4

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
29
30
31
32
# 启动frida-server(模拟器)
./data/local/tmp/frida-server-12.8.0-android-x86

# 启动frida-server(Pixel真机)
./data/local/tmp/frida-server-12.8.0-android-arm64

# 转发端口
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

# 列举出来所有连接到电脑上的设备
frida-ls-devices

# 连接到指定设备
frida-ps -D tcp

# 列举出来设备上的所有进程
frida-ps -U

# 列举出来设备上的所有应用程序
frida-ps -Ua

# 列举出来设备上的所有已安装应用程序和对应的名字
frida-ps -Uai

# 跟踪某个函数
frida-trace -U -f Name -i "函数名"
# frida-trace -U -f com.autonavi.minimap -i "getRequestParams"

# 跟踪某个方法
frida-trace -U -f Name -m "方法名"
# frida-trace -U -f com.autonavi.minimap -m "MapLoader"