12306手机版App逆向研究

抽空研究了一下 12306 放出的 Android 版手机 App,本想挖一挖有没有手机版协议可供调用,不过就结果来看意义已经不大。以下简单记一下过程。

12306 的 apk 安装包可以用 WinRAR 打开,解压出 classes.dex,然后用 dex2jar 转换成 jar 包,就可以放到 jd-gui 里看 java 源代码了,源代码未经混淆,看起来十分方便。

在 jd-gui 里可以看到有好几个 java package,cn.domob 这个包是多盟的广告联盟代码,12306 App 的界面上未见到有广告,估计是用来做统计分析的。有 com.worklight 这个包,可以证明是用了 IBM Worklight 这个框架。真正实打实有内容的就是 com.MobileTicket 这一个包,其它的都是引入的库,并非 12306 相关的程序代码。阅读入口类 MobileTicket.class 的代码,可以发现其调用了本地的浏览器显示一个网页。也就是说:12306 App 的主界面其实是由网页组成的,整个程序的逻辑实现也都放在网页里了,java 源代码的干货其实很少。

  protected void bindBrowser(CordovaWebView paramCordovaWebView, boolean paramBoolean)
  {
    super.bindBrowser(paramCordovaWebView, paramBoolean);
    mDService = new DService(this, "56OJyf1IuNPVnYbzz4", "16TLwHboApIGwNU-ypi0ThBk");
    mDViewManager = new DViewManager(this, mDService);
    mDViewManager.doAppStartReport();
    paramCordovaWebView.pluginManager.addService("WebResourcesDownloader", "com.worklight.androidgap.plugin.SSLWebResourcesDownloaderPlugin");
    paramCordovaWebView.pluginManager.addService("NativeBusyIndicator", "com.worklight.androidgap.plugin.MyBusyIndicator");
  }

  public void init()
  {
    SSLWLWebView localSSLWLWebView = new SSLWLWebView(this);
    if (Build.VERSION.SDK_INT < 11) {}
    for (Object localObject = new CordovaWebViewClient(this, localSSLWLWebView);; localObject = new IceCreamCordovaWebViewClient(this, localSSLWLWebView))
    {
      super.init(localSSLWLWebView, (CordovaWebViewClient)localObject, new CordovaChromeClient(this, localSSLWLWebView));
      return;
    }
  }

知道是怎么回事,就得提取网页资源了。apk 安装包中的 assets/www 目录里有两个 resources.zip 文件,但似乎经过特殊处理,直接用 WinRAR 解压缩无法打开。另辟奚径,把手机程序安装到 Android 手机里(模拟器也行),然后用手机文件管理器打开 /data/data/com.MobileTicket 这个数据目录,就能看到程序使用的关键数据了。网页就存在 files/www/default 这里。

看了看代码,是基于 worklight 框架用 MVC 的方式实现的,view 都是 html 网页,js 来实现 model 和 controller,12306 的程序猿很厚道,js 都没有混淆,连中文注释都在,读起来十分方便,嘿嘿。比较关键的有 MobileTicket.js util.js 还有 controller 目录里的各个 js,看看文件名就知道是用来干什么的很好读。

这里有一个比较关键的 CheckCodePlugin(此 check code 并非我们看到的四位验证码,而是请求里自带的一个参数),通过网络抓包也会发现每次都会提交一个请求的参数是check_code,而这个东东实现上貌似是由 libcheckcode.so 这个 native 库来实现的(CheckCodePlugin.js 中有一份调试版本,但未可知调试版本与实现版本有何不同)。把 libcheckcode.so 放到 IDA Pro 里进行反汇编分析,在导出表中能看到 main, MD5_Init, MD5_Update 等几个模块,看了看汇编代码,也就是简单地做了个 MD5 Hash,看来 libcheckcode.so 与 js 的行为应该是一致的。很好奇为什么做 MD5 这活儿非要用一个 native 的库来完成,为了故弄玄虚?也许是为了保密或者准备以后换成更复杂的 checkcode 算法。

关于抓包,12306 App 需要读取本地网页,单纯给 Android 设置系统级代理然后在 PC 上用 Fiddler 抓包的话,打开程序便会跳出错误,因为无法读取本地的 file:/// 开头的 URL (其实就是本地网页)。如此,需要用 ProxyDroid 这样的代理 App,使之忽略掉 localhost 和 intranet address,然后在 Fiddler 中启用 HTTPS 分析,就可以轻松抓到 12306 手机 App 的协议。

协议中最开始有 IBM Worklight 框架自带的验证过程,由于此验证过程由 Worklight 框架实现,分析起来较为复杂。App 里用到了 wl_antiXSRFRealm,wl_deviceNoProvisioningRealm 和 wl_authenticityRealm,这些实现都可以在 apk java 源代码的 com.worklight.wlclient.challengehandler 包中找到。

以上是主要的研究心得。最开始以为 libcheckcode.so 是用来生成图片验证码的,这样 12306 App 实际上不需要从服务器取得验证码,而是在本地完成,没想到这个猜测是错误的。实际上 App 仍然要从服务器请求验证码,这一流程在 orderManager.js 中可以看到 refreshCaptcha() 即是。总结一下,手机协议的优势是验证码简单容易破解,但是代价是 worklight 框架复杂,初始验证过程不好模拟,且容易被服务器端的协议升级所反制,写一个手机协议的刷票软件仍然是十分费力且不讨好的事。