基于Xposed框架的分析整理

2,115 阅读7分钟

一.Xposed系列框架介绍

一.VirtualXposed简洁

VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~10.0)。

与 Xposed 相比,目前 VirtualXposed 有两个限制:

不支持修改系统(可以修改普通APP中对系统API的调用),因此重力工具箱,应用控制器等无法使用。 暂不支持资源HOOK,因此资源钩子不会起任何作用;使用资源HOOK的模块,相应的功能不会生效。

二.VirtualApp简介

VirtualApp(以下简称:VA)是一款运行于Android系统的沙盒产品,可以理解为轻量级的“Android虚拟机”。其产品形态为高可扩展,可定制的集成SDK,您可以基于VA或者使用VA定制开发各种看似不可能完成的项目。VA目前被广泛应用于插件化开发、无感知热更新、云控自动化、多开、手游租号、手游手柄免激活、区块链、移动办公安全、军队政府保密、手机模拟信息、脚本自动化、自动化测试等技术领域。

三.Epic简介

Epic 是一个在虚拟机层面、以 Java Method 为粒度的 运行时 AOP Hook 框架。简单来说,Epic 就是 ART 上的 Dexposed(支持 Android 5.0 ~ 11)。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。

Epic 被 VirtualXposed 以及 太极 使用,用来实现非 Root 场景下的 Xposed 功能,已经经过了相当广泛的验证。

四.EdXposed简介

基于 Riru 的 ART hook 框架 (最初用于 Android Pie) ,提供与原版 Xposed 相同的 API, 使用 YAHFA (或 SandHook) 进行 hook, supports Android 8.0 ~ 11.

Xposed 框架是一套开放源代码的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下修改程序的运行,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作

二.针对系统API进行HOOK

一.针对Application进行hook

通过对ContextWrapper的attachBaseContext方法进行hook.就可以对app刚启动时初始化获取到该app对应对application对象实例

private inline fun hookAttachBaseContext(loader: ClassLoader, crossinline callback: (Context) -> Unit) {
    findAndHookMethod(
            "android.content.ContextWrapper", loader, "attachBaseContext",
            Context::class.java, object : XC_MethodHook() {
        override fun afterHookedMethod(param: MethodHookParam) {
            callback(param.thisObject as? Application ?: return)
        }
    })
}

二.针对Activity进行hook

通过对Activity的onCreate方法进行hook,就可以对app对所有页面进行hook.并且拿到页面的运行时对象实例,

private inline fun hookActivityOnCreate(loader: ClassLoader, crossinline callback: (Activity) -> Unit) {
    findAndHookMethod(
            "android.app.Activity", loader, "onCreate",
            Bundle::class.java, object : XC_MethodHook() {
        override fun afterHookedMethod(param: MethodHookParam) {
            Log.e("amaya","hookActivityOnCreate()...param=$param")
            callback(param.thisObject as? Activity ?: return)
        }
    })
}

三.针对View进行hook

通过对View的onClick方法进行hook.就可以对app的所有页面点击事件进行hook,拿到点击的运行时View对象实例

private inline fun hookViewOnClick(loader: ClassLoader, crossinline callback: (View) -> Unit) {
    findAndHookMethod(
            "android.view.View", loader, "onClick",
            View::class.java, object : XC_MethodHook() {
        override fun afterHookedMethod(param: MethodHookParam) {
            Log.e("amaya","hookActivityOnCreate()...param=$param")
            callback(param.thisObject as? View ?: return)
        }
    })
}

三.HOOK经纪人APP

在hook经纪人的过程中,总结了三种途径去hook获取信息

通过反编译脱壳apk直接获取类代码信息
通过对类方法实时hook获取信息 通过Android Studio内置监测CPU方法栈调用信息

一.通过反编译脱壳apk直接获取类代码信息

通过该途径进行调研对时候发现需要经过脱壳这一核心过程,具体脱壳过程最终选定了2个.

一个是(zhuanlan.zhihu.com/p/45591754) 里面提供的方案.该方案的核心处理方式为通过hook系统classLoader,通过app的内部调用机制直接获取到脱完壳到dex路径,然后直接对dex路径进行io导出操作,但是该方案目前已不适用,原因在于Android版本升级导致dex的API已被废弃,在最新的系统中无法调用到方案中到类了,

一个是根据该方案到思路在GitHub上找到思路类似到解决方案dumpDex(github.com/WrBug/dumpD…) 该方案到技术流程已经实现完成,其核心操作需要将脱壳到核心so文件先拷贝到data/local/tmp文件夹中,同时进行chmod权限操作,然后再需要将脱壳的项目选择为目标项目后在Xposed框架里运行,dumpDex会自动解析到原始dex文件并拷贝到项目目录下,但是在运行时报权限异常.该方案走不通

二.通过三方HOOK项目Inspeckage获取项目关键信息

Inspeckage(github.com/ac-pm/Inspe…:

  • Shared Preferences (log and file);

  • Serialization;

  • Crypto;

  • Hashes;

  • SQLite;

  • HTTP (an HTTP proxy tool is still the best alternative);

  • File System;

  • Miscellaneous (Clipboard, URL.Parse());

  • WebView;

    本着通过支持HTTP数据的功能反向获取app的通信数据进行调研,结果发现HTTP监听只能监听到Inspeckage本身的请求,无法监听到hook项目到http请求.但是其他数据都能hook到.包括项目到所有activity,service,permission,sp等信息

截屏2021-02-01 上午10.05.12.png

三.通过Xposed的API主动HOOK项目类方法

通过对类进行主动hook获取相关数据信息.该方案目前针对登录页面进行登录数据的捕获处理.目前做到了对Application初始化,Activity初始化 ,View点击事件的hook 通过对登录页面对hook,获取到该页面对所有方法一共有99个:

index=62...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$G0fG_rSTD6s0Q3_lK4GMubnRGrk(com.anjuke.android.newbroker.login.activity.LoginActivity,android.view.View)
index=63...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$I4e_xipeLya3h-eEoZNwjlXQ9Y0(com.anjuke.android.newbroker.login.activity.LoginActivity,android.view.View)
index=64...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$MT5f5uQD8OQ0Y2mqEDEuIB3Mj9I(com.anjuke.android.newbroker.login.activity.LoginActivity,android.view.View)
index=65...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$ODONjH7LpaJk-jcKWuJ7_rRclNY(com.anjuke.android.newbroker.login.activity.LoginActivity)
index=66...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$V2wqee-KOoLgkAI8OeoOT594IuY(com.anjuke.android.newbroker.login.activity.LoginActivity,android.view.View,boolean)
index=67...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$VBHOvEflsxAytiivtfRytGVTsN0(com.anjuke.android.newbroker.login.activity.LoginActivity,android.view.View)
index=68...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$Z8NppXfEkHh2lmM2WdrzDMKYiMI(com.anjuke.android.newbroker.login.activity.LoginActivity)
index=69...method=public static void com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$bebfyOXwVA2Gh9JSl3JFeS_R3qY(com.anjuke.android.newbroker.login.activity.LoginActivity,android.view.View)
index=70...method=public static boolean com.anjuke.android.newbroker.login.activity.LoginActivity.lambda$rp8otnjpbXkr6nBIKj2C7_dVXuI(com.anjuke.android.newbroker.login.activity.LoginActivity,android.widget.TextView,int,android.view.KeyEvent)
index=71...method=private void com.anjuke.android.newbroker.login.activity.LoginActivity.login()
index=72...method=static float[][] com.anjuke.android.newbroker.login.activity.LoginActivity.m(com.anjuke.android.newbroker.login.activity.LoginActivity)
index=73...method=private void com.anjuke.android.newbroker.login.activity.LoginActivity.nM()
index=74...method=private void com.anjuke.android.newbroker.login.activity.LoginActivity.r(android.os.Bundle)
index=75...method=private boolean com.anjuke.android.newbroker.login.activity.LoginActivity.verify()
index=76...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.ET()
index=77...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.a(com.anjuke.android.newbroker.login.utils.FingerprintLoginHelper,java.lang.String)
index=78...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.cl(boolean)
index=79...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.finish()
index=80...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.g(com.anjuke.android.newbrokerlibrary.model.Broker)
index=81...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.ne()
index=82...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.oj()
index=83...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.ol()
index=84...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.onActivityResult(int,int,android.content.Intent)
index=85...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.onBackPressed()
index=86...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.onClick(android.view.View)
index=87...method=protected native void com.anjuke.android.newbroker.login.activity.LoginActivity.onCreate(android.os.Bundle)
index=88...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.onDestroy()
index=89...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.onFoundUser(com.anjuke.android.newbroker.login.entity.FoundUserNameEvent)
index=90...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.onNewIntent(android.content.Intent)
index=91...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.onPause()
index=92...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.onPostResume()
index=93...method=public native void com.anjuke.android.newbroker.login.activity.LoginActivity.onRequestPermissionsResult(int,java.lang.String[],int[])
index=94...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.onResume()
index=95...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.onSafeVerifySuccessed(com.anjuke.apimodule.model.LoginResult)
index=96...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.onSaveInstanceState(android.os.Bundle)
index=97...method=public void com.anjuke.android.newbroker.login.activity.LoginActivity.onWindowFocusChanged(boolean)
index=98...method=protected void com.anjuke.android.newbroker.login.activity.LoginActivity.t(java.lang.String[])

由于无法定位到登录回调的哪个方法,再进行类一番尝试后发现带有LoginResult的方法回调在点击登录以后都没有触发.进而尝试对所有方法进行全局hook.但是全局hook后在进入登录页面后发现会黑屏卡死,这个过程有70%概率发生,偶尔能成功进入到登录页面,发现点击登录后会回调到ck方法,该方法仅回传一个boolean参数,无法获取到真正到API回调数据.还在继续研究中

四.通过CPU运行时监控捕获方法调用栈信息

通过CPU监测线程的调用信息可以清晰获取到关键方法的调用顺序以及名称等关键信息,所以该方式是最直接的方式.只要全局拿到类名和方法名就可以进行直接HOOK,不需要全局HOOK这种笨方式了.

由于不知道是IDE环境问题还是APK加密导致无法进行Profiler下的CPU监控功能,后来发现项目BDOpener(github.com/riusksk/BDO…) 支持修改程序的debugable选项

安装完成并开启模块后运行发现还是无法调试,由于项目都是几年前的AS环境也不一样,所以不排除版本不兼容导致.

后来寻思有可能是在Xposed框架里面追踪的可能中间隔了一层Xposed所以无法调试,为了简单期间,我先把目标APP存放在data/app下面的备份base.apk拷贝出来重新安装直接在正常模式下进行监测,发现可以监测了.以下是监测到到方法栈调用截图:

点击调用的顺序如下图.下图为登录按钮点击的事件触发方法调用顺序: 20210201191214.jpeg

蓝色框住区域即为LoginActivity页面需要hook的方法,下图为登录接口调用API后的方法调用顺序: 20210201191423.jpeg