iami233
iami233
文章175
标签37
分类4

文章分类

文章归档

校友邦小程序签到加密逻辑解析

校友邦小程序签到加密逻辑解析

通过抓包校友邦小程序实现自动签到一文,我们知道了签到和签退是通过请求下面这个接口实现的

1
https://xcx.xybsyw.com/student/clock/Post.action

其中请求头中 Cookie 我们可以通过接口获取,vn 为固定值,而 tsm 三个参数是通过代码生成的

1
2
3
4
5
6
7
8
9
$headers = array(
'Content-Type: application/x-www-form-urlencoded',
'Cookie: JSESSIONID=' . $c,
'v: 1.6.36',
't: ' .$token['t'],
's: ' .$token['s'],
'm: ' .$token['m'],
'n: content,deviceName,keyWord,blogBody,blogTitle,getType,responsibilities,street,text,reason,searchvalue,key,answers,leaveReason,personRemark,selfAppraisal,imgUrl,wxname,deviceId,avatarTempPath,file,file,model,brand,system,deviceId,platform,code,openId,unionid'
);

原本找到关键代码后我是直接 Node 起了个服务当接口调用,但是近期服务器一直宕机,干脆研究一下改成 PHP 生成

下面简单描述一下加密逻辑。我们先反编译校友邦小程序,首先在 微信数据目录 里找到相应的小程序 id,数据目录在微信 设置 里可以看到,建议找到后先清空一下小程序目录,以免找不到对应的小程序 id

image-20230322163748492

然后使用 pc_wxapkg_decrypt 或者 PC微信小程序一键解密 工具解密即可,

1
./pc_wxapkg_decrypt.exe -wxid wx603676be1fddb4f9  -in 'Wechat Files\Applet\wx9f1c2e0bbc10673c\40\__APP__.wxapkg' -out 'D:\Desktop\decrypt.wxapkg'

image-20230322163830102

随后进入 wxappUnpacker 所在目录安装好依赖

1
2
3
4
5
6
7
npm install
npm install esprima
npm install css-tree
npm install cssbeautify
npm install vm2
npm install uglify-es
npm install js-beautify

运行命令即可解包

1
node ./wuWxapkg.js 主包路径

解包后发现代码不太完整但是不影响,全局搜索 token 发现关键代码

image-20230322163847653

简单格式化了一下,具体代码如下,关键的部分就是我写注释的地方

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
getTokenData: function(e, a) {
// 定义一个包含62个数字和字母的数组t和一个包含0~61的数字的数组n
for (var t = ["5", "b", "f", "A", "J", "Q", "g", "a", "l", "p", "s", "q", "H", "4", "L", "Q", "g", "1", "6", "Q", "Z", "v", "w", "b", "c", "e", "2", "2", "m", "l", "E", "g", "G", "H", "I", "r", "o", "s", "d", "5", "7", "x", "t", "J", "S", "T", "F", "v", "w", "4", "8", "9", "0", "K", "E", "3", "4", "0", "m", "r", "i", "n"], n = [], o = 0; o < 62; o++) n.push(o + "");
// 获取当前时间戳
var i = Math.round((new Date).getTime() / 1e3),
// 对数组n进行乱序操作,截取前20个字符作为iArrStr属性的值
r = function(e, a) {
for (var t, n, o = e.slice(0), i = e.length, r = i - a; i-- > r;) t = o[n = Math.floor((i + 1) * Math.random())], o[n] = o[i], o[i] = t;
return o.slice(r)
}(n, 20),
// 定义一个空字符串s,用于存储拼接后的属性值
s = "";
// 遍历数组r,将对应位置的字符从数组t中取出,拼接到字符串s中
r.forEach((function(e, a) {
s += t[e]
}));
// 定义一个空字符串d,用于存储拼接后的属性值
var g, p = function(e) {
for (var a = Object.keys(e).sort(), t = {}, n = 0; n < a.length; n++) t[a[n]] = e[a[n]];
return t
}(e),
d = "";
for (g in p) {
if (-1 != ["content", "deviceName", "keyWord", "blogBody", "blogTitle", "getType", "responsibilities", "street", "text", "reason", "searchvalue", "key", "answers", "leaveReason", "personRemark", "selfAppraisal", "imgUrl", "wxname", "deviceId", "avatarTempPath", "file", "file", "model", "brand", "system", "deviceId", "platform", "code", "openId", "unionid"].indexOf(g) || l.test(p[g])) {
continue;
}
d += p[g];
}
d += i,
d = (d = (d = (d = (d = (d = (d = (d = (d += s).replace(/\s+/g, "")).replace(/\n+/g, "")).replace(/\r+/g, "")).replace(/</g, "")).replace(/>/g, "")).replace(/&/g, "")).replace(/-/g, "")).replace(/\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, ""),
d = encodeURIComponent(d);
return {
md5: d = c.a.hexMD5(d),
tstr: i,
iArrStr: r && 0 < r.length ? r.join("_") : ""
}
}

简单来说就是从这 62 个字符中随机取出 20 个,得到(这里我只关注了签到和签退所需要的代码片段,其他代码片段用途暂未了解)

1
5bfAJQgalpsqH4LQg16QZvwbce22mlEgGHIrosd57xtJSTFvw4890KE340mr

得到的结果如下

1
2
3
4
5
6
// 随机字符的下标
r: ['29','7','13','2','34','36','60','8','55','44','3','19','53','59','48','58','40','45','27','1']
// 随机字符
s: la4fIoil3SAQKrwm7T2b
i: 当前时间戳
d: adcode,address,clockStatus,punchInStatus,traineeId,当前时间戳,随机字符串s按顺序拼接后进行url编码然后md5加密

最终输出结果为

1
2
3
4
5
{
md5: 'ec008344def5a714212dc44e627bdec5',
tstr: 1679473312,
iArrStr: '29_7_13_2_34_36_60_8_55_44_3_19_53_59_48_58_40_45_27_1'
}

知道了加密逻辑,直接根据代码逻辑写成 PHP 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getHeaderToken($d){
$characters = ["5", "b", "f", "A", "J", "Q", "g", "a", "l", "p", "s", "q", "H", "4", "L", "Q", "g", "1", "6", "Q", "Z", "v", "w", "b", "c", "e", "2", "2", "m", "l", "E", "g", "G", "H", "I", "r", "o", "s", "d", "5", "7", "x", "t", "J", "S", "T", "F", "v", "w", "4", "8", "9", "0", "K", "E", "3", "4", "0", "m", "r", "i", "n"];
$indexes = range(0, count($characters) - 1);
shuffle($indexes);
$r = array_slice($indexes, -20);
$s = "";
$x = '';
foreach ($r as $key => $index) {
$s .= $characters[$index];
$x .= $index;
if ($key < count($r) - 1) {
$x .= '_';
}
}
$time = time();
$data = array(
'm' => md5(urlencode($d['adcode'].$d['address'].$d['clockStatus'].$d['punchInStatus'].$d['traineeId'].$time.$s)),
't' => $time,
's' => $x,
);
return $data;
}
本文作者:iami233
本文链接:https://5ime.cn/xybsyw-re.html
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可