使用frida获取jni动态注册函数的地址

众所周知,frida是一款跨平台的(适用于Windows,Android,IOS等等)的Hook框架,由于其使用的是ptrace注入方法,所以相比于Xposed框架,优点是Hook代码写完或修改后,不需要重启手机。由于使用的是js,python脚本语言,所以支持代码热更新(更新Hook代码时不需要重启应用)。

而我这次使用frida目的是获取Android应用里jni动态注册函数的地址,大家都知道,jni注册native函数有两种:一种是静态方式(通过函数名称来匹配);而另一种就是动态注册(通过env->RegisterNatives函数),所以可以通过hook RegisterNatives函数来获取所有动态注册的native函数地址,但是由于我不知道如何用frida来找到RegisterNatives的地址,所以没有选择hook这个函数(如果有同学知道,可以告诉我。。)

我选择的是hook dvmCallJNIMethod,因为native层函数都是通过dvmCallJNIMethod函数来调用的(参考android源码),所以hook这个函数也可以达到我们的目的。

dvmCallJNIMethod的定义如下:

void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self);

其中第三个参数Method结构体中保存了Native函数的一些信息,包括函数名,函数地址等等,其定义如下:

struct Method {
    /* the class we are a part of */
    ClassObject*    clazz;

    /* access flags; low 16 bits are defined by spec (could be u2?) */
    u4              accessFlags;

    /*
     * For concrete virtual methods, this is the offset of the method
     * in "vtable".
     *
     * For abstract methods in an interface class, this is the offset
     * of the method in "iftable[n]->methodIndexArray".
     */
    u2             methodIndex;

    /*
     * Method bounds; not needed for an abstract method.
     *
     * For a native method, we compute the size of the argument list, and
     * set "insSize" and "registerSize" equal to it.
     */
    u2              registersSize;  /* ins + locals */
    u2              outsSize;
    u2              insSize;

    /* method name, e.g. "" or "eatLunch" */
    const char*     name;

    /*
     * Method prototype descriptor string (return and argument types).
     *
     * TODO: This currently must specify the DexFile as well as the proto_ids
     * index, because generated Proxy classes don't have a DexFile.  We can
     * remove the DexFile* and reduce the size of this struct if we generate
     * a DEX for proxies.
     */
    DexProto        prototype;

    /* short-form method descriptor string */
    const char*     shorty;

    /*
     * The remaining items are not used for abstract or native methods.
     * (JNI is currently hijacking "insns" as a function pointer, set
     * after the first call.  For internal-native this stays null.)
     */

    /* the actual code */
    const u2*       insns;          /* instructions, in memory-mapped .dex */

    /* JNI: cached argument and return-type hints */
    int             jniArgInfo;

    /*
     * JNI: native method ptr; could be actual function or a JNI bridge.  We
     * don't currently discriminate between DalvikBridgeFunc and
     * DalvikNativeFunc; the former takes an argument superset (i.e. two
     * extra args) which will be ignored.  If necessary we can use
     * insns==NULL to detect JNI bridge vs. internal native.
     */
    DalvikBridgeFunc nativeFunc;

    /*
     * JNI: true if this static non-synchronized native method (that has no
     * reference arguments) needs a JNIEnv* and jclass/jobject. Libcore
     * uses this.
     */
    bool fastJni;

    /*
     * JNI: true if this method has no reference arguments. This lets the JNI
     * bridge avoid scanning the shorty for direct pointers that need to be
     * converted to local references.
     *
     * TODO: replace this with a list of indexes of the reference arguments.
     */
    bool noRef;

    /*
     * JNI: true if we should log entry and exit. This is the only way
     * developers can log the local references that are passed into their code.
     * Used for debugging JNI problems in third-party code.
     */
    bool shouldTrace;

    /*
     * Register map data, if available.  This will point into the DEX file
     * if the data was computed during pre-verification, or into the
     * linear alloc area if not.
     */
    const RegisterMap* registerMap;

    /* set if method was called during method profiling */
    bool            inProfile;
};

其中name字段(偏移为0x10)指向了函数名,而insns字段(偏移为0x20)则保存了函数的地址。

 

这次我们要获取的是某app的一个native函数的地址,名为doCommandNative。。

接下来我们通过使用frida来hook dvmCallJNIMethod,js代码如下:

Interceptor.attach(Module.getExportByName('libdvm.so','_Z16dvmCallJNIMethodPKjP6JValuePK6MethodP6Thread'),{
    onEnter:function(args){
        //获取函数名
        var method_name=args[2].add(0x10).readPointer().readUtf8String();

        //获取函数地址
        var method_insns=args[2].add(0x20).readPointer();
        
        //判断是否是所要查找函数
        if(method_name=='doCommandNative'){
            //创建模块快照
            var mod_map=new ModuleMap();
            var mod=mod_map.find(method_insns);
            var offset=method_insns.sub(mod.base);

            console.log("module_name: "+mod.name+"       "+"offset: "+offset.toString());
        }         
    }
});

简要说明:

1. dvmCallJNIMethod位于libdvm.so中,由于c++名称粉碎机制,_Z16dvmCallJNIMethodPKjP6JValuePK6MethodP6Thread是其导出名称。

2. 使用frida中的ModuleMap对象来获取进程内的模块快照,可快速判断进程中的一个地址位于哪个模块内。

 

启动应用,命令行输入frida -U 进程名 -l js脚本名(全路径),向目标进程注入frida脚本执行。

使用应用,触发其调用doCommandNative

使用frida获取jni动态注册函数的地址_第1张图片

bingo。。成功获取!!!

打开IDA继续我们的苦逼生活。。。。。。。。。。。。。

你可能感兴趣的:(使用frida获取jni动态注册函数的地址)