猿人学第三题逆向分析
目标网址:
aHR0cHM6Ly9tYXRjaDIwMjUueXVhbnJlbnh1ZS5jbi9tYXRjaDIwMjUvdG9waWMvMw==
1、代码还原
1.1、if转switch 1
主要讲一下上面的if是如何还原的,以下面的代码举个最简例子。
if (++loc_up1 > 125) {
if (loc_up1 < 127) {
code = 1
}
}
想要运行到code这个位置。
1、从上往下的条件是[++loc_up1 > 125, loc_up1 < 127]。
2、++loc_up1 > 125 就是 loc_up1 > 124。
3、由于上面loc_up1自增了,所以这个分支下面所有的loc_up1实际都加1 就相当于 (loc_up1+1) < 127 也就是 loc_up1 < 126。
由此可以得到下面代码
let pPath = if_path, old_path;
// 运行到代码块时loc_up1的值、case loc_up1的值
let values = 0, case_value;
// test数组
let s_arr = [];
while(true){
let right_v = test.right.value, s = {}, value = 0;
if(tmp_if){
s.operator =`${test.operator}`
}else{
s.operator = `${dict_oper[test.operator]}`
}
// ++a
if(test.left.prefix){
if(test.left.operator == '++'){
s.value = right_v - 1
value += 1
}else if(test.left.operator == '--'){
s.value = right_v + 1
value -= 1
}
}else if(test.left.prefix === false){// a--
s.value = right_v
value += test.left.operator == '++' ? 1 : -1
}else{// a
s.value = right_v
}
// 自增会影响到下面的值
if(value != 0){
if(value > 0){
s_arr.map(n => n.value--)
}else{
s_arr.map(n => n.value++)
}
}
s_arr.push(s)
values += value
old_path = pPath;
pPath = pPath.parentPath.parentPath
test = pPath.node.test
tmp_if = pPath.node.consequent == old_path.parentPath.node
if(pPath.type != 'IfStatement' || pPath.parentPath.type != 'BlockStatement'){
break
}
}
1.2、if转switch 2
不想麻烦的话我们也可以模拟运行,说下大致思路,当code=1时,其对代码就是case loc_up1_start: loc_up1_end=loc_up1;code;
loc_up1 = loc_up1_start
if (++loc_up1 > 125) {
if (loc_up1 < 127) {
code = 1
}
}
code,loc_up1_end = loc_up1
1.3、VMP虚假分支
if转switch处理完后的代码如上图。
这里的虚假分支类型还挺多,比如最开始mJ(258)中指令Exp_arg14[258]-Exp_arg14[262] 执行的是 (!!null) !== (!false) 明显这个是true在case 2中就不会走else,所以碰到UnaryExpression之类的节点自己判断一下是否可以转成BooleanLiteral节点。
还有像下面这个,经常用这个自执行函数传的Exp_arg15里面的数字做一些运算,也有虚假分支。
部分还原结果如下。
2、逆向分析
2.1、首先打开f12抓包
很明显就是/topic/3_captcha_jpg接口就是我们需要分析的,由于动态调试下面的字符可能会不一样,自己复现的时候可以固定Date.now 和 Math.random=()=>0.5。
另外在函数bZ是生成字符串的,也需要输出一下结果。
2.2、插桩分析
直接搜索 mJ(,发现有3处,2处是自执行函数,直接在mJ(Exp_arg1, loc_array1, [], loc_call1)处添加日志断点Exp_arg1, JSON.stringify(loc_array1)。
1、可以看到mJ(7394) 传入的数组经过btoa(String.fromCharCode(...arr_7394)) 恰好就是FYvF9MS4vf3JxuioegzO3wHeceH0vV2IrZOtWPp876/w/uCBoCgL+Oyz/b2fOds6
2、再向上看7172 [-251731839,-1607988232,-323748419,-1623598278]
其实就是arr_7394后16位就是new Uint8Array(n
ew Uint32Array(arr_7172.reverse()).buffer).reverse()
3、其实也没什么好说的,直接看结果就打印 Exp_arg1,loc_array1和mJ(Exp_arg1, loc_array1, [], loc_call1)的结果,如果不理解结果怎么来的就在switch开始的位置添加日志断点 JSON.stringify(Exp_arg11), loc_up1,loc_mem1, JSON.stringify(Exp_arg10) 主要看Exp_arg11的变化就行了,日志太长可以自己看怎么过滤合适 JSON.stringify(Exp_arg11, (key,value) => {return (key && typeof value == 'object' && value && !Array.isArray(value)) ? 'obj' : value})
4、
最后比较关键的地方,就是mJ(5126,) 这个位置,明显是Date.now() + location.pathname + ''.padEnd(16,'f') 16个f很明显是由6537生成的 *str.charAt(Math.floor(Math.random() 62))。
5、后面的/3_captcha_check接口也是一样的道理,hook XMLHttpRequest.prototype.send 或者在jquery.js的 r.send(i.hasContent && i.data || null)断点,不然会刷新网页。
可以看到起始节点就是mJ(5755,), val(验证码结果)+3_captcha_jpg的fff和Date.now()。
3、JS代码
3.1、3_captcha_jpg接口
function encrypt_1(str){
function get_value(buf, key){
// 大小端转换
buf = new Uint32Array(new Uint8Array(buf.reverse()).buffer).reverse()
// key的前16位
buf = buf.map((n,i) => n ^ key[i])
res = []
for(var i = 0; i < buf.length; i++){
t = buf[i]
tt = 0;
switch(i%2){
case 0:// 2949
// 1111 -> 15 共移动了4次
tt = ((t >>> 4) | ((t & 15) << 28)) >>> 0
break
case 1:// 2897
tt = ((t << 4) | ((t >>> 28))) >>> 0
break
}
res[(i+2)%4] = tt
}
// key的后16位
res = res.map((n,i) => n ^ key[i+4]).reverse()
res = new Uint8Array(new Uint32Array(res).buffer)
res = [...res].reverse()
// debugger
return res
}
key = [-427576346,-1801525873,-2098817616,-442851354,-1632178244,-1612212864,-457334555,-1330518619]
pad = str.length % 16 ? 16 - str.length % 16 : 0;
str += String.fromCharCode(pad).repeat(pad)
buf = Array.from(str, n => n.charCodeAt())
res_arr = []
for(var index = 0; index < buf.length; index+=16){
res_arr.push(...get_value(buf.slice(index, index+16), key))
}
res = btoa(String.fromCharCode(...res_arr))
return res
}
上面的key是固定的,其实是来源于下图
3.2、3_captcha_check接口
这个接口是将类似"1ffffffffffffffff1774880015396"的字符进行魔改md5加密,没什么好说的,太长就不贴了,对着标准的md5改就行了。
就是要注意下面,这两个其实是一个东西。
最后返回这个 {"success": true, "msg": "success"} 就是成功了。
3.7z
(18.05 KB, 下载次数: 7)