文章目录
- 写在前面
- 目标
- 正向梳理
- 主要加密函数
- 主要算法
- 解析
- 逆向梳理
- 结论
- 测试
- 进阶
- 写在后面
写在前面
首先推荐一篇资料:
某音短视频 X-Bogus 逆向分析,JSVMP 纯算法还原
这篇文章介绍了通过插桩日志抠算法的过程,很有参考价值。
文章已经实现的部分,本文不再重复,本文主要介绍从X-Bogus还原19位数组的过程,方便理解算法。
目标
// 从
var x_bogus = 'DFSzswVVUyXANrqJSkdAml9WX7jG';
// 还原出
var x_array = [64, 0.00390625, 1, 28, 7, 22, 69, 63, 0, 186, 99, 164, 90, 214, 32, 0, 190, 144, 201];
正向梳理
主要加密函数
function _0x2f2740(a, c, e, b, d, f, t, n, o, i, r, _, x, u, s, l, v, h, g) {
let w = new Uint8Array(19);
return w[0] = a,
w[1] = r,
w[2] = c,
w[3] = _,
w[4] = e,
w[5] = x,
w[6] = b,
w[7] = u,
w[8] = d,
w[9] = s,
w[10] = f,
w[11] = l,
w[12] = t,
w[13] = v,
w[14] = n,
w[15] = h,
w[16] = o,
w[17] = g,
w[18] = i,
String.fromCharCode.apply(null, w);
}
function _0x46fa4c(a, c) {
let e, b = [], d = 0, f = "";
for (let a = 0; a < 256; a++) {
b[a] = a;
}
for (let c = 0; c < 256; c++) {
d = (d + b[c] + a.charCodeAt(c % a.length)) % 256,
e = b[c],
b[c] = b[d],
b[d] = e;
}
let t = 0;
d = 0;
for (let a = 0; a < c.length; a++) {
t = (t + 1) % 256,
d = (d + b[t]) % 256,
e = b[t],
b[t] = b[d],
b[d] = e,
f += String.fromCharCode(c.charCodeAt(a) ^ b[(b[t] + b[d]) % 256]);
}
return f;
}
function _0x2b6720(a, c, e) {
return String.fromCharCode(a) + String.fromCharCode(c) + e;
}
主要算法
以上三个函数来自某音web端webmssdk.js
。x_array打乱顺序后,经过上述三个函数的混淆,最终变成21个字节的乱码字符串,乱码字符串再经过一系列变换,得到28位的x_bogus。
具体过程如下:
var short_str = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=";
function U8ArrayToXBogus(array1){
// 打乱数组顺序
array2 = [array1[0], array1[2], array1[4], array1[6], array1[8], array1[10], array1[12], array1[14], array1[16], array1[18], array1[1], array1[3], array1[5], array1[7], array1[9], array1[11], array1[13], array1[15], array1[17]];
// 再一次打乱顺序,得到19位乱码字符串
u1 = _0x2f2740.apply(null,array2);
// 对乱码字符串重新编码(实际上是异或加密)
u2 = _0x46fa4c.apply(null,[String.fromCharCode(255),u1]);
// 在乱码字符串开头添加两个固定字符
u = _0x2b6720.apply(null,[2,255,u2]);
var XBogus = "";
// 每次循环生成4个字符,循环7次,每次使用乱码字符串的三个字符
for (var i = 0; i <= 20; i += 3) {
var charCodeAtNum0 = u.charCodeAt(i);
var charCodeAtNum1 = u.charCodeAt(i + 1);
var charCodeAtNum2 = u.charCodeAt(i + 2);
var baseNum = charCodeAtNum2 | charCodeAtNum1 << 8 | charCodeAtNum0 << 16;
var str1 = short_str[(baseNum & 0xfc0000) >> 18];
var str2 = short_str[(baseNum & 0x3f000) >> 12];
var str3 = short_str[(baseNum & 0xfc0) >> 6];
var str4 = short_str[(baseNum & 0x3f) >> 0];
XBogus += str1 + str2 + str3 + str4;
}
return XBogus;
}
解析
x_array打乱顺序得到如下数组:
array2 = [64, 1, 7, 69, 0, 99, 90, 32, 190, 201, 0.00390625, 28, 22, 63, 186, 164, 214, 0, 144];
_0x2f2740.apply(null,array2)
得到:
var u1 = '@\x00\x01\x1C\x07\x16E?\x00ºc¤ZÖ \x00¾\x90É';
// 数组形式
// [64, 0, 1, 28, 7, 22, 69, 63, 0, 186, 99, 164, 90, 214, 32, 0, 190, 144, 201]
_0x2f2740
函数打乱数组顺序,并以字符串形式返回。
_0x46fa4c.apply(null,[String.fromCharCode(255),u1])
得到:
var u2 = '-%.8wE^\x8F9ñð\x10\x9E¹ý\x9CV,9';
// 数组形式
// [45, 37, 46, 56, 119, 69, 94, 143, 57, 241, 240, 16, 158, 185, 253, 156, 86, 44, 57]
_0x46fa4c
根据传入的第一个参数生成长度256的数组,u1与数组成员做异或运算,得到u2;
异或运算存在以下特征,明文与key异或得到加密结果,加密结果与key异或得到明文:
a = 11,b = 12;
a ^ b = 7;
a ^ 7 = b;
b ^ 7 = a;
所以_0x46fa4c.apply(null,[String.fromCharCode(255),u2])
可还原到u1。
_0x2b6720.apply(null,[2,255,u2])
得到:
u = '\x02ÿ-%.8wE^\x8F9ñð\x10\x9E¹ý\x9CV,9';
// 数组形式
u_arr = [2, 255, 45, 37, 46, 56, 119, 69, 94, 143, 57, 241, 240, 16, 158, 185, 253, 156, 86, 44, 57]
取u_arr前三位,进行位运算得到第一个种子数字:
>>> (2 << 16) | (255 << 8) | 45
196397
>>> hex(196397)
'0x2ff2d'
>>>
对种子数字再进行运算,这里需要配合二进制一起看:
>>> short_str = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe="
>>> binex = lambda x:'0' * (24 - len(bin(x)) + 2) + bin(x).replace('0b','')
>>> binex(0x2ff2d)
'000000101111111100101101'
>>> binex(0xfc0000)
'111111000000000000000000'
>>> binex(0xfc0000 & 0x2ff2d)
'000000000000000000000000'
>>> binex(0x3f000)
'000000111111000000000000'
>>> binex(0x3f000 & 0x2ff2d)
'000000101111000000000000'
>>> binex(0xfc0)
'000000000000111111000000'
>>> binex(0xfc0 & 0x2ff2d)
'000000000000111100000000'
>>> binex(0x3f)
'000000000000000000111111'
>>> binex(0x3f & 0x2ff2d)
'000000000000000000101101'
实际上是将一个24位二进制数拆分为4个6位二进制数字,得到4个下标:
>>> bin((0x2ff2d & 0xfc0000) >> 18)
'0b0'
>>> bin((0x2ff2d & 0x3f000) >> 12)
'0b101111'
>>> bin((0x2ff2d & 0xfc0) >> 6)
'0b111100'
>>> bin((0x2ff2d & 0x3f) >> 0)
'0b101101'
换算成十进制,分别是0,47,60,45
>>> short_str[0]
'D'
>>> short_str[47]
'F'
>>> short_str[60]
'S'
>>> short_str[45]
'z'
与原始字符串的DFSzswVVUyXANrqJSkdAml9WX7jG
的前四个字符一致。
再进行六次循环,即可得到完整的x_bogus。
逆向梳理
前面的内容已经把整体的思路写出来了,再简单梳理一下流程吧。
>>> short_str.index('D')
0
>>> short_str.index('F')
47
>>> short_str.index('S')
60
>>> short_str.index('z')
45
>>> 0 << 18 | 47 << 12 | 60 << 6 | 45 << 0
196397
>>> binex(0 << 18 | 47 << 12 | 60 << 6 | 45 << 0)
'000000101111111100101101'
>>> hex(0 << 18 | 47 << 12 | 60 << 6 | 45 << 0)
'0x2ff2d'
>>> 0x2ff2d >> 16 & 0xff
2
>>> 0x2ff2d >> 8 & 0xff
255
>>> 0x2ff2d >> 0 & 0xff
45
这样就成功还原了数组u_arr的第0到第2个成员,再来看第二组:
>>> short_str.index('s')
9
>>> short_str.index('w')
18
>>> short_str.index('V')
56
>>> short_str.index('V')
56
>>> binex(9 << 18 | 18 << 12 | 56 << 6 | 56 << 0)
'001001010010111000111000'
>>> hex(9 << 18 | 18 << 12 | 56 << 6 | 56 << 0)
'0x252e38'
>>> 9 << 18 | 18 << 12 | 56 << 6 | 56 << 0
2436664
>>> 0x252e38 >> 16 & 0xff
37
>>> 0x252e38 >> 8 & 0xff
46
>>> 0x252e38 >> 0 & 0xff
56
也成功还原了u_arr的第3到第5个成员。
结论
根据上述分析,可整理出还原函数:
function XBogusToU8Array(x_bogus){
u = "";
for(i = 0;i<x_bogus.length;i+=4){
seed = 0;
seed |= short_str.indexOf(x_bogus[i]) << 18;
seed |= short_str.indexOf(x_bogus[i + 1]) << 12;
seed |= short_str.indexOf(x_bogus[i + 2]) << 6;
seed |= short_str.indexOf(x_bogus[i + 3]) << 0;
u += String.fromCharCode(seed >> 16 & 0xff);
u += String.fromCharCode(seed >> 8 & 0xff);
u += String.fromCharCode(seed & 0xff);
}
u2 = _0x46fa4c.apply(null,[String.fromCharCode(255),u.slice(2)]);
a = u2.charCodeAt(0),r = u2.charCodeAt(1),c = u2.charCodeAt(2),_ = u2.charCodeAt(3),
e = u2.charCodeAt(4),x = u2.charCodeAt(5),b = u2.charCodeAt(6),u = u2.charCodeAt(7),
d = u2.charCodeAt(8),s = u2.charCodeAt(9),f = u2.charCodeAt(10),l = u2.charCodeAt(11),
t = u2.charCodeAt(12),v = u2.charCodeAt(13),n = u2.charCodeAt(14),h = u2.charCodeAt(15),
o = u2.charCodeAt(16),g = u2.charCodeAt(17),i = u2.charCodeAt(18);
arr = [a, c, e, b, d, f, t, n, o, i, r, _, x, u, s, l, v, h, g];
arr = [arr[0],arr[10],arr[1],arr[11],arr[2],arr[12],arr[3],arr[13],arr[4],arr[14],arr[5],arr[15],
arr[6],arr[16],arr[7],arr[17],arr[8],arr[18],arr[9]];
arr[1] = arr[1] + 0.00390625;
return arr;
}
测试
进阶
x_array可拆分成8个部分:
x_array = [64, 0.00390625, 1, 28, 7, 22, 69, 63, 0, 186, 99, 164, 90, 214, 32, 0, 190, 144, 201];
// 第一部分,固定值
x_1 = [64];
// 第二部分,UA加密过程使用的salt,小数在运算过程中会取整
x_2 = [0.00390625, 1, 28];
// 第三部分,Query String Parameters经过md5变换得到
x_3 = [7,22];
// 第四部分,form data经过md5变换得到,[69,63]对应的是空字符串
x_4 = [69,63];
// 第五部分,UA和x_2经过一系列变换得到long_str,long_str经过md5变换得到x_5
x_5 = [0,186];
// 第六部分,32位时间戳转换为byte数组
x_6 = [99, 164, 90, 214];
// 第七部分,canvas生成dataurl,与3735928559经过位运算得到32位数字,转换为byte数组
x_7 = [32, 0, 190, 144];
// 第八部分,0和前面18个数字逐个进行异或得到
x_8 = [201];
由此可见,X-Bogus会对params、form-data、user-agent、时间、canvas进行校验。
写在后面
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
若有侵权,请联系我立即删除。