1.前沿
本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!
2.网址
aHR0cHM6Ly93d3cueGlhb2hvbmdzaHUuY29tL2V4cGxvcmU/Y2hhbm5lbF9pZD1ob21lZmVlZF9yZWNvbW1lbmQ=
3.加密参数定位
3.1:搜索定位
全局搜索加密参数X-S在图中位置下断点
可以看出X-S的值来自l["X-s"]
l = (a && void 0 !== window._webmsxyw ? window._webmsxyw : encrypt_sign)(c, i) || {};
l是一个三元表达式
这段代码简化就是window._webmsxyw(c,i)
传入两个参数分别是c和i,如下图
X-S的值是从window._webmsxyw(c,i)函数中获取到的
3.2:hook定位
观察x-s加密字段值的结构,前4位‘XYW_’值不变,对base64熟悉的朋友eyj并不陌生,ey - > base64解码 -> result = '{',对XYW_后面的整块值进行解码
解密发现其它字段均为明文,只有payload一处加密,定位到payload加密位置。Json对象转化为字符串必定会用到JSON.stringify()方法,对该方法进行hook
JSON.stringify_ = JSON.stringify;
// 重写JSON.stringify方法
JSON.stringify = function () {
if (arguments[0] && arguments[0]['payload']) {
// 设置断点
debugger;
}
// 调用原始的JSON.stringify方法并返回其结果
return JSON.stringify_.apply(this, arguments);
};
跟栈进入函数内部,发现是jsvmp
如何解决?
1.rpc调用
2.浏览器补环境
3.纯算还原算法
本次我们采用纯算法分析
4.纯算分析
对于小红书的纯算分析还原,可以看看志远大佬的视频,干货给的相当足。
4.1JSVMP是什么
jsvmp(JavaScript Virtual Machine Protection)是一种前端代码虚拟化保护技术。它将JavaScript源代码首先编译为自定义的字节码,只有对应的解释器才能执行这种字节码。(图片来源老妖哥)
在JSVMP逆向分析中,寄存器,循环是重要的特征,还有有一个特性,加密结果是一个一个字符生成的,基本由一个循环就是for循环或者while循环以及switch case控制。这一点至关重要,算法的分析还原就依赖这个特性。
JSVMP,一般在代码中会有一个超长字符串(指令),然后会把这个字符串decode成一个大数组,不断循环从数组中取指令,分配函数执行运算操作,运算过程会在一个数组或者对象中不断放进或取出生成的值,最后生成目标结果,这个过程会有+,/,%,*操作,会有个apply操作,这些都是插桩的关键位置。
4.2代码简单分析
代码复制vscode,折叠
查看指令集大数组_ace_aec23,数组里面大部分函数都调用_ace_1ae3,而_ace_1ae3函数内部调用_ace_b81ca函数,所以_ace_b81ca是个插桩的日志点
插桩分析
4.3第一个插桩日志点
回到payload加密生成的位置,追溯堆栈,根据日志点对apply函数进行插桩。
本次的log日志点可以看一个大致的流程,如果有方法(split、trim等方法)对字符串操作,可以打印出来,加密结果也是字符串形式,所以本日志点可能打印出解密结果值。
//对应apply 函数 ,this , 参数
'11-111'.split("-") // 方法:split , this:11-111 , 参数:-
// 为false断点不生效,但前面的js代码仍然运行
console.log(_ace_8712,_ace_25a6._ace_936,_ace_bdcc);false;
日志打印:
div检测
检测dom、vm环境
window._webmsxyw(c,i),c的值/api/sns/web/v1/homefeed,参数传入应该会用到,搜索c的值,定位到最后一个
滑倒底部,1732584193, -271733879, -1732584194, 271733878,md5的初始化魔数
猜测对传入的参数进行md5加密:md5(url=/api/sns/web/v1/homefeed{"cursor_score":"","num":27,"refresh_type":1,"note_index":27,"unread_begin_note_id":"","unread_end_note_id":"","unread_note_count":0,"category":"homefeed_recommend","search_key":"","need_num":12,"image_formats":["jpg","webp","avif"],"need_filter_image":false})
result:755c054d97e5450acda5f156156232c2
继续向下查看日志:猜想正确,是对请求url进行md5加密
第一次插桩位置来看,没有看出太多的信息,主要是对环境的校验,以及对请求参数做md5加密
4.4第二个插桩日志点
把文件中指令集展开来看,定位到加法指令集,有字符拼接的过程,然后进行插桩(本地js文件替换目标js文件)
日志15w多行,
encrypt_sign(c, i) ,参数c的值 /api/redcaptcha/v2/getconfig
日志中定位到最后一次出现该参数的位置
由第一个插桩日志分析可知,对入参数做了md5
md5(url=/api/redcaptcha/v2/getconfig{}) ,result = 6667296f17398985a2a087300d1474e4
既然做了md5,后续肯定有调用的地方,我们直接定位到最后一次调用md5加密值的位置
x1=6667296f17398985a2a087300d1474e4;x2=0|0|0|1|0|0|1|0|0|0|1|0|0|0|0;x3=18f7634e135lgkcv8aeo3eaixuf91jo1lopan6w4a30000208736;x4=1715678349667; (x1234)
该字符串由:
x1 = 6667296f17398985a2a087300d1474e4
x2 =0|0|0|1|0|0|1|0|0|0|1|0|0|0|0
x3 = 18f7634e135lgkcv8aeo3eaixuf91jo1lopan6w4a30000208736
x4 = 1715678349667
这4部分组成
x1 : md5(请求参数{})
x2:检测浏览器环境信息(不变),从两次插桩可以看出有大量的环境检测信息
x3:cookie中的a1 值
x4:当前时间的毫秒时间戳
继续查看日志,发现有个字符串一直逐字符增加
定位到最后
eDE9NjY2NzI5NmYxNzM5ODk4NWEyYTA4NzMwMGQxNDc0ZTQ7eDI9MHwwfDB8MXwwfDB8MXwwfDB8MHwxfDB8MHwwfDA7eDM9MThmNzYzNGUxMzVsZ2tjdjhhZW8zZWFpeHVmOTFqbzFsb3BhbjZ3NGEzMDAwMDIwODczNjt4ND0xNzE1Njc4MzQ5NjY3Ow==
看字符串格式应该是base64编码:
原来是对x1234进行了base64编码,但是这仍然不是我们目标payload的值,猜测可能是对base64(x1234)进行了某种加密运算
4.5第三个插桩日志点
上面分析_ace_b81ca是个插桩的日志点,现在对该函数进行插桩
该函数第一个参数在第二个插桩日志点已经分析过
从第二个参数_ace_7e97a开始分析
分析日志点,第二部插桩可知,对x1234做一个base64编码,直接搜索ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
继续向下分析,发现很多数字数组,遇到这种数组需要留意,向着常见的加密算法初始化魔数联想,方便快速识别出加密算法
520,134349312是des常用的标识数,找一份js 的des源码进行对比验证
16进制->10进制
0x208 - > 520
0x8020200 -> 134349312
现在确定是des算法,需要找key,key一般在加密的时候传入,直接定位到加密算法的头部。
在分析日志的时候发现'encrypt'相关的字眼,搜素发现第一次出现'encrypt'下方有一个数组,疑似key
des源码实现,git找一份源码实现,加解密正确
var encodeHex = (s) => {
let r = '';
const hexes = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
for (let i = 0; i < s.length; i++) {r += hexes [s.charCodeAt(i) >> 4] + hexes [s.charCodeAt(i) & 0xf];}
return r;
}
var decodeHex = function (h) {
let r = '';
for (let i = (h.substring(0, 2) === '0x') ? 2 : 0; i < h.length; i += 2) {
r += String.fromCharCode(parseInt(h.substring(i, i + 2), 16));
}
return r;
}
//加密
param1 = "x1=a6bcbb01f6f503ed31a72e5699f50058;x2=1|0|0|1|0|0|1|0|0|0|1|0|0|0|0;x3=18e7f1486c6kbmd6ngao21fez0b9o9xw2pep1912330000407442;x4=1713171492576;"
result = encodeHex(des("", btoa(param1), 1, 0, undefined, 0))
console.log(result) //b0b6165929b8dfb112304496eb5ae3979e0cd4a7b8cc5483dc760b9dc8bb62bf2853bc3c9adade8de749dc8064bff3bd277705ea9f4abd360d74aa31b54c72cd0d74aa31b54c72cdac489b9da8ce5e48f4afb9acfc3ea26fe0b266a6b4cc3cb55feb2433893ad2194003d4f0991acda0cf6ab2786d98b16e03535f6b11656835f6b1d67df72fe1bb1132db9ac45bda86e0c6c2c1ddc6d73e45fa8a11b55a7c95af4d4230e9c63f5afc0b4ded490db4e36e5949374a6b095328cdc7a2bb270cf1
//解密
encrypt_param = "b0b6165929b8dfb112304496eb5ae3979e0cd4a7b8cc5483dc760b9dc8bb62bf2853bc3c9adade8de749dc8064bff3bd277705ea9f4abd360d74aa31b54c72cd0d74aa31b54c72cdac489b9da8ce5e48f4afb9acfc3ea26fe0b266a6b4cc3cb55feb2433893ad2194003d4f0991acda0cf6ab2786d98b16e03535f6b11656835f6b1d67df72fe1bb1132db9ac45bda86e0c6c2c1ddc6d73e45fa8a11b55a7c95af4d4230e9c63f5afc0b4ded490db4e36e5949374a6b095328cdc7a2bb270cf1"
res = atob(des("", decodeHex(encrypt_param), 0, 0, undefined, 0))
console.log(res) //x1=a6bcbb01f6f503ed31a72e5699f50058;x2=1|0|0|1|0|0|1|0|0|0|1|0|0|0|0;x3=18e7f1486c6kbmd6ngao21fez0b9o9xw2pep1912330000407442;x4=1713171492576;
5.总结
分析推到过程大部分靠着经验而言,需要对常见的加密算法有所了解
-
分析x1是md5加密,md5初始化魔数要熟悉,在算法还原时,可以根据特征识别判断出MD5加密,从而能够节约大量的时间。
A:0x67452301 (1732584193)
B:0xefcdab89 (-271733879)
C:0x98badcfe (-1732584194)
D:0x10325476 (271733878)
-
base64编码编号特征比较明显,字符串的尾部带有==号,或者代码中带着 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
-
分析Des。不知道秘钥和IV没关系。尝试插桩自吐出iv或者key的值也能完成加密。
0x208 - > 520
0x8020200 -> 134349312
-
插桩位置比较关键
4.1 apply函数,涉及对字符串的操作
4.2 加法指令集,涉及字符串的拼接
4.3 通用函数,指令集都调用这个函数,可以抛出更多日志信息
4.4 插桩根据日志分析,可以加一些过滤条件,减少无用日志输出。