【JS逆向练习】抖音a_bogus-1.0.1.19-fix.01-jsvmp纯算还原分析2025-04-04
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
算法流程分析#
1、请求参数+dhzx,标准sm3加密两次,生成32位数组
2、dhzx,标准sm3加密两次,生成32位数组
3、user-agent,魔改RC4加密,将RC4加密结果fromCharCode转字符串,再用魔改base64加密
4、根据浏览器属性,生成41位数组。
5、根据时间戳,生成4位数组。
6、通过随机数加一些操作,生成前4位数组。再通过随机数加一些操作,生成后4位数组。和前面的生成规则不一样
7、将第6步的两个数组concat成一个8位数组
8、将第7步的8位数组结合1、2、3、4、5步生成的数组,异或生成一个1位数的数组
9、将1、2、3、4、5步生成的数组,结合随机数时间戳等一些操作,生成一个50位数组。
10、通过随机数加一系列操作,生成4位数组,将数组formCharCode转字符串。
11、将第9步的50位数组+第4步的41位数组+第5步的4位数组+第8步的1位数组生成96位数组
12、上一步的96位数组,对3取余,每3个一组,用生成随机数开始,经过一些操作,生成4位数组。然后放到新数组里面,循环。也就是说96/3=32,96+32结果是128位的。如果数组长度%3有余数,比如是95位的,95%3=2,结果就是93+31+2=126位的。多出来的2位,不需要任何操作直接放到新数组里。
13、用第7步生成的8位数组concat上一步的128位数组,形成136位数组。
14、魔改rc4加密136位数组,将加密结果formCharCode转字符串。拼接第10步的4位数组转字符串。然后魔改base64加密字符串,最后得到ab参数。
总结:总体来说过程比较多,业余时间我是搞了很久才还原完。
二级评论接口验证展示
分析准备:#
找到jsvmp加密入口
怎么找到入口不演示了,主要是d()原本这里是ifelse混淆了,非常不方便阅读。所以我这里ast还原了下。
刷新验证了下,还原的没啥问题,无报错, 断点 __ 也能正常执行。
ast还原后的d()函数完整代码
function d() {
for (; ; ) {
var t = o[a++];
switch (t) {
case 0:
var r = o[a++];
p -= r;
var e = v.slice(p + 1, p + r + 1)
, n = v[p--]
, d = v[p--];
if ("function" != typeof n)
return f = 3,
void (l = new TypeError(typeof n + " is not a function"));
var y = V.get(n);
if (y)
h.push([o, i, u, s, c, a, f, l]),
g(y[0], d, e, y[1]);
else {
var m = n.apply(d, e);
v[++p] = m;
}
break;
case 1:
var w = v[p--];
v[p] = v[p] <= w;
break;
case 2:
w = v[p--],
v[p] = v[p] > w;
break;
case 3:
var x = o[a++]
, S = v[p--]
, P = [];
for (var j in S)
P.push(j);
s[x] = [P, S];
break;
case 4:
x = o[a++];
var O = v[p--]
R = v[p--];
P = s[x],
j = void 0;
do {
j = P[0].shift();
} while (j !== undefined && !(j in P[1]));
if (j !== undefined) {
R[O] = j;
v[++p] = true;
} else {
v[++p] = false;
}
break;
case 5:
x = o[a++];
var A = Z[x]
, E = b(A, i);
v[++p] = E,
v[++p] = A;
break;
case 6:
w = v[p--],
v[p] = v[p] !== w;
break;
case 7:
v[++p] = {};
break;
case 8:
var k = v[p--];
v[p] = v[p][k];
break;
case 9:
v[++p] = !0;
break;
case 10:
v[p] = void 0;
break;
case 11:
(E = v[p--],
v[p] %= E);
break;
case 12:
(w = v[p--],
v[p] = v[p] & w);
break;
case 13:
S = v[p--],
v[p] = v[p]instanceof S;
break;
case 14:
E = v[p--];
var T = v[p--];
(S = v[p--])[T] = E;
break;
case 15:
x = o[a++],
E = v[p--];
var C = Z[x];
if (i && !(C in globalThis))
return f = 3,
globalThis[C] = E;
break;
case 16:
var L = v[p--];
(S = v[p--])[L] = v[p];
break;
case 17:
var U = o[a++];
v[p] ? --p : a += U;
break;
case 18:
E = v[p],
v[++p] = E;
break;
case 19:
w = v[p--],
v[p] = v[p] >>> w;
break;
case 20:
x = o[a++],
E = v[p--],
(S = v[p--])[Z[x]] = E;
break;
case 21:
E = v[p--],
v[p] -= E;
break;
case 22:
if (0 !== f)
return;
break;
case 23:
U = o[a++],
E = v[p--],
v[p] === E && (--p,
a += U);
break;
case 24:
v[p] = typeof v[p];
break;
case 25:
var I = v[p--];
E = delete (S = v[p--])[I],
v[++p] = E;
break;
case 26:
--p;
break;
case 27:
v[++p] = !1;
break;
case 28:
v[++p] = NaN;
break;
case 29:
v[p] = !v[p];
break;
case 30:
x = o[a++];
v[p] = v[p][Z[x]];
break;
case 31:
U = o[a++];
if (v[p])
a += U;
else
--p;
break;
case 32:
w = v[p--];
v[p] = v[p] < w;
break;
case 33:
v[++p] = void 0;
break;
case 34:
v[++p] = c;
break;
case 35:
w = v[p--];
v[p] = v[p] >> w;
break;
case 36:
var temp = v[p];
v[p] = +v[p];
break;
case 37:
v[p] = ~v[p];
break;
case 38:
v[++p] = o[a++];
break;
case 39:
var F = o[a++];
v[p = p - F + 1] = v.slice(p, p + F);
break;
case 40:
var M = v[p--];
E = ++(S = v[p--])[M],
v[++p] = E;
break;
case 41:
U = o[a++],
v[p--] || (a += U);
break;
case 42:
(E = v[p--],
v[p] /= E)
break;
case 43:
v[p] = -v[p];
break;
case 44:
var B = v[p--];
E = --(S = v[p--])[B],
v[++p] = E;
break;
case 45:
(E = v[p--],
v[p] *= E);
break;
case 46:
(x = o[a++],
v[++p] = +Z[x]);
break;
case 47:
x = o[a++];
var Q = v[p--];
Object.defineProperty(v[p], Z[x], {
get: Q,
enumerable: !0,
configurable: !0
});
break;
case 48:
x = o[a++];
var H = v[p--];
Object.defineProperty(v[p], Z[x], {
set: H,
enumerable: !0,
configurable: !0
});
break;
case 49:
return f = 3,
void (l = v[p--]);
break;
case 50:
var q = v[p--];
E = (S = v[p--])[q]++,
v[++p] = E;
break;
case 51:
w = v[p--],
v[p] = v[p] | w;
break;
case 52:
return U = o[a++],
f = 1,
void (l = a + U);
break;
case 53:
U = o[a++],
a += U;
break;
case 54:
var N = o[a++];
for (x = o[a++],
U = s; N > 0; )
U = U[0],
--N;
U[x] = v[p--];
break;
case 55:
(S = v[p--],
v[p] = v[p]in S)
break;
case 56:
(w = v[p--],
v[p] = v[p] << w);
break;
case 57:
(w = v[p--],
v[p] = v[p] === w)
break;
case 58:
(w = v[p--],
v[p] = v[p] == w);
break;
case 59:
r = o[a++];
for (var G = [void 0]; r > 0; )
G[r--] = v[p--];
var z = v[p--];
m = new (Function.bind.apply(z, G))(),
v[++p] = m;
break;
case 60:
x = o[a++];
var Y = Z[x];
if (!(Y in globalThis))
return f = 3,
void (l = new ReferenceError(Y + " is not defined"));
E = globalThis[Y],
v[++p] = E;
break;
case 61:
for (N = o[a++],
x = o[a++],
U = s; N > 0; )
U = U[0],
--N;
v[++p] = U,
v[++p] = x;
break;
case 62:
w = v[p--],
v[p] = v[p] != w;
break;
case 63:
E = D(o[a++], s),
v[++p] = E;
break;
case 64:
w = v[p--],
v[p] = v[p] >= w;
break;
case 65:
v[++p] = 1 / 0;
break;
case 66:
var J = v[p--];
E = (S = v[p--])[J]--,
v[++p] = E;
break;
case 67:
x = o[a++],
E = v[p--],
Object.defineProperty(v[p], Z[x], {
value: E,
writable: !0,
configurable: !0,
enumerable: !0
})
break;
case 68:
E = v[p--],
v[p] += E;
break;
case 69:
x = o[a++];
var X = Z[x];
E = typeof globalThis[X],
v[++p] = E;
break;
case 70:
w = v[p--],
v[p] = v[p] ^ w;
break;
case 71:
U = o[a++],
v[p--] && (a += U);
break;
case 72:
x = o[a++];
var W = Z[x];
W in globalThis || (globalThis[W] = void 0);
break;
case 73:
x = o[a++],
v[++p] = Z[x];
break;
case 74:
for (N = o[a++],x = o[a++], U = s; N > 0; )
U = U[0],--N;
E = U[x],
v[++p] = E;
break;
case 75:
v[++p] = null;
break;
default:
return f = 2,
void (l = v[p--]);
}
}
}
插桩
插桩也不演示了,我是用的插桩框架。只推荐一些插桩位置吧,指令0、5、8、11、12、14、15、19、20、21、26、30、35、36、37、39、42、45、51、54、56、57、58、60、64、68、71、73、74 关键指令,0:函数调用,74:从作用域取值。其它的都是压栈、出栈、全局变量加载还有+=/*^等操作符指令,自行分析吧。
日志分析#
整个过程就是按照开头的分析过程,由上至下的。
1、请求参数标准SM3加密2次,dhzx标准SM3加密2次
2、ua获取并trim去掉空格,放到作用域U里
[0.00390625,1,12]是RC4加密的key,不同环境不一样,我试过[0.00390625,1,8],[0.00390625,1,0]都有。根据自己来的。
rc4加密完,就开始base64加密
base64加密完,开始sm3加密。
3、浏览器属性获取,生成41位数组
开始生成
生成结束
4、时间戳生成4位数组
5、8位数组生成,前4位数组生成
6、8位数组生成,后4位数组生成
7、拼接成8位数组,开始生成1位数组
8、50位数组生成
9、拼接成96位数组
可以到首先是[1,12]开头的50位数组 concat[49,55开头的根据浏览器属性生成的41位数组 concat4位的 时间戳 __ 数组 concat最后的1位数组。 刚好50+41+4+1=96长度
10、生成4位数组
11、96位数组生成128位数组
每3个数一组进行操作,生成4位后push新数组
12、8位数组拼接128位数组,得到136位数组
13、开始对136位数组rc4加密的准备
首先转成 字符串
然后初始化256位的数组
rc4结果拼接4位的数组生成前缀,然后开始base64
base64加密完成,最终输出内容就是ab参数了。
































