Android的java的native hook

22 篇文章 0 订阅
21 篇文章 0 订阅

由于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逻辑


 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值