# 1.首先charles抓包发现每个请求Url后都接了一个sign的参数且每次都不一样。也没有其他的一些别的特别参数,那么关键问题就是分析sign参数的生成了
# 2.jadx反编译,寻找sign的生成的位置
> 直接搜索sign参数匹配的出来的结果太多了,一时间不好区分哪个是真的。于是使用getSign为前缀搜索
> 运气不错搜索到一个native方法 前缀也是getSign的方法名,打开这个类。找下引用位置进一步确认下是否为计算sign的类
> 最终在BaseApplication里面找到这个类引用,可以确定这个就是计算sign的类
# 3.开始分析sign方法的入参
frida hook这个方法得到结果如下:
1 2 3 4 5 6 7 8 9 10 11 |
|
> 根据hook结果稍加分析大概知道各个参数代表的意义:
>context:上下文对象
>str:url路径
>str2:请求body信息
>str3:55a9c688729bb118 为KEY 16位长度,且每次请求都固定
>str4:android 为平台
>str5:版本号
> 查看源码大概得出str3 的为installationId生成规则是UUID 随机16位长度,在app第一次安装的时候创建保存在缓存中.
# 4.使用unidbg进行黑盒调用
> 1.第一步当然是补环境了:
> 需要补充返回一个application对象,偷个懒想从AbstractJni 这里copy一份
> 重新运行一下又报错了,这个错是找不到对应的methodId、于是想着把Application换成Activity也可以,毕竟Activity也是可以获取Application对象的
1 2 3 4 5 6 7 8 9 10 |
|
> 重新运行就没问题了,接下来继续报错补环境,返回apk的路径。很简单
1 2 3 4 5 6 7 8 9 |
|
> 图上这个返回刚开始不知道传具体什么参数,于是打算看看源码。结果没有反编译出来具体的函数实现。于是用frida hook了这个方法拿到图下的返回
> 第一个参数为:app安装目录 第二个参数为META-INF 目录 第三个是RSA
> 既然这样就直接返回RSA公钥了
1 2 3 4 5 6 7 8 9 |
|
> 继续补充环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
> 继续补充环境,需补充如下
1 2 3 4 5 6 7 8 9 10 11 12 |
|
> 继续补充环境,从反编译的源码中copy 一份objectToBytes方法即可然后构造ByteArray返回即可
1 2 3 4 5 |
|
# 5.开始黑盒调用计算sign的方法
1 2 3 4 5 6 7 8 9 10 11 |
|
> 运行报错,还缺少环境
1 2 3 4 5 6 7 8 |
|
> 重新继续补充环境 这部分环境补充比较简单 直接上最后运行结果
> 黑盒调用没问题了,接下来开始分析sign具体是怎么生成的。全部代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
|
# 6.ida+unidbg分析sign的具体如何生成的
ida导入so 包 export导出中找到sign生成方法 截图如下:
F5生成伪代码
根据方法的具体逻辑修改了下方法名称 方便直观查看。初步观察是讲传入参数通过key=value 的方式拼接起来
通过unidbg的执行日志中也可以发现这点
接下来开始从函数返回的地方开始往上分析,单独调用执行返回如下:
st=1648374072802&sign=8102aefd41782d405174725cd433c5a0&sv=111
从上可知要分析的为st 、sign、sv这个三个值
1.st生成分析:生成一个时间戳 长度为13位的 用于计算sign
2.sign的生成:
分析伪代码找到sign的生成方法为sub_126AC,该方法上面的伪代码都是一些拼接字符串的操作,就是把入参拼接起来。
> 接下来开始unidbg hook该方法
> 然后开启无尽的S 单步执行往下走当程序走到switch判断时 发现bl指令跳转到sub_10de4函数 多次在这个位置debugger 到 发现 到 sv =111 走的是 case 2 还遇到过sv = 110 走的是case 1 、这部分我们只分析为2的情况也就是走sub_10DE4,至于为啥呢?就是因为这后面走的加密算法看起来简单些 像md5
> 查看下函数sub_10de4入参,hook该函数
> 打印mr1,为一串固定的字符串
> mr2 为0x1 固定值
> mr3 打印如下,为前面拼接的参数串 长度为0x106
> 接着分析sub_10de4 这个函数,点击进入sub_12ECC函数 ,这里为啥不分析sub_12FF0 呢 因为具体看了下没啥特别的。
hook sub_12ECC函数分析下入参
> 参数1:打印看看 ,看不太出来是什么 但是是64位 看后面的伪代码这个值主要是 跟 md5魔数有关系
> 参数2: 固定字符串 80306f4370b39fd5630ad0529f77adb6
> 参数3: 0x1
> 参数4: 为明文的参数拼接串
> functionId=personinfoBusiness&body={"callCJH":"1","callNPS":"1","closeJX":"0","headTaskRefresh":"1","locationArea":"0_0_0_0","menuStaticSource":"0","menuTimeStamp":"1631586010000"}&uuid=55a9c688729bb118&client=android&clientVersion=10.4.6&st=1648455172760&sv=111
> 参数5:为长度 0x106
#开始分析代码
> 从上面分析 v27 应该就是 md5 魔数 数组咯 大致为
> v27[1]= 0x68449237;
> v27[2]= 0x7FCC3DA5;
> v27[3] = 0x88D90FBB;
> v27[4] = 0x5AE99AEE;
> 继续往下分析
这个区域是具体的实现的位置,flag 是之前传入的0x1 所以走的是if 判断 。这个do-while 大致是 从v27中取具体魔数 s 往下走 v18 = 0x37 猜测应该是不断从v27中取值 还原算法时v27 就是这个下面这个数组
1 2 |
|
> 继续S 往下走 0x38 就是传入固定字符串的第一个字符 80306f4370b39fd5630ad0529f77adb6
> r0 = 0x66 是从拼接的字符串中取出的第一个
> r0=0x66 r1=0x0 r2=0x37 r3=0x1 r4=0x38
> R0 = R0 ^ R2 = 0x51
> R0 = R0 ^ R4 = 0x69
> 按照汇编 分析猜测 后面就是 将前面异或的结果 加上R2 0x37 然后在 R2 = R2 ^ R0
1 |
|
> 根据ida 上伪代码 结和汇编 大致可知 这部分是每执行一次do while 就修改一次v5的索引对应的值 一直循环到循环到v5数据长度,所以结合伪代码 用C还原 sign 的算法 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
> 用python 来调用C代码 跟unidbg 黑盒调用结果比对如下:
> 总结: 某东的sign有三个算法 这边只还原了其中比较简单的 sv =111 的这种 。还原这部分算法也花的时间挺多的,结合unidbg 来分析算法 真的方便。