记一次逆向某医院挂号软件的经历

背景

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

出师不利

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

记一次逆向某医院挂号软件的经历_第1张图片

先截取下发送的数据请求,看下大致的流程。Android 7.0 以后,应用不会信任用户安装的根证书了,所以要抓包要么把根证书加到系统证书目录,要么就是通过Hook的手段强行绕过证书,我这边选第二种方法。安装 TrustmeAlready 以后,在 Lsposed 里面配置绕过证书。
记一次逆向某医院挂号软件的经历_第2张图片 记一次逆向某医院挂号软件的经历_第3张图片

顺利抓到数据。记一次逆向某医院挂号软件的经历_第4张图片 记一次逆向某医院挂号软件的经历_第5张图片

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

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

记一次逆向某医院挂号软件的经历_第6张图片

 所以开起来还是要分析源代码看下这个参数的构建过程,使用 JADX 打开,看下源代码。

记一次逆向某医院挂号软件的经历_第7张图片

使用了阿里的加固方案,而且是把 Dex 转成了 SO 的文件,然后在运行时使用自定义的 ClassLoader 去加载的,看来要解决这个问题还是要先脱壳了。暂时先止步于此。

柳岸花明

等等!在再次查看抓包的数据,发现在请求的过程中加载了大量的 HTML 的文件,所以我猜测这个 APP 只是一个套壳的网页应用,在分析可能的入口网址后,我拿地址在网页中打开。果然,跟我想象的一样。

记一次逆向某医院挂号软件的经历_第8张图片

 那么 uuid 的生成是否可能在网页应用的源码里面呢,打开 devtools 尝试搜索 uuid 相关的字符串,果然找到了相应的代码。记一次逆向某医院挂号软件的经历_第9张图片

至此,搞定了 uuid 的生成逻辑,距离成功又迈进了一大步。

最后的守卫

现在距离最后的成功还差一步,就是在发起预约的时候,有个图形验证码需要校验。记一次逆向某医院挂号软件的经历_第10张图片

 通过多次调用请求图形验证码的接口,我发现它生成的规则比较固定,基本都是数字的组合,而且有一点就是,数字在图片上的布局也是固定的。所以这个地方如果用TensorFlow去做识别就比较简单了。记一次逆向某医院挂号软件的经历_第11张图片

首先收集数据集。记一次逆向某医院挂号软件的经历_第12张图片 

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

def paa(file):

    img = Image.open(file).convert('L'# 读取图片并灰度化

    img = img.crop((01010036)) # 120*44图片裁剪为100*26

    # 分离数字

    img1 = img.crop((001926)) # 单个数字图片大小为19*26

    img2 = img.crop((3004926))

    img3 = img.crop((6007926))

    img4 = img.crop((8009926))

    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)

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

import tensorflow.compat.v1 as tf

tf.compat.v1.disable_eager_execution()

from data import train_images, train_labels

DLEN = len(train_images.data[0])

DNUM = len(train_images.data)

= tf.placeholder(tf.float32, [None, DLEN])

= tf.Variable(tf.zeros([DLEN, 10]))

= tf.Variable(tf.zeros([10]))

= tf.nn.softmax(tf.matmul(x, W) + b)

y_ = tf.placeholder("float", [None10])

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

train_step = tf.train.GradientDescentOptimizer(0.001).minimize(cross_entropy)

saver = tf.train.Saver()

sess = tf.Session()

sess.run(tf.global_variables_initializer())

for in range(DNUM):

    batch_xs = [train_images.data[i]]

    batch_ys = [train_labels.data[i]]

    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

saver.save(sess, 'model/model')

sess.close()

验证下训练的模型。记一次逆向某医院挂号软件的经历_第13张图片

 

告捷

最后模拟请求发送,完成预约挂号。记一次逆向某医院挂号软件的经历_第14张图片

你可能感兴趣的:(逆向开发,服务器,运维)