Android的java的native hook

由於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邏輯


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章