技术背景与目标
目标网址:aHR0cHM6Ly93d3cubG9vbmdhaXIuY24vIy9sb2dpbg==
逆向目标与技术难点
JS混淆和ASCALL编码是逆向阅读的难点,本文主要讲轨迹的生成。个别接口会一并带过
JS逆向分析流程
初始化
打开网址,输入手机号点击获取验证码。会触发风控,加密参数有dt,irToken,cb,fp

打上断点,此时数据已经生成

继续跟栈,这里actions里面的_0x8f2900存放值,
搜索一下,在cb打上断点
进入该方法,前面三个是定值,可以写死,分别是suffix :m25b40 code:vfnv46和一个定值数组
_0x58ee17 = _0x11cfc7a0_0x1e60(496)(32);这个是uuid,控制台输出一下a0_0x1e60(496)是uuid

然后for循环对数三个值进行charat拼接,剩下的进入下一个方法进行操作。这个扣代码就可以。不详细讲
javascript
var _0x11cfc7 = {
uuid: function(length) {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var result = '';
for (var i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
};
function _0x62692() {
var _0x28d664 = {};
// _0x28d664['suffix'] = a0_0x1e60(1387),
// _0x28d664['code'] = a0_0x1e60(1388),
_0x28d664['suffix'] = "m25b40",
_0x28d664['code'] = "vfnv46",
_0x28d664["pos"] = [
1,
10,
12,
13,
26,
31
];
var _0x183b74 = _0x28d664 ||
{
},
_0x86f269 = _0x183b74["code"],
_0x22d54d = _0x183b74["pos"],
_0x58ee17 = _0x11cfc7["uuid"](32);
if (_0x86f269 && _0x22d54d && Array['isArray'](_0x22d54d)) {
for (
var _0x56eeac = _0x58ee17['split'](''),
_0x3d43fc = 0;
_0x3d43fc < _0x22d54d['length'];
_0x3d43fc++
) _0x56eeac[_0x22d54d[_0x3d43fc]] = _0x86f269['charAt'](_0x3d43fc);
_0x58ee17 = _0x56eeac['join']('');
}
return _0x3ebd00(_0x58ee17);
}
a=_0x62692()
console.log("生产的cb值为"+a)
up,conf接口
初始化接口的别的参数,fp扣代码复现,dt conf接口返回,irToken up接口返回
getconf接口:可固定,直接默认请求,
up接口:参数固定,除了n 是一个uuid,可以python复现之后动态替换
check验证接口(主要讲解)
触发一下接口验证,里面除了data是轨迹生成。别的参数都是扣代码。别的接口返回。cb和fp特别注意!!!由于python的字节码和js生成的字节码有出入,这两个值不能转python。只能node生成
打上断点
继续跟栈,data在这生成。打上断点继续跟栈
继续跟栈这里存放data里面各种属性的生成这里的d值就是轨迹真实的值
轨迹算法(主要讲解)
traceData逻辑
打上断点,走到轨迹生成的步骤,这里的a0_0x1e60(611)是traceData,这个是真实的帧轨迹,也是加密的。然后组合到一起,通过d声明的方法来整体加密
搜一下traceData打上断点,此时帧轨迹加密生成。
将代码还原一下
javascript
'onMouseMove': function (_0x240a05) {
var _0x466917 = _0x240a05['clientX'],
_0x1e9e11 = _0x240a05["clientY" ],
_0x557353 = this['drag'],
_0x550d04 = _0x557353["status" ],
_0xea33d8 = _0x557353["beginTime" ],
_0x3b3633 = _0x557353['startX'];
if (
_0x557353["status" ] = _0xea33d8 &&
_0x466917 - _0x3b3633 > 3 &&
'dragend' === _0x550d04 ? 'dragstart' : _0x550d04,
"dragend" !== _0x557353['status']
) {
!(
_0x240a05['type']["indexOf" ]("touch" ) !== - 1 &&
_0x4332c6["supportPassive" ] ||
_0x240a05["event"]["cancelable" ] !== !1
) &&
_0x240a05["preventDefault"](),
Object['assign'](
_0x557353,
{
'clientX': _0x466917,
'clientY': _0x1e9e11,
'dragX': _0x466917 - _0x557353["startX" ]
}
);
var _0x2c044c = this["$store" ]["state" ]['token'],
_0x1d0d76 = [
Math["round" ](_0x557353['dragX'] < 0 ? 0 : _0x557353["dragX"]),
Math['round'](_0x557353['clientY'] - _0x557353["startY" ]),
_0x1b7568[now"]() - _0x557353['beginTime'],
null == _0x240a05["isTrusted" ] ? 0 : _0x240a05["isTrusted" ] ? 1 : 2
];
this["atomTraceData" ]['push'](_0x1d0d76);
var _0x4cde17 = _0x564a9f(_0x2c044c, _0x1d0d76 + '');
this["traceData" ]['push'](_0x4cde17),
"dragstart" === _0x557353["status" ] &&
this['onMouseMoveStart'](_0x240a05),
'dragging' === _0x557353["status" ] &&
this["onMouseMoving" ](_0x240a05);
}
},
_0x2c044c之前的操作是为了声明变量
_0x1d0d76:首先判断移动距离dragX是正向还是反方向,如果反方向就为0,正方向就四舍五入取整
其次点击的y值和开始的y值相减(因为是滑块,所以只有x变化,这个可以默认写0)
紧接着是当前时间戳和开始时间戳相减的差值
最后是判断是真人还是脚本触发,真人是1,脚本是2,其它为0
然后放入atomTraceData数组,经过_0x564a9f通过up接口返回的token进行加密处理。最后放入traceData
重新触发断点进入_0x564a9f方法:将参数进行了ASCII 码转换并进入_0x228898c方法
跟栈进入_0x228898c方法:对数据进行加密处理,根据data和key来进行xor加密
加密完之后进入另一个方法里面声明了两个定值变量去访问_0x4c4b3e方法
进入_0x4c4b3e方法
将代码还原一下,这个方法做了自定义base64:将字节组每三个调用_0x2d84c6方法编码,最后拼接返回
javascript
function _0x4c4b3e(_0x368459, _0xcf7f71, _0x4a9800) {
if (!_0x368459 || 0 === _0x368459["length" ]) return '';
try {
for (var _0x402ac6 = 0, _0x373bfd = []; _0x402ac6 < _0x368459['length']; ) {
if (!(_0x402ac6 + 3 <= _0x368459["length" ])) {
var _0x38562e = _0x368459['slice'](_0x402ac6);
_0x373bfd['push'](_0x2d84c6(_0x38562e, _0xcf7f71, _0x4a9800));
break;
}
var _0x2627da = _0x368459["slice"](_0x402ac6, _0x402ac6 + 3);
_0x373bfd['push'](_0x2d84c6(_0x2627da, _0xcf7f71, _0x4a9800)),
_0x402ac6 += 3;
}
return _0x373bfd['join']('');
} catch (_0xce575b) {
return '';
}
}
响应接密:和加密一样使用同样代码,只需要进行xor解密即可
部分代码
纯算轨迹代码
python
def get_track(distance):
"""生成轨迹"""
# 73.5px 22.8125 300 x 160 6.5
base_track = [
[4, 0, 94], [6, 0, 102], [9, 0, 111], [13, 0, 118], [18, 0, 126], [22, 0, 134], [28, 0, 140], [32, 0, 148],
[35, 0, 156], [40, 0, 164], [42, 1, 172], [45, 2, 180], [46, 2, 189], [47, 3, 196], [49, 4, 204],
[50, 4, 212], [51, 4, 220], [52, 4, 237], [53, 4, 244], [54, 4, 252], [55, 4, 260], [57, 4, 268],
[58, 4, 276], [60, 4, 294], [62, 4, 373], [62, 4, 380], [63, 4, 388], [65, 4, 396], [66, 4, 405],
[67, 4, 412], [68, 4, 421], [70, 4, 428], [73, 5, 437], [74, 5, 444], [75, 6, 452], [78, 6, 460],
[80, 7, 468], [82, 8, 477], [84, 8, 485], [86, 8, 492], [90, 8, 501], [94, 8, 509], [95, 8, 518],
[98, 8, 525], [102, 9, 533], [105, 10, 541], [106, 10, 588], [107, 10, 604], [109, 10, 612], [110, 10, 620],
[110, 10, 628], [113, 11, 636], [115, 11, 644], [116, 11, 653], [118, 11, 660], [118, 11, 668],
[120, 11, 676], [122, 11, 684], [122, 11, 692], [123, 11, 700], [124, 12, 764], [125, 12, 772],
[126, 12, 788], [128, 12, 804], [129, 12, 812], [130, 12, 1190], [130, 12, 1252], [131, 12, 1268],
[132, 12, 1340], [134, 12, 1710]
]
random_y = random.randint(-3, 5)
radio = distance / (base_track[-1][0] - base_track[0][0])
new_track = []
for x, y, t in base_track:
y = y + random_y if y else 0
point = [round(x * radio), y, round(t * radio)]
new_track.append(point)
return new_track
Tips
cb和fp特别注意!!!由于python的字节码和js生成的字节码有出入,这两个值不能转python。只能node生成
技术验证与测试

法律与伦理声明
- 强调技术研究边界
- 注明实际应用需获得授权
- 个人观点,禁止商业行为,侵权请联系下架