frida 常用命令

frida

  • https://frida.re/docs/android/
  • https://github.com/frida/frida/releases
  • https://frida.re/docs/javascript-api/
  • https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/
1
2
3
4
5
6
7
$ adb root # might be required
$ adb push frida-server /data/local/tmp/
$ adb shell "chmod 755 /data/local/tmp/frida-server"
$ adb shell "/data/local/tmp/frida-server &"

frida-ps -D emulator-5554

frida

监控和修改APP行为,适合临时调试的场景

安装服务端

检查CPU版本

1
2
3
4
5
6
7
8
9
# 连接模拟器(夜神)
~ adb connect 127.0.0.1:62001
connected to 127.0.0.1:62001

➜  ~ adb shell
dream2lte:/ getprop ro.product.cpu.abi
x86


https://github.com/frida/frida/releases 最新的下载,试着装一下看看是否适配,frida-server开启后,模拟器是否正常,打开点击app之类。可以查看到Frida server与Frida-tools对应关系。如果最新可以用,pip就不指定版本

根据abi和服务器类型,选择下载frida-server-15.2.2-android-x86.xz

image-20221127113951107

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 重命名发送到模拟器
adb push frida-server /data/local/tmp



# 模拟器操作
adb shell

su
cd /data/local/tmp
dream2lte:/data/local/tmp  chmod 755 frida-server
dream2lte:/data/local/tmp  ll
total 52348
-rwxr-xr-x 1 root root 53604108 2022-11-26 22:15 frida-server


安装客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pip install frida-tools==11.0.0

frida --version
15.2.2

# 测试展示已安装app
 -U, --usb             connect to USB device
  -a, --applications    list only applications
  -i, --installed       include all installed applications



frida-ps -Uai

 PID  Name       Identifier                        
4  ---------  ----------------------------------
3211  图库         com.android.gallery3d             
3899  游戏中心       com.bignox.app.store.hd           
3184  相机         com.android.camera2               
2573  设置         com.android.settings              
   -  Amaze      com.amaze.filemanager             

客户端接入方式

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
# attach app
  -U, --usb             connect to USB device
 -F, --attach-frontmost
                        attach to frontmost application
  -l SCRIPT, --load SCRIPT
                        load SCRIPT
                        


使用js脚本hook当前app,按保存自动reload脚本

frida -UF -l [js path]


 -f TARGET, --file TARGET
                        spawn FILE
 --no-pause            automatically start main thread after startup

# 启动前注入
frida -U --no-pause -f com.xx.messenger -l hook.js


rdev = frida.get_usb_device()
pid = rdev.spawn(["com.xiaojianbang.app"])    #已挂起方式创建进程
process = rdev.attach(pid)                  #附加到该进程
script = process.create_script(jscode)
script.on('message', message)
script.load()
rdev.resume(pid)            #创建完脚本, 恢复进程运行
sys.stdin.read()



# 多设备
rdev = frida.get_device_manager().enumerate_devices()
print(rdev)
rdev = rdev[int(sys.argv[1])]
pid = rdev.spawn(["com.xiaojianbang.app"])    #已挂起方式创建进程
process = rdev.attach(pid)                  #附加到该进程
script = process.create_script(jscode)
script.on('message', message)
script.load()
rdev.resume(pid)            #创建完脚本, 恢复进程运行
sys.stdin.read()

非标准端口

1
2
3
4
5
./fs -l 127.0.0.1:9999

adb forward tcp:9999 tcp:9999

frida -H 127.0.0.1:9999 -F -l xx.js

python client

image-20221203161340451

常用函数

image-20221127115915381

在AndroidMainfest.xml里面查看想控制的activity。或者使用MT

image-20221127114919703image-20221127115045719image-20221127115227744

拿到activity name

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function main() {
    Java.perform(() => {
        // Java类路径
        // 静态内部类: 包名.类名$内部类名
        // 匿名内部类: 包名.类名$1  (按顺序排单个类文件smail)
        let activity = Java.use("包名.类名");
        activity.check.implementation = function (a,b) {
            return true
        }
        // 重载  frida会有错误提示可以直接选
         activity.check.overload("int","类全路径").implementation = function (a,b) {
           	// 类静态参数修改
          	activity.attr.value = "fdafafa"
	          send(activity.attr.value)
            return true
        }
        // 遍历重载
        for(var i = 0; i < messageDigest.update.overloads.length; i++){
              messageDigest.update.overloads[i].implementation = function(){
                // 参数
								if(arguments.length == 1){
                    send(arguments[0]);
                    // 调用函数返回值,apply省略参数个数
                    a = this.update.apply(this)
                  	return this.update.apply(this,arguments)
                  }
              }
         }
      
      
        // 构造函数
        activity.$init.implementation = function (a,b) {
            // 实例化对象
            var b = useObj.$new(xx,xx)
            send(b.attr.value)
            return this.$init()
        }
      
         // 非静态类字段修改
        Java.choose("com.zj.wuaipojie.ui.ChallengeFourth", {
                onMatch: function (obj) {
                    // 遍历对象修改
                    obj.attr.value = 1
                    // 方法名冲突,属性加下划线
                    obj._attr.value = 2
                },
                onComplete: function () {
                //    遍历完调用
                }
            })
              
    });
}

setImmediate(main)

枚举

遍历已加载的类

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
    // 异步遍历已加载的类
    Java.enumerateLoadedClasses({
        onMatch: function (name, handle) {
            if (name.indexOf("过滤类名") !== -1) {
                var clazz = Java.use(name)
                // 获取所有方法
                var methods = clazz.class.getDeclaredMethods()

                // hook 所有方法, 指定某个类也可以
                for (var i = 0; i < methods.length; i++) {
                    var methodName = methods[i].getName()
                    clazz[methodName].implementation = function () {
                        return clazz[methodName].apply(this, arguments)
                    }

                }
            }
        },
        onComplete: function () {
            //    遍历完执行
        }
    })

    // 同步获取所有类
    var classes = Java.enumerateLoadedClassesSync()
    for (var i = 0; i < classes.length; i++) {
        if (classes[i].indexOf("过滤类名") !== -1) {
            var clazz = Java.use(classes[i])
            // 所有方法
            var methods = clazz.class.getDeclaredMethods()
        }
    }

动态加载的dex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  Java.enumerateClassLoaders({
        // hook 动态加载的dex
        onMatch: function (loader) {
            try {
                // 有可能枚举到的加载不到这个类 抛异常所以 try catch
                if (loader.loadClass("类全路径")){
                    // 替换默认Java的classloader 直接用use还会用旧的loader
                    Java.classFactory.loader = loader
                    Java.use("类全路径")


                }
            }catch (e) {

            }

        },
        onComplete: function () {

        }
    })

主动调用类函数

image-20221203120056379

dex注入

写一个java class 打包成dex,可以删掉一些没用的smail,重新打包成dex,放到手机目录

image-20221203155400509

写文件

1
2
3
4
var ios = new File("/sdcard/xiaojianbang.txt", "w");
ios.write("xiaojianbang is very good!!!\n");
ios.flush();
ios.close();

调用栈

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

SO

枚举

导入导出表

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(){

    var imports = Module.enumerateImportsSync("libhello.so");
    for(var i = 0; i < imports.length; i++) {
        if(imports[i].name == 'strncat'){
            send(imports[i].name + ": " + imports[i].address);
            break;
        }
    }

    var exports = Module.enumerateExportsSync("libhello.so");
    for(var i = 0; i < exports.length; i++) {
        if(exports[i].name.indexOf('add') != -1){
            send(exports[i].name + ": " + exports[i].address);
            break;
        }
    }
    for(var i = 0; i < imports.length; i++) {
            send(imports[i].name + ": " + imports[i].address);
        }
        var exports = Module.enumerateExportsSync("libhello.so");
        for(var i = 0; i < exports.length; i++) {
                send(exports[i].name + ": " + exports[i].address);
    }

});

so 文件

1
2
3
4
5
6
7
8
9
10
function hook_native(){
    var modules = Process.enumerateModules();
    for (var i in modules){
        var module = modules[i];
        console.log(module.name);
        if (module.name.indexOf("target.so") > -1 ){
            console.log(module.base);
        }
    }
}

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
//  通过导出函数名定位 native方法
var nativePointer = Module.findExportByName("libhello.so", "Java_com_xiaojianbang_app_NativeHelper_add");
    send("native: " + nativePointer);
// 通过 Intercept 拦截器打印 native 方法参数和返回值, 并修改返回值
    Interceptor.attach(nativePointer, {
         // 在进入该函数时调用  args传入该函数的参数
        onEnter: function(args){
            send(args[0]);
            send(args[1]);
            send(args[2].toInt32());
            send(args[3].toInt32());
            send(args[4].toInt32());
        },
      // 函数返回时被调用 retval该函数的返回值
        onLeave: function(retval){
            send(retval.toInt32());
                // const dstAddr = Java.vm.getEnv().newStringUtf("adf大发发热热气人");
                // retval.replace(dstAddr)
          // 修改返回值
            returnValue.replace(100);
          //  以替换为指针 ptr()接收一个字符串,用于构造一个指针
          retval.replace(ptr("0x1234"));

        }
    });

hook未导出函数sub_1232 之类,这里需要根据函数特征(比如字符串等),手动搜索关键字符串定位函数地址

  • 写代码时使用attribute((visibility("hidden")))关键字隐藏导出
  • 编译后被开发者、加固壳或者第三方框架修改elf格式被加密抹去相关信息。
1
2
3
4
5
//extern "C" c语言格式导出
__attribute__((visibility("hidden"))) void func_no_exp()
{
    LOGD("hidden");
}

因为无导出的函数无法通过函数名去定位地址,所以这里只能通过手动定位去找到函数对应的偏移,注意确定偏移的时候要注意使用的 so 是v7 arm32 还是 v8 arm64

这里可以根据情况用字符串或者看上下级调用定位到偏移,这里函数 func_no_exp的偏移是 0x7078(这种函数在 IDA 里面一般是 sub_xxx xxx 是 16 进制的地址),如下图: 在这里插入图片描述

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
var str_name_so = "libnative-lib.so";    //要hook的so名
var n_addr_func_offset = 0x7078;         //要hook的函数在函数里面的偏移

//加载到内存后 函数地址 = so地址 + 函数偏移 (IDA address)
var n_addr_so = Module.findBaseAddress(str_name_so);
var n_addr_func = n_addr_so.add(n_addr_func_offset);


Interceptor.attach(n_addr_func, 
{
    onEnter: function(args) 
    {
        console.log("hook on enter no exp");
    },
    onLeave:function(retval)
    {
        console.log("hook on Leave no exp");
    }
});


// 主动调用
var add1 = new NativeFunction(n_addr_func, "int", ["int", "int"]);
console.log("add1 result is ->" + add1(10, 20));

在 Thumb 指令集下,偏移地址需要进行 +1 操作

判断指令

通过Edit->segments->change segment register value;或者 alt +G

改变T的值

  • 0为Arm指令,

  • 1为Thumb指令

替换原方法

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

指针返回值

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
Java.perform(function(){

    var soAddr = Module.findBaseAddress("libhello.so");
    send('soAddr: ' + soAddr);
    var resultPtr = null;
    var MD5FinalAddr = soAddr.add(0x1768 + 1);
    send('MD5FinalAddr: ' + MD5FinalAddr);
    Interceptor.attach(MD5FinalAddr, {
        onEnter: function(args){
            send(args[0]);
            send(args[1]);
            resultPtr = args[1];
        },
        onLeave: function(retval){
            send(retval);
            // 返回值是指针的指针char **p, ptr才是真正值的指针
             ptr = Memory.readPointer(resultPtr)
            
            //
            var buffer = Memory.readByteArray(resultPtr, 16);
            console.log(hexdump(buffer, {
                offset: 0,
                length: 16,
                header: true,
                ansi: false
            }));
        }
    });

});

hook dlopen

  • dlopen:该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。这种机制使得在系统中添加或者删除一个模块时,都不需要重新进行编译。
  • dlsym:在打开的动态库中查找符号的值
  • dlclose:关闭动态库。

  • dlerror:返回一个描述最后一次调用dlopen、dlsym,或dlclose的错误信息的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
console.log(android_dlopen_ext);
if(android_dlopen_ext != null){
    Interceptor.attach(android_dlopen_ext,{
        onEnter: function(args){
            var soName = args[0].readCString();
            console.log(soName);
            if(soName.indexOf("libc.so") != -1){
                this.hook = true;
            }
        },
        onLeave: function(retval){
            if(this.hook) {
                dlopentodo();
            };
        }
    });

jni

JNIEnv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java.perform(function(){

    var nativePointer = Module.findExportByName("libhello.so", "Java_com_xiaojianbang_app_NativeHelper_helloFromC")
    send("native: " + nativePointer);
    Interceptor.attach(nativePointer, {
        onEnter: function(args){

        },
        onLeave: function(retval){
            var env = Java.vm.getEnv();
            var jstring = env.newStringUtf('xiaojianbang');
            send(jstring);
            retval.replace(jstring);
        }
    });

});

libart https://github.com/lasting-yang/frida_hook_libart

libart.so 动态库是 Android 的 Art 虚拟机使用的动态库 。Android 5.1 及以上系统使用 Art 虚拟机

java虚拟机一般都是以动态库的形式暴露给外界,供外部开发者来集成使用的。 在art中,它的文件名叫做libart.so

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
// jni 系统函数都在 libart.so 中
var module_libart = Process.findModuleByName("libart.so");
var symbols = module_libart.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
  var name = symbols[i].name;
  if ((name.indexOf("JNI") >= 0) 
      && (name.indexOf("CheckJNI") == -1) 
      && (name.indexOf("art") >= 0)) {
    if (name.indexOf("GetStringUTFChars") >= 0) {
      console.log(name);
      // 获取到指定 jni 方法地址
      GetStringUTFChars_addr = symbols[i].address;
    }
  }
}

 Interceptor.attach(GetStringUTFChars_addr, {
            onEnter: function(args){
                // console.log("args[0] is : ", args[0]);
                // console.log("args[1] is : ", args[1]);
                console.log("native args[1] is :",Java.vm.getEnv().getStringUtfChars(args[1],null).readCString());
                console.log('GetStringUTFChars onEnter called from:\n' +
                    Thread.backtrace(this.context, Backtracer.FUZZY)
                    .map(DebugSymbol.fromAddress).join('\n') + '\n');
                // console.log("native args[1] is :", Java.cast(args[1], Java.use("java.lang.String")));
                // console.log("native args[1] is :", Memory.readCString(Java.vm.getEnv().getStringUtfChars(args[1],null)));
            }, onLeave: function(retval){
                // retval const char*
                console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString());
            }
        })

libc

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
// hook libc.so
var pthread_create_addr = null;

// console.log(JSON.stringify(Process.enumerateModules())); 
// Process.enumerateModules() 枚举加载的so文件
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++){
    if (symbols[i].name === "pthread_create"){
        // console.log("symbols name is -> " + symbols[i].name);
        // console.log("symbols address is -> " + symbols[i].address);
        pthread_create_addr = symbols[i].address;
    }
}

Interceptor.attach(pthread_create_addr,{
    onEnter: function(args){
        console.log("args is ->" + args[0], args[1], args[2],args[3]);
    },
    onLeave: function(retval){
        console.log(retval);
    }
});

}

libc 替换

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
// hook 检测frida 的方法
function main() {
    // var exports = Process.findModuleByName("libnative-lib.so").enumerateExports(); 导出
    // var imports = Process.findModuleByName("libnative-lib.so").enumerateImports(); 导入
    // var symbols = Process.findModuleByName("libnative-lib.so").enumerateSymbols(); 符号

    var pthread_create_addr = null;
    var symbols = Process.getModuleByName("libc.so").enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        if (symbol.name === "pthread_create") {
            pthread_create_addr = symbol.address;
            console.log("pthread_create name is ->", symbol.name);
            console.log("pthread_create address is ->", pthread_create_addr);
        }
    }

    Java.perform(function(){
        // 定义方法 之后主动调用的时候使用
        var pthread_create = new NativeFunction(pthread_create_addr, 'int', ['pointer', 'pointer','pointer','pointer'])
        Interceptor.replace(pthread_create_addr,new NativeCallback(function (a0, a1, a2, a3) {
            var result = null;
            var detect_frida_loop = Module.findExportByName("libnative-lib.so", "_Z17detect_frida_loopPv");
            console.log("a0,a1,a2,a3 ->",a0,a1,a2,a3);
            if (String(a2) === String(detect_frida_loop)) {
                result = 0;
                console.log("阻止frida反调试启动");
            } else {
                result = pthread_create(a0,a1,a2,a3);
                console.log("正常启动");
            }
            return result;
        }, 'int', ['pointer', 'pointer','pointer','pointer']));
    })
}

调用栈

1
2
3
4
5
6
7
8
Interceptor.attach(f, {
  onEnter: function (args) {
    console.log('RegisterNatives called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
  }
});