支付宝小程序框架逆向分析

本文对支付宝小程序的正向开发做了简单介绍,并从正向开发的文件类型入手,对小程序的宿主框架进行了逆向分析,包括运行机制、通信模型以及安全防护体系等内容。

代码开发

支付宝小程序开发在语法方面与传统的前端网页开发非常类似,开发者主要编写 .axml、.acss、.js三部分文件,分别对标前端开发中的HTML、CSS、JS。

其中 .axml 的内容如下所示,AXML 是小程序框架设计的一套标签语言,用于描述小程序页面的结构。

<view> Hello {{name}}! </view>
<button onTap="changeName"> Click me! </button>

其中 .js 的内容如下所示,用于实现小程序的业务逻辑。

// 逻辑层
var initialData = {name: 'taobao',
};
// 注册一个页面
Page({data: initialData,changeName(e) {// 改变数据this.setData({name: 'alipay',});},
});

其中 .acss 的内容如下所示,ACSS 是一套样式语言,用于描述 AXML 的组件样式,决定 AXML 的组件的显示效果。

view {padding-left: 10px;
}

小程序开发者完成代码开发后,会提交相应代码给平台审核,审核通过后,便会在支付宝上架,通过搜索小程序名称即可使用对应小程序。

代码打包

小程序启动时,客户端会从CDN下载小程序离线包,这个离线包是将原项目打包后的一个 .tar 文件,存放在 /data/data/com.eg.android.AlipayGphone/files/nebulaInstallApps 目录下。从真机上拖出来解压后得到目录如下

其中 index.htmlindex.jsindex.worker.js就是之前我们编写的代码所编译出的js代码。其中index.worker.js是小程序所有页面的业务逻辑代码,对应着开发者编写的pageName.js中的内容;index.htmlindex.js中的内容对应着acss与axml,其中axml的组件信息、层次结构同样被会被编译成js代码,在运行时由这些js进行渲染。

代码加载

开发者写的所有代码最终将会打包成一份 JavaScript 脚本,在小程序启动的时候运行,在小程序结束运行时销毁。

双线程模型框架

开发者开发的小程序源码打包之后主要分为两部分,第一部分负责小程序的视图展示,打包产物为index.js,称之为Render部分;第二部分负责小程序的业务逻辑、视图更新等,打包产物为index.worker.js,称之为Worker部分。

支付宝小程序的前端框架APPX也分为Render部分,对应需要加载的文件为af-appx.min.js,以及Worker部分,对应加载的文件为af-appx.worker.min.js。前端框架主要负责准备业务代码需要的一些对象和数据,在准备环境的时候开始加载,用于初始化环境、往Render和Worker所处的运行环境中注册对象之类的。

Render

整个Render部分,包含index.js(开发者编写的视图层代码)和af-appx.min.js(小程序前端框架代码),均运行在WebView上(视图线程)。

Worker

整个Worker部分,包含index.worker.js(开发者编写的逻辑层代码)和af-appx.worker.min.js,均运行在V8引擎上(专有的JavaScript引擎)(应用服务线程)。

代码加载

Render

对于Render部分而言,需要加载af-appx.min.jsindex.htmlindex.js,从实现上,是通过WebView的loadUrl()方法,该方法可以加载网页,也可以加载字符串格式的js代码。

Hook住WVUCWebView 类中的loadUrl函数,可以看到Render部分的业务逻辑代码加载

观察index.html文件,我们可以发现,af-appx.min.js通过writeln()函数动态加载进来,同时index.js通过<script>标签引入

同时通过hook可以观察到af-appx.min.js内容的加载,在通过writeln()函数动态加载时,会触发WVUCWebView对应WebViewClient的shouldInterceptRequest()函数

Worker

进入 com.alibaba.ariver.v8worker.V8Worker 类,从构造函数开始梳理加载逻辑如下

逆向代码截图示意如下

为方便阅读,仅将Worker部分的逻辑整理为如下内容

d("V8_Preparing");
d("V8_InitJSEngine");
d("V8_createJsiInstance");
d("V8_CreateIsolate"); ==> 创建V8 Isolate(V8中的概念,在下方做简单介绍)
d("V8_CreateJSContext"); ==> 创建Context
d("V8_SetupWebAPI");
d("V8_ReadJSBridge");
d("V8_ExecuteJSBridge");
d("V8_InjectInitialParams");
d("V8_LoadAppxWorkerJS"); ==> 加载前端框架worker部分的js:https://appx/af-appx.worker.min.js
d("V8_ExecuteAppxWorkerJS"); ==> 执行前端框架worker部分的js
d("V8_JSBridgeReady"); ==> V8 Worker和原生APP之间的JSBridge准备就绪
d("V8_PushWorker");
d("V8_MergeJsApiCacheParams");
d("V8_InjectFullParams");
d("V8_ImportScripts_BizJS"); ==> 加载小程序业务逻辑worker部分的js:index.worker.js
d("V8_WorkerReady"); ==> 至此Worker部分已经准备就绪

注解:

Isolate:Isolate和操作系统中进程的概念有些类似,进程是完全相互隔离的,一个进程里有多个线程,同时各个进程之间并不互相共享资源。Isolate也是一样,Isolate1和Isolate2拥有各自堆栈的虚拟机实例,且相互完全隔离。

Context:在V8中,一个Context就是一个执行环境,它使得可以在一个V8实例中运行相互隔离且无关的JavaScript代码,必须为将要执行的JavaScript代码显式地指定一个Context。同一个Isolate中可以创建多个Context执行环境,多个Context执行环境中的JavaScript代码互不影响。

通过Hook,可以看到V8引擎会先加载前端框架worker部分的js代码:https://appx/af-appx.worker.min.js,然后加载小程序业务worker部分的js代码:index.worker.js(具体JS内容均被混淆过),最后将worker的状态置为ready状态。

通过Hook抓取到的业务逻辑中的JS代码和从小程序源码文件中提取出来的代码一致

运行机制

架构组件

小程序实际由H5应用发展而来,且H5应用仍在支付宝上进行使用。支付宝架构组件如下图所示,移动应用如小程序和H5应用,是使用前端技术编写的应用,开发起来方便简单;H5容器为移动应用提供运行环境,开放 JSAPI 供移动应用使用,提供宿主APP的原生能力;支付宝底层支持提供支付宝的功能,如网络、存储等等。

运行机制

细化小程序和H5容器组件中的内容,框架解析图如下图所示。

H5容器提供两个JS运行环境加载移动应用,并提供 JSAPI 供其调用。其中H5应用仅运行于WebView中,且运行于主进程中;小程序的两部分Worker和Render分别运行于V8引擎和WebView中,且小程序与宿主APP运行在不同进程中,每个小程序运行在单独进程中,每个小程序的Render和Worker也运行在不同的线程中。

线程模型

小程序框架启动会启动 LiteProcessActivity,该activity运行在独立的进程中,并在其 onCreate() 函数中初始化Render和Worker(暂未发现该部分代码?),Render线程运行于主线程中,Worker线程运行于 "worker-jsapi"线程中,相互跨线程的调用主要依靠互相持有引用。

双栈结构(多线程)运行机制

双栈结构,其中Render(WebView)属于前端,负责渲染页面;Worker属于后端,负责执行功能。

以下图为例,描述前后端的各自作用。Render部分负责渲染出含有一个Button的页面,该Button绑定了 getLocation 事件,并且使用WebView组件加载该页面,当该Button被触发时,Render向Worker传递消息,getLocation事件被触发了,需要执行相应的JS代码,在Worker加载的业务代码中,getLocation事件对应的是 my.getLocation()函数,于是执行该函数,该函数通过Worker与宿主APP之间的JS Bridge向下调用,使用宿主APP的原生能力获取地理位置信息,并且将地理位置从宿主APP传递到Worker,再由Worker将数据传递回Render,交由Render进行重新渲染,将数据显示在可视界面上。

数据绑定、前后分离的新实践

小程序的核心是一个响应式的数据绑定系统,分为视图层(Render)和逻辑层(Worker)。这两层始终保持同步,只要在逻辑层修改数据,视图层就会相应的更新

举例如下

<!-- 视图层 -->
<view> Hello {{name}}! </view>
<button onTap="changeName"> Click me! </button>
// 逻辑层
var initialData = {name: 'taobao',
};
// 注册一个页面
Page({data: initialData,changeName(e) {// 改变数据this.setData({name: 'alipay',});},
});

小程序框架会自动将逻辑层数据中的 name与视图层的 name进行了绑定,所以在页面一打开的时候会显示 Hello taobao!。当用户点击视图层中定义的按钮时,视图层会发送 changeName的事件给逻辑层,逻辑层找到对应的事件处理函数(具体涉及到Render和Worker的通信信道,将在后续章节中详细说明)。逻辑层执行了 setData的操作,将nametaobao变成alipay,因为该数据和视图层已经绑定了,从而视图层会自动改变为Hello alipay!

小程序主要靠视图线程(WebView)和应用服务线程(Worker)来控制管理,两个线程同时运行。

Worker线程启动后,会初始化小程序,小程序初始化完成时会触发 app.onLaunch 回调,当小程序启动时,会触发 app.onShow 回调,然后完成App的创建。

当页面Page初始化时,会触发 page.onLoad 回调,页面完成显示时会触发 page.onShow 回调,然后完成Page创建,此时Worker线程等待Webview线程初始化完成通知。

Webview线程初始化完成通知Worker线程,然后Worker将初始化数据(如上示例中的initialData)发送给Webview进行渲染,此时Webview线程完成第一次数据渲染。

第一次渲染完成后,Webview线程进入就绪状态并通知Worker线程,Worker线程调用 page.onReady 函数并进入活动状态。

Worker线程进入活动状态后,每次数据修改将会通知Webview线程进行渲染。当切换页面进入后台,应用线程调用 page.onHide 函数后,进入存活状态;页面返回到前台将调用 page.onShow 函数,再次进入活动状态;当调用返回或重定向页面后将调用 page.onUnload函数,进行页面销毁。

Render与Worker 通信

小程序运行在宿主APP上,因此需要小程序到宿主APP的通信信道;与此同时,由于双栈结构的天然隔离,还需要Worker与Render之间的通信信道。总的通信模型如下图所示

Render

  • 与宿主APP通信

    JS环境内将 JSAPI 的请求与参数拼接成字符串并调用 Console.log() ,容器通过拦截给 WebView 设置的对应的 WebViewClient 中的onConsoleMessage() 函数,解析字符串并完成对应API的调用实现功能。

    容器执行完对应API计算得到结果后,通过调用 WebView 的 loadUrl() 函数向JS环境回传字符串格式的JS代码。

  • 与Worker通信

    Render和Worker的双向通信是通过 WebMessageChannel 实现的,hook相应接口可以看到 Render 向 Worker 传递的请求调用信息

    MsgFromMsgChannel: {"func":"postMessage","param":{"data":{"i":1,"p":{"a":1,"p":[[[],[],[],[],[],[],null],[0,"getNetworkType",{"currentTarget":{"dataset":{},"id":"","offsetLeft":18,"offsetTop":84,"tagName":"view"},"detail":{"clientX":71.23809814453125,"clientY":107.04762268066406,"pageX":71.23809814453125,"pageY":107.04762268066406},"target":{"dataset":{},"id":"","offsetLeft":18,"offsetTop":84,"tagName":"view","targetDataset":{}},"timeStamp":1600233550045,"type":"tap"}]]},"t":4},"msgPortId":1,"type":"messagePort","viewId":2},"msgType":"call","clientId":"16002335500450.7511516905653794","__FastPath__":1}
    

Worker

  • 与宿主APP通信

    在V8中注入的 JSBridge 会在Java侧(宿主APP)注册回调(JsApiHandler),用于响应V8中发起的请求,然后在宿主侧完成相应API的功能调用

    通过hook相关接口,可以看到Worker调用NativeBridge的请求调用信息

    handleAsyncJsapiRequest: {"callbackId":"getNetworkType##30","handlerName":"getNetworkType","data":{}} null
    

    宿主侧完成API调用后,计算得出相应信息,并回传给Worker

    通过hook相关接口,可以看到回调返回结果信息

    sendJsonToWorker(MsgFromCallback): null null {"responseData":{"err_msg":"network_type:wifi","networkAvailable":true,"networkInfo":"WIFI","networkType":"wifi"},"responseId":"getNetworkType##30"}
    
  • 与Render通信

    Worker 拿到相关结果后需要将数据交由 Render 进行渲染,前面已经提过,Render和Worker的双向通信是由 WebMessageChannel 实现的

    通过 hook 相关接口,可以看到Worker向Render传递的信息如下

    tryPostMessageByMessageChannel: postMessage,2,{"callbackId":"postMessage##31","handlerName":"postMessage","data":{"data":{"i":3,"p":{"a":3,"s":"[[{\"q\":[[1,{\"hasNetworkType\":true,\"networkType\":\"WIFI\"}]],\"t\":0}],[0,[],[],null,[]]]"},"sn":1600233535779,"t":2},"type":"messagePort","msgPortId":1,"viewId":"2","pageId":"2"}},
    

如果将以上通信信道用更详细的模型图表示,如下

通信示例

举例一次Render–>Worker–>NativeBridge–>Worker–>Render的调用示例,如下图

对以上通信示例做详细说明。

开发者在 index.axml 文件中部署了一个 Button 按钮,该按钮绑定了对应的事件为:getLocation,当用户使用该小程序时,会看到Render部分呈现的就是一个Button按钮,实际上是通过WebView加载了对应的页面。

当用户点击该Button时,Render向V8Worker传递消息,告诉Worker需要执行 getLocation 事件对应的代码(上图中的标号1); Worker 接收到该信息后,执行事件对应的代码,也就是调用 my.getLocation() (上图中的标号2); 通过绑定的Java侧回调(上图中的标号3),NativeBridge开始执行本次 JSAPI 的调用(上图中的标号4); 在API的调用过程中,会遇到很多的权限检查(上图中的标号5);当通过了所有的权限检查后,API执行成功,调用宿主底层能力拿到 getLocation 的计算结果,并将该结果回传给 V8Worker(上图中的标号6);Worker拿到结果后,回传给Render进行重新渲染(上图中的标号7),将结果展示给用户。

小程序安全防护体系

  1. 域名通信

    小程序开发者可以在后台配置允许通信的域名白名单,该白名单存在于打包后的api-permission文件中,对应内容如下图所示。白名单限制了小程序的业务代码与外部通信的能力,仅允许向白名单中的域名发送request请求。在框架代码中,会在调用到 my.request、my.uploadFile 对应的API的实现类之前对是否允许本次操作进行校验


  1. 域名加载

    支付宝小程序提供了开放组件 web-view,用于在小程序中加载H5页面或网页。使用 web-view 组件时,需要完成H5页面中所有域名地址(含静态资源地址,如图片、.js文件地址等)配置,仅允许配置于白名单中的域名加载到小程序中。

    该白名单同样存在于打包后的 api-permission文件中,对应内容如下图所示。

当使用 web-view 组件加载对应的H5网页时,在使用 WebView.loadUrl() 实现页面加载之前,会对当前待加载的H5域名进行白名单正则匹配安全校验,只有当校验通过时,才会允许当前页面加载并显示。

注意:此时用于加载H5的WebView是一个新的WebView实例,跟之前用于加载 index.html并不是同一个WebView实例。

  1. API调用能力限制

    在之前的叙述中,只有Worker拥有调用 JSAPI的能力,因为Render部分加载的仅仅是 index.axml 和 index.acss的内容,这两个文件中并不能写入JS代码(此处暂不讲SJS的情况)。

    但在上面的第二点"域名加载"中,我们提到了 web-view 开放组件,用于加载H5网页,使用该组件加载H5内容同样归属于Render的范畴。但只要在H5网页中引入对应的 JSBridge文件:https://appx/web-view.min.js ,即可在H5中通过JavaScript调用 JSAPI。

    所以针对 API 调用能力限制的安全防护就分为了两个方面,一是针对Worker能调用API的能力限制,二是针对Render中加载外部H5能调用API的能力限制。

  • Worker

    Worker能调用的 JSAPI 同样使用白名单进行限制,存在于 api-permission文件中,如下所示。在调用到框架层对应API的实现类之前,会先判断该小程序是否有能力调用对应API

  • Render

    WebView加载的H5能调用的API能力分为3类校验,第一类是通过校验当前加载的H5的域名,定义其权限等级再分配API白名单;第二类是存在部分高权限小程序appid白名单,在这个白名单上的小程序中加载的H5拥有调用所有API的能力;第三类是框架中写死的仅允许外部H5调用的API白名单

API权限控制示意图如下所示

  1. worker 沙箱

    1. 单V8 Context结构(存在安全问题)

      如上图所示,在V8 Worker的初期,一个小程序占用一个V8 Isolate,一个V8 Isolate只创建一个V8 Context。于是小程序的前端框架APPX的代码appx.worker.min.js和小程序的业务代码index.worker.js运行于同一个V8 Isolate上的同一个V8 Context上。这样的设计就会存在JS安全性问题,业务JS代码就可以通过拼接冒名的形式访问到APPX注入的内部JS对象和内部JSAPI,在同一个V8 Context中,是无法隔离开业务JS代码和APPX框架JS代码的运行环境的。所以这种单V8 Context的结构是不安全的

    2. 多Context隔离的V8 Worker结构(解决1中的安全问题)

      如上图所示,对于同一个小程序,在同一个V8 Isolate下,分别为前端框架脚本(af-app.worker.min.js)、小程序业务脚本(index.worker.js)和小程序插件脚本(plugin/index.worker.js)创建单独的APPX Context、Biz Context、Plugin Context,默认情况下不同的Context是不能互相访问的,除非通过SetSecurityToken设定安全令牌。

    3. 多Isolate隔离的多线程Worker

      在小程序中,对于一些异步处理的任务,可以放置于后台Worker线程去运行,待运行结束后,再把结果返回到小程序主线程,这就是多线程Worker。

      小程序Worker主线程运行于单独的V8 Isolate上,同时,业务JS、APPX框架JS、插件JS会运行在属于各自的V8 Context上。同时对于每一个Worker任务,都会单独起一个Worker线程,创建单独的V8 Isolate和V8 Context实例。每一个Worker任务和小程序主线程中的任务都是相互线程隔离的、Isolate隔离的。Isolate隔离意味着V8堆的隔离,因此Worker主线程和后台Worker线程,是无法直接传递数据的。Worker主线程和后台Worker线程想要实现数据传递,则需要进行序列化和反序列化。序列化即将数据从源V8堆上拷贝至C++堆上,反序列化即将数据从C++堆上拷贝至目标V8堆上。Worker主线程和后台Worker线程通过序列化和反序列化的接口postMessage和onMessage来进行数据传递。

参考文献

支付宝小程序V8Worker技术揭秘 - 掘金

支付宝小程序开发文档

支付宝小程序框架分析相关推荐

  1. 微信小程序框架分析思维导图

    微信小程序框架分析思维导图

  2. 微信小程序框架分析小练手(三)——仿香哈菜谱小程序制作

    香哈菜谱是一款围绕美食而成的小程序,在这里可以查看各式各样的菜谱. 一.打开微信开发者工具,新建一个项目:xhcp.如下图: 二.建立如下的一些目录: 三.将底部标签导航图标.美食轮播图片.宫格导航图 ...

  3. 微信小程序开发学习笔记002--微信小程序框架解密

    1.今天内容比较多, 框架解密 • 目录结构 • 配置文件详解 • 逻辑层 • Api简介 ----------------------- 2.打开微信开发工具,   点击添加项目,选择无appid模 ...

  4. 揭秘:支付宝小程序 V8 Worker 技术演进

    简介: 本文分享支付宝小程序 V8 Worker 相关工作沉淀和总结,包括技术演进.基础架构.基础功能.以及 JS 引擎能力输出,以及一些优化方案等.欢迎同学们共同探讨,指正.(文末福利:<小程 ...

  5. 用户超5亿,三年投10亿,开发者如何抢滩支付宝小程序蓝海?

    2018 年,被称为小程序正式搭建互联网生态圈的一年. 各大互联网巨头纷纷围猎小程序,意图用小程序丰富自己的服务形态. 而随着入局者越来越多,竞争愈发激烈.虽有"小程序红利期将持续 5 年& ...

  6. 独家!支付宝小程序技术架构全解析

    在轻应用混战的当下,小程序已经成为巨头们角逐的焦点,阿里自然也不甘落后.据阿里官方的数据,截止到今年1月28日为止,支付宝小程序应用数已经达到12万,总用户数突破5亿,日活跃用户数突破2.3亿,用户通 ...

  7. 滴滴小程序框架Mpx2.0

    @hiyuki,滴滴出行网约车webapp乘客团队的负责人,也是滴滴开源的小程序框架Mpx的负责人和核心作者 Mpx是一款致力于提高小程序开发体验和效率的增强型小程序框架,目前在滴滴公司内部支撑了包括 ...

  8. 滴滴开源小程序框架 Mpx

    Mpx 是一款致力于提高小程序开发体验的增强型小程序框架,通过 Mpx,能够以最先进的 Web 开发体验 ( Vue + Webpack ) 来开发生产性能深度优化的小程序,Mpx 具有以下一些优秀特 ...

  9. 支付宝小程序开发体验

    在使用过程中想到一点记录一点,只是个人感想. 支付宝小程序的底层应该是React Native的,但是,小程序界面的语法,跟weex更接近.比如,.axml文件,相当于<templete> ...

  10. 小程序框架选型必看:Taro vs uni-app选型经历!

    公司新产品要求发布到各家小程序,最近研究对比了社区主流的几家小程序开发框架,独坑不如拉人众坑,分享给各位,欢迎和我一起入坑:) 背景 最近老板不知怎的很重视各种小程序平台,感觉要靠小程序完成今年大半k ...

最新文章

  1. Nature重磅:管轶等发现穿山甲是SARS-CoV-2的中间宿主
  2. Apache Samza流处理框架介绍——kafka+LevelDB的Key/Value数据库来存储历史消息+?
  3. getopt();getopt_long();getopt_long_only();option
  4. 一个5节点的polardb mysql_POLARDB问题
  5. HDFS--分布式文件系统
  6. php截取3位数,使用php实现截取指定长度
  7. EasyUI 收藏夹(私藏)
  8. 计算机操作系统--思维导图
  9. Polar SI9000阻抗计算
  10. Endnotex8 运行时出现错误 unknown error -0xA84c的解决办法
  11. HTTP协议的基本格式
  12. Verilog实现的格雷码与二进制码的互相转换
  13. 群相册上传照片显示服务器繁忙,QQ相册上传速度慢怎么办 QQ相册上传不了照片解决方法...
  14. ffmpeg中的时间单位以及时间转换函数(av_q2d av_rescale_q)
  15. 遍历数组-forEach
  16. C#随机生成姓名、电话类
  17. Python selenium 实现大麦网自动购票过程
  18. 0704函数的递归调用
  19. 使用MyQR和qrcode来制作二维码
  20. 影视后期行业概述、制作流程、岗位划分、薪资待遇、课程介绍详解

热门文章

  1. gbdt算法 java实现_决策树之 GBDT 算法的回归部分
  2. 计算机的字体要怎么删除,计算机安装删除字体
  3. PTA 程序设计天梯赛(1~180题)
  4. 信息收集--空间搜索引擎/网盘
  5. 苹果手机打不开html文件,苹果手机描述文件打不开怎么办
  6. 计算机单机管理软件,小财迷电脑收银系统(收银记账软件) 单机版
  7. IEEE Transactions Latex模板使用经验总结
  8. 树莓派控制台达伺服控制器
  9. 欧拉角Yaw、Pitch、Roll
  10. JavaScript 调用 Windows Win32 API