一、前 言
在前面的博客中已经学习了作者crmulliner编写的,针对Android系统的跨进程 inline Hook的实现即Android native Hook框架adbi的实现。Android Hook框架adbi主要是针对的Android的native函数进行inline Hook操作,那么如果需要对Android系统中Java编写的函数进行Hook,又该怎么操作呢?作者crmulliner后面又实现了针对Android的java函数进行Hook的框架ddi。adbi Hook框架和ddi java Hook框架的实现流程都差不多,首先实现root权限下Android跨进程的so注入,在so库文件被加载注入到目标进程中时,调用该so库文件的构造函数有一次代码执行的机会,利用这一次代码执行的机会既可以进行针对Android系统底层native函数的inline Hook或者.got Hook等,也可以进行针对Android系统的java函数进行dalvik Hook或者art Hook。Android平台的所有跨进程Hook都是基于Android系统root权限下的so注入和一次代码执行机会来实现的,只要能实现Android的跨进程注入(这个注入既可以是shellcode代码片段的注入也可以是so库文件的注入)就可以在目标进程中做很多的事情。
这里简要说下Android平台针对java函数的dalvik Hook(暂时不讨论基于art模式下的java函数的Hook,后面有时间再讨论)。dalvik虚拟机模式下java函数的Hook主要是修改存储java函数的信息结构体Method。在dalvik虚拟机中,java函数的形式是以Method结构体来表现的,每一个java编写的类成员函数最终在dalvik虚拟机中以Method结构体的形式存在。基于dalvik虚拟机的java Hook通过修改java函数的Method结构体中的函数属性值access_flags将一个java层函数修改为native属性的函数,这样一个java层实现的函数被修改为native层实现的函数,我们就可以将自定义编写的native函数替换掉原来的java层函数实现,从而实现基于dalvik虚拟机的java Hook。
二、Android平台java Hook框架ddi实现dalvik 模式下Hook的步骤
1)在so库文件被加载注入到目标进程中时,调用该so库文件.init段的构造函数获取dalvik模式下执行Hook java函数代码的机会;
2) dalvik虚拟机模式下,动态加载”libdvm.so”库文件并获取该动态加载的”libdvm.so”库文件中java Hook实现需要的导出函数的指针和导出全局变量,例如获取导出函数dvmFindLoadedClass、dvmFindVirtualMethodHierByDescriptor、dvmFindDirectMethodByDescriptor、dvmUseJNIBridge等的函数指针;
3)调用上面步骤中提到的”libdvm.so”库文件中的导出函数 dvmFindLoadedClass 获取被java Hook的java目标函数所在的目标类,然后再调用导出函数 dvmFindVirtualMethodHierByDescriptor、dvmFindDirectMethodByDescriptor 在查找到的目标类中获取被java Hook的目标函数(java层实现的函数)的信息结构体Method;
4)查找到被java Hook的目标函数(java层实现的函数)的信息结构体Method以后,先保存该目标函数的信息结构体Method的原始值,用以后面对目标函数进行java Hook操作后的恢复还原,如果没有保存目标函数的原始Method信息结构体值的话,想在java Hook操作之后再调用原来的java层实现的目标函数的话就不可能了;
5)对将被java Hook的目标函数的信息结构体Method进行修改即修改目标函数信息结构体Method的成员变量 access_flags 的值,实现将一个java层实现的方法改为 native 层实现的本地方法,这样一个java函数就变成了可以被我们自定义替换的native函数;
6)java层实现的目标函数被修改为native属性的本地方法以后,还需要对该被java Hook的目标函数信息结构体Method的成员变量 insSize(函数传参寄存器的数量)、outsSize(局部变量使用寄存器数量)、registersSize(函数调用使用的寄存器的总数)、jniArgInfo、native_func(改为jni桥接函数,例如 dvmResolveNativeMethod)的值进行修正;
7)为被java Hook的目标函数设置新的函数实现,按照被java Hook的目标函数的原始java函数声明实现对应函数声明的native层的jni函数(java Hook的替换函数),然后修改该目标函数的信息结构体Method的成员变量 insns 的值为该native层实现的jni函数,到这里java层实现的目标函数就替换为我们自定义实现的native层的jni函数,从而实现dalvik虚拟机模式下的java Hook。
提示:ddi 框架的 java Hook 实现原理和前面的博客《Android进程so注入Hook java方法》中提到的 java Hook 实现原理是一样的,只是在细节处理上稍有不同,ddi 框架处理的更好,不需要再次进行jni函数的注册。
三、Android平台java Hook框架ddi实现dalvik 模式下Hook的代码分析
1. 在dalvik虚拟机中每一个方法都由一个称作Method的结构体来表示(包括JNI方法),ddi框架实现java Hook就是通过修改目标函数的信息结构体Method来实现的。下面来了解一下Method结构体构成:
/*
* A method. We create one of these for every method in every class
* we load, so try to keep the size to a minimum.
*
* Much of this comes from and could be accessed in the data held in shared
* memory. We hold it all together here for speed. Everything but the
* pointers could be held in a shared table generated by the optimizer;
* if we're willing to convert them to offsets and take the performance
* hit (e.g. "meth->insns" becomes "baseAddr + meth->insnsOffset") we
* could move everything but "nativeFunc".
*/
// Android 4.4.4r1源码文件路径 /dalvik/vm/oo/Object.h
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;
};
(1)clazz:当前方法所在的类;
(2)accessFlags:当前方法所具有的属性,例如:类访问属性、是否静态函数等,accessFlags属性值 的定义如下:
/*
* access flags and masks; the "standard" ones are all <= 0x4000
*
* Note: There are related declarations in vm/oo/Object.h in the ClassFlags
* enum.
*/
// Android 4.4.4r1 源码路径 /dalvik/libdex/DexFile.h
enum {
ACC_PUBLIC = 0x00000001, // class, field, method, ic
ACC_PRIVATE = 0x00000002, // field, method, ic
ACC_PROTECTED = 0x00000004, // field, method, ic
ACC_STATIC = 0x00000008, // field, method, ic
ACC_FINAL = 0x00000010, // class, field, method, ic
ACC_SYNCHRONIZED = 0x00000020, // method (only allowed on natives)
ACC_SUPER = 0x00000020, // class (not used in Dalvik)
ACC_VOLATILE = 0x00000040, // field
ACC_BRIDGE = 0x00000040, // method (1.5)
ACC_TRANSIENT = 0x00000080, // field
ACC_VARARGS = 0x00000080, // method (1.5)
ACC_NATIVE = 0x00000100, // method
ACC_INTERFACE = 0x00000200, // class, ic
ACC_ABSTRACT = 0x00000400, // class, method, ic
ACC_STRICT = 0x00000800, // method
ACC_SYNTHETIC = 0x00001000, // field, method, ic
ACC_ANNOTATION = 0x00002000, // class, ic (1.5)
ACC_ENUM = 0x00004000, // class, field, ic (1.5)
ACC_CONSTRUCTOR = 0x00010000, // method (Dalvik only)
ACC_DECLARED_SYNCHRONIZED =
0x00020000, // method (Dalvik only)
ACC_CLASS_MASK =
(ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT
| ACC_SYNTHETIC | ACC_ANNOTATION