【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
本次分析的mtgsig参数经测试,适应于点评与某团两个端。该参数是一串由多个参数组成的JSON,是一个核心参数!其中需要分析的参数分别是a5、a6、d1,另外三个像a1、a2、a3则无需过多的分析,如下所示:
a1: 版本号
a2: 时间戳
a3: Cookie内的WEBDFPID
本期最终的补环境流程很详细了,作者把每一步都尽量截图出来了,大家可以自行参考动手尝试一下~
2. 接口分析
首先,我们打开某点评或者是某团的商家后台,都是一样的!这里作者均测试过。开始发包看一下请求,这里作者找的是评价的一个接口,如下所示:
除了这个核心的mtgsig参数外,其它的参数貌似只有那个_token有待测试,有点跟eid-token相似,也许可以固定(这类参数大概率是埋点或蜜罐之类风控校验参数)
这里我们直接把评论的接口CURL拿出来重放是没有问题的,浏览器中请求重放如下所示:
3. 加密调试分析
这里全局搜索是没有信息的,直接可以看调用栈,找到入口H5guard.js,它这个还是比较明显的,如下所示:
然后下一个断,从此处可以作为一个切入点。我们直接单步跟一下,主要先了解一下流程跟看看有没有细节的地方。如下所示:
来到一个叫做he(hO, hP)的函数处,核心参数的组成部分从这里开始,后续扣算法可以直接从此处开始入手,如下所示:
在这个方法内部调试了一下,基本上核心参数的组成部分像什么a5、a6以及d1都在这里已经生成,如下所示:
分析到这里其实核心的加密逻辑就在这个H5guard.js文件内,如果我们采用补环境的方式来还原这个参数,就不需要再过多的去分析了,直接把这个文件拿下来调试,缺什么补什么即可!
4. 补环境还原算法
把he(hO, hP)方法拿下来,从这个函数开始向外扩散去完成算法的扣取(这里可以把整个JS全部拿下来),如下所示:
function he(hO, hP) {
for (var hQ, T = [], g = Function.prototype.call, D = 1672; ; )
switch (N[D++]) {
case 0:
T[T.length - 2] = T[T.length - 2][T[T.length - 1]];
continue;
case 1:
hQ = T.pop();
continue;
case 2:
try {
if (fw += 1,
fm[b(3783)] = fw,
hO) {
var hS = (hO[b(61)] || b(479))[b(3839)]()
, hU = (new Date)[b(2097)]()
, hW = hO[b(3834)] || "";
hW = hc(hW),
null === hO[b(1811)] && (hO[b(1811)] = void 0);
var hX = !1;
if (typeof hO[b(1811)] === b(324))
var hY = hO[b(1811)];
else if (hd(hO[b(1811)], hS)) {
hX = !0;
hY = hO[b(1811)]
} else
hY = JSON[b(429)](hO[b(1811)]);
(!hO[b(3826)] || typeof hO[b(3826)] !== b(44)) && (hO[b(3826)] = {});
var i0 = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z_]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/[b(327)](hW)
, i1 = "/"
, i2 = [];
i0 && (i0[5] && (i1 += i0[5]),
i0[6] && (i2 = fU(i0[6])));
var i3 = [];
if (hS === b(479))
if (typeof hY === b(44) && Object[b(187)](hY)[b(147)] > 0) {
if (fS(i3, hY, !0),
i0 && i0[6] && i2[b(147)] > 0) {
var i4 = {};
(i2 = fU(i0[6], !0))[b(38)](function(ih) {
!hY[b(7)](ih[0]) && (i4[ih[0]] = ih[1])
}),
fS(i3, i4, !0)
}
} else
fS(i3, i2);
else
fS(i3, i2);
var i5 = "";
i5 = hP ? fn : fo;
[][b(1546)](i3);
i3[b(1641)](h7);
var i7 = [];
i3[b(38)](function(ih) {
ih[0] == b(3827) || i7[b(135)](ih[0] + "=" + ih[1])
});
var i8 = i7[b(718)]("&")
, ia = hY
, ib = fW(hS + " " + i1 + " " + i8);
hS !== b(479) && void 0 != hY && (hX ? ib[b(135)][b(313)](ib, fX(ia)) : ib[b(135)][b(313)](ib, fX(fW(ia)))),
fm[b(3795)] = fr,
function() {
for (var ih, ij, ik, im, io, iq, ir, is, it, iv, iw, ix, iy, iz, iA, iB, iC, iE, iF, iG, iH, iI, iJ, iK, iM, iO, iP, iQ, iL, iR, iS, iU, iW, iX, iY, T = [], g = Function.prototype.call, D = 508; ; )
switch (N[D++]) {
case 0:
T.pop() || (D += 6);
continue;
case 1:
T.length -= 2;
continue;
case 2:
T.push(N[D++]);
continue;
case 3:
T.push(iy);
continue;
case 4:
T[T.length - 3] = g.call(T[T.length - 3], T[T.length - 2], T[T.length - 1]);
continue;
case 5:
T[T.length - 2] = T[T.length - 2][T[T.length - 1]];
continue;
case 8:
T.pop();
continue;
case 20:
iv = T.pop();
continue;
case 22:
T.push(b);
continue;
case 25:
T.push(iC);
continue;
case 27:
iC[b(3808)] = T[T.length - 1];
continue;
case 34:
D += 7;
continue;
case 37:
iO = T[T.length - 1];
continue;
case 38:
T[T.length - 2] = T[T.length - 2] << T[T.length - 1];
continue;
case 42:
D += 0;
continue;
case 43:
T.push(null);
continue;
case 46:
T.push(iJ);
continue;
case 56:
T.push(iG);
continue;
case 60:
iW = T[T.length - 1];
continue;
case 64:
iG[0] = T[T.length - 1];
continue;
case 66:
T.push(iS);
continue;
case 69:
iG = T.pop();
continue;
case 73:
T.push(is);
continue;
case 74:
T.push("?");
continue;
case 83:
iG[2] = T[T.length - 1];
continue;
case 86:
T.push(md5);
continue;
case 91:
T[T.length - 2] = T[T.length - 2] ^ T[T.length - 1];
continue;
case 94:
T[T.length - 2] = T[T.length - 2] == T[T.length - 1];
continue;
case 97:
T[T.length - 2] = T[T.length - 2] + T[T.length - 1];
continue;
case 98:
T.push({});
continue;
case 101:
T[T.length - 2] = T[T.length - 2] | T[T.length - 1];
continue;
case 102:
T.push(d0);
continue;
case 105:
T.push(hW);
continue;
case 106:
T.push(fx);
continue;
case 117:
iB = T.pop();
continue;
case 122:
T.push(iK);
continue;
case 124:
D += 88;
continue;
case 132:
iU = T.pop();
continue;
case 137:
T.push(iv);
continue;
case 141:
T.push(iw);
continue;
case 144:
iC = T.pop();
continue;
case 145:
T.push(iE);
continue;
case 160:
T.push(iL);
continue;
case 164:
T.push(ib);
continue;
case 165:
iw = T.pop();
continue;
case 171:
T[T.length - 4] = g.call(T[T.length - 4], T[T.length - 3], T[T.length - 2], T[T.length - 1]);
continue;
case 178:
D += 23;
continue;
case 182:
iJ[10] |= T[T.length - 1];
continue;
case 183:
D += 72;
continue;
case 185:
T.push(hP);
continue;
case 187:
T[T.length - 2] = T[T.length - 2] & T[T.length - 1];
continue;
case 195:
T.push(ih);
continue;
case 200:
iz = T.pop();
continue;
case 209:
T.push(iO);
continue;
case 216:
T.push(iX);
continue;
case 222:
T[T.length - 4] = [T[T.length - 4], T[T.length - 3], T[T.length - 2], T[T.length - 1]];
continue;
case 234:
T.push(encodeURIComponent);
continue;
case 238:
T[T.length - 2] = T[T.length - 2] - T[T.length - 1];
continue;
case 244:
T.push(ix);
continue;
case 257:
iC[b(3811)] = T[T.length - 1];
continue;
case 262:
T.push(parseInt);
continue;
case 268:
it = T.pop();
continue;
case 274:
T.push(iI);
continue;
case 275:
iJ[11] |= T[T.length - 1];
continue;
case 276:
T.push(iW);
continue;
case 280:
hO[b(3826)][b(3827)] = T[T.length - 1];
continue;
case 281:
T.push(fm);
continue;
case 284:
T[T.length - 2] = T[T.length - 2] < T[T.length - 1];
continue;
case 297:
iJ[9] |= T[T.length - 1];
continue;
case 305:
ix = T.pop();
continue;
case 308:
D -= 77;
continue;
case 309:
D += 4;
continue;
case 310:
iK = T.pop();
continue;
case 315:
hW = T[T.length - 1];
continue;
case 330:
T.push(hU);
continue;
case 346:
iG[3] = T[T.length - 1];
continue;
case 356:
hO[b(3834)] = T[T.length - 1];
continue;
case 358:
io = T.pop();
continue;
case 359:
T.push(fZ);
continue;
case 360:
T.length -= 3;
continue;
case 365:
T.push(hb);
continue;
case 375:
T[T.length - 0] = [];
continue;
case 380:
return;
case 381:
T.push(fw);
continue;
case 389:
T.pop();
continue;
case 392:
T.pop() || (D += 32);
continue;
case 394:
iH = T.pop();
continue;
case 396:
T.push(JSON);
continue;
case 405:
T[T.length - 2] = T[T.length - 2] >>> T[T.length - 1];
continue;
case 410:
T.push(fK);
continue;
case 423:
T.push(new Uint8Array(fW(i5)[b(1546)](ij)));
continue;
case 432:
T.pop() || (D += 76);
continue;
case 433:
iJ[14] = T[T.length - 1];
continue;
case 435:
T.push(new Uint8Array(fW(iF)));
continue;
case 436:
T.push("0");
continue;
case 448:
T.push(fs);
continue;
case 454:
T.push(iY);
continue;
case 457:
T.push(new Uint8Array(fW(i5)));
continue;
case 479:
T.push(window);
continue;
case 485:
iF = T.pop();
continue;
case 488:
iQ += T[T.length - 1];
continue;
case 501:
T.push(iP);
continue;
case 504:
T.push(guardRaptor && guardRaptor[b(485)](b(3828), 200, 200, JSON[b(429)](iC)[b(147)], .001, fz));
continue;
case 508:
!T.pop() && (D += 67);
continue;
case 512:
T.push(-1);
continue;
case 513:
T.push(iM);
continue;
case 546:
iL = T.pop();
continue;
case 547:
!T.pop() && (D += 64);
continue;
case 549:
T.push(ir);
continue;
case 551:
iX = T.pop();
continue;
case 554:
is = T.pop();
continue;
case 556:
ir = T.pop();
continue;
case 557:
iC[b(3819)] = T[T.length - 1];
continue;
case 569:
T[T.length - 2] = T[T.length - 2] != T[T.length - 1];
continue;
case 583:
iJ[8] = T[T.length - 1];
continue;
case 586:
T.push("");
continue;
case 594:
im = T.pop();
continue;
case 635:
T.push(guardRaptor && guardRaptor[b(485)](b(3836), 200, 200, iX[b(147)], .001, fz));
continue;
case 636:
T.push(iz);
continue;
case 641:
iJ[12] = T[T.length - 1];
continue;
case 650:
iE = T.pop();
continue;
case 652:
T.push("&");
continue;
case 656:
T.push(iq);
continue;
case 660:
ij = T.pop();
continue;
case 670:
T.push(iL++);
continue;
case 671:
T.push(hf);
continue;
case 679:
T.push(fY);
continue;
case 695:
T.push(iH);
continue;
case 718:
iY = T.pop();
continue;
case 730:
T.push(iA);
continue;
case 732:
T.push(iU);
continue;
case 733:
iC[b(3810)] = T[T.length - 1];
continue;
case 753:
iG[1] = T[T.length - 1];
continue;
case 754:
iS = T.pop();
continue;
case 757:
T.push(h8);
continue;
case 778:
iJ[10] = T[T.length - 1];
continue;
case 786:
T.push(fE);
continue;
case 796:
iR = T.pop();
continue;
case 819:
T.push(iQ);
continue;
case 842:
ik = T.pop();
continue;
case 854:
iO = T.pop();
continue;
case 856:
T.pop() || (D += 9);
continue;
case 861:
iA = T.pop();
continue;
case 881:
iC[b(3807)] = T[T.length - 1];
continue;
case 905:
iC[b(3809)] = T[T.length - 1];
continue;
case 917:
iJ[13] = T[T.length - 1];
continue;
case 918:
T.push(ik);
continue;
case 919:
T.push(h1);
continue;
case 926:
iL += T[T.length - 1];
continue;
case 939:
T.push(it);
continue;
case 952:
T.pop() || (D += 8);
continue;
case 961:
iJ[9] = T[T.length - 1];
continue;
case 976:
ih = T.pop();
continue;
case 982:
T.push(new Uint8Array(fW(it)));
continue;
case 988:
iJ = T.pop();
continue;
case 993:
iP = T.pop();
continue;
case 999:
T.push(im);
continue;
case 1003:
T.pop() || (D += 7);
continue;
case 1010:
D -= 80;
continue;
case 1024:
iC[b(3806)] = T[T.length - 1];
continue;
case 1030:
T.push(iR);
continue;
case 1032:
T.push(iB);
continue;
case 1042:
T.push(h6);
continue;
case 1055:
T.push(i5);
continue;
case 1061:
iy = T.pop();
continue;
case 1075:
iM = T.pop();
continue;
case 1090:
iR = T[T.length - 1];
continue;
case 1103:
T[T.length - 2] = T[T.length - 2] !== T[T.length - 1];
continue;
case 1117:
iq = T.pop();
continue;
case 1126:
iJ[11] = T[T.length - 1];
continue;
case 1128:
iI = T.pop();
continue;
case 1139:
T.push(io);
continue;
case 1143:
T.pop() || (D += 27);
continue;
case 1147:
iQ = T.pop();
continue;
case 1149:
iJ[15] = T[T.length - 1];
continue;
case 1151:
iW = T.pop();
continue
}
}()
}
var ic = b(hP ? 3885 : 3886);
return guardRaptor && guardRaptor[b(485)](ic, 200, 200, Date[b(420)]() - hQ, .001, fz),
hO
} catch (ij) {
ic = b(hP ? 3885 : 3886);
throw guardRaptor && guardRaptor[b(485)](ic, 200, 9401, Date[b(420)]() - hQ, .001, fz),
dc(b(3895), ij[b(333)], b(509), fz),
ij
}
continue;
case 3:
T.push(!0);
continue;
case 4:
T[T.length - 3] = g.call(T[T.length - 3], T[T.length - 2], T[T.length - 1]);
continue;
case 5:
T.pop() || (D += 4);
continue;
case 6:
T.pop();
continue;
case 7:
D += 0;
continue;
case 8:
T.push(Date);
continue;
case 9:
T.push(fI);
continue;
case 10:
T[T.length - 2] = T[T.length - 2] == T[T.length - 1];
continue;
case 11:
T.push(null);
continue;
case 13:
T.push(N[D++]);
continue;
case 14:
T.length -= 2;
continue;
case 15:
T[T.length - 2] = g.call(T[T.length - 2], T[T.length - 1]);
continue;
case 16:
T.push(hO);
continue;
case 18:
return;
case 20:
return T.pop();
case 23:
T.push(b);
continue
}
}
完整的JS差不多6900+行,我们现在开始调试这个JS文件,一步步还还原完整的加密算法效果,我们直接运行JS,如下所示:
把jsdom拿出来,补上异常提示的window运行,如下所示:
const { JSDOM } = require('jsdom');
const dom = new JSDOM(`<!DOCTYPE html></html>`, {
url: '自行获取'
});
继续把location给补上,可能有些小伙伴不知道怎么补哈,有时候作者分析的时候尽可能贴全细节与步骤,把详细操作尽可能全部截图出来,如下所示:
把上图的环境信息补到JS内,我们再次往下进行调试,运行如下:
一切皆对象,按照上面的操作在浏览器控制台查看一下screen:
继续运行JS调试,这次提示的是缺失XMLHttpRequest,如下:
补上XMLHttpRequest之后我们继续运行调试,再次运行提示缺失navigator,这里我们补上再次运行会提示缺失innerHeight、innerWidth
按照上述的流程与方法补上所有缺失的环境后,细节的地方就来了!我们需要开始编写入口函数,调用加密算法!去测试调用JS传递参数生成加密的mtgsig
首先是上面这个警告异常,虽然说不会影响到实际算法的效果,但是也是可以去掉的,代码实现如下:
window.HTMLCanvasElement.prototype.getContext = function() {
return {
fillRect: function() {},
clearRect: function() {},
getImageData: function(x, y, w, h) {
return {
data: new Uint8ClampedArray(w * h * 4)
};
},
putImageData: function() {},
createImageData: function() {
return [];
},
setTransform: function() {},
drawImage: function() {},
save: function() {},
fillText: function() {},
restore: function() {},
beginPath: function() {},
moveTo: function() {},
lineTo: function() {},
closePath: function() {},
stroke: function() {},
translate: function() {},
scale: function() {},
rotate: function() {},
arc: function() {},
fill: function() {},
measureText: function() {
return { width: 0 };
},
transform: function() {},
rect: function() {},
clip: function() {},
};
};
window.HTMLCanvasElement.prototype.toDataURL = function() {
return "";
};
解决方法添加getContext|toDataURL到HTMLCanvasElement的原型上
接下来到了最终关键的时间,实现一个自定义方法调用JS拿到最终的mtgsig签名值,代码实现如下:
function GetMtgsig(params) {
return new Promise((resolve, reject) => {
if (params.hasOwnProperty('data') && params['data'] !== null) {
if (typeof params['data'] === 'string') {
params['data'] = encodeURIComponent(params['data']);
} else {
params['data'] = Object.keys(params['data']).map(key => {
let value = params['data'][key];
if (typeof value === 'string') {
return `${key}=${encodeURIComponent(value)}`;
} else {
return `${key}=${value}`;
}
}).join('&');
}
}
const { H5guard } = window;
H5guard.sign(params.info).then(function (res) {
resolve(res.headers.mtgsig);
setTimeout(() => {
process.exit();
}, 1000);
}).catch(reject);
});
}
// 定义需要传递的参数
const params = {
info: {
url: "", # 自行填写
}
};
GetMtgsig(params).then((result) => {
console.log('Mtgsig:', result);
}).catch((err) => {
console.error('Error:', err);
});
上述自定义方法中H5guard.sign主要生生成签名值,调用GetMtgsig函数来返回一个Promise对象
为什么要使用Promise,因为H5guard.sign的方式是异步的!它会返回一个Promise,然后通过resolve或者reject来处理结果
另外,函数内部需要对传入的参数进行处理。是字符串则编码,对象则转换为查询参数格式
最终,整个JS的补环境到这里基本就结束了!直接在Node环境下运行测试,如下如下:
这里作者简单的封装了一个Demo,调用JS算法生成mtgsig参数,携带它去请求接口测试一下效果,如下所示: