一、JS逆向调试流程
- 如果网页有跳转,必须勾选 preservelog 防止丢包
- 看一下有没有框架 右键查看框架源代码(弹出式登陆界面)
- 登陆尽量使用错误密码 防止跳转
- 查看关键登陆包 分析哪些参数是加密的
- 使用别的浏览器分析哪些参数是固定的值
- 初步猜测加密方法
- 搜索,直接搜索参数,例如pwd ,Encrypt,decrypt
- 密码框地方右键 检查 查看 id name type
- 找到加密的地方(重点)(一般debug)
- 找出所有的加密代码
- 从最后一步开始写起,缺啥找啥
- 如果找的是函数的话 search 要带上 function xxx
- 如果看到加密的地方有个类,并且之后是用 prototype 把方法加在原生对象上的话,要把 所有加在原生对象上的方法都找出来
- 函数找多了没关系,只要不报错不会影响结果,但是不能找少了
二、加密破解
- 始于前端js对密码加密实现的需要,目前使用最多是AES、RSA、MD5,当然这三个的嵌套和混合使用情况也比较多
- 发现有加密时,搜索
encrypt,JSON.parse,JSON.stringify,CryptoJS
等相关 - 关于算法总结:
- 对称加密(加密解密密钥相同):DES、DES3、AES
- 非对称加密(分公钥私钥):RSA
- 信息摘要算法/签名算法:MD5、HMAC、SHA
- 前端实际使用中
MD5、AES、RSA
使用频率是最高的- 几种加密方式配合次序:
采用非对称加密算法管理对称算法的密钥,然后用对称加密算法加密数据,用签名算法生成非对称加密的摘要
- DES、DES3、AES、RSA、MD5、SHA、HMAC传入的消息或者密钥都是bytes数据类型,不是bytes数据类型的需要先转换;密钥一般是8的倍数
参考项目
三、有eval语句体的,直接把eval改为console.log,在控制台输出查看
四、补环境
- 什么是 “补浏览器环境”
- 浏览器环境: 是指JS代码在浏览器中的运行时环境,它
包括V8自动构建的对象(即ECMAScript的内容,如Date、Array),浏览器(内置)传递给V8的操作DOM和BOM的对象(如document、navigator)
;- Node环境:是基于V8引擎的Js运行时环境,它包括V8与其自己的内置API,如fs,http,path;
Node环境 与 浏览器环境 的异同点可以简单概括如图:
补浏览器环境” 其实是补浏览器有 而Node没有的环境,即补BOM和DOM的对象
- 关于bom对象和dom对象:
- 为什么要 “补浏览器环境”?
对于逆向老手而言,“补环境” 这个词不会陌生,当我们每次把辛辛苦苦扣出来的 “js加密算法代码”,并且放在浏览器环境中能正确执行后,就需要将它放到Node环境 中去执行,而由于Node环境与浏览器环境之间存在差异,会导致部分JS代码在浏览器中运行的结果 与在node中运行得到的结果不一样或者报异常,从而影响我们最终逆向成果
;eg:
function decrypt() {
document = false;
var flag = document?true:false;
if (flag) {
return "正确加密"
} else {
return "错误加密";
}
}
在浏览器环境运行时 flag为true,然后得到正常结果;
在Node环境运行时 flag为false,然后得到错误结果;
所以我们需要 “补浏览器环境”,使得扣出来的 “js加密算法代码” 在Node环境中运行得到的加密值,与其在 浏览器环境中运行得到的加密值一致
。 即对于这段 “js加密算法代码” 而言,我们补出来的环境与浏览器环境一致。
- 怎么 “补浏览器环境”?
要想 “补浏览器环境”,首先我们得知道 “js加密算法代码” 到底使用了哪些浏览器环境API,然后再对应去补上这些环境
;
那么我们该如何监测 “js加密算法代码” 对浏览器环境API的使用呢?毫无争议:使用Proxy来监测浏览器环境API的使用,辅助补浏览器环境
”
Proxy是ES6提供的代理器,
用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
。它可以代理任何类型的对象,包括原生数组,函数,甚至另一个代理;拥有递归套娃的能力
也就是说我们代理某个对象后,我们就成了它的中间商,任何JS代码对它的任何操作都可以被我们所拦截
- 基于Proxy的特性,衍生了两种补环境思路:
- 递归嵌套Proxy以此来代理浏览器所有的BOM、DOM对象及其属性,再配合node vm2模块提供的纯净V8环境,就相当于在node中,对整个浏览器环境对象进行了代理,JS代码使用任何浏览器环境 api都能被我们所拦截。然后我们针对拦截到的环境检测点去补。
-
搭建补环境框架
,用JS模拟浏览器基于原型链去伪造实现各个BOM、DOM对象,然后将这些JS组织起来,形成一个纯JS丐版浏览器环境,我们补的纯JS丐版浏览器环境越完善,就越接近真实浏览器环境,能通杀的js环境检测就越多
。最终完美通杀所有JS环境检测
- 第一种思路虽然实现简单,主要是对Proxy拦截器的使用,但是具备的环境监测能力有限,对较复杂的原型链等难以监测,即使是二次开发也上限不高;并且遇到JS使用了很多环境时手补也相当麻烦;
- 第二种思路虽然实现较为复杂,但是上限极高,且可以完美兼容第一种思路,具备可成长的通杀潜质。
所以业内补环境框架几乎都是基于第二种思路,先搭建一个补环境框架的骨架,将常见浏览器环境BOM、DOM对象补齐,如:window、location、Document、navigator等,等空闲时或工作遇到其他浏览器环境BOM、DOM对象,再将它补进来
。补的越完善,我们能通杀JS环境检测越多。
- 优点:
- 补的越完善,能通杀JS环境检测越多。
- 一键运行输出目标JS中所有环境检测点;
- 生成的最终代码可直接用于生产环境(可直接供nodejs、v8使用); 告别玄学补环境,不再一行行去debugger,极大提高工作效率。
- 可以在Chrome浏览器进行无浏览器环境调试。
- 常见的补环境方式
- window is not defined;
修改为: window = global或者,var window = {}
- ASN1 is not define
尝试window = global
- navigator is not defined
修改为: var navigator = {}
- Cannot read property ‘userAgent’ of undefined
navigator.userAgent = ‘xxxx’
- Cannot read property ‘body’ of undefined:
这个是 document里面的。
修改为:document.body = {xxx(具体的某个参数)}
- Cannot read property ‘x’ of undefined
缺少函数,具体缺少什么,可根据调试来添加。
- Cannot read property ‘href’ of undefined
是 location.href
修改为:
window.location = {
"xxxx": {},
"href": "https://www.xxxx.***/",
(其他参数保持)
}
- Cannot read property ‘length’ of undefined
具体原因是取值的时候,取到的是null。。。
具体调试可得知。
- Cannot read property ‘cookie’ of undefined
var document = {
cookie:"xxxxxx"
}
- Object.getOwnPropertyDescriptor
var Navigator = function() {};
Navigator.prototype = {"platform": "win32"};
navigator = new Navigator();
- 补齐window.localStorage:
window.localStorage = {
removeItem: function (key) {
delete this[key]
},
getItem: function (key) {
return this[key] ? this[key]: null;
},
setItem: function (key, value) {
this[key] = "" + value; // 将数字转为字符串
},
};
- .document补齐
location = {
"href": "https://www.w3school.***.***/jsref/prop_anchor_href.asp",
"origin": "https://www.w3school.***.***",
"protocol": "https:",
}
document = {
createElement: function () {
var loc = {
href: ""
};
var temp_href = loc.href;
Object.defineProperty(loc, 'href', {
// Hook loc.href,当为其赋值时,按下面的规则强制改变
get: function () {
return temp_href
},
set: function (val) {
if (val.indexOf('http://') === 0 || val.indexOf('https://') === 0) {
// 1.当值为http://或https://开头时,即为该值
temp_href = val;
} else if (val.indexOf('//') === 0) {
// 2.当值为//开头时,即为location.protocol加上该值
temp_href = location.protocol + val;
} else if (val.indexOf('/') === 0) {
// 3.当值为/开头时,即为location.origin加上该值
temp_href = location.origin + val;
} else {
// 4.除以上3种情况,即为location.href中最后一个/之前的值加上该值
var s = location.href
temp_href = s.substring(0, s.lastIndexOf("/")+1) + val
}
return temp_href
}
});
return loc;
}
}
五、控制流平坦化
将顺序执行的代码混淆成乱序执行,并加以混淆,以下两段代码的执行结果是相同的
// 正常形态
function test(a){
var b = a;
b += 1;
b += 2;
b += 3;
b += 4;
return a + b
}
// 乱序形态
//(这里比较简单,在很多加密网站上case 后面往往不是数字或字符串,而是类似 YFp[15][45][4]这样的对象,相当恶心)
function test1(a){
var arr = [1,2,3,4,5,6]
for(var i = 0, i < arr.lenght, i++){
switch (arr[i]) {
case 4:
b += 3;
break;
case 2:
b += 1;
break;
case 1:
var b = a;
break;
case 3:
b += 2;
break;
case 6:
return a + b
case 5:
b += 4;
break;
}
}
}
// 结果都是30 但是test1看着费劲
console.log(test1(10));
console.log(test(10));
六、去除无限debug
//去除无限debugger
Function.prototype.__constructor_back = Function.prototype.constructor;
Function.prototype.constructor = function() {
if(arguments && typeof arguments[0]==='string'){
//alert("new function: "+ arguments[0]);
if("debugger" === arguments[0]){
//arguments[0]="console.log(\"anti debugger\");";
//arguments[0]=";";
return
}
}
return Function.prototype.__constructor_back.apply(this,arguments);
}
未完待续