frida汇总(待续)

总论

bootstrapper 是一个在后台运行的进程,当 Frida 附加到一个正在运行的应用程序时,它会使用 ptrace 来劫持线程。然后,bootstrapper 会创建一个新线程,连接到设备上运行的 Frida 服务器,并加载一个包含 Frida 代理和我们的instrumentation 代码的动态生成的库。最后,这个被劫持的线程会被恢复到原来的状态并继续执行,进程也会继续正常运行。

发现设备和应用

#To list the available devices for frida
frida-ls-devices

# Connect Frida to an iPad over USB and list running processes
$ frida-ps -U

# List running applications
$ frida-ps -Ua

# List installed applications
$ frida-ps -Uai

# Connect Frida to the specific device
$ frida-ps -D 0216027d1d6d3a03

启动Frida - CLI

#Hooking before starting the app
frida -U --no-pause -l hookNative.js -f com.erev0s.jniapp

#Basic frida hooking
frida -U com.erev0s.jniapp -l hookNative.js

用Python操作frida

import frida

def on_message(message, data):
    print("[on_message] Message: {}".format(message))
    if message['type'] == 'send':
        print("[on_message] Data: {}".format(data))
    else:
        print("[on_message] Error: {}".format(message['description']))

device = frida.get_usb_device()
pid = device.spawn(["com.example.app"])
session = device.attach(pid)

with open("hook_example.js") as f:
    script = session.create_script(f.read())

script.on("message", on_message)
script.load()

device.resume(pid)

Interceptor.attach(ptr('0x10000000'), {
    onEnter: function (args) {
        send({ type: 'log', data: 'Hooked function called!' });
    },
    onLeave: function (retval) {
        send({ type: 'log', data: 'Hooked function returned!' });
    }
});

运行python

python hook_example.py

插桩 instrumentation

启动Frida主要有两种方式:
一种是在早期进行代码修改(早期 instrumentation),另一种则不进行早期代码修改(没有早期 instrumentation)。
早期 instrumentation:这是在目标进程启动之前修改其二进制文件。这样你可以将自定义代码插入到目标进程执行的特定点。这可以通过在frida命令行中使用–patches或-p选项来实现。 早期插桩意味着我们在应用程序执行之前就加载我们的 JavaScript 脚本。这样,如果应用程序在初始化时有一些安全检查,我们也能够拦截这些检查。

# Early instrumentation
import frida, sys, time

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

js = """js code here"""

device = frida.get_usb_device()
pid = device.spawn(["com.erev0s.jniapp"])
session = device.attach(pid)
script = session.create_script(js)
script.on('message', on_message)
script.load()
device.resume(pid)
sys.stdin.read()

没有早期 instrumentation:这是在不修改目标进程的二进制文件的情况下启动Frida。相反,你依赖Frida内置的仪器来监控和与目标进程交互。这是Frida的默认操作模式。

# Normal start - app needs to be opened
import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

js = """js code here"""

process = frida.get_usb_device().attach('com.erev0s.jniapp')
script = process.create_script(js)
script.on('message', on_message)
script.load()
sys.stdin.read()

查看android的本地方法

var RevealNativeMethods = function() {
  var pSize = Process.pointerSize;
  var env = Java.vm.getEnv();
  var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
  var jclassAddress2NameMap = {};
  function getNativeAddress(idx) {
    return env.handle.readPointer().add(idx * pSize).readPointer();
  }
  // intercepting FindClass to populate Map<address, jclass>
  Interceptor.attach(getNativeAddress(FindClassIndex), {
    onEnter: function(args) {
      jclassAddress2NameMap[args[0]] = args[1].readCString();
    }
  });
  // RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977
  Interceptor.attach(getNativeAddress(RegisterNatives), {
    onEnter: function(args) {
      for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) {
        /*
          https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129
          typedef struct {
             const char* name;
             const char* signature;
             void* fnPtr;
          } JNINativeMethod;
        */
        var structSize = pSize * 3; // = sizeof(JNINativeMethod)
        var methodsPtr = ptr(args[2]);
        var signature = methodsPtr.add(i * structSize + pSize).readPointer();
        var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr
        var jClass = jclassAddress2NameMap[args[0]].split('/');
        console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({
          module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol
          package: jClass.slice(0, -1).join('.'),
          class: jClass[jClass.length - 1],
          method: methodsPtr.readPointer().readCString(), // char* name
          signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java
          address: fnPtr
        }), '\x1b[39;49;00m');
      }
    }
  });
}

Java.perform(RevealNativeMethods);

枚举本地库

Module.enumerateExports("mylib.so", {
    onMatch: function(e) {
        if (e.type == 'function') {
            console.log("name of function = " + e.name);

            if (e.name == "Java_example_decrypt") {
                console.log("Function Decrypt recognized by name");
                Interceptor.attach(e.address, {
                    onEnter: function(args) {
                        console.log("Interceptor attached onEnter...");
                    },
                    onLeave: function(retval) {
                        console.log("Interceptor attached onLeave...");
                    }
                });
            }
        }
    },
    onComplete: function() {}
});

修改本地函数的返回值

Interceptor.attach(Module.getExportByName('libnative-lib.so', 'Jniint'), {
    onEnter: function(args) {
      this.first = args[0].toInt32(); // int
      console.log("on enter with: " + this.first)
    },
    onLeave: function(retval) {
      const dstAddr = Java.vm.getEnv().newIntArray(1117878);
      console.log("dstAddr is : " + dstAddr.toInt32())
      retval.replace(dstAddr);
    }
});

如果知道函数名,用findExportByName来拦截函数

Interceptor.attach(Module.findExportByName(null, "fopen"), {
    onEnter: function(args) {
        console.log("Interceptor attached onEnter...");
    },
    onLeave: function(args) {
        console.log("Interceptor attached onLeave...");
    }
}

null表示搜索所有动态库

搜索所有的动态库,查找想要拦截的导出函数

Process.enumerateModules()
    .filter(function(m) {
        return m["path"].toLowerCase().indexOf("libnative") != -1;
    })
    .forEach(function(mod) {
        console.log(JSON.stringify(mod));
        mod.enumerateExports().forEach(function(exp) {
            if (exp.name.indexOf("fopen") != -1) {
                console.log("fopen found!");
            }
        })
    });

用地址拦截JNI

var moduleName = "mylib.so"; 
var nativeFuncAddr = 0x1111;

Interceptor.attach(Module.findExportByName(null, "fopen"), {
    onEnter: function(args) {
        this.lib = Memory.readUtf8String(args[0]);
        console.log("fopen ==> " + this.lib);
    },
    onLeave: function(retval) {
        if (this.lib.endsWith(moduleName)) {
            console.log(retval);
            var baseAddr = Module.findBaseAddress(moduleName);
            Interceptor.attach(baseAddr.add(nativeFuncAddr), {
                onEnter: function(args) {
                    console.log("hook invoked");
                    console.log(JSON.stringify({
                        a1: args[1].toInt32(),
                        a2: Memory.readUtf8String(Memory.readPointer(args[2])),
                        a3: Boolean(args[3])
                    }, null, '\t'));
                }
            });
        }
    }
});

可用nm --demangle --dynamic mylib.so 来获取函数地址。
nm 是 “name” 的缩写,表示显示符号表;
–demangle 选项用于将C++编译器生成的符号名进行反混淆,以便于阅读;它会尝试将编译后的符号名称“解码”回人类可读的源代码名称。这通常用于识别编译后的函数名,它们可能会因为编译器的优化而被转换成缩写或混淆的名称。
–dynamic 选项用于指示 nm 专注于动态链接时使用的符号。这意味着它会列出与动态链接相关的符号,这通常包括外部引用的函数和变量,以及动态加载的模块中的符号

$ nm --demangle --dynamic libnative-lib.so 
00002000 A __bss_start
         U __cxa_atexit
         U __cxa_finalize
00002000 A _edata
00002000 A _end
00000630 T Java_com_erev0s_jniapp_MainActivity_Jniint
000005d0 T Jniint
         U rand
         U srand
         U __stack_chk_fail
         U time

我们的目标是改变apk的执行流程,以便Jniint返回一个由我们定义的值。
这个问题有两种解决方法:
在Java层进行hook,也就是说我们拦截Java对JNI的调用,因此我们根本不需要处理C代码。
深入到C语言中实现Jniint,并在那里进行我们的调整。
通常第一种方法更容易实现,但有时第二种方法可能更方便。这完全取决于应用程序正在做什么以及你试图实现什么。
方法1

Java.perform(function () {
  // we create a javascript wrapper for MainActivity
  var Activity = Java.use('com.erev0s.jniapp.MainActivity');
  // replace the Jniint implementation
  Activity.Jniint.implementation = function () {
    // console.log is used to report information back to us
    console.log("Inside Jniint now...");
    // return this number of our choice
    return 80085
  };
});

方法2

Interceptor.attach(Module.getExportByName('libnative-lib.so', 'Jniint'), {
    onEnter: function(args) {
    },
    onLeave: function(retval) {
      // simply replace the value to be returned with 0
      retval.replace(0);
    }
});

枚举所有的类并过滤包含某字符串的类

Java.perform(function() {
    Java.enumerateLoadedClasses({
        "onMatch": function(c) {
            if (c.includes("erev0s")) {
                console.log(c);
            }
        },
        onComplete: function() {}
    });
});

拦截setText并改变设置的文本

Java.perform(function() {
    var textViewClass = Java.use("android.widget.TextView");
    // Lets overload the setText here
    textViewClass.setText.overload("java.lang.CharSequence").implementation = function(x) {
        var string = Java.use('java.lang.String');
        return this.setText(string.$new("erev0s.com is CHANGED"));
    }
});

你可能感兴趣的:(frida)