# 补环境介绍
对于存在 js 加密的网站,我们往往通过分析网站 js,了解到其加密 js 的逻辑,然后通过本地运行的方式,获取加密后的参数,达到获取 Js 的目的。
对于加密代码量较少,亦或者是扣取复杂度较低的代码,我们可以通过扣代码,缺什么补什么,进而在本地扣取一个完整的加密算法,最终在本地运行。但是,对于加密量大,同时运行流程错纵复杂的代码,扣取其加密流程的复杂度呈几何倍的增长,因此,我们希望通过直接运行整个 js 代码,进而达到获取其结果的目的。
试想,为什么我们在浏览器中运行 js 可以直接成功,但是在本地运行就不行了呢?
首先,本地运行的时候,我们大部分是采用的 node 直接运行的,node 可以看作是浏览器引擎的一个子集,只包含其中的一部分内容,因此,对于加密算法破解,就抽象到找寻函数入口,比较环境差异。
如何找寻函数入口在此不做赘述,而比较环境差异主要是为了知晓其缺什么浏览器环境,进而我们在本地对其进行补充,以达到本地成功运行 js 的目的。
# 环境检测
之前已经提到,node 可以看作浏览器的子集,那么最直白的环境检测就是调用 node 中没有的对象,进而导致在本地运行抛出错误,但是现实往往不会这么美好,有些环境检测隐含在很深的地方,而且采取的隐式调用的方法,即使不存在该对象运行流程也不会出错。因此,这里将环境检测主要分为两类:
- 显式检测
- 隐式检测
# 显式检测
** 定义:** 以 A ['B']、A.function (val), 这种方式调用,同时,A、B、function 都可能为检测的对象。
以下面这段 js 为例:
var _N = function () { | |
setTimeout('location.href=location.pathname+location.search.replace(/[\?|&]captcha-challenge/,\'\')', 1500); | |
document.cookie = '__jsl_clearance=1640094880.604|0|' + (function () { | |
var _t = [function (_N) { | |
return _N | |
}, function (_t) { | |
return _t | |
}, (function () { | |
var _N = document.createElement('div'); | |
_N.innerHTML = '<a href=\'/\'>_1H</a>'; | |
_N = _N.firstChild.href; | |
var _t = _N.match(/https?:\/\//)[0]; | |
_N = _N.substr(_t.length).toLowerCase(); | |
return function (_t) { | |
for (var _1H = 0; _1H < _t.length; _1H++) { | |
_t[_1H] = _N.charAt(_t[_1H]) | |
}; | |
return _t.join('') | |
} | |
})(), function (_N) { | |
for (var _t = 0; _t < _N.length; _t++) { | |
_N[_t] = parseInt(_N[_t]).toString(36) | |
}; | |
return _N.join('') | |
}], | |
_N = ['clD', [(-~~~{} << -~~~{}) + (-~~~{} << -~~~{})], 'V', [(-~[] + [] + [[]][0]) + [-~-~{}]], 'fq', [(-~[] + [] + [[]][0]) + [-~[] - ~[] - ~!/!/ + (-~[] - ~[]) * [-~[] - ~[]]], (-~[] + [] + [[]][0]) + (-~[-~-~{}] + [[]][0]), (-~[] + [] + [[]][0]) + [(+!![[][[]]][1])]], 'LBWywKW', [(2 - ~[-~-~{}] + [] + [[]][0])], '%2FZyf', [(-~[] + [] + [[]][0]) + (-~[-~-~{}] + [[]][0])], '6', [(-~[] + [] + [[]][0]) + (-~[-~-~{}] + [[]][0])], '_b938d514e58d7772015c7ad84677513a', (-~[-~-~{}] + [[]][0]), 'D']; | |
for (var _1H = 0; _1H < _N.length; _1H++) { | |
_N[_1H] = _t[[1, 0, 1, 2, 1, 3, 1, 2, 1, 2, 1, 3, 1, 0, 1][_1H]](_N[_1H]) | |
}; | |
return _N.join('') | |
})() + ';Expires=Tue, 12-Dec-30 09:50:26 GMT;Path=/;' | |
}; | |
if ((function () { | |
try { | |
return !!window.addEventListener; | |
} catch (e) { | |
return false; | |
} | |
})()) { | |
document.addEventListener('DOMContentLoaded', _N, false) | |
} else { | |
document.attachEvent('onreadystatechange', _N) | |
} |
在上述代码中,我们可以看到形如 document、window 这些对象,在 node 中是不包含这种对象的,因此我们需要构造一个和浏览器一样的对象出来。
很明显,这里的 Window 指代全局对象,document 指代的是网页源码,DOM 对象在 node 中无法完全模拟,因此我们换一种思路,只需要补到其不报错不就行了嘛?
# STEP1 整体代码分析:
先看第一个位置,setTimeout 创建了一个定时器,我们在这里直接置空即可。
setTimeout = function(){}; |
由于在 node 中的全局为 global, 那么我们需要对其进行一个赋值操作,把 window 构建出来。
同时由于后面还调用了 document, 我们先创建一个空对象。
window = global; | |
document = {}; |
# STEP2 环境搭建:
对于 document.createElement ('div'),createElement ('div') 明显是一个方法,在网页端返回了一个 dom 对象,同时我们发现其后续创建了 innerHTML 属性,然后通过 firstChild.href 重新赋值。这样一来,我们在网页中运行_N.firstChild.href, 发现,打印的结果为'https://www.python-spider.com/', 后续还跟上了对其的操作。也就是说,我们只需要把 href 补出来,同时让 innerHTML 属性存在即可。
var _N = document.createElement('div') | |
_N.innerHTML = '<a href=\'/\'>_1H</a>'; | |
_N = _N.firstChild.href; | |
var _t = _N.match(/https?:\/\//)[0]; |
document.createElement = function(){ | |
return { | |
innerHTML:'', | |
firstChild:{ | |
href:'https://www.python-spider.com/', | |
} | |
} | |
}; |
在末尾我们还看到了形如 addEventListener 的这种调用,对于这种方法,我们不知道其返回了什么,那么我们就需要去 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript 官方文档里查找。
最后补出来如下:
window.addEventListener = function(){}; | |
document.addEventListener = function(a,b,c){return b()}; | |
document.attachEvent = function(a,b){}; |
# 隐式检测
以显示检测的补环境来看,在代码结构清晰的情况下,我们可以很轻易的找到补环境入口,但是假使代码流程复杂,同时调用方法不是以上述形式调用,那么单纯的看代码几乎找不到补环境入口。
先看如下例子:
//type1 | |
window['hr'+'ef']; | |
//type2 | |
S = [window,'hr','ef']; | |
var A = S[0][S[1]+S[2]]; |
第一种为稍微明显的调用,但是假如被 ASCII 码混淆了呢,假如被 Ob 混淆了呢,当然,针对这种情况,我们可以直接放到 node 中运行,让其报错后在比对即可。但是,假如我们这里采用第二种方法检测环境,这样,即使在 node 中运行也不会报错,返回的是 undefined,那么这样在运行的流程中就发生了变化,进而导致最终的结果与我们想要的所差甚远。
我们想要知道到底缺少什么环境,可以通过插桩 (logpoint) 的方式去寻找。
# 奇 yin 巧计
补环境可以通过半置空函数,判断其操作。
nullfunction = function(){console.log(arguments)}; |
代理环境 proxy.
test = new Proxy(test,{ | |
get(target, key) { | |
console.log('获取了'+key+'属性'); | |
return target[key]; | |
} | |
}); |
本质上是通过截取 window.a,这种操作,把 a 打印到控制台,然后判断到底做了什么。