首页
社区
课程
招聘
[原创]记一次逆向某医院挂号软件的经历
发表于: 2023-9-21 10:29 13074

[原创]记一次逆向某医院挂号软件的经历

2023-9-21 10:29
13074

最近家里娃需要挂专家号的儿保,奈何专家号实在过于抢手,身为程序员的我也没有其他的社会资源渠道可以去弄个号,只能发挥自己的技术力量来解决这个问题了。

首先把应用安装到我已经 Root 过的 Pixel 3 上面,点击应用图标打开的时候提示该手机已经Root过,请慎用,然后闪退,看来应该是加固保护做了 Root 检测了。
Root提示

先尝试绕过 Root 检测,打开安装过 Shamiko 的 Magisk,配置 Deny List。
绕过Root
绕过Root

正常打开应用。
正常打开

先截取下发送的数据请求,看下大致的流程。Android 7.0 以后,应用不会信任用户安装的根证书了,所以要抓包要么把根证书加到系统证书目录,要么就是通过Hook的手段强行绕过证书,我这边选第二种方法。安装 TrustmeAlready 以后,在 Lsposed 里面配置绕过证书。
绕过证书信任
绕过证书信任

顺利抓到数据。
Fiddler抓包
Fiddler抓包

接下来就是分析参数,这里面多数接口的信息都是比较明确的,比如获取医生信息、就诊信息等,基本上都是明文的信息,这些略过不表。比较关键的是一些涉及用户信息的,比如 tempCustId 以及 userCustId ,从实践来看,tempCustId 应该是就诊人的 ID,而 userCustId 应该是账号登录的人的 ID,因为可以登录自己账号帮别人挂号,这个也是合理的。然后 appKey 应该是登录的 token 信息,这边实践也是证明每次登录都是不一样的。最后有个关键的是 uuid,看起来就是平时理解的 uuid,可以到时候尝试随机生成试下。好了,基本参数分析完以后,开始构造参数来模拟请求,看看对不对。

很不幸,失败了。从尝试来看,唯一影响的就是上面的 uuid,因为从抓包的请求中扣下来填入模拟请求的话,这个请求是成功的,但是自己随机构造的话,就是失败的。看起来这个 uuid 应该涉及到一些加密算法的校验,解密失败在服务端认为异常的请求。
Postman模拟请求

所以开起来还是要分析源代码看下这个参数的构建过程,使用 JADX 打开,看下源代码。
JADX
使用了阿里的加固方案,而且是把 Dex 转成了 SO 的文件,然后在运行时使用自定义的 ClassLoader 去加载的,看来要解决这个问题还是要先脱壳了。暂时先止步于此。

等等!在再次查看抓包的数据,发现在请求的过程中加载了大量的 HTML 的文件,所以我猜测这个 APP 只是一个套壳的网页应用,在分析可能的入口网址后,我拿地址在网页中打开。果然,跟我想象的一样。
Edge
那么 uuid 的生成是否可能在网页应用的源码里面呢,打开 devtools 尝试搜索 uuid 相关的字符串,果然找到了相应的代码。
devtools
至此,搞定了 uuid 的生成逻辑,距离成功又迈进了一大步。

现在距离最后的成功还差一步,就是在发起预约的时候,有个图形验证码需要校验。
验证码

通过多次调用请求图形验证码的接口,我发现它生成的规则比较固定,基本都是数字的组合,而且有一点就是,数字在图片上的布局也是固定的。所以这个地方如果用TensorFlow去做识别就比较简单了。
验证码位置

首先收集数据集。
验证码数据集

然后对采集到的数据做标注提取以及灰度化处理。

单张图片采集出来的数字图片。
数据批注

构建拟合函数并保存训练模型。

验证下训练的模型。
验证训练模型

最后模拟请求发送,完成预约挂号。
成功挂号

def paa(file):
    img = Image.open(file).convert('L') # 读取图片并灰度化
 
    img = img.crop((0, 10, 100, 36)) # 120*44图片裁剪为100*26
 
    # 分离数字
    img1 = img.crop((0, 0, 19, 26)) # 单个数字图片大小为19*26
    img2 = img.crop((30, 0, 49, 26))
    img3 = img.crop((60, 0, 79, 26))
    img4 = img.crop((80, 0, 99, 26))
 
    img1 = np.array(img1).flatten() # 扁平化,把二维弄成一维度
    img1 = list(map(lambda x: 1 if x <= 180 else 0, img1))
    img2 = np.array(img2).flatten()
    img2 = list(map(lambda x: 1 if x <= 180 else 0, img2))
    img3 = np.array(img3).flatten()
    img3 = list(map(lambda x: 1 if x <= 180 else 0, img3))
    img4 = np.array(img4).flatten()
    img4 = list(map(lambda x: 1 if x <= 180 else 0, img4))
 
    return (img1, img2, img3, img4)
def paa(file):
    img = Image.open(file).convert('L') # 读取图片并灰度化
 
    img = img.crop((0, 10, 100, 36)) # 120*44图片裁剪为100*26
 
    # 分离数字
    img1 = img.crop((0, 0, 19, 26)) # 单个数字图片大小为19*26
    img2 = img.crop((30, 0, 49, 26))
    img3 = img.crop((60, 0, 79, 26))
    img4 = img.crop((80, 0, 99, 26))
 
    img1 = np.array(img1).flatten() # 扁平化,把二维弄成一维度
    img1 = list(map(lambda x: 1 if x <= 180 else 0, img1))
    img2 = np.array(img2).flatten()
    img2 = list(map(lambda x: 1 if x <= 180 else 0, img2))
    img3 = np.array(img3).flatten()
    img3 = list(map(lambda x: 1 if x <= 180 else 0, img3))
    img4 = np.array(img4).flatten()

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-9-21 10:37 被灵杰之舞编辑 ,原因: 图片加载失败
收藏
免费 5
支持
分享
最新回复 (10)
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢 分享
2023-9-21 10:46
0
雪    币: 1721
活跃值: (1565)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
技术分享支持,但求别搞医院挂号项目
2023-9-21 11:08
1
雪    币: 1392
活跃值: (5157)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
4
抢号抢得好,牢饭吃到饱。老哥别搞医院项目啊
2023-9-21 14:50
1
雪    币: 859
活跃值: (915)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
section title写的好啊
2023-9-21 16:01
0
雪    币: 3004
活跃值: (30866)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2023-9-22 09:20
1
雪    币: 498
活跃值: (4186)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
很刑啊
2023-9-23 16:45
0
雪    币: 3944
活跃值: (2380)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
暴露楼主位置了。
我都是微信12320挂号
2023-9-23 18:57
0
雪    币: 4055
活跃值: (4792)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
真刑,太可拷了
2023-9-23 19:40
0
雪    币: 3862
活跃值: (1504)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
人家自用,没啥不可吧
2023-9-24 22:12
0
雪    币: 15
活跃值: (135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
抢过口罩
2023-10-9 00:55
0
游客
登录 | 注册 方可回帖
返回
//