由于linux操作系统驱动模块加载机制的限制和release版的rom通常未开启模块加载机制,使得我们不能像Windows操作系统编译驱动,在R0层做驱动监控和对抗。Hook的目标就从内核层转到了应用层。
Android平台由于分层特点,可将hook分为两种:
Java层hook,通过注入一个dex文件,hook java函数
Native层hook so注入,hook c函数
HOOK函数分三步:
找到该函数
替换该函数 -- hook
恢复该函数 -- unhook
Java hook
Hook思路:利用java语言的反射特性进行查找替换。
Hook方法:
直接java层hook替换
优点:简单
缺点:功能有限
Native层反射,解析Method替换
优点:功能完善
缺点:实现复杂
Java反射是Java被视为动态(或准动态)语言的一个关键 性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable)也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
Hook思路:利用java语言的反射特性进行查找替换。
Hook方法:
直接java层hook替换
利用反射替换java static和私有变量方法
Class<?> classThread = Class.forName(cls);
Field fieldActs;
fieldActs = classThread.getDeclaredField(member);
fieldActs.setAccessible(true);
objOrg = fieldActs.get(obj);
fieldActs.set(obj, newObj);
native hook
由于Android虚拟机分为Dalvik和art,具体实现方法有所不同,下面以Dalvik虚拟机为例。
基本原理:利用java的反射特性,获取某个方法的MethodID,修改其真正的结构体Method字段,实现hook
实现重难点:
类的查找,这个类可以能没有被加载起来,需要找到系统的classload,然后再操作,Method结构体的解析和替换
struct Method {
ClassObject* clazz;
u4 accessFlags; //访问权限,public,private,acc_native,所以把一个java函数这里改成native,修改下,java方法就变成了native方法,这样穿透虚拟机,达到hook、
u2 methodIndex;
u2 registersSize;
u2 outsSize;
u2 insSize;
const char* name; //方法名
DexProto prototype;
const char* shorty; //短格式方法描述
const u2* insns; //指令
int jniArgInfo;
DalvikBridgeFunc nativeFunc; //隐式注册时候为空,虚拟机通过桥函数调用的,JNIOnload就填上这个了。
bool fastJni;
bool noRef;
bool shouldTrace;
const RegisterMap* registerMap;
bool inProfile;
};
#include <android_runtime/AndroidRuntime.h>
#include "JavaMethodHook.h"
#include "common.h"
#include "dvm_func.h"
using android::AndroidRuntime;
#ifdef DEBUG
#define STATIC
#else
#define STATIC static
#endif
STATIC int calcMethodArgsSize(const char* shorty) {
int count = 0;
/* Skip the return type. */
shorty++;
for (;;) {
switch (*(shorty++)) {
case '\0': {
return count;
}
case 'D':
case 'J': {
count += 2;
break;
}
default: {
count++;
break;
}
}
}
return count;
}
STATIC u4 dvmPlatformInvokeHints(const char* shorty) {
const char* sig = shorty;
int padFlags, jniHints;
char sigByte;
int stackOffset, padMask;
stackOffset = padFlags = 0;
padMask = 0x00000001;
/* Skip past the return type */
sig++;
while (true) {
sigByte = *(sig++);
if (sigByte == '\0')
break;
if (sigByte == 'D' || sigByte == 'J') {
if ((stackOffset & 1) != 0) {
padFlags |= padMask;
stackOffset++;
padMask <<= 1;
}
stackOffset += 2;
padMask <<= 2;
} else {
stackOffset++;
padMask <<= 1;
}
}
jniHints = 0;
if (stackOffset > DALVIK_JNI_COUNT_SHIFT) {
/* too big for "fast" version */
jniHints = DALVIK_JNI_NO_ARG_INFO;
} else {
assert((padFlags & (0xffffffff << DALVIK_JNI_COUNT_SHIFT)) == 0);
stackOffset -= 2; // r2/r3 holds first two items
if (stackOffset < 0)
stackOffset = 0;
jniHints |= ((stackOffset + 1) / 2) << DALVIK_JNI_COUNT_SHIFT;
jniHints |= padFlags;
}
return jniHints;
}
STATIC int dvmComputeJniArgInfo(const char* shorty) {
const char* sig = shorty;
int returnType, jniArgInfo;
u4 hints;
/* The first shorty character is the return type. */
switch (*(sig++)) {
case 'V':
returnType = DALVIK_JNI_RETURN_VOID;
break;
case 'F':
returnType = DALVIK_JNI_RETURN_FLOAT;
break;
case 'D':
returnType = DALVIK_JNI_RETURN_DOUBLE;
break;
case 'J':
returnType = DALVIK_JNI_RETURN_S8;
break;
case 'Z':
case 'B':
returnType = DALVIK_JNI_RETURN_S1;
break;
case 'C':
returnType = DALVIK_JNI_RETURN_U2;
break;
case 'S':
returnType = DALVIK_JNI_RETURN_S2;
break;
default:
returnType = DALVIK_JNI_RETURN_S4;
break;
}
jniArgInfo = returnType << DALVIK_JNI_RETURN_SHIFT;
hints = dvmPlatformInvokeHints(shorty);
if (hints & DALVIK_JNI_NO_ARG_INFO) {
jniArgInfo |= DALVIK_JNI_NO_ARG_INFO;
} else {
assert((hints & DALVIK_JNI_RETURN_MASK) == 0);
jniArgInfo |= hints;
}
return jniArgInfo;
}
STATIC jclass dvmFindJNIClass(JNIEnv *env,const char *classDesc){
jclass classObj = env->FindClass(classDesc);
if(env->ExceptionCheck() == JNI_TRUE)//找不到
env->ExceptionClear();
}
if(classObj == NULL){
jclass clazzApplicationLoaders = env->FindClass("android/app/ApplicationLoaders");
CHECK_VALID(clazzApplicationLoaders);
jfieldID fieldApplicationLoaders = env->GetStaticFieldID(clazzApplicationLoaders,"gApplicationLoaders","Landroid/app/ApplicationLoaders;");
CHECK_VALID(fieldApplicationLoaders);
jobject objApplicationLoaders = env->GetStaticObjectField(clazzApplicationLoaders,fieldApplicationLoaders);
CHECK_VALID(objApplicationLoaders);
jfieldID fieldLoaders = env->GetFieldID(clazzApplicationLoaders,"mLoaders","Ljava/util/Map;");
CHECK_VALID(fieldLoaders);
jobject objLoaders = env->GetObjectField(objApplicationLoaders,fieldLoaders);
CHECK_VALID(objLoaders);
jclass clazzHashMap = env->GetObjectClass(objLoaders);
static jmethodID methodValues = env->GetMethodID(clazzHashMap,"values","()Ljava/util/Collection;");
jobject values = env->CallObjectMethod(objLoaders,methodValues);
jclass clazzValues = env->GetObjectClass(values);
static jmethodID methodToArray = env->GetMethodID(clazzValues,"toArray","()[Ljava/lang/Object;");
jobjectArray classLoaders = (jobjectArray)env->CallObjectMethod(values,methodToArray);
int size = env->GetArrayLength(classLoaders);
jstring param = env->NewStringUTF(classDesc);
for(int i = 0 ; i < size ; i ++){
jobject classLoader = env->GetObjectArrayElement(classLoaders,i);
jclass clazzCL = env->GetObjectClass(classLoader);
static jmethodID loadClass = env->GetMethodID(clazzCL,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;");
classObj = (jclass)env->CallObjectMethod(classLoader,loadClass,param);
if(classObj != NULL){
break;
}
}
}
return (jclass)env->NewGlobalRef(classObj);
}
STATIC ClassObject* dvmFindClass(const char *classDesc){
JNIEnv *env = AndroidRuntime::getJNIEnv();
assert(env != NULL);
char *newclassDesc = dvmDescriptorToName(classDesc);
jclass jnicls = dvmFindJNIClass(env, newclassDesc);
ClassObject *res = jnicls ? static_cast<ClassObject*>(dvmDecodeIndirectRef(dvmThreadSelf(), jnicls)) : NULL;
env->DeleteGlobalRef(jnicls);
free(newclassDesc);
return res;
}
STATIC ArrayObject* dvmBoxMethodArgs(const Method* method, const u4* args){
const char* desc = &method->shorty[1]; // [0] is the return type.
/* count args */
size_t argCount = dexProtoGetParameterCount(&method->prototype);
STATIC ClassObject* java_lang_object_array = dvmFindSystemClass("[Ljava/lang/Object;");
/* allocate storage */
ArrayObject* argArray = dvmAllocArrayByClass(java_lang_object_array, argCount, ALLOC_DEFAULT);
if (argArray == NULL)
return NULL;
Object** argObjects = (Object**) (void*) argArray->contents;
/*
* Fill in the array.
*/
size_t srcIndex = 0;
size_t dstIndex = 0;
while (*desc != '\0') {
char descChar = *(desc++);
JValue value;
switch (descChar) {
case 'Z':
case 'C':
case 'F':
case 'B':
case 'S':
case 'I':
value.i = args[srcIndex++];
argObjects[dstIndex] = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));
/* argObjects is tracked, don't need to hold this too */
dvmReleaseTrackedAlloc(argObjects[dstIndex], NULL);
dstIndex++;
break;
case 'D':
case 'J':
value.j = dvmGetArgLong(args, srcIndex);
srcIndex += 2;
argObjects[dstIndex] = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));
dvmReleaseTrackedAlloc(argObjects[dstIndex], NULL);
dstIndex++;
break;
case '[':
case 'L':
argObjects[dstIndex++] = (Object*) args[srcIndex++];
break;
}
}
return argArray;
}
STATIC ArrayObject* dvmGetMethodParamTypes(const Method* method, const char* methodsig){
/* count args */
size_t argCount = dexProtoGetParameterCount(&method->prototype);
STATIC ClassObject* java_lang_object_array = dvmFindSystemClass("[Ljava/lang/Object;");
/* allocate storage */
ArrayObject* argTypes = dvmAllocArrayByClass(java_lang_object_array, argCount, ALLOC_DEFAULT);
if(argTypes == NULL){
return NULL;
}
Object** argObjects = (Object**) argTypes->contents;
const char *desc = (const char *)(strchr(methodsig, '(') + 1);
/*
* Fill in the array.
*/
size_t desc_index = 0;
size_t arg_index = 0;
bool isArray = false;
char descChar = desc[desc_index];
while (descChar != ')') {
switch (descChar) {
case 'Z':
case 'C':
case 'F':
case 'B':
case 'S':
case 'I':
case 'D':
case 'J':
if(!isArray){
argObjects[arg_index++] = dvmFindPrimitiveClass(descChar);
isArray = false;
}else{
char buf[3] = {0};
memcpy(buf, desc + desc_index - 1, 2);
argObjects[arg_index++] = dvmFindSystemClass(buf);
}
desc_index++;
break;
case '[':
isArray = true;
desc_index++;
break;
case 'L':
int s_pos = desc_index, e_pos = desc_index;
while(desc[++e_pos] != ';');
s_pos = isArray ? s_pos - 1 : s_pos;
isArray = false;
size_t len = e_pos - s_pos + 1;
char buf[128] = { 0 };
memcpy((void *)buf, (const void *)(desc + s_pos), len);
argObjects[arg_index++] = dvmFindClass(buf);
desc_index = e_pos + 1;
break;
}
descChar = desc[desc_index];
}
return argTypes;
}
STATIC void method_handler(const u4* args, JValue* pResult, const Method* method, struct Thread* self){
HookInfo* info = (HookInfo*)method->insns;
LOGI("[+] entry DvmHandler %s->%s", info->classDesc, info->methodName);
Method* originalMethod = reinterpret_cast<Method*>(info->originalMethod);
Object* thisObject = !info->isStaticMethod ? (Object*)args[0]: NULL;
ArrayObject* argTypes = dvmBoxMethodArgs(originalMethod, info->isStaticMethod ? args : args + 1);
pResult->l = (void *)dvmInvokeMethod(thisObject, originalMethod, argTypes, (ArrayObject *)info->paramTypes, (ClassObject *)info->returnType, true);
dvmReleaseTrackedAlloc((Object *)argTypes, self);
}
extern int __attribute__ ((visibility ("hidden"))) dalvik_java_method_hook(JNIEnv* env, HookInfo *info) {//入口点
const char* classDesc = info->classDesc;
const char* methodName = info->methodName;
const char* methodSig = info->methodSig;
const bool isStaticMethod = info->isStaticMethod;
jclass classObj = dvmFindJNIClass(env, classDesc);
if (classObj == NULL) {
LOGE("[-] %s class not found", classDesc);
return -1;
}
jmethodID methodId =
isStaticMethod ?
env->GetStaticMethodID(classObj, methodName, methodSig) :
env->GetMethodID(classObj, methodName, methodSig);
if (methodId == NULL) {
LOGE("[-] %s->%s method not found", classDesc, methodName);
return -1;
}
// backup method
Method* method = (Method*) methodId;
if(method->nativeFunc == method_handler){
LOGW("[*] %s->%s method had been hooked", classDesc, methodName);
return -1;
}
Method* bakMethod = (Method*) malloc(sizeof(Method));
memcpy(bakMethod, method, sizeof(Method));
// init info
info->originalMethod = (void *)bakMethod;
info->returnType = (void *)dvmGetBoxedReturnType(bakMethod);
info->paramTypes = dvmGetMethodParamTypes(bakMethod, info->methodSig);//拿到虚拟机的参数。
// hook method
int argsSize = calcMethodArgsSize(method->shorty);//寄存器的个数
if (!dvmIsStaticMethod(method))
argsSize++;//如果非静态多个this参数
SET_METHOD_FLAG(method, ACC_NATIVE);//改成native
method->registersSize = method->insSize = argsSize;
method->outsSize = 0;
method->jniArgInfo = dvmComputeJniArgInfo(method->shorty);
// save info to insns
method->insns = (u2*)info;
// bind the bridge func,only one line
method->nativeFunc = method_handler;//hook实现
LOGI("[+] %s->%s was hooked\n", classDesc, methodName);
return 0;
}
Hook框架
Xposed
替换app_process里插了一些桩,在app启动时候检测。
使用方法:
1、新建Android工程
2、AndoridMainfest.xml application标签添加Xposed的meta-data
3、lib目录添加XposedBridge库
4、assets目录新建xposed_init文件,添加hook模块
包名+类名
5、编写hook逻辑