本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77942585
前面的博客《Android平台dalvik模式下java Hook框架 ddi 的分析(1)》中,已经分析了dalvik模式下 ddi 框架Hook java方法的原理和流程,这里来学习一下ddi框架代码中涉及到的 dex文件的注入和调用。将一个Android的so库文件跨进程注入到另一个进程中,在so库文件的实现里,我们可以做很多的事情,例如:inline Hook,java方法的Hook,dex文件的注入和调用,ndk的jni函数的Hook等等。
1.ddi框架在进行dex文件的注入和调用是在原来dalvik模式下java方法Hook的基础上修改过来的,在 hijack注入工具将android so库文件注入到目标pid进程时实现android的inline Hook操作,为了保证android的inline Hook操作的顺利执行,需要为注入到目标pid进程中android so库文件定义 .init段或者.init_array段的构造函数,如下图中的my_init构造函数;在inline Hook操作的自定义函数my_epoll_wait里进行dalvik虚拟机模式下的java方法Hook操作。
代码的流程梳理如下:
1. 在被注入到目标pid进程中动态库文件 libsmsdispatch.so 的Android.mk配置文件。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libsmsdispatch
LOCAL_SRC_FILES := smsdispatch.c.arm
LOCAL_C_INCLUDES := ../../../../adbi/instruments/base/ ../../../dalvikhook/jni/
LOCAL_LDLIBS := -L../../../dalvikhook/jni/libs -ldl -ldvm
LOCAL_LDLIBS := -Wl,--start-group ../../../../adbi/instruments/base/obj/local/armeabi/libbase.a ../../../dalvikhook/obj/local/armeabi/libdalvikhook.a -Wl,--end-group
LOCAL_CFLAGS := -g
# 生成动态库文件libsmsdispatch.so
include $(BUILD_SHARED_LIBRARY)
2. 动态库文件 libsmsdispatch.so 的.init段或者.init_array段的构造函数的实现,在Android so库文件的构造函数中实现inline Hook操作。
// set my_init as the entry point
void __attribute__ ((constructor)) my_init(void);
void my_init(void)
{
log("libsmsdispatch: started
")
debug = 1;
// set log function for libbase (very important!)
set_logfunction(my_log2);
// set log function for libdalvikhook (very important!)
dalvikhook_set_logfunction(my_log2);
// 对libc.so库文件的epoll_wait函数进行inline Hook操作
hook(&eph, getpid(), "libc.", "epoll_wait", my_epoll_wait, 0);
}
3. 在inline Hook操作的自定义实现函数里实现dalvik模式下的java方法Hook。
// 自定义的inline Hook替换函数
static int my_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
int (*orig_epoll_wait)(int epfd, struct epoll_event *events, int maxevents, int timeout);
orig_epoll_wait = (void*)eph.orig;
// remove hook for epoll_wait
hook_precall(&eph);
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 从加载的libdvm.so库文件中,获取需要的导出函数的地址
dexstuff_resolv_dvm(&d);
// 对com.android.internal.telephony.SMSDispatcher类的dispatchPdus函数进行dalvik Hook操作
dalvik_hook_setup(&dpdu, "Lcom/android/internal/telephony/SMSDispatcher;", "dispatchPdus", "([[B)V", 2, my_dispatch);
dalvik_hook(&d, &dpdu);
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// call original function
int res = orig_epoll_wait(epfd, events, maxevents, timeout);
return res;
}
2.在dalvik模式下的java方法Hook实现的自定义函数里进行dex文件的加载和调用,先来复习一下dalvik模式下java方法的Hook实现步骤。
dalvik模式下java方法Hook的代码流程梳理:
1. dexstuff_resolv_dvm函数--动态加载"libdvm.so"库文件并获取该加载的动态库中dalvik Hook实现需要的导出函数的调用地址和导出全局变量。
// 源码文件 dexstuff.c
// 获取加载的动态库中导出函数的调用地址
static void* mydlsym(void *hand, const char *name)
{
void* ret = dlsym(hand, name);
log("%s = 0x%x
", name, ret)
return ret;
}
// 动态加载"libdvm.so"库文件并获取该加载的动态库中dalvik Hook实现需要的导出函数的调用地址和导出全局变量
void dexstuff_resolv_dvm(struct dexstuff_t *d)
{
// 动态加载"libdvm.so"库文件并保存文件句柄
d->dvm_hand = dlopen("libdvm.so", RTLD_NOW);
log("dvm_hand = 0x%x
", d->dvm_hand)
// 获取加载的文件句柄成功的情况
if (d->dvm_hand) {
// 获取加载的"libdvm.so"库文件中导出函数dvm_dalvik_system_DexFile的调用地址
d->dvm_dalvik_system_DexFile = (DalvikNativeMethod*) mydlsym(d->dvm_hand, "dvm_dalvik_system_DexFile");
// 获取加载的"libdvm.so"库文件中导出函数dvm_java_lang_Class的调用地址
d->dvm_java_lang_Class = (DalvikNativeMethod*) mydlsym(d->dvm_hand, "dvm_java_lang_Class");
// 获取加载的"libdvm.so"库文件中导出函数dvmThreadSelf的调用地址
d->dvmThreadSelf_fnPtr = mydlsym(d->dvm_hand, "_Z13dvmThreadSelfv");
if (!d->dvmThreadSelf_fnPtr)
d->dvmThreadSelf_fnPtr = mydlsym(d->dvm_hand, "dvmThreadSelf");
// 获取加载的"libdvm.so"库文件中导出函数dvmStringFromCStr的调用地址
d->dvmStringFromCStr_fnPtr = mydlsym(d->dvm_hand, "_Z32dvmCreateStringFromCstrAndLengthPKcj");
// 获取加载的"libdvm.so"库文件中导出函数dvmGetSystemClassLoader的调用地址
d->dvmGetSystemClassLoader_fnPtr = mydlsym(d->dvm_hand, "_Z23dvmGetSystemClassLoaderv");
// 获取加载的"libdvm.so"库文件中导出函数dvmIsClassInitialized的调用地址
d->dvmIsClassInitialized_fnPtr = mydlsym(d->dvm_hand, "_Z21dvmIsClassInitializedPK11ClassObject");
// 获取加载的"libdvm.so"库文件中导出函数dvmInitClass的调用地址
d->dvmInitClass_fnPtr = mydlsym(d->dvm_hand, "dvmInitClass");
// 获取加载的"libdvm.so"库文件中导出函数dvmFindVirtualMethodHierByDescriptor的调用地址
d->dvmFindVirtualMethodHierByDescriptor_fnPtr = mydlsym(d->dvm_hand, "_Z36dvmFindVirtualMethodHierByDescriptorPK11ClassObjectPKcS3_");
if (!d->dvmFindVirtualMethodHierByDescriptor_fnPtr)
d->dvmFindVirtualMethodHierByDescriptor_fnPtr = mydlsym(d->dvm_hand, "dvmFindVirtualMethodHierByDescriptor");
// 获取加载的"libdvm.so"库文件中导出函数dvmFindDirectMethodByDescriptor的调用地址
d->dvmFindDirectMethodByDescriptor_fnPtr = mydlsym(d->dvm_hand, "_Z31dvmFindDirectMethodByDescriptorPK11ClassObjectPKcS3_");
if (!d->dvmFindDirectMethodByDescriptor_fnPtr)
d->dvmFindDirectMethodByDescriptor_fnPtr = mydlsym(d->dvm_hand, "dvmFindDirectMethodByDescriptor");
// 获取加载的"libdvm.so"库文件中导出函数dvmIsStaticMethod的调用地址
d->dvmIsStaticMethod_fnPtr = mydlsym(d->dvm_hand, "_Z17dvmIsStaticMethodPK6Method");
// 获取加载的"libdvm.so"库文件中导出函数dvmAllocObject的调用地址
d->dvmAllocObject_fnPtr = mydlsym(d->dvm_hand, "dvmAllocObject");
// 获取加载的"libdvm.so"库文件中导出函数dvmCallMethodV的调用地址
d->dvmCallMethodV_fnPtr = mydlsym(d->dvm_hand, "_Z14dvmCallMethodVP6ThreadPK6MethodP6ObjectbP6JValueSt9__va_list");
// 获取加载的"libdvm.so"库文件中导出函数dvmCallMethodA的调用地址
d->dvmCallMethodA_fnPtr = mydlsym(d->dvm_hand, "_Z14dvmCallMethodAP6ThreadPK6MethodP6ObjectbP6JValuePK6jvalue");
// 获取加载的"libdvm.so"库文件中导出函数dvmAddToReferenceTable的调用地址
d->dvmAddToReferenceTable_fnPtr = mydlsym(d->dvm_hand, "_Z22dvmAddToReferenceTableP14ReferenceTableP6Object");
// 获取加载的"libdvm.so"库文件中导出函数dvmSetNativeFunc的调用地址
d->dvmSetNativeFunc_fnPtr = mydlsym(d->dvm_hand, "_Z16dvmSetNativeFuncP6MethodPFvPKjP6JValuePKS_P6ThreadEPKt");
// 获取加载的"libdvm.so"库文件中导出函数dvmUseJNIBridge的调用地址
d->dvmUseJNIBridge_fnPtr = mydlsym(d->dvm_hand, "_Z15dvmUseJNIBridgeP6MethodPv");
if (!d->dvmUseJNIBridge_fnPtr)
d->dvmUseJNIBridge_fnPtr = mydlsym(d->dvm_hand, "dvmUseJNIBridge");
// 获取加载的"libdvm.so"库文件中导出函数dvmDecodeIndirectRef的调用地址
d->dvmDecodeIndirectRef_fnPtr = mydlsym(d->dvm_hand, "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject");
// 获取加载的"libdvm.so"库文件中导出函数dvmLinearSetReadWrite的调用地址
d->dvmLinearSetReadWrite_fnPtr = mydlsym(d->dvm_hand, "_Z21dvmLinearSetReadWriteP6ObjectPv");
// 获取加载的"libdvm.so"库文件中导出函数dvmGetCurrentJNIMethod的调用地址
d->dvmGetCurrentJNIMethod_fnPtr = mydlsym(d->dvm_hand, "_Z22dvmGetCurrentJNIMethodv");
// 获取加载的"libdvm.so"库文件中导出函数dvmFindInstanceField的调用地址
d->dvmFindInstanceField_fnPtr = mydlsym(d->dvm_hand, "_Z20dvmFindInstanceFieldPK11ClassObjectPKcS3_");
//d->dvmCallJNIMethod_fnPtr = mydlsym(d->dvm_hand, "_Z21dvmCheckCallJNIMethodPKjP6JValuePK6MethodP6Thread");
// 获取加载的"libdvm.so"库文件中导出函数dvmCallJNIMethod的调用地址
d->dvmCallJNIMethod_fnPtr = mydlsym(d->dvm_hand, "_Z16dvmCallJNIMethodPKjP6JValuePK6MethodP6Thread");
// 获取加载的"libdvm.so"库文件中导出函数dvmDumpAllClasses的调用地址
d->dvmDumpAllClasses_fnPtr = mydlsym(d->dvm_hand, "_Z17dvmDumpAllClassesi");
// 获取加载的"libdvm.so"库文件中导出函数dvmDumpClass的调用地址
d->dvmDumpClass_fnPtr = mydlsym(d->dvm_hand, "_Z12dvmDumpClassPK11ClassObjecti");
// 获取加载的"libdvm.so"库文件中导出函数dvmFindLoadedClass的调用地址
d->dvmFindLoadedClass_fnPtr = mydlsym(d->dvm_hand, "_Z18dvmFindLoadedClassPKc");
if (!d->dvmFindLoadedClass_fnPtr)
d->dvmFindLoadedClass_fnPtr = mydlsym(d->dvm_hand, "dvmFindLoadedClass");
// 获取加载的"libdvm.so"库文件中导出函数dvmHashTableLock的调用地址
d->dvmHashTableLock_fnPtr = mydlsym(d->dvm_hand, "_Z16dvmHashTableLockP9HashTable");
// 获取加载的"libdvm.so"库文件中导出函数dvmHashTableUnlock的调用地址
d->dvmHashTableUnlock_fnPtr = mydlsym(d->dvm_hand, "_Z18dvmHashTableUnlockP9HashTable");
// 获取加载的"libdvm.so"库文件中导出函数dvmHashForeach的调用地址
d->dvmHashForeach_fnPtr = mydlsym(d->dvm_hand, "_Z14dvmHashForeachP9HashTablePFiPvS1_ES1_");
// 获取加载的"libdvm.so"库文件中导出函数dvmInstanceof的调用地址
d->dvmInstanceof_fnPtr = mydlsym(d->dvm_hand, "_Z13dvmInstanceofPK11ClassObjectS1_");
// 获取加载的"libdvm.so"库文件中导出全部变量gDvm的调用地址
d->gDvm = mydlsym(d->dvm_hand, "gDvm");
}
}
2. dalvik_hook_setup函数--记录java层目标函数被dalvik Hook操作需要的相关信息结构体,也就是预先设置好dalvik模式下java方法的Hook需要的参数值,方便后面进行java方法的Hook操作。
// 源码文件 dalvik_hook.c
/*
* dalvik_hook_t用于记录被dalvik Hook的java类成员方法的有关信息
* cls为被dalvik Hook的java类成员方法所在的类的签名
* meth为被dalvik Hook的java类成员方法的名称
* sig为被dalvik Hook的java类成员方法的函数签名
* ns为java成员方法被修改为native层实现的方法后调用,传参需要的寄存器的个数
* func为被dalvik Hook的java类成员方法的替换自定义Hook函数
* 调用实例:dalvik_hook_setup(&sb1, "Ljava/lang/StringBuffer;", "toString", "()Ljava/lang/String;", 1, sb1_tostring);
*
* 注意:
* 1.Android的dalvik虚拟机是基于寄存器的,因此java语言实现的函数在执行的时候需要预先计算出函数调用时传递参数需要的寄存器数量insSize以及函数内部
* 局部变量要用到的寄存器的数量outsSize,需要的寄存器总数量为registersSize=outsSize+insSize。
* 2.java的类成员方法中非静态的成员方法的第一个函数参数是指向本身的对象指针然后是其他的传入参数,静态成员方法不存在这样的问题。
* 3.当java层的类成员方法被修改为native属性的成员方法后,由于native属性方法的局部变量的内存申请是通过堆栈来完成的,因此在计算该方法的寄存器数量时,
* 局部变量用到的寄存器的数量outsSize为0,并且总的寄存器数量与传参寄存器的数量相同即registersSize=insSize。
*
*/
// 记录java层目标函数被dalvik Hook操作需要的相关信息结构体
int dalvik_hook_setup(struct dalvik_hook_t *h, char *cls, char *meth, char *sig, int ns, void *func)
{
if (!h)
return 0;
// 保存被dalvik Hook的类成员方法所在的类的名称
strcpy(h->clname, cls);
// 保存被dalvik Hook的类成员方法所在的类的协议名称,如:"java/lang/StringBuffer"
strncpy(h->clnamep, cls+1, strlen(cls)-2);
// 保存被dalvik Hook的类成员方法的名称
strcpy(h->method_name, meth);
// 保存被dalvik Hook的类成员方法的函数签名
strcpy(h->method_sig, sig);
// 提示:下面的部分变量值主要用于该java层的类成员方法meth被修改为native层后寄存器记录变量值的修改
// dalvik Hook的实现其实就是将java层实现的类成员函数修改为native层实现的自定义Hook函数
// 保存被dalvik Hook后java层类成员方法的寄存器数量
// 被dalvik Hook后该native层的函数传参需要的寄存器数量
h->n_iss = ns;
// 被dalvik Hook后该native层的函数需要的总的寄存器的数量
h->n_rss = ns;
// 被dalvik Hook后该native层的函数局部变量需要的寄存器的数量
h->n_oss = 0;
// 被dalvik Hook后该native层的函数被替换的自定义Hook函数
h->native_func = func;
// 记录被dalvik Hook的函数是否是静态成员方法的标志,默认为非静态成员方法即0
h->sm = 0; // set by hand if needed
// java类成员函数的类型属性标志accessFlags,其中accessFlags=0x0100表示该函数为native层实现的函数
h->af = 0x0100; // native, modify by hand if needed
// 记录是否需要保存查找到,将被dalvik Hook的目标函数所在类的指针和方法结构体指针的标志,默认为0-需要
h->resolvm = 0; // don't resolve method on-the-fly, change by hand if needed
// debug调试打印Log日志的开关,默认为0-不打印日志
h->debug_me = 0;
return 1;
}
3. dalvik_hook函数--修改java层目标函数为native属性的jni函数并修正该函数正确调用相关的Method结构体成员的值,主要通过修改java类方法的Method结构体成员的相关值达到实现dalvik模式下java方法的Hook操作。
// 修改java层目标函数为native属性的jni函数并修正该函数正确调用相关的Method结构体成员的值
void* dalvik_hook(struct dexstuff_t *dex, struct dalvik_hook_t *h)
{
if (h->debug_me)
log("dalvik_hook: class %s
", h->clname)
// 调用dalvik虚拟机的函数dvmFindLoadedClass获取被dalvik hook的目标函数所在类的指针
void *target_cls = dex->dvmFindLoadedClass_fnPtr(h->clname);
if (h->debug_me)
log("class = 0x%x
", target_cls)
// print class in logcat
if (h->dump && dex && target_cls){
// 打印找到被dalvik hook的目标函数所在类的名称信息
dex->dvmDumpClass_fnPtr(target_cls, (void*)1);
}
// 判断被dalvik hook的目标函数所在类的指针是否为null并打印日志
if (!target_cls) {
if (h->debug_me)
log("target_cls == 0
")
return (void*)0;
}
// 在被dalvik hook的目标函数所在类的 非静态成员函数 中查找将被dalvik hook的java层目标函数
h->method = dex->dvmFindVirtualMethodHierByDescriptor_fnPtr(target_cls, h->method_name, h->method_sig);
// 如果查找失败
if (h->method == 0) {
// 在被dalvik hook的目标函数所在类的 静态成员函数 中查找将被dalvik hook的java层目标函数
h->method = dex->dvmFindDirectMethodByDescriptor_fnPtr(target_cls, h->method_name, h->method_sig);
}
// constrcutor workaround, see "dalvik_prepare" below
// 保存查找到的,将被dalvik hook的java层目标函数所在类指针和方法结构体指针的信息,用以dalvik Hook后的还原
if (!h->resolvm) {
// 保存查找到将被dalvik hook的java层目标函数所在类的指针
h->cls = target_cls;
// 保存查找到将被dalvik hook的java层目标函数的方法体结构指针
h->mid = (void*)h->method;
}
// 打印查找到被dalvik hook的java层目标函数的信息
if (h->debug_me)
log("%s(%s) = 0x%x
", h->method_name, h->method_sig, h->method)
// 进行目标函数被dalvik Hook操作的修改
if (h->method) {
/*
* dalvik虚拟机中方法结构体Method的提示:
* -----------------------------------------------------------------------------------------------------------
* 1.method->insns:
* 如果这个方法不是Native层实现即java层实现的函数,则这里存放了指向该方法具体的Dalvik指令的指针
* (这个变量指向的是实际加载到内存中的Dalvik指令代码,而不是在Dex文件中的);
* 如果这个方法是一个Dalvik虚拟机自带的Native函数(即Internal Native函数),则这个变量会是Null。
* 如果这个方法是一个普通的Native函数即jni实现的自定义函数,则这里存放了指向jni实际函数机器码的首地址;
* -----------------------------------------------------------------------------------------------------------
* 2.method->jniArgInfo:
* 这个变量记录了一些预先计算好的函数参数信息,从而不需要在函数调用的时候再通过方法的参数和返回值实时计算了,
* 方便了JNI的调用,提高了调用的速度。如果第一位为1(即0x80000000),则Dalvik虚拟机会忽略后面的所有信息,强制在调用时实时计算;
* -----------------------------------------------------------------------------------------------------------
* 3.method->nativeFunc:
* 如果这个方法是一个Dalvik虚拟机自带的Native函数(Internal Native)的话,则这里存放了指向JNI实际函数机器码的首地址。
* 如果这个方法是一个普通的Native函数,则这里将指向jni跳转桥接函数;
* -----------------------------------------------------------------------------------------------------------
* 4.通过method->accessFlags可以判断一个方法是不是Native的(和0x00100相与),如果是Native的话,就直接执行nativeFunc所指向的本地代码,
* 如果不是Native的话,就执行insns所指向的Dalvik代码。
*
* <---------------参考自:http://blog.csdn.net/roland_sun/article/details/38640297--------------->
* 有关Method结构体每一项的实现具体可以参考android的源码
*/
// 保存被dalvik Hook的java层目标函数的dalvik字节码指针method->insns
h->insns = h->method->insns;
if (h->debug_me) {
// 打印被dalvik Hook操作的java层目标函数的原始信息
log("nativeFunc %x
", h->method->nativeFunc)
log("insSize = 0x%x registersSize = 0x%x outsSize = 0x%x
", h->method->insSize, h->method->registersSize, h->method->outsSize)
}
// 保存被dalvik Hook的java层目标函数的寄存器数量信息,用以后面dalvik Hook的恢复还原
// 被dalvik Hook的java层目标函数的传参寄存器的个数
h->iss = h->method->insSize;
// 保存被dalvik Hook的java层目标函数的局部变量使用的寄存器个数
h->oss = h->method->outsSize;
// 保存被dalvik Hook的java层目标函数的总寄存器个数
h->rss = h->method->registersSize;
// 修改被dalvik Hook的java层目标函数的寄存器个数为该目标函数为native属性时的正确个数
h->method->insSize = h->n_iss;
h->method->registersSize = h->n_rss;
h->method->outsSize = h->n_oss;
if (h->debug_me) {
log("shorty %s
", h->method->shorty)
log("name %s
", h->method->name)
log("arginfo %x
", h->method->jniArgInfo)
}
// 修改被dalvik Hook的目标函数的jni参数为运行时实时计算
// 原本函数可能并不是Native的,现在被偷偷改成了Native的,所以肯定不能使用这个域进行优化
h->method->jniArgInfo = 0x80000000; // <--- also important
if (h->debug_me) {
log("noref %c
", h->method->noRef)
log("access %x
", h->method->a)
}
// 保存被dalvik Hook的java层目标函数的原始函数属性标志值
h->access_flags = h->method->a;
// 修改被dalvik Hook的java层目标函数的为native层实现的jni函数
h->method->a = h->method->a | h->af; // make method native
if (h->debug_me)
log("access %x
", h->method->a)
// 调用libdvm.so中的dvmUseJNIBridge函数来实现将method->nativeFunc域改成指向一个JNI桥跳转函数地址(dvmCallJNIMethod)
// 并将method->insns域改成指向真正的jni函数代码即我们自定义实现的dalvik Hook函数代码首地址处
dex->dvmUseJNIBridge_fnPtr(h->method, h->native_func);
if (h->debug_me)
log("patched %s to: 0x%x
", h->method_name, h->native_func)
// 到这里,java层目标函数的dalvik Hook实现完成
return (void*)1;
} else {
// 查找被dalvik hook的java层目标函数失败的情况
if (h->debug_me)
log("could NOT patch %s
", h->method_name)
}
return (void*)0;
}
// 恢复还原被dalvik Hook的java层目标函数
int dalvik_prepare(struct dexstuff_t *dex, struct dalvik_hook_t *h, JNIEnv *env)
{
// this seems to crash when hooking "constructors"
// 判断需要被恢复还原的dalvik Hook目标函数是否存在
if (h->resolvm) {
// 查找指定的目标类
h->cls = (*env)->FindClass(env, h->clnamep);
if (h->debug_me)
log("cls = 0x%x
", h->cls)
if (!h->cls)
return 0;
// 查找指定的目标函数
if (h->sm)
h->mid = (*env)->GetStaticMethodID(env, h->cls, h->method_name, h->method_sig);
else
h->mid = (*env)->GetMethodID(env, h->cls, h->method_name, h->method_sig);
if (h->debug_me)
log("mid = 0x%x
", h-> mid)
if (!h->mid)
return 0;
}
// 恢复被dalvik Hook的java层目标函数的原始寄存器数量
h->method->insSize = h->iss;
h->method->registersSize = h->rss;
h->method->outsSize = h->oss;
// 恢复被dalvik Hook的java层目标函数的原始函数类型属性
h->method->a = h->access_flags;
h->method->jniArgInfo = 0;
// 恢复被dalvik Hook的java层目标函数的dalvik字节码指针
h->method->insns = h->insns;
return 1;
}
// 再次对java层实现的目标函数进行dalvik Hook操作
void dalvik_postcall(struct dexstuff_t *dex, struct dalvik_hook_t *h)
{
// 修改被dalvik Hook的java层目标函数的寄存器数量值
h->method->insSize = h->n_iss;
h->method->registersSize = h->n_rss;
h->method->outsSize = h->n_oss;
//log("shorty %s
", h->method->shorty)
//log("name %s
", h->method->name)
//log("arginfo %x
", h->method->jniArgInfo)
// 修改被dalvik Hook的java层目标函数的参数的计算方法为运行时实时计算
h->method->jniArgInfo = 0x80000000;
//log("noref %c
", h->method->noRef)
//log("access %x
", h->method->a)
// 修改被dalvik Hook的java层目标函数的类型属性为native
h->access_flags = h->method->a;
h->method->a = h->method->a | h->af;
//log("access %x
", h->method->a)
// 修改被dalvik Hook的目标函数的insns为我们自定义的dalvik Hook函数的代码首地址
// 修改nativeFunc为函数正确调用需要的jni跳转桥代码的地址
dex->dvmUseJNIBridge_fnPtr(h->method, h->native_func);
if (h->debug_me)
log("patched BACK %s to: 0x%x
", h->method_name, h->native_func)
}
3.在dalvik模式下java方法Hook的自定义实现函数里进行dex文件的加载和调用。
代码实现流程梳理:
1. 调用libdvm.so库文件中导出函数Dalvik_dalvik_system_DexFile_openDexFileNative加载dex文件到目标pid进程的内存中,该函数对应java层的openDexFile函数。
// 调用libdvm.so库文件中Dalvik_dalvik_system_DexFile_openDexFileNative函数加载dex文件到内存中
int dexstuff_loaddex(struct dexstuff_t *d, char *path)
{
jvalue pResult;
jint result;
log("dexstuff_loaddex, path = 0x%x
", path)
// 调用libdvm.so库文件中函数dvmStringFromCStr将C语言形式的字符串转换成dvmString字符串
void *jpath = d->dvmStringFromCStr_fnPtr(path, strlen(path), ALLOC_DEFAULT);
u4 args[2] = { (u4)jpath, (u4)NULL };
/***
typedef struct DalvikNativeMethod_t {
const char* name; //函数名称
const char* signature; // 函数签名
DalvikNativeFunc fnPtr; // 函数指针
} DalvikNativeMethod;
// dvm_dalvik_system_DexFile是native层函数指针信息的结构体
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{ "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFileNative },
{ "openDexFile", "([B)I",
Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
{ "closeDexFile", "(I)V",
Dalvik_dalvik_system_DexFile_closeDexFile },
{ "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
Dalvik_dalvik_system_DexFile_defineClassNative },
{ "getClassNameList", "(I)[Ljava/lang/String;",
Dalvik_dalvik_system_DexFile_getClassNameList },
{ "isDexOptNeeded", "(Ljava/lang/String;)Z",
Dalvik_dalvik_system_DexFile_isDexOptNeeded },
{ NULL, NULL, NULL },
};
***/
// 调用libdvm.so库文件中函数Dalvik_dalvik_system_DexFile_openDexFileNative实现加载dex文件到内存中,
// 调用DexClassLoader将dex文件加载到内存中最终也是调用native层的Dalvik_dalvik_system_DexFile_openDexFileNative实现dex加载的
d->dvm_dalvik_system_DexFile[0].fnPtr(args, &pResult);
// 函数Dalvik_dalvik_system_DexFile_openDexFileNative被调用后返回的是映射底层DvmDex数据的DexClassLoader.mDexs[0].mCookie值
result = (jint) pResult.l;
log("cookie = 0x%x
", pResult.l)
return result;
}
2. 调用libdvm.so库文件中导出函数Dalvik_dalvik_system_DexFile_defineClassNative实现对指定名称java类的加载。
// 调用Dalvik_dalvik_system_DexFile_defineClassNative函数实现指定名称类的加载
void* dexstuff_defineclass(struct dexstuff_t *d, char *name, int cookie)
{
u4 *nameObj = (u4*) name;
jvalue pResult;
log("dexstuff_defineclass: %s using %x
", name, cookie)
// 调用libdvm.so库文件中的dvmGetSystemClassLoader函数获取当前进程的SystemClassLoader即当前进程的类加载器
void* cl = d->dvmGetSystemClassLoader_fnPtr();
log("sys classloader = 0x%x
", cl)
// 调用libdvm.so库文件中的dvmGetCurrentJNIMethod函数获取当前方法的Method结构体信息
Method *m = d->dvmGetCurrentJNIMethod_fnPtr();
// 打印当前方法所在的classLoader的地址
log("cur m classloader = 0x%x
", m->clazz->classLoader)
// 调用dvmStringFromCStr函数将c语言形式的类名称字符串转换成dvmString形式的字符串
void *jname = d->dvmStringFromCStr_fnPtr(name, strlen(name), ALLOC_DEFAULT);
//log("called string...
")
u4 args[3] = { (u4)jname, (u4) m->clazz->classLoader, (u4) cookie };
// 调用libdvm.so库文件中的(Dalvik_dalvik_system_DexFile_defineClassNative函数)实现目标类name的加载
// 具体就是将cookie中的目标类name加载到内存中并和当前方法的classLoader联系起来
d->dvm_dalvik_system_DexFile[3].fnPtr( args , &pResult );
jobject *ret = pResult.l;
log("class = 0x%x
", ret)
return ret;
}
3. 通过 jni方法反射调用dex文件中的java类方法。
// 替换Android系统原始的dispatchPdus函数的自定义dalvik Hook函数
static void my_dispatch(JNIEnv *env, jobject obj, jobjectArray pdu)
{
// load dex classes
// 调用libdvm.so库文件中Dalvik_dalvik_system_DexFile_openDexFileNative函数加载dex文件到内存中
int cookie = dexstuff_loaddex(&d, "/data/local/tmp/ddiclasses.dex");
log("libsmsdispatch: loaddex res = %x
", cookie)
if (!cookie)
log("libsmsdispatch: make sure /data/dalvik-cache/ is world writable and delete data@local@tmp@ddiclasses.dex
")
// 调用libdvm.so库文件中Dalvik_dalvik_system_DexFile_defineClassNative函数实现指定名称类org/mulliner/ddiexample/SMSDispatch的加载
void *clazz = dexstuff_defineclass(&d, "org/mulliner/ddiexample/SMSDispatch", cookie);
log("libsmsdispatch: clazz = 0x%x
", clazz)
// call constructor and passin the pdu
// 上面的步骤中已经实现了org.mulliner.ddiexample.SMSDispatch类的加载
// 在当前进程中查找获取目标类org.mulliner.ddiexample.SMSDispatch
jclass smsd = (*env)->FindClass(env, "org/mulliner/ddiexample/SMSDispatch");
// 获取目标类org.mulliner.ddiexample.SMSDispatch构造函数的Method结构体的信息
jmethodID constructor = (*env)->GetMethodID(env, smsd, "<init>", "([[B)V");
if (constructor) {
jvalue args[1];
args[0].l = pdu;
// 调用目标类org.mulliner.ddiexample.SMSDispatch的构造函数实现对象org.mulliner.ddiexample.SMSDispatch的实例化
jobject objj = (*env)->NewObjectA(env, smsd, constructor, args);
log("libsmsdispatch: new obj = 0x%x
", obj)
if (!objj)
log("libsmsdispatch: failed to create smsdispatch class, FATAL!
")
} else {
// 获取目标类org.mulliner.ddiexample.SMSDispatch的构造函数失败
log("libsmsdispatch: constructor not found!
")
}
// call original SMS dispatch method
jvalue args[1];
args[0].l = pdu;
// 恢复还原被dalvik Hook的java层com.android.internal.telephony.SMSDispatcher类的目标函数dispatchPdus
dalvik_prepare(&d, &dpdu, env);
// 调用原始的java层目标函数dispatchPdus(即调用原来的函数)
(*env)->CallVoidMethodA(env, obj, dpdu.mid, args);
log("success calling : %s
", dpdu.method_name)
// 再次对java层目标函数dispatchPdus进行dalvik Hook操作
dalvik_postcall(&d, &dpdu);
}
4.Android dex文件注入和调用的总结
1. root权限下实现跨进程注入android so库文件到指定pid目标进程中,编写实现注入so库文件的.init段或者.init_array段的构造函数。
2. 在注入so库文件的代码中采用类似于android加固的方法,在目标pid进程中通过 DexClassLoader来加载dex文件并进行dex文件中指定目标java类方法的调用。
5. 被注入的dex文件的代码 SMSDispatch.java 的实现。
为了实现Android dex文件的注入和调用,因此在进行Android dex文件注入之前需要先将 SMSDispatch.java文件编译为dex文件格式。
/*
* Collin's Dynamic Dalvik Instrumentation Toolkit for Android
* Collin Mulliner <collin[at]mulliner.org>
*
* (c) 2012,2013
*
* License: LGPL v2.1
*
*/
package org.mulliner.ddiexample;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.GregorianCalendar;
import android.app.Application;
import android.content.Intent;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsMessage;
// takes incoming SMS message, reverses the body message and injects it back into the system (will appear as a 2nd SMS message)
public class SMSDispatch
{
public SMSDispatch(byte pdus[][])
{
System.out.println("org.mulliner.ddiexample.SMSDispatch(pdu)");
SmsMessage s1 = SmsMessage.createFromPdu(pdus[0]);
if (s1 != null) {
System.out.println("ddiexample: incoming SMS");
System.out.println("ddiexample: " + s1.getMessageBody());
System.out.println("ddiexample: " + s1.getOriginatingAddress());
if (s1.getMessageBody() != null) {
String rs = "";
// reverse message
for (int i = s1.getMessageBody().length() - 1; i >= 0; i--)
rs = rs + s1.getMessageBody().charAt(i);
byte p[] = createFakeSms(s1.getOriginatingAddress(), rs);
byte sp[][] = new byte[1][p.length];
System.arraycopy(p, 0, sp[0], 0, p.length);
System.out.println("ddiexample: fake SMS");
System.out.println("ddiexample: " + rs);
Intent intent = new Intent("android.provider.Telephony.SMS_RECEIVED");
intent.putExtra("pdus", sp);
intent.putExtra("format", "3gpp");
System.out.println(intent.toString());
// get a context
Application a = getcon();
// send intent
a.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
System.out.println("ddiexample: appname: " + a.toString());
}
}
}
public Application getcon()
{
try {
final Class<?> activityThreadClass =
Class.forName("android.app.ActivityThread");
if (activityThreadClass == null)
System.out.println("activityThreadClass == null");
final Method method = activityThreadClass.getMethod("currentApplication");
Application app = (Application) method.invoke(null, (Object[]) null);
if (app == null) {
System.out.println("getcon app == null");
final Method method2 = activityThreadClass.getMethod("getApplication");
if (method2 == null)
System.out.println("method2 == null");
if (app == null) {
System.out.println("getcon 2 app == null");
try {
Field f = activityThreadClass.getField("mInitialApplication");
app = (Application) f.get(activityThreadClass);
} catch (Exception e) {}
}
if (app == null)
System.out.println("getcon 3 app == null");
}
return app;
} catch (final ClassNotFoundException e) {
// handle exception
System.out.println(e.toString());
} catch (final NoSuchMethodException e) {
// handle exception
System.out.println(e.toString());
} catch (final IllegalArgumentException e) {
// handle exception
System.out.println(e.toString());
} catch (final IllegalAccessException e) {
// handle exception
System.out.println(e.toString());
} catch (final InvocationTargetException e) {
// handle exception
System.out.println(e.toString());
}
System.out.println("getcon == null :-(");
return null;
}
//
// reverseBytes and createFakesSms code taken from Thomas Cannon's SMSSpoof.java
// https://github.com/thomascannon/android-sms-spoof/blob/master/SMSSpoofer/src/net/thomascannon/smsspoofer/SMSSpoof.java
//
private static byte reverseByte(byte b)
{
return (byte) ((b & 0xF0) >> 4 | (b & 0x0F) << 4);
}
private static byte[] createFakeSms(String sender, String body)
{
//Source: http://stackoverflow.com/a/12338541
//Source: http://blog.dev001.net/post/14085892020/android-generate-incoming-sms-from-within-your-app
byte[] scBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD("0000000000");
byte[] senderBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(sender);
int lsmcs = scBytes.length;
byte[] dateBytes = new byte[7];
Calendar calendar = new GregorianCalendar();
dateBytes[0] = reverseByte((byte) (calendar.get(Calendar.YEAR)));
dateBytes[1] = reverseByte((byte) (calendar.get(Calendar.MONTH) + 1));
dateBytes[2] = reverseByte((byte) (calendar.get(Calendar.DAY_OF_MONTH)));
dateBytes[3] = reverseByte((byte) (calendar.get(Calendar.HOUR_OF_DAY)));
dateBytes[4] = reverseByte((byte) (calendar.get(Calendar.MINUTE)));
dateBytes[5] = reverseByte((byte) (calendar.get(Calendar.SECOND)));
dateBytes[6] = reverseByte((byte) ((calendar.get(Calendar.ZONE_OFFSET) + calendar
.get(Calendar.DST_OFFSET)) / (60 * 1000 * 15)));
try {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
bo.write(lsmcs);
bo.write(scBytes);
bo.write(0x04);
bo.write((byte) sender.length());
bo.write(senderBytes);
bo.write(0x00);
bo.write(0x00); // encoding: 0 for default 7bit
bo.write(dateBytes);
try {
String sReflectedClassName = "com.android.internal.telephony.GsmAlphabet";
Class cReflectedNFCExtras = Class.forName(sReflectedClassName);
Method stringToGsm7BitPacked = cReflectedNFCExtras.getMethod("stringToGsm7BitPacked", new Class[] { String.class });
stringToGsm7BitPacked.setAccessible(true);
byte[] bodybytes = (byte[]) stringToGsm7BitPacked.invoke(null, body);
bo.write(bodybytes);
} catch (Exception e) {
}
return bo.toByteArray();
} catch (IOException e) {}
return null;
}
}
完全注释版ddi Hook框架的代码学习下载地址:http://download.csdn.net/download/qq1084283172/9975180
6.参考连接:
https://github.com/crmulliner/ddi
《Android平台下Dalvik层hook框架ddi的研究》
《Android平台dalvik模式下java Hook框架ddi的分析(1)》