前言
最近需要研究小程序 API 与调用到的 Android API 映射的问题,因此研究文章CCS20 Haoran Lu中 Apinat 工具原理,并在微信 7.0.20 版本上实现。
Apinat
目标
在小程序中调用小程序 API,给出为实现该小程序 API,Java 层实现所用到的 Android API。作者开发本工具的目的是为了找到没有被微信小程序权限保护(scope.xxx)但是底层实现的 Android API被 AOSP dangerous permission 保护的那些小程序 API,这些小程序 API 被称为 Encapsulated API。详见前文paper summary CCS20。
实现
背景知识
Dispatch function(wx api)线程不直接调用 Android API,而是触发新的线程来调用 Android API,这么实现可能是出于不阻塞 dispatch 线程的考虑。
这就导致 dispatch 在 Thread1,实际执行的 Android APIs 在 Thread2。
问题
因此这就需要解决一个问题,就是处理多线程环境,将相关 threads 联系起来。
解决思路
通常 dispatch thread 会使用 Handler 机制(微信是这样,其他小程序平台如支付宝可能用的其他机制)来触发多线程。因此只需要基于 Handler 机制的推送及处理消息的原理就可以将线程联系起来。
- Triggering Thread:有 handler 实例,通过调用 handler.post(runnable) 来向消息队列提交一个 runnable
- Handler thread:将 runnable 从队列拿出,并执行
- 如果 handler thread 中处理的 runnable 与 triggering thread 添加到队列的 runnable 相同,则可以将两个线程连接。
实现
本实现基于前面提到的 Apinat 提供的参考代码有修改,因为作者给出的代码没有指明在何版本微信上运行,因此笔者在参考工具框架基础上,结合微信 7.0.20 版本的小程序 API 调用的 Handler 实现而修改开发了适用 7.0.20 版本微信的 Apinat 工具,后续 8.x.x 版本应该也可用,只要 Handler 实现原理不变就适用。
工具代码中 hook 的 apk 版本未给出,因此需要根据实际测试的微信 apk 版本修改。具体修改项:Xp_demo.java 中的 “com.tencent.mm.plugin.appbrand.jsapi.d” 类中被 hook 的是 “y” 方法,改方法是逆向对应的 7.0.20 中小程序 API invokeHandler 处理后必调方法。(原本这里是“n”,对应的是作者分析的未知版本的微信 apk)。
1
2
3
4
5
6
7
8
9
10
11
12
13XposedHelpers.findAndHookMethod(
//7.0.20 d 类的 y 方法
"com.tencent.mm.plugin.appbrand.jsapi.d", lpparam.classLoader, "y", String.class, String.class, int.class,// 被Hook函数的名称
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param)
throws Throwable {
String tag = "[Dump Stack]";
String msg = "=====miniAppApi" + String.valueOf(param.args[0]) + "*******";
printLog(tag,msg);
}
});hook 到小程序 thread 的 handler post
原代码是 hook android.os.Handler.getPostMessage,可能是微信 apk 版本不同,7.0.20 中是通过 handler.post(runnable),因此 hook 作者给出的 getPostMessage 拿不到任何调用信息,hook handler.post 方法有信息,并且检查调用栈可以找到上述 jsapi.d.a 方法。通过 hook 并进行调用栈筛选可以拿到 __实现小程序 API 的 runnable 实例__。hook handler 的消息处理函数
作者 hook 的是 handleCallback,笔者认为应该是基于 handler.dispatchMessage 中下列代码的考虑1
2
3
4// dispatchMessage 内部
if(message.callback){
handleCallback(message);
}因此作者的 hook 代码为
1
2
3// 作者 hook 并做了 message.what != 0 的过滤
// hook android.os.Handler.handleCallback
if(message.what == 0) return;但是这里 hook 不到任何东西,因为这里想 hook 的消息处理逻辑跟前面的 getPostMessage 是对应的,因为在那种情况下 message.what 是对应的处理线程 id,但是如果是用 handler.post 的话会直接回调运行 runnable.run 代码,因此 message.what 不是 threadId,而是 0(调度线程id),所以即使 hook 到也会被第二行 if(message.what == 0) 判断过滤掉,所以笔者修改 hook 函数为为 android.os.Handler.dispatchMessage,修改筛选条件为
1
2
3
4// hook android.os.Handler.dispatchMessage
if(message.callback == null){
return
}hook Android API
这个就不多说了,正常 hook 系统 API 并打印Log 处理
Hook 到的以上信息都打印在 log 中,还需要后续追加对 log 处理,但是逻辑很简单,就是提取上述四个信息,构建(小程序 API thread —- runnable 实例 name —- 处理含 runnable 实例的 Handler thread —- 该 Handler Thread 的 Android API 调用),即可得到 __小程序 API —- Android API Mapping__。
完整代码
1 | package com.example.a91377.xpdemo; |