Skip to content
Emerson Lin edited this page May 19, 2018 · 23 revisions

环境搭建

WechatSpellbook推荐使用Android Studio 3.0.1及以上版本进行开发,对开发端的操作系统没有强制要求。另外教程中出现的代码均由Kotlin编写,但是遇到Java中没有的概念会停下来额外讲解一下。

测试手机推荐使用以下配置:

  • Android 5.0 ~ 6.0.1
  • Xposed Framework v89及以上 (卡刷版或Magisk版均可)

该配置目前最为稳定,不会因为框架级甚至系统级的Bug干扰正常开发。

新建项目

这里使用默认配置新建项目即可,需要强调的是,虽然WechatSpellbook目前最低支持Android 4.4,但是已经不再耗费精力对4.4进行兼容性测试了。想要保证稳定性的话,最好还是从Android 5.0开始支持。

项目创建完成后,应该会有一个默认的模块叫做“app”,接下来的开发将围绕它展开。

添加Xposed基础库

打开app/build.gradle脚本,在dependencies结尾部分添加如下依赖:

dependencies {
    // ......

    //noinspection GradleDependency
    compileOnly 'de.robv.android.xposed:api:53'
    compileOnly 'de.robv.android.xposed:api:53:sources'
}

添加Spellbook基础库

目前WechatSpellbook支持两种接入方式:使用Maven库或直接拉取GitHub项目。使用Maven库极其简单,但更新较慢。而直接拉取GitHub项目能获取开发中的版本。

使用Maven库

打开app/build.gradle脚本,在dependencies结尾部分添加如下依赖:

dependencies {
    // ......

    // 添加编译好的Spellbook库
    implementation 'com.github.gh0u1l5:wechat-spellbook:0.0.5'
    // 添加Spellbook源码方便查询注释
    implementation 'com.github.gh0u1l5:wechat-spellbook:0.0.5:sources'
}

注意:文档中的版本号未必是最新的,目前WechatSpellbook的最新版本为 Maven Central

拉取GitHub项目

如果刚刚新建的项目并非是Git项目,仅仅是一个本地文件夹的话,可以直接用clone命令将WechatSpellbook克隆到spellbook文件夹中。

git clone https://github.com/Gh0u1L5/WechatSpellbook.git spellbook

如果新建的项目是一个Git项目,那么可以使用submodule命令将WechatSpellbook拉取到spellbook文件夹中。

git submodule add https://github.com/Gh0u1L5/WechatSpellbook.git spellbook

然后将spellbook文件夹作为一个Module导入到当前项目中,并在app/build.gradle中添加如下依赖

dependencies {
    // ......

    implementation project(':spellbook')
}

接入Xposed框架

首先,在AndroidManifest.xml文件中,添加以下内容到<application>块,让Xposed框架能够在安装的时候认出这个应用是Xposed插件

    <application
        <!-- ...... -->

        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <meta-data
            android:name="xposeddescription"
            android:value="@string/app_name" />
        <meta-data
            android:name="xposedminversion"
            android:value="53" />
    </application>

一个Xposed插件可以提供多个类作为入口。假设应用包名为com.gh0u1l5.wechatexample,入口类名叫WechatHook。那么就需要在app/src/main/assets文件夹中,建立一个名为xposed_init的文本文件,填写

com.gh0u1l5.wechatexample.WechatHook

接下来,创建这个WechatHook类,并且实现IXposedHookLoadPackage接口

class WechatHook : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        try {
            if (SpellBook.isImportantWechatProcess(lpparam)) {
                XposedBridge.log("Hello Wechat!")
            }
        } catch (t: Throwable) {
            XposedBridge.log(t)
        }
    }
}

这里有两点需要额外解释:

  1. 实现IXposedHookLoadPackage接口之后,在任意一个应用开启新进程时,Xposed都会唤醒插件、调用handleLoadPackage方法。所以需要使用isImportantWechatProcess来检查这个进程属不属于微信。
  2. 如果微信开机自启的话,一旦handleLoadPackage中出现任何一个未能catch的异常,都会导致系统反复重启花式重启。所以一定要保证在这个函数里用try完整地裹住所有代码。因为类似这样的地方还有不少,所以为了简化代码WechatSpellbook中特意提供了一个方法:
    BasicUtil.tryVerbosely {
        if (SpellBook.isImportantWechatProcess(lpparam)) {
            XposedBridge.log("Hello Wechat!")
        }
    }

这段代码和之前的try-catch结构是一样的,只是长得更简洁好看而已。

接下来将应用编译、安装、在Xposed安装器中启用插件并重启。编译前记得在Android Studio的设置里关闭Instant Run,该功能与Xposed不兼容。

接下来打开微信,就能在Xposed的日志或者Logcat中看到类似这样的日志记录

03-23 10:31:59.474 5131-5131/? I/Xposed: Hello Wechat!

接入Spellbook框架

那么小试牛刀之后,让我们开始真正接触Spellbook框架。首先,创建一个类,或者说一个 Spellbook插件

object Alert : IActivityHook {
    override fun onActivityStarting(activity: Activity) {
        Toast.makeText(activity, "Hello Wechat!", Toast.LENGTH_LONG).show()
    }
}

这个类实现了IActivityHook接口,它的功能极其简单,就是在一个Activity将要调用onStart的时候,弹出一个Toast。

Spellbook插件”是对Spellbook框架中所有监视微信事件并进行处理的类的统称,通过实现Spellbook制定的接口并且在启动时进行注册,这个插件就能够从Spellbook接收到微信事件的回调。插件可以实现的接口有两种风格:EventCenter和HookerProvider,关于这两种风格的详情参见事件机制章节。

另外,给Java背景的开发者简单解释一下什么是“object”:所谓object,其实就是“有且只有一个实例的类”,也就是Kotlin下的单例模式。在声明了Alert这个object之后,想要访问它的话直接用Alert这个名字就可以了,比如调用onActivityStarting方法就是

Alert.onActivityStarting(activity)

由此可见,Java中常见的设计模式,在Kotlin里面也会存在,只是说把Java写十几行的问题用两三行解决罢了,想要从Java转Kotlin的朋友大可不必对这门新语言感到战战兢兢。

接下来,我们修改一下之前的入口函数,将刚刚写的Alert插件提供给Spellbook引擎

class WechatHook : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        tryVerbosely {
            if (isImportantWechatProcess(lpparam)) {
                SpellBook.startup(lpparam, listOf(Alert))
            }
        }
    }
}

再次重复之前的安装步骤并重启,接下来当你随便打开一个微信窗口时,都会看到“Hello Wechat!”字样弹出来,满意?Are you okay?

稍微炫酷一点

好,在学会了走路之后,让我们稍微跑两步。接下来的例子中,我们来试着窃取用户收到的所有新消息,并且把它记录/上传。

object Message : IDatabaseHook {
    override fun onDatabaseInserted(thisObject: Any, table: String, nullColumnHack: String?, initialValues: ContentValues?, conflictAlgorithm: Int, result: Long): Operation<Long> {
        if (table == "message") {
            log("New Message: $initialValues")
        }
        return super.onDatabaseInserted(thisObject, table, nullColumnHack, initialValues, conflictAlgorithm, result)
    }
}

这个插件实现了IDatabaseHook,通过继承onDatabaseInserted函数,可以在数据库插入操作完成后截获插入的内容。写完重启之前,不要忘了把它添加到Spellbook的启动参数里。

class WechatHook : IXposedHookLoadPackage {
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        tryVerbosely {
            if (isImportantWechatProcess(lpparam)) {
                SpellBook.startup(lpparam, listOf(Alert, Message))
            }
        }
    }
}

重启之后再看Xposed日志,就能发现自己和别人聊天收到的消息全都被悄悄记录下来了,是不是感到一阵恶寒?

告一段落

那么快速上手就到这里了,在这篇文档里主要是初步演示了一下EventCenter风格的事件处理。在下一章节事件机制里,会再深入解释EventCenter和HookerProvider这两套风格的逻辑和应用场景。

  • 简介
  • 快速上手
  • 开发教程
  • 逆向技巧
    • 常见逆向工具
    • 调试输出
    • 堆栈跟踪
    • Support库
    • 免重启调试插件(未书写)
Clone this wiki locally