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 框架复杂,初始验证过程不好模拟,且容易被服务器端的协议升级所反制,写一个手机协议的刷票软件仍然是十分费力且不讨好的事。

缺乏 ipv6 支持导致的 curl 库错误

wordpress 的 dashboard 中报错 WP HTTP: getpeername() failed with errno 57: Socket is not connected

上 Google 搜,也有人遇到了不过没有看到解决方案。自己通过 nslookup 查询以及 curl -v 调用,发现问题的来源是 curl 在 DNS 解析的时候,遇到 DNS 的 A 记录中有 ipv6 地址,就会出现这个问题。查询 port 里 curl 的安装选项,发现关掉了 ipv6 支持,因此 A 记录中的 ipv6 地址不能访问,就产生了以上的问题。

感叹一下国内的 ipv6 部署进展太慢了,以至于俺也没有动力在 vps 上折腾 ipv6 。

FreeBSD下载机达成

折腾了两个星期,终于通过 DIY 方式搭建了一台迷你主机。系统目前是 FreeBSD 8.1-RELEASE,跑着 mldonkey 和 samba 服务,专司下载共享之职。

 

硬件

硬件配置如下,每一个部件都历经了千辛万苦:

部件 品牌 价格 备注
CPU Intel U1300 1.06GHz(Yonah) 0 主板自带
主板 深圳智星工控ITX主板 945GM + ICH7M 300 网购
内存 Kingston 1G DDR2 667 120 无运费
硬盘 Seagate 120G ATA100 33 闲置旧硬盘,仅邮费
显卡 Intel GMA950 0 主板集成
声卡 Realtek AC97 0 主板集成
网卡 Marvell Yukon 88E8056 Gigabyte Ethernet 0 主板集成
机箱 AOpen S145 324 网购
风扇 超频三 花无缺/CoolMaster 6cm 41 CPU、机箱各一
键鼠 0 不需要
显示器 0 不需要
总计 938 含其它费用

CPU: 本来打算用自己笔记本上闲置的迅驰2代 Pentium M 740 1.73GHz CPU。但起先买的 ASUS YNRC-BR 主板上由于不明原因未能点亮,故换了主板后闲置了。

主板: 合适的主板实在是很难挑。开始要求 [1]能上移动版的迅驰CPU [2]尺寸必须是17x17cm [3]支持 IDE 接口。 现在的主板这三个条件能满足一个就不错了,挑起来太困难。后来从深圳华强北某卖家处买到 ASUS YNRC-BR 主板,符合以上要求,但偏偏我这边点不亮它,等退货回去之后发现可能是我用的电源线接触不好导致…… 太丧了,而且主板退货耗费 40 元邮费,外加没退回的附件 12 元。后来在淘宝上看到某卖家卖的工控945GM主板,还带一个 U1300 CPU,心想不折腾了于是就 300 大元买下。
这个主板的另外两件是是关于接口的。主板上没有标准的PS/2键盘接口,取代的是主板上的四根针让自己扩展。另外 IDE 接口由于空间限制采用了 2.0mm 针距的小口,笔记本硬盘那种。后来所有的东西都齐了,偏偏这两个接口让我抓狂,有点欲哭无泪的感觉。末了还是在中发电子市场改装了一根四针的键盘延长线(下图中从主板穿出来的那根线即是),解决了键盘问题(针脚定义是通过枚举法一个一个试出来的……)。IDE 接口则是买了一个大口转小口的转换卡,又请手机维修铺的人改装,总算完成。这里一共耗资 33 大洋。

内存: 在水木二手版看到某人转让 OCZ 1G DDR2 800 内存,于是 100 大元买下。回来插到台式机上试,发现两台机器均点不亮。后来与卖家联系,人已经去了大洋彼岸。好在买卖不成仁义在,卖家通过支付宝把钱退给我了,大赞。尔后,在中关村鼎好柜台花 120 大元于某奸商处购得 Kingston 1G 内存一条。

硬盘: 硬盘是家里闲置的一块,让偶弟弟寄来。他傻乎乎的跑邮局给我寄 EMS,结果耗资 33 大洋,无比心痛。以后一定要记得还是顺丰靠谱。

机箱: 机箱是本次配置中最贵的部件( -_-b 不太正常)。小机箱实在没有便宜的,而且当初要求能装笔记本光驱,挑起来就格外费劲。后来看中 AOpen S152,没想到太贵了,将近 500 块,心疼之余换了 S145 ,只能放一块硬盘所以便宜一点点,但还是在所有部件中价格一举夺冠!机箱买回来发现做工挺好,用料结实,螺丝等附件一应俱全,安装起来不费劲。起码也算是物有所值了。

风扇:风扇上当了一回冤大头。CPU 风扇其实是为那个退掉的 YNRC-BR 主板买的。这个主板也是设计得怪,必须用挂钩的方式来固定,再加上体积小,必须找一个用挂钩而且个头小的风扇,最后选择了超频三旗下的花无缺北桥散热器,正好用于小主板上的 CPU。可惜后来换了主板,新板子上 CPU 和风扇都备好了,于是这个散热器就闲置了。
机箱风扇则是买的 CoolMaster 的某 6cm 尺寸,还辛苦地挑了一个带螺丝的包装,没想到寄来之后那螺丝完全用不上,倒是用机箱自带的螺丝使劲拧入塑料孔中即可。
随风扇还有一个 IDE 电源口取电的转换接头,也闲置了。 这三项一共花了 41 大元。

软件

软件系统决定上 FreeBSD,不是 Linux 更不是 Windows Server。 不用 Windows 是想借此熟悉、深入 unix-like 系统;不用 Linux,是因为不想纠缠于 Linux 各发行版的差别。有时候就因为这些差别,遇到问题的解决方案也不一样,叫人很难适应。相反地,FreeBSD 的基本系统在统一性上是有保障的。我的这个配置也足够幸运地可以正常运行 FreeBSD 系统。

FreeBSD 是从 U盘上安装的,下载了 FreeBSD-8.1-RELEASE-i386-memstick.img 这个映像,然后用 dd for windows 写入。参见 FreeBSDChina.org 的 wiki王炜的这篇文章

随后安装 mldonkey 和 samba,FreeBSD 的 ports 让这一切都变得简单。部分过程可以参考 chinaunix 上某人博客的三篇文章

收到德生寄来的支撑板

07年暑假买的德生 R-9700DX 收音机,前阵子打开支撑板收听的时候不小心把手放上去按了一下,结果“啪”地一声支撑板就断掉了。后来在 BBS 的 Radio 版上求助,又跑到德生论坛骚扰用户服务部门的经理候传,终于在今天收到了德生寄过来的 R-9700DX 收音机支撑板,顺利安上,正常使用如初。(就是图中的那个支撑板此篇文章为博客迁移后导入,图君已经壮烈牺牲。)

如今收音机已经是比较小众的产品了,除了会在地震灾难的时候向灾区投放一点,平时有使用习惯的人不多(当然,堵车倒是普及了交通广播的收听率)。德生在这方面一贯坚持“享受广播 enjoy broadcasting” 的理念,不光在产品上不断地推陈出新,同时也依靠完善的售后服务巩固了用户对于德生品牌的信心,必须赞一下。