利昂图书馆预约App分析(2020年9月3日)

内容纲要

受学长启发,打算去研究下学校图书馆的预约App实现自习室自动抢座,以下是我的研究过程。

Disclaimer

仅供学习使用,请勿用于非法用途,也请尊重早起抢座的同学的利益。

从Google开始

先去Google了下,发现好多利昂图书馆预约脚本,但看相关的issue,好像在去年五月(那时候我还在摸高考)利昂图书馆预约软件升级后而无法使用。

抓包分析

这里使用了Packet Capture(Google Play),发现其启用了https,装CA。

Leo Auth Request
登录请求

发现其存在以下有趣的内容:

  • x-hmac-request-key
  • x-request-date
  • x-request-id

由于自己之前曾经尝试implement过yubico的OTP插件,对hmac signing略有了解,HMAC signing需要message与key,而message一般是http request的body,而且往往伴随着nonce,key一般预分发的,应该会被硬编码进app中。而x-hmac-request-key是64字节,应该是HMAC-SHA256

这里并没有看到nonce,所以就把x-request-id看作nonce,接下来的目标就是去逆向app。

app的逆向(寻Key大挑战)

逐渐发现是使用Flutter AOT

这里使用了jadx-gui,在source code中翻了大半天还是没找到任何有关http请求或hmac算法相关的代码,感觉应该是编译成了.so文件。

这个.so文件一看不得了,只有一个libflutter.so,而且resources\assets目录下还有以下文件。

  • isolate_snapshot_data
  • isolate_snapshot_instr
  • vm_snapshot_data
  • vm_snapshot_instr

linux下file下?data!??

Google了下才知道是用flutter完成的开发,而且启用了flutter AOT。再查flutter逆向相关,并没有任何工具,而且还真的是劝退了不少人。

我使用binary ninja demo把每个文件都试了个遍,并无法识别平台。

确定努力方向为Snapshot

通过官方文档Flutter-engine-operation-in-AOT-Mode可知其使用了Dart VM。突然感觉难度上升了超多。Jesus...

不过一个好消息是文档中提到其中的Snapshot保存了heap的初始状态,那么应该可以拿到key。我花了一晚上去找Key并没有任何成果。第二天有课先睡觉。

第二天下午没课打开Cutter继续寻找线索。在Snapshot中我发现x-request-idx-request-key这两个字符串是在一起的,于是我猜测Key应该也是与HMAC算法相关的在一起,通过我不断努力搜索,终于找到了一个令我感兴趣的字符串leos3cr3t,先将其看作key。

Leo snapshot Secret
Leo snapshot Secret

一点有趣的东西

在寻Key过程中我也发现了x-request-id的生成方式为UUIDv1

Leo Snapshot UUIDv1
Leo Snapshot UUIDv1

寻找Message组成以及验证Key是否正确

从原本的经验出发

依照自己之前implement yubico OTP的经验,message应该是request body。于是我尝试对其进行各种变换(包括加入x-request-id),发现HMAC-SHA256的结果总是不对,那么应该还有其他的东西。

尝试确定有哪些项目用于HMAC-SHA256的生成

做到后面我开始有些找不到着手点,打算推翻自己之前靠经验做出的判断。从头开始确定到底哪些东西会用于HMAC-SHA256的message部分,这里使用了Postman发送请求。

让我比较傻眼的是,我尝试改变request params,服务器并没有返回非法访问错误,那么body应该与其无关。

那么API endpoint、server url与其有关吗?我也尝试访问奇怪的路径、使用IP访问,发现返回信息也正常,那么与这些因素也无关。

那么只有x-request-datex-request-id了,我尝试改变这两个参数,都会触发非法访问错误,那么这两个参数应该是与HMAC-SHA256的message/key有关了。

但我无论怎么改变这两个参数的排列组合,HMAC-SHA256出来的结果还是有问题,要么Key不对、要么是还有其他信息,但我相信Key是正确的。

换一个思路从内存入手

我更相信message中还有更多的信息,所以我打算直接去dump这个app的运行时内存,既然x-request-id是其message中的一员,那么在不做变形的情况下应该是可以在内存中追踪到完整变化的字符串的。

并没找到比较好用的Android设备中dump内存的工具,因此使用了Game Guardian,同时启用抓包工具对发送的请求进行记录。

最终成功dump出约800M的文件,先检验下Key(暂定)是否存在于内存,这里使用了个简单的bash循环+strings+grep来快速找出相关的信息。

for i in cn.com.libseatapp-*
do
    strings $i | grep leos3cr3t
done
Leo Memdump Key
Leo Memdump Key

看起来蛮不错的,让我充满了信心。

再去看下能不能拿出对应的UUID变换过程。

Leo Memdump message
Leo Memdump message

漂亮!

那么去验证下。

import hmac
import hashlib

time = '1599126887803'
UUID = '807cd4b0-e010-11ea-b4b5-11d182874e0f'
secret = 'leos3cr3t'
expected = 'adf116c5a2b46fbf0753022213e0d25050b6c5620995c71a61d98abe9f18bf13'

message = "seat::{}::{}::GET".format(UUID, time)

signature = hmac.new(bytes(secret, 'utf-8'), msg=bytes(message, 'utf-8'), digestmod=hashlib.sha256).hexdigest()

print('Message: {}'.format(message))
print('Expected: {}'.format(expected))
print('Result: {}'.format(signature))

if signature == expected:
    print('Match')
Leo Result Validation
Leo Result Validation

CHEERS!

总结

利昂客户端验证相关

根据上文得出的message组成,大概可以推断其组成如下:
seat::<UUIDv1>::<Timestamp>::<Request Method>
Key为leos3cr3t
而且在测试过程中我发现他会对时间差进行验证,超时即使正确的签名也会被返回非法访问。
Anyway,message的组成与key都弄出来了,也不怕什么了。

个人感受

Flutter AOT是真心难折腾,如果可以的话真的不想再碰一次了。总之也是学到不少东西的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据