漏签真的很烦。
前言
本文仅供学习探讨之用,如果侵犯了您的权益请联系我删除。
工具
抓包
重放测试
使用Shift + R
进行一个包的重放。
没有问题,这其中有个DS
参数看起来挺可疑的,去掉再试试。
服务器直接不认了,那么可以确定这就是个时间戳签名信息。
JADX
将apk拖进jadx
并等待分析完成后打开搜索窗口直接搜索DS
关键字
然后啥也没搜到,那么换个"DS"
关键字
看到几个可疑的目标,比如这个圈起来的类名就叫做GetDSMethodImpl
,可以说非常的直接,那么跟过去看看
经过分析代码后,发现DS
来自与这个函数,继续跟过去看看
可以看到到这里就已经进入了so
层了,那么接下来进入下一层的分析。
IDA
全部的伪代码比较长,我就截取一小部分先来看看
v4 = (*a1)->GetStringUTFChars(a1, a3, 0LL);
v64 = 0LL;
v65 = 0LL;
v66 = 0LL;
v5 = strlen(v4);
v6 = v5;
if ( v5 >= 0x17 )
{
v8 = (v5 + 16) & 0xFFFFFFFFFFFFFFF0LL;
v7 = (char *)operator new(v8);
v65 = v6;
v66 = v7;
v64 = v8 | 1;
goto LABEL_5;
}
v7 = (char *)&v64 + 1;
LOBYTE(v64) = 2 * v5;
if ( v5 )
LABEL_5:
memcpy(v7, v4, v6);
v7[v6] = 0;
gettimeofday(&tv, 0LL);
std::to_string((std::__ndk1 *)(v63 / 1000000 + *(_QWORD *)&tv), v9);
Random::random((Random *)v58);
std::operator+<char>("salt=", &v64);
v10 = std::string::append((int)&v48, "&t=", 3u);
可以看到其中大量使用了std::string::append
,那么我们来Hook这个函数看看。
需要说明一下的是米游社有Anti-Frida
,所以需要反一下,可以参考一下这篇文章:反Frida检测与禁止SSLPinning的一些思路和方法。
来看下Hook结果
可以看到这个结果跟我们抓到的包的DS
参数最终效果是一致的
DS: 1663216865,2bfm47,b6069f6088a02f2ff35b1407034b520c
那么我们根据这个结果再回到IDA进行一波分析逻辑。
经过一番梳理之后得到以下流程
CMD5::CMD5((CMD5 *)&v70); // new MD5类
v4 = (*a1)->GetStringUTFChars(a1, a3, 0LL); // 获取传进来的salt参数
v64 = 0LL;
v65 = 0LL;
v66 = 0LL;
v5 = strlen(v4); // 计算salt的长度
v6 = v5;
if ( v5 >= 0x17 )
{
v8 = (v5 + 16) & 0xFFFFFFFFFFFFFFF0LL;
v7 = (char *)operator new(v8);
v65 = v6;
v66 = v7;
v64 = v8 | 1;
goto LABEL_5;
}
v7 = (char *)&v64 + 1;
LOBYTE(v64) = 2 * v5;
if ( v5 )
LABEL_5:
memcpy(v7, v4, v6); // copy v4(salt字符串)给v64
v7[v6] = 0; // 结尾设\0
gettimeofday(&tv, 0LL);
std::to_string((std::__ndk1 *)(v63 / 1000000 + *(_QWORD *)&tv), v9);
Random::random((Random *)v58); // 生成随机字符串
std::operator+<char>("salt=", &v64); // 拼接salt=传进来的salt
v10 = std::string::append((int)&v48, "&t=", 3u);// 拼接&t=
v11 = *(_OWORD *)v10;
v51 = *(void **)(v10 + 16);
v50 = v11;
*(_QWORD *)(v10 + 8) = 0LL;
*(_QWORD *)(v10 + 16) = 0LL;
*(_QWORD *)v10 = 0LL;
if ( (v59 & 1) != 0 )
v12 = v61;
else
v12 = v60;
if ( (v59 & 1) != 0 )
LODWORD(v13) = *(_DWORD *)&v60[7];
else
v13 = (unsigned __int64)v59 >> 1;
v14 = std::string::append((int)&v50, v12, v13);// 拼接当前时间戳
v15 = *(_OWORD *)v14;
v53 = *(void **)(v14 + 16);
v52 = v15;
*(_QWORD *)(v14 + 8) = 0LL;
*(_QWORD *)(v14 + 16) = 0LL;
*(_QWORD *)v14 = 0LL;
v16 = std::string::append((int)&v52, "&r=", 3u);// 拼接&r=
v17 = *(_OWORD *)v16;
v71 = *(void **)(v16 + 16);
v70 = v17;
*(_QWORD *)(v16 + 8) = 0LL;
*(_QWORD *)(v16 + 16) = 0LL;
*(_QWORD *)v16 = 0LL;
if ( (v54 & 1) != 0 )
v18 = v57;
else
v18 = v55;
if ( (v54 & 1) != 0 )
LODWORD(v19) = v56;
else
v19 = (unsigned __int64)v54 >> 1;
v20 = std::string::append((int)&v70, v18, v19);// 拼接随机字符串
v21 = *(_WORD *)(v20 + 5);
v22 = *(_DWORD *)(v20 + 1);
v23 = *(_BYTE *)v20; // v23获得待计算字符串所有权
v69 = *(_BYTE *)(v20 + 7);
v68 = v21;
v67 = v22;
v25 = *(_QWORD *)(v20 + 8);
v24 = *(void **)(v20 + 16);
*(_QWORD *)v20 = 0LL;
*(_QWORD *)(v20 + 8) = 0LL;
*(_QWORD *)(v20 + 16) = 0LL;
v40 = 0LL;
v41 = 0LL;
v42 = 0LL;
if ( (v23 & 1) == 0 )
{
LOBYTE(v40) = v23; // v40获得待计算字符串所有权
HIBYTE(v40) = v69;
*(_WORD *)((char *)&v40 + 5) = v68;
*(_DWORD *)((char *)&v40 + 1) = v67;
v41 = v25;
v42 = v24;
goto LABEL_42;
}
CMD5::md5(&v70, &v40); // 计算v40 md5hash值
if ( (v43 & 1) != 0 )
v33 = v45;
else
v33 = v44;
if ( (v43 & 1) != 0 )
LODWORD(v34) = *(_DWORD *)&v44[7];
else
v34 = (unsigned __int64)v43 >> 1;
sub_13C40((int)&v59, ","); // 拼接 当前时间戳 + ","
if ( (v54 & 1) != 0 )
v26 = v57;
else
v26 = v55;
if ( (v54 & 1) != 0 )
LODWORD(v27) = v56;
else
v27 = (unsigned __int64)v54 >> 1;
v28 = std::string::append((int)&v46, v26, v27);// 拼接随机字符串
v29 = *(_OWORD *)v28;
ptr = *(void **)(v28 + 16);
v48 = v29;
*(_QWORD *)(v28 + 8) = 0LL;
*(_QWORD *)(v28 + 16) = 0LL;
*(_QWORD *)v28 = 0LL;
v30 = std::string::append((int)&v48, ",", 1u);// 拼接 ,
v35 = std::string::append((int)&v50, v33, v34);// 拼接hash值,完成整个签名过程
计算过程
可能上面看起来会有点乱,接下来总结一下对于DS
的计算过程大概是这样的
input salt
input timestamp
input randomString
process params = "salt=" + salt + "&t=" + timestamp + "&r=" + randomString
output hash = md5(params)
output DS = timestamp + "," + randomString + "," + hash
代码验证
来写一份代码验证一下。
import hashlib
import time
import random
salt = 'n0KjuIrKgLHh08LWSCYP0WXlVXaYvV64'
# timestamp = str(int(time.time()))
timestamp = '1663216865'
# randomString = ''.join(random.sample('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 6))
randomString = '2bfm47'
params = 'salt={}&t={}&r={}'.format(salt, timestamp, randomString)
md5 = hashlib.md5()
md5.update(params.encode('utf-8'))
hash = md5.hexdigest()
DS = '{},{},{}'.format(timestamp, randomString, hash)
print('DS', DS)
输出
预计输出
1663216865,2bfm47,b6069f6088a02f2ff35b1407034b520c
实际输出
DS 1663216865,2bfm47,b6069f6088a02f2ff35b1407034b520c
可以看到预想与实际的结果完全一致。
实际测试
来实际发个包试试吧
嗯,居然不认。根据结果知道我们的推理应该是不会错的,那么我们回到jadx
看看是什么情况。
回到JADX
对a2222
这个函数查找引用后看到是有两个地方的
根据代码分析它们所使用的salt
非同一个,那么我们需要找一下salt
的来源。
经过分析之后我们抠出来了这样一段代码
public class Main {
public static final int[] iArr = {-90, 114, -70, -74, -108, 222, 60, 66, 90, 72, 84, -102, -76, 120, 84, 216, 222, -114, -68, -66, -14348907, 108, 222, 216, -68, 192, -88, -120, 150, -74, 150, -108};
public static void main(String[] args) {
int i;
StringBuilder sb = new StringBuilder();
ArrayList<Number> arrayList = new ArrayList(iArr.length);
for (int i2 : iArr) {
if (i2 < 0) {
i = ((double) (-i2)) >= Math.pow(3.0d, 6.0d) ? (int) (((Math.log(-((double) i2)) / Math.log(3.0d)) - ((double) 6)) + ((double) 48)) : ~i2;
} else {
i = (i2 / 3) + 48;
}
arrayList.add(Integer.valueOf(i));
}
ArrayList arrayList2 = new ArrayList(arrayList.size());
for (Number number : arrayList) {
sb.append((char) number.intValue());
arrayList2.add(sb);
}
String sb2 = sb.toString();
System.out.println(sb2);
}
}
执行后得到输出
YVEIkzDFNHLeKXLxzqCA9TzxCpWwbIbk
然后我们将代码中的salt
替换为这个输出之后再来发个包试试
嗯,可以看到服务器已经认可我们的包了,没问题。
TODO
- Taskcloud版本的自动签到脚本
- 抓米游币每日签到的接口
- 抓米游币浏览3个帖子的接口
- 抓米游币点赞5次的接口
- 抓米游币分享帖子的接口
自动签到脚本慢慢再写,因为Taskcloud目前好像还有点问题,但是还没修。
米游社的其他接口可能不搞。
先放脚本的仓库链接,之后如果有新脚本的话都会更新到仓库里。
脚本仓库:scripts
结语
原神真好玩,嘿嘿。
那就这样了,有缘再见~