# 补环境介绍

对于存在 js 加密的网站,我们往往通过分析网站 js,了解到其加密 js 的逻辑,然后通过本地运行的方式,获取加密后的参数,达到获取 Js 的目的。

对于加密代码量较少,亦或者是扣取复杂度较低的代码,我们可以通过扣代码,缺什么补什么,进而在本地扣取一个完整的加密算法,最终在本地运行。但是,对于加密量大,同时运行流程错纵复杂的代码,扣取其加密流程的复杂度呈几何倍的增长,因此,我们希望通过直接运行整个 js 代码,进而达到获取其结果的目的。

试想,为什么我们在浏览器中运行 js 可以直接成功,但是在本地运行就不行了呢?

首先,本地运行的时候,我们大部分是采用的 node 直接运行的,node 可以看作是浏览器引擎的一个子集,只包含其中的一部分内容,因此,对于加密算法破解,就抽象到找寻函数入口,比较环境差异。

如何找寻函数入口在此不做赘述,而比较环境差异主要是为了知晓其缺什么浏览器环境,进而我们在本地对其进行补充,以达到本地成功运行 js 的目的。

# 环境检测

之前已经提到,node 可以看作浏览器的子集,那么最直白的环境检测就是调用 node 中没有的对象,进而导致在本地运行抛出错误,但是现实往往不会这么美好,有些环境检测隐含在很深的地方,而且采取的隐式调用的方法,即使不存在该对象运行流程也不会出错。因此,这里将环境检测主要分为两类:

  1. 显式检测
  2. 隐式检测

# 显式检测

** 定义:** 以 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 打印到控制台,然后判断到底做了什么。

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

Mr2 WeChat Pay

WeChat Pay