jsvmp逆向实战x-s、x-t算法还原
- 什么是jsvmp
- 定位关键点
- log插桩
- 日志分析
- 代码还原
什么是jsvmp
jsvmp就是将js源代码首先编译为自定义的字节码,只有对应的解释器才能执行这种字节码,这是一种前端代码虚拟化保护技术。
整体架构流程是服务器端通过对JavaScript代码词法分析 -> 语法分析 -> 语法树->生成AST->生成私有指令->生成对应私有解释器,将私有指令加密与私有解释器发送给浏览器,就开始一边解释,一边执行。
这些都不重要,只要知道有这么个技术就行了。jsvmp有一个特性,加密结果是一个一个字符生成的,要想一个个生成肯定需要循环,那无非就是for循环或者while循环以及switch case。这一点至关重要,算法的分析还原就依赖这个特性。
定位关键点
全局搜索关键字“X-s”,定位到main.***.js,这个js文件中会有多处生成x-s的地方,全部打上断点,最终会定位到 c = (a || void 0 !== window._webmsxyw ? window._webmsxyw : sign)(u, i) || {};,只有这行代码被调用并且生成了x-s,其它的几处都是原来的老版本已经废弃了。
log插桩
在断点处单步调试就会进入到jsvmp,把这个js文件保存到本地并用v_jstools进行ast混淆解密,选择仅变量压缩,压缩变量后替换原来的代码。
还记得我前面说过的jsvmp的特性吗?如果你还记得那将会为你节省大量时间,这个时候如果去盲目的动态调试你就会进入一个迷宫永远走不出来,正确做法就是直接在js文件中寻找循环的位置。
144行到164行有两个while循环,在这两个循环里面添加日志点,144-150这个while循环会无限执行,32g内存都能撑爆,暂时不管这个循环,在161行位置插入日志"h:",JSON.stringify(h[0]),"C:",C,"H:",H
,运行结果如下图。
分析日志可知,当C==781的时候x-s已经生成,但具体生成步骤日志里并没有,那就在160行插入条件断点C==780&&H[0]==31&&H[1]==3&&H[2]==83&&H[3]==1&&H[4]==0
,并且把所有进行过运算的方法里都插入日志点,比如185行"%","左:",r(A, B),"右:",r(E, Q)
,203行"|","左:",r(A, B),"右:",r(E, Q)
等,142行特别重要"h3:",JSON.stringify(h[0]),"A:",A,"B:",B
,这个日志点一定要打上。
日志点打好之后先停用断点,只保留160行的条件断点,然后继续执行,再次断在160行。
清空控制台日志信息,启用所有断点(263行的断点停用,这个方法就是拼接字符串),继续执行代码,执行完毕后将日志保存到本地。
日志分析
notepad打开日志,拉到最底下,复制X-s前面一小部分,全局搜索,全词匹配,然后全部标记。
仔细观察下图中标红的位置,会发现明显规律,“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=”是固定的,这个时候就可以大胆猜测了,它可能是调用了字符串的charAt方法,传入参数就是下方的数字,返回一个字符,然后依次将字符拼接。
控制台验证一下,果然如此。
接下来就得找出13、19、0、34这四个数字怎么来的,往上翻看日志,找到了可疑之处,这里出现了一些位移计算,也生成了上一步需要的数字。
控制台验证一下,经过反复验证得知,13 = 53 >> 2,19 =((53&3)<<4)|(48>>4),0 = ((48&15)<<2)|(34>>6),34 = 34&63,而且加粗的数字是固定的,那么下一步就是要确定53,48,34的生成逻辑。
接着往上翻看日志,又找到可疑之处,一个json字符串"{\"signSvn\":\"50\",\"signType\":\"x1\",\"appId\":\"xhs-pc-web\",\"signVersion\":\"1\",\"payload\":\"5c401130ca247f32ba75cbf05e0e58476fd112ed52f233f67fce4c61c5bc93dfc25b0c6065eda2f639a922d4481a611116e2e3bfb89e2da1dad61c5041d6ac2bdad61c5041d6ac2bba1c4fcc5520a3e3f9f6b953ff819f7c4d3964d610405efa8a7e6e067da7404c0381f161cf3a5952aefd4645c665dc827a83a6069b97009fd07c9b6d6913acbc9074093d7367e83ad4cdc39c2b950152b2ef9cd5fbe10084ffcb49de03a9400d0a36d7b01017de7dfffbe9b83b736a194ddc12db02f7193f\"}"
数字12、13、14有规律的增长,也生成了上一步需要的数字53,48,34,还有三字符’5’、‘0’,‘"’,这三个字符像是从json字符串里面取出来的,12、13、14分别是索引,然后调用’5’.charCodeAt(),‘0’.charCodeAt(),‘"’.charCodeAt()。控制台验证一下猜想,果然如此。
代码还原
经过上面几个步骤分析,大致生成逻辑已经捋清楚,除了json字符串里面的payload还没还原外,其它的步骤都已还原,并对比了浏览器和代码生成的结果,是一致的,完整代码如下,下一篇文章将分析payload的还原过程。
var str1 = "{\"signSvn\":\"50\",\"signType\":\"x1\",\"appId\":\"xhs-pc-web\",\"signVersion\":\"1\",\"payload\":\"16e0c0c108d9481f25fa55e5d0b68ca2a3754a5a6fd39af8945de60083f572f85fbf196a7df36325df512bc23f13ef9616e2e3bfb89e2da1dad61c5041d6ac2bdad61c5041d6ac2bba1c4fcc5520a3e3f9f6b953ff819f7c4d3964d610405efa8a7e6e067da7404c0381f161cf3a5952aefd4645c665dc827a83a6069b97009fd07c9b6d6913acbc9074093d7367e83ad4cdc39c2b950152b2ef9cd5fbe10084ffcb49de03a9400d0a36d7b01017de7daaeec9560d5a42f0e8cf56bde5e4d9b8\"}";
var str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var data = "XYW_";
var arr1 = []
for (var i = 0; i < str1.length; i++){
var code = str1[i].charCodeAt();
arr1.push(code)
}
var arr3 = [];
for(var i = 0; i< arr1.length; i += 3){
arr3.push(arr1.slice(i, i + 3));
}
for (var i = 0; i < arr3.length; i++){
var arr = arr3[i];
var arg1 = move_r(arr[0],2);
var arg2 = move_l(arr[0]&3,4) | move_r(arr[1],4);
var arg3 = move_l(arr[1]&15,2) | move_r(arr[2],6)
get_data([arg1,arg2,arg3,arr[2]&63])
}
function move_r(l,r){
return l >> r;
}
function move_l(l,r){
return l << r;
}
function get_data(arr){
for (var i = 0; i < arr.length; i++){
var code = arr[i];
var result = str2.charAt(code);
data += result;
}
}
console.log(data)