万物皆可 Hook,探究 Xposed 框架 Hook 原理

作者 |俞家欢

低头需要勇气,抬头需要实力

引言

平时用着 Android 手机,喜欢折腾的同学或多或少都接触过 Xposed 框架,解锁、Root、刷包,一气呵成。本文将从原理和实践两部分带大家了解 Xposed 框架。

Xposed 框架介绍

Xposed 框架是一个运行在 Android 操作系统之上的钩子框架,是以模块扩展方式来实现对系统部分功能的修改。其可以通过编写模块代码,在不修改 Apk 文件的情况下拦截 Java 函数的调用,自定义函数行为,需要安装在 Root 过的 Android 手机上,需要系统最高权限。其是由 rovo89 大佬开发的,并发布在 GitHub 上的开源项目,项目地址为:https://github.com/rovo89。当前作者已经不再维护项目,仅支持 Android 9 以下系统。对于 Android 9 及其以上的系统,有一些的衍生框架可以支持,如 Exposed、VirtualXposed、太极框架。

Xposed 框架能做什么

Xposed 框架可以说是无所不能,可以编写模块欺骗微信获取随意设置的步数,让你每天都荣登榜首;可以随意设定虚拟定位,让 App 获取到你设定的地理位置;可以解放双手,实现自动化抢红包、领蚂蚁森林等等。总的来说,你可以对安装在设备上的 App 为所欲为。

Tips: 有些应用会检测 Xposed 框架,会造成封号等不可挽回的损失,请谨慎使用。

Xposed 框架的组成

此框架工程由下面五个子项目构成:

  • Xposed

    Xposed 框架 Native 部分,Xposed 框架版的 app_process,用于替换原生 app_process,并为 XposedBridge 提供 JNI 方法。

  • XposedBridge

    Xposed 框架 Java 部分,编译后会生成一个 jar 包,Xposed 框架的 app_process 会将此加入到系统 class path 中。

  • android_art

    Xposed 框架定制的 Android ART。

  • XposedInstaller

    Xposed 框架插件管理 App。

  • XposedTools

    用于编译项目的工具集。

Xposed 框架原理

Android 系统是基于 Linux 的,其第一个由内核启动的用户进程是 init 进程。init 进程随后会创建 zygote 进程,Android 应用程序进程都是由 zygote 进程孵化而来。zygote 所对应的可执行程序是 app_process,xposed 框架通过替换系统的 app_process 可执行文件以及虚拟机动态链接库,让 zygote 在启动应用程序进程时注入框架代码,进而实现对应用程序进程的劫持。

Tips:

以下涉及到的 Android 源代码都是基于 android-8.1.0_r62,在线查看 Android 源码的地址为:https://cs.android.com

以下涉及到的 Xposed 框架原理都是基于 Android 5.0 及其版本之后的分析,也就是基于 ART 虚拟机的实现分析。

Android 系统启动流程
  1. 按下电源,引导芯片会从固化的 ROM 处执行预设代码,将 Bootloader 加载到 RAM 中。

  2. Bootloader 设置系统硬件参数,检查 RAM,把操作系统映像文件拷贝到RAM中去,然后跳转到它的入口处去执行。

  3. 内核启动,创建第一个内核进程 idle 进程,最终创建第一个用户空间进程 init。

  4. init 进程负责创建 zygote 进程。

init 进程代码分析

init 的入口函数对应的是 system/core/init/init.cpp 文件的 main 方法。

int main(int argc, char** argv) {
  	......
		bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
    if (is_first_stage) {
        // 一阶段工作
    }
		// 二阶段工作  	
		......
}

从上面代码中可以看到,init 进程启动主要分两个阶段:

  • 一阶段工作

    ......
    // 挂在文件系统
    mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
    mkdir("/dev/pts", 0755);
    mkdir("/dev/socket", 0755);
    mount("devpts", "/dev/pts", "devpts", 0, NULL);
    mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
    chmod("/proc/cmdline", 0440);
    gid_t groups[] = { AID_READPROC };
    setgroups(arraysize(groups), groups);
    mount("sysfs", "/sys", "sysfs", 0, NULL);
    mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
    mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
    mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
    mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
    ......
    setenv("INIT_SECOND_STAGE", "true", 1);
    char* path = argv[0];
    char* args[] = { path, nullptr };
    execv(path, args);
    

    此阶段的主要工作是挂载一些虚拟文件系统。方法最后会将 INIT_SECOND_STAGE 置入环境中,并使用 execv 函数,重新执行当前 main 方法。

  • 二阶段工作

    ......
    // 初始化 property 服务
    property_init();
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
    unsetenv("INIT_SECOND_STAGE");
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");
    ......
    // 解析执行 rc 配置文件
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
    parser.ParseConfig("/init.rc");
      parser.set_is_system_etc_init_loaded(
        parser.ParseConfig("/system/etc/init"));
      parser.set_is_vendor_etc_init_loaded(
        parser.ParseConfig("/vendor/etc/init"));
      parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
    } else {
      parser.ParseConfig(bootscript);
      parser.set_is_system_etc_init_loaded(true);
      parser.set_is_vendor_etc_init_loaded(true);
      parser.set_is_odm_etc_init_loaded(true);
    }
    ......
    

    此阶段的主要工作是启动 property 服务(可以简单类比理解为 Windows 操作系统下的注册表服务)以及加载 .rc 文件。.rc 文件是配置文件,是由 Android 初始化语言(Android Init Language)编写的脚本,这里直接跟看下 system/core/rootdir/init.zygote64.rc 的内容:

    service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    	......
    

    service:Android 初始化语言中的一种语法,表示启动一个服务进程。

    zygote:为启动服务进程的名字。

    /system/bin/app_process:服务执行的入口文件路径。

    -Xzygote /system/bin、--zygote 以及 --start-system-server:执行时传递进去的参数。

    这段内容的解析与执行标志着 zygote 进程的启动,接下来正式进入 zygote 进程的启动流程。

总结一下 init 进程的启动:

  1. 创建和挂载启动相关的文件目录。

  2. 初始化和启动属性服务。

  3. 解析 .rc 配置文件,启动 zygote 进程。

Zygote 进程代码分析

zygote 启动流程对应的源代码是在 frameworks/base/cmds/app_process/app_main.cpp 中:

int main(int argc, char* const argv[])
{
	  ......
    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) !&
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值