Xposed实现过程

我的博客地址


Xposed实现过程

1. 介绍

1.1 概述

Xposed是一个用于实现Android动态劫持项目的平台。通过控制替换/system/bin/app_process文件来控制Zygote进程。由于Android中的大部分程序(包括一些服务进程)都是由Zygote创建,app_process在启动时加载XposedBridge.jar来完成进程的劫持,也就可以达到劫持手机系统的目的。

Xposed的资源地址为:

Xposed Github

其中主要包含三个项目:

  • XposedBridge: 该工程编译后是一个jar包,负责劫持框架的java层的实现,并负责与native层进行交互。其他劫持模块的开发和操作都是基于这个jar包。

  • Xposed: Xposed的c++部分,主要用于替换/system/bin/app_process, 其次还为XposedBridge提供jni的支持。

  • XposedInstaller: Xposed的安装程序,负责Xposed的环境设置以及提供xposed模块的管理框架。

1.2 运行机制

1.2.1 Zygote

在Android系统中,应用的进程都是由Zygote来孵化,而Zygote由init进程启动。在系统启动时Zygote进程就创建了一个Dalvik虚拟机的实例。当Zygote孵化新的应用进程时,这个Dalvik虚拟机的实例也会被复制到新的进程中。每个App进程都拥有一个独立的虚拟机实例。

Zygote在启动时不仅会创建虚拟机,而且还会预加载一些基础的java运行库,以及注册一些必要的jni的方法。这是正是加载XposedBridge的时机,XposedBreidge中有一个专门用于hook的native方法,该方法通过java的反射机制,覆盖掉了原先也就是我们想要hook的java方法。

1.2.2 Hook/Replace

Xposed的主要工作是进行hook。相较Xposed来说,为了修改app,重打包的话就需要修改smali代码,即便是动态的替换smali代码,也会因为代码的复杂使得工作变得繁杂。Xposed的好处在于省去了重打包和修改smali代码带来的麻烦。
Zygote加载XposedBridge之后,将会把所有需要hook的方法都替换为名为hookMethodNative的jni函数,该函数处理相应的参数(被hook的函数名称,hook的方法)后,通过反射调用java方法handleHookedMethod,并由handleHookedMethod完成用户指定的hook逻辑。其主要逻辑为将被HOOK的目标函数的属性由Java修改为Native,并赋予特定的Native函数实现。而在对应的Native函数中,xposed反射调用XposedBridge.jar中的Java函数handleHookedMethod,从而实现对目标函数的Hook。这样的实现巧妙之处在于,不影像被hook函数的具体实现的情况下,将目标函数的执行重新指向了一个新的Java函数,而在这个Java函数中可以实现各种各样的hook逻辑,如执行多个不同模块的hook逻辑,并且区分各个模块的优先级。
Xposed的hook逻辑是这样的,每个hook模块中可以定义相应的函数在目标函数执行之前或执行之后执行。Xposed的Hook可以用流程图表示为。




在安装Xposed框架之后的手机中,每个App进程启动之后在内存空间中都加载了XposedBridge.jar和modules.jar(已经安装的hook模块)。其中XposedBridge.jar负责执行modules.jar中的hook代码。这个过程可以表示如下图。




2. 代码分析

2.1 app_process的替换

Xposed是通过替换app_process来实现Android上的所有App都加载了XposedBridge.jar。而这个过程的代码就在Xposed项目中。app_process的main函数十分简短,Xposed的实现是修改了main函数的实现之后,编译一份新的来替换目标手机原有的app_process文件。

其代码差异为

android 源码

if (zygote) {
    runtime.start("com.android.internal.os.ZygoteInit",
            startSystemServer ? "start-system-server" : "");
} else if (className) {
    // Remainder of args get passed to startup class main()
    runtime.mClassName = className;
    runtime.mArgC = argc - i;
    runtime.mArgV = argv + i;
    runtime.start("com.android.internal.os.RuntimeInit",
            application ? "application" : "tool");
} else {
    fprintf(stderr, "Error: no class name or --zygote supplied.\n");
    app_usage();
    LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    return 10;
}


Xposed(app_main.cpp) 源码

if (zygote) {
        runtime.start(isXposedLoaded ? "de.robv.android.xposed.XposedBridge" : "com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
    } else if (className) {
        // Remainder of args get passed to startup class main()
        runtime.mClassName = className;
        runtime.mArgC = argc - i;
        runtime.mArgV = argv + i;
        runtime.start(isXposedLoaded ? "de.robv.android.xposed.XposedBridge$ToolEntryPoint" : "com.android.internal.os.RuntimeInit",
                application ? "application" : "tool");
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10; 
    }  


从代码差异即可知道xposed的实现过程。

2.2 目标函数的hook

目标函数的hook主要由Xposed项目中的XposedBridge_hookMethodNative函数来完成,其是XposedBridge.jar中hookMethodNative函数的jni实现。
代码为:


void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect,
            jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) {
    ClassObject* classObject = NULL;
    // Find the internal representation of the method
    ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect);
    Method* method = dvmSlotToMethod(declaredClass, slot);
   
    classObject = method->clazz;
   
    // Save a copy of the original method and other hook info
    XposedHookInfo* hookInfo = (XposedHookInfo*) calloc(1, sizeof(XposedHookInfo));
    memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct));
    hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));
    hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect));
    // Replace method with our own code
    SET_METHOD_FLAG(method, ACC_NATIVE);
    method->nativeFunc = &hookedMethodCallback;
    method->insns = (const u2*) hookInfo;
    method->registersSize = method->insSize;
    method->outsSize = 0;
}


从代码中看到指针method指向了原函数的对象,通过宏SET_METHOD_FLAG将method设置为了native属性。

而hookInfo指针指向了对应的保存原函数实现和hook函数的内存空间。

而method->insns,原先指向的内存空间是原函数的字节码,现在指向了hookInfo。method->nativeFunc指向了一个固定的实现hook逻辑的函数。通过这里的一个关键的操作,xposed实现了hook目标函数的第一步,修改原函数的属性,劫持函数执行过程。

当hookedMethodCallback执行时,xposed可以通过hookInfo中的信息,来执行原函数和hook函数。

3. 相关的点

3.1 Xposed的两个Hook接口

Android中的Java进程分为App进程和系统进程(如System_process)。这两类进程的hook对应了两个不同的hook接口。

  • IXposedHookLoadPackage

    开发者需要实现接口handleLoadPackage()。该接口在App被加载时调用,用于App应用的Hook。

  • IXposedHookZygoteInit

    开发需要实现接口initZygote()。该接口在Zygote启动时调用,用于系统服务的Hook。

3.2 多个ClassLoader

由于Android可以动态加载jar包,想要hook动态加载的jar包中的函数,需要开发人员指定目标ClassLoader再次进行Hook。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值