众所周知,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
bingo。。成功获取!!!
打开IDA继续我们的苦逼生活。。。。。。。。。。。。。