某居客滑块逆向分析
1.前言
我是菜鸟,不会ast,只能硬扣啦。
参考了K哥爬虫的文章【验证码逆向专栏】安某客滑块逆向 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn=
自己也是做了一个总结更了这个贴。
2.网址
aHR0cHM6Ly9hcGkuYW5qdWtlLmNvbS93ZWIvZ2VuZXJhbC9jYXB0Y2hhTmV3Lmh0bWw/
3.正言
拉动滑块抓包,有个checkInfo,返回包里面有个校验失败。这个包就是滑块校验的包。
参数分析:
checkInfo里面总共需要五个参数sessionId,responseId,dInfo,language,data五个参数。
sessionId
全局搜索sessionId,在captchaNew.html里面,请求这个页面用正则就能提取到啦
responseId
responseId在getInfoTp这个数据包里面
getInfoTp只有两个加密参数,sessionId和dInfo,sessionId已经有了,主要分析dInfo的生成。
dInfo
进入发包的地方
在发包的地方下断点。
回溯找到dInfo生成的地方,在572行下断点
打印_fSL[_0x90o[2]][_0x90o[67]]((0,_taN)(), _Lnn)
,可以确定这个地方就是代码生成的地方。
对代码手动解个混淆_0x90o[2]—>"Z",_0x90o[67]—>"AESEncrypt"
_fSL["Z"]["AESEncrypt"](_taN(), _Lnn)
_fSL["Z"]["AESEncrypt"]函数总共有两个参数,_Lnn就是sessionId。还有一个参数是_taN()函数。
进入_taN()函数,分析这段代码。
函数里面有个逻辑判断,我们打印undefined - (8 * 564 << 5 > 0),他是NaN。这个js的逻辑判断我自己其实也搞的不是很清楚。也懒的去记了,懒人自有懒人的用法,就是我有歪门诀窍。
就是把他这个判断重写,如果是true的话他就会弹出,是false就不会弹出。在浏览器运行并没有弹出。这段代码不执行!
那么_taN()函数的作用就是返回那些参数!经过多次复现,这些参数是写死的值!
return { sdkv: "3.0.1", busurl: "https://api.anjuke.com/web/general/captchaNew.html", useragent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36", cid: undefined, devicefg: undefined, clienttype:"1" };
_fSL["Z"]["AESEncrypt"]函数的两个参数都已经解决了!那我们便来慢慢扣这个函数。这个函数我不是改写的,我是用扣代码的方法硬扣的。虽然方法笨,但是好用就好好方法!!!
在控制台打印_fSL["Z"]["AESEncrypt"]函数双击进入函数内部。
1161行到1181行就是这段代码啦!扣下来,然后手动解个混淆!!!
_0x90o[115]—>"split"
_0x90o[17]—>""
_0x90o[164]—>"reduce"
_0x90o[54]—>"parse"
_0x90o[53]—>"string"
_0x90o[108]—>"stringify"
_0x90o[165]—>"encrypt"
_0x90o[166]—>"mode"
_0x90o[167]—>"CBC"
_0x90o[108]—>"stringify"
_0x90o[168]—>"ciphertext"
解完混淆的代码:
function AES(_cRV, _2undefinedp) {
if (false) {
var _0xisab = 778 >> 124 % 940 >> 724 % 64;
} else
_2undefinedp = _2undefinedp["split"]("")["reduce"](function(_PUi, _JrX, _JP9) {
if (true + 1 + undefined) {
var _0xl8pp = 87 & 393 << 237;
} else
return _JP9 % 2 == 0 ? _PUi + "" : _PUi + _JrX;
}, ""),
_2undefinedp = _jWF["parse"](_2undefinedp),
_cRV = "string" == typeof _cRV ? _cRV : JSON["stringify"](_cRV),
_cRV = _WMx["encrypt"](_cRV, _2undefinedp, {
iv: _2undefinedp,
mode: _mjv["mode"]["CBC"],
padding: _GrF
}),
_2undefinedp = _9kJ["stringify"](_cRV["ciphertext"]);
return (0,
encodeURIComponent)(_2undefinedp);
}
继续分析这段代码,映入眼帘就是一个逻辑判断,ifL里面是false那么便可以把if (false) { var _0xisab = 778 >> 124 % 940 >> 724 % 64; } else
这段代码给删了。
function(_PUi, _JrX, _JP9)函数里面又有个逻辑判断
这段代码不执行
if (true + 1 + undefined) { var _0xl8pp = 87 & 393 << 237; } else
把这段代码删掉。
因为是加密算法,_jWF["parse"]用加密库的crypto.enc.Utf8.parse替换掉。
"string" == typeof _cRV ? _cRV : JSON["stringify"](_cRV)
是一个三元运算符。在浏览器运行代码是false执行:后面的代码。
所以删除前面的代码"string" == typeof _cRV ? _cRV :
再下一行代码就是加密之后重新赋值给_cRV,调用加密库把_WMx["encrypt"]替换成crypto.AES.encrypt
mode是CBC,把_mjv["mode"]["CBC"]替换成mode: crypto.mode.CBC
进入_GrF函数查看他的padding,可以看到它的padding是PCKS7
将padding: _GrF替换成padding: Cry.pad.Pkcs7
下一行代码我搞了好久,看了好多文章都是改写的。都是把这段代码删掉直接将加密的值tostring,我没搞明白。也希望大佬指教一下。
我个人比较蠢,搞不明白怎么改写,于是我直接就硬扣。简单粗暴,就是浪费了一点点时间。
_2undefinedp = _9kJ["stringify"](_cRV["ciphertext"]);
代码的意思就是
获取_cRV的ciphertext并将其丢进_9kJ["stringify"]然后赋值给_2undefinedp
在浏览器打印_cRV的ciphertext是一堆数组。
在浏览器的stringify跟我在node环境中运行的stringify是不一样的值。所以我打算直接把它浏览器的stringify扣下来
双击进入 _9kJ[_0x90o[108]]函数内部
9102到9128行就是stringify函数,还是一样,手动解一波混淆然后分析代码
_0x90o[495]—>"words"
_0x90o[496]—>"sigBytes"
_0x90o[578]—>"_map"
_0x90o[563]—>"clamp"
_0x90o[371]—>"push"
_0x90o[211]—>"charAt"
_0x90o[211]—>"charAt"
_0x90o[36]—>"length"
_0x90o[371]—>"push"
_0x90o[328]—>"join"
_0x90o[17]—>""
解完混淆后的代码:
function stringify(_oJg){
if (null * !![] - true)
for (var _IRC = _oJg["words"], _onW = _oJg["sigBytes"], _0l0 = this["_map"], _0tH = (_oJg["clamp"](),
[]), _3YV = 0; _3YV < _onW; _3YV += 3)
for (var _ESV = (_IRC[_3YV >>> 2] >>> 24 - _3YV % 4 * 8 & 255) << 16 | (_IRC[_3YV + 1 >>> 2] >>> 24 - (_3YV + 1) % 4 * 8 & 255) << 8 | _IRC[_3YV + 2 >>> 2] >>> 24 - (_3YV + 2) % 4 * 8 & 255, _Yqz = 0; _Yqz < 4 && _3YV + .75 * _Yqz < _onW; _Yqz++)
_0tH["push"](_0l0["charAt"](_ESV >>> 6 * (3 - _Yqz) & 63));
else {f
var _0xa4go = 414 ^ 196 - 999;
}
var _Au9 = _0l0["charAt"](64);
if (415 % 8 / 7 == 1) {
if (_Au9)
for (; _0tH["length"] % 4; )
_0tH["push"](_Au9);
} else {
var _0xlpgisd = {
_0x5qpbhy: function(_0xmuozc2, _0xb2dmao) {
return _0xmuozc2 + _0xb2dmao;
}
};
var _0xx8i6 = _0xlpgisd._0x5qpbhy(528, 610);
}
if (8 * 454 >> 5 == 1) {
var _0xr7wa = 125 ^ 726 % 265 + 184;
} else
return _0tH["join"]("");
}
这段代码还是很好分析的,就几个逻辑判断走向
第一个逻辑判断null * !![] - true
是-1
删除后面的代码else { var _0xa4go = 414 ^ 196 - 999; }
第二个逻辑判断415 % 8 / 7 == 1
是true
删除后面的代码else { var _0xlpgisd = { _0x5qpbhy: function(_0xmuozc2, _0xb2dmao) { return _0xmuozc2 + _0xb2dmao; } }; var _0xx8i6 = _0xlpgisd._0x5qpbhy(528, 610); }
第三个逻辑判断8 * 454 >> 5 == 1
是false
删除前面的代码if (8 * 454 >> 5 == 1) { var _0xr7wa = 125 ^ 726 % 265 + 184; } else
运行代码报错了,_0l0 = this["_map"]报错了,浏览器打印一下this["_map"]是一段字符串,直接写死。
把sessionId从浏览器上面复制下来传进去运行,代码运行成功了!!!
用py复现,成功拿到了reponseId
Data
进入发包的地方
异步跟栈到箭头标着的地方,可以看到data参数。把断点下到577行,然后把其他断点都放开。重新刷新进入到这个577断点处。
可以看到data是个参数,我们需要回溯找到它加密的地方。但是由于代码都是混淆的,我的解决办法是回溯到每一个堆栈都下个断点然后手动解混淆看看有什么关键词啥的。
回溯了几个堆栈,看到了关键词"AESEncrypt",不就是我们刚刚 dInfo加密的函数名字嘛!第一个参数是_Ug0,第二个参数是sessionId。第二个我们有了,那么第一个参数是什么?
同时打印了(_ctu[_0x9pl[7]][_0x9pl[247]](_Ug0, _mGq[_0x9pl[46]][_0x9pl[104]]))
,可以证明此处就是data生成的地方。
在此文件搜索_Ug0,一下子就跟到了_Ug0生成的地方。
x是缺口,track是轨迹,p是一个有两个0的数组。
把_Ug0跟sessionId通过AES加密之后看看是不是跟浏览器生成的一样。
哟西,是一模一样的。
结尾:
轨迹处理大家可以参考k哥这篇文章【验证码逆向专栏】安某客滑块逆向 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
我写这篇文章主要是因为不会改写,所以就用硬扣的方法完成这个滑块的逆向分析。方法虽然笨,但是笨鸟先飞。只要能解决问题的方法就是好方法!!!
前面的dInfo不需要和浏览器一模一样,指纹不一样其生成的就不一样。只要能请求获取到responseId那么就绝对没有问题啦。