本文基于这篇论文这个代码仓库(及其 Pages 服务)。
有些人绕了反调试就没管了,有些整理之后直接顶会顶刊,这就是人与人的差距吧。

ShortCut(妨碍分析过程)

工作原理

用 JS 禁用常用快捷键触发的事件,如 F12Ctrl+Shift+ICtrl+Shift+J 等。

应对方式

从浏览器菜单栏里面打开 DevTools,或者本地 mitmproxy 一下把响应里不让开 DevTools 的代码去掉;此外可以通过 Ctrl+U(一般不会禁用这个)或修改 URL 前缀将协议改为 view-source 查看网页源代码。

参考代码

下面的代码没有禁用 Ctrl+U

window.addEventListener('keydown', function(event) {
    console.log(event);
    if (event.key == "F12" || ((event.ctrlKey || event.altKey) && (event.code == "KeyI" || event.key == "KeyJ" || event.key == "KeyU"))) {
        event.preventDefault();
        return false;
    }
});

window.addEventListener('contextmenu', function(event) {
    event.preventDefault();
    return false;
});

TrigBreak(妨碍分析过程)

工作原理

通过 debugger 语句可在 JS 中自动下断点;通过 setInterval 高频执行包含 debugger 语句的函数即可阻止用户下断点调试。

应对方式

在 DevTools 里停用断点可禁用所有断点;在 DevTools 里 debugger 语句所在的行号右键可禁用这一行的断点(一些毒瘤的变种会持续创造包含debugger 语句的匿名函数并执行,导致这种方法失效);此外也可以使用支持去掉 debugger 语句的浏览器扩展或本地 mitmproxy 一下把响应里干坏事的代码去掉。

参考代码

下面的代码是一个简单的实现:

function debug() {
    debugger;
    setTimeout(debug, 1);
}
debug();

下面的代码是一个更加阴暗(使用 obfuscator 混淆)的实现:

var _0x1452cb = function () {
    var _0x373b34 = !![];
    return function (_0x5bd40f, _0x424dd9) {
        var _0x502238 = _0x373b34 ? function () {
            if (_0x424dd9) {
                var _0x476265 = _0x424dd9['apply'](_0x5bd40f, arguments);
                _0x424dd9 = null;
                return _0x476265;
            }
        } : function () {
        };
        _0x373b34 = ![];
        return _0x502238;
    };
}();

(function () {
    _0x1452cb(this, function () {
        var _0xd0dec9 = new RegExp('function\x20*\x5c(\x20*\x5c)');
        var _0x4c1d0d = new RegExp('\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)', 'i');
        var _0x257572 = _0x448e86('init');
        if (!_0xd0dec9['test'](_0x257572 + 'chain') || !_0x4c1d0d['test'](_0x257572 + 'input')) {
            _0x257572('0');
        } else {
            _0x448e86();
        }
    })();
}());

function _0x448e86(_0x3d32ad) {
    function _0x596cba(_0x4f5e6e) {
        if (typeof _0x4f5e6e === 'string') {
            return function (_0x43d248) {
            }['constructor']('while\x20(true)\x20{}')['apply']('counter');
        } else {
            if (('' + _0x4f5e6e / _0x4f5e6e)['length'] !== 0x1 || _0x4f5e6e % 0x14 === 0x0) {
                (function () {
                    return !![];
                }['constructor']('debu' + 'gger')['call']('action'));
            } else {
                (function () {
                    return ![];
                }['constructor']('debu' + 'gger')['apply']('stateObject'));
            }
        }
        _0x596cba(++_0x4f5e6e);
    }
    try {
        if (_0x3d32ad) {
            return _0x596cba;
        } else {
            _0x596cba(0x0);
        }
    } catch (_0x4c5b3a) {
    }
}

setInterval(function () {
    _0x448e86();
}, 0xfa0);

ConClear(妨碍分析过程)

工作原理

不断调用 console.clear 函数使运行期间,如果不用调试器设置断点,几乎不可能检查输出。

应对方式

在控制台设置里面把“保留日志”打开,或者把 console.clear 覆盖成空函数;当然也可以 mitmproxy 一下把响应里的相关代码去掉。

参考代码

下面的代码是一个简单的实现:

function clear() {
    console.clear();
    setTimeout(clear, 10);
}
clear();

ModBuilt(改变分析结果)

工作原理

出于支持旧版浏览器等原因,JS 中所有的内置函数都可以被任意地重新定义,因此可重新定义分析者常用的 consoleStringJSON 等对象及内置的函数。

应对方式

相比于妨碍分析过程,改变分析结果更加隐蔽;可在执行待测试的代码前,先保存一份 JS 的内置函数的引用,之后将使用 JS 内置函数改为使用保存的引用。

参考代码

下面的代码是一个简单的实现(仅示意,未考虑递归的情况):

let originalStringify = JSON.stringify;

JSON.stringify = function(obj) {
    if (typeof obj != "object") {
        return originalStringify(obj);
    }
    let newObj = {};
    for (let key of Object.keys(obj)) {
        if (typeof obj[key] == "string") {
            newObj[key] = obj[key].replace("shellcode", "benign code").replace("want to hide", "do not want to hide");
        } else {
            newObj[key] = obj[key];
        }
    }
    return originalStringify(newObj);
}

let originalLog = console.log;

console.log = function(arg) {
    arg = arg.replace("shellcode", "benign code").replace("want to hide", "do not want to hide");
    originalLog(arg);
}

下面的代码使用 obfuscator 生成,禁用了控制台输出:

var _0x4bde55 = function () {
    var _0x16e614 = !![];
    return function (_0x41e722, _0xf342eb) {
        var _0x280f64 = _0x16e614 ? function () {
            if (_0xf342eb) {
                var _0x24a5ce = _0xf342eb['apply'](_0x41e722, arguments);
                _0xf342eb = null;
                return _0x24a5ce;
            }
        } : function () {
        };
        _0x16e614 = ![];
        return _0x280f64;
    };
}();

var _0x54fe5d = _0x4bde55(this, function () {
    var _0xb720ec = function () {
    };
    var _0x217a18;
    try {
        var _0xc3cc4a = Function('return\x20(function()\x20' + '{}.constructor(\x22return\x20this\x22)(\x20)' + ');');
        _0x217a18 = _0xc3cc4a();
    } catch (_0x228a20) {
        _0x217a18 = window;
    }
    if (!_0x217a18['console']) {
        _0x217a18['console'] = function (_0x2266c8) {
            var _0x104cf8 = {};
            _0x104cf8['log'] = _0x2266c8;
            _0x104cf8['warn'] = _0x2266c8;
            _0x104cf8['debug'] = _0x2266c8;
            _0x104cf8['info'] = _0x2266c8;
            _0x104cf8['error'] = _0x2266c8;
            _0x104cf8['exception'] = _0x2266c8;
            _0x104cf8['table'] = _0x2266c8;
            _0x104cf8['trace'] = _0x2266c8;
            return _0x104cf8;
        }(_0xb720ec);
    } else {
        _0x217a18['console']['log'] = _0xb720ec;
        _0x217a18['console']['warn'] = _0xb720ec;
        _0x217a18['console']['debug'] = _0xb720ec;
        _0x217a18['console']['info'] = _0xb720ec;
        _0x217a18['console']['error'] = _0xb720ec;
        _0x217a18['console']['exception'] = _0xb720ec;
        _0x217a18['console']['table'] = _0xb720ec;
        _0x217a18['console']['trace'] = _0xb720ec;
    }
});

_0x54fe5d();

WidthDiff(检测分析行为)

工作原理

更多的情况下,打开 DevTools 将水平或垂直地分割浏览器窗口,因而可以同时获得包括所有工具栏在内的整个浏览器窗口的大小(外部大小)和没有任何工具栏的内容区域的大小(内部大小),当差异超出阈值时认为开启了 DevTools。

应对方式

把 DevTools 在单独的窗口打开即可(感觉身边不少人都默认单独窗口打开了),此外其它侧边栏的存在可能导致这种方式出现误判。

参考代码

下面的代码是一个参考的实现:

function detect() {
    const devtools = { isOpen: false, orientation: undefined };
    const threshold = 160;
    const emitEvent = (isOpen, orientation) => {
        let string = "<p>DevTools are " + (isOpen ? "open" : "closed") + "</p>";
        console.log(string);
        document.write(string);
    };

    setInterval(() => {
        const widthThreshold = window.outerWidth - window.innerWidth > threshold;
        const heightThreshold = window.outerHeight - window.innerHeight > threshold;
        const orientation = widthThreshold ? 'vertical' : 'horizontal';

        if (
            !(heightThreshold && widthThreshold) &&
            ((window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized) || widthThreshold || heightThreshold)
        ) {
            if (!devtools.isOpen || devtools.orientation !== orientation) {
                emitEvent(true, orientation);
            }
            devtools.isOpen = true;
            devtools.orientation = orientation;
        } else {
            console.log(devtools.isOpen);
            if (devtools.isOpen) {
                emitEvent(false, undefined);
            }
            devtools.isOpen = false;
            devtools.orientation = undefined;
        }
    }, 500);

    if (typeof module !== 'undefined' && module.exports) {
        module.exports = devtools;
    } else {
        window.devtools = devtools;
    }
}

detect();

LogGet(检测分析行为)

工作原理

在一些控制台输出的方式中,被输出对象的 toString 方法在打开 DevTools 查看控制台的时候才被调用(如 console.profile + console.profileEnd),可以此进行区分。

应对方式

这是检测 DevTools 是否打开的一个非常可靠的方法,比较难应对;除了删除控制台对象的所有记录功能外,只能尝试本地 mitmproxy 来修改响应了。

参考代码

下面的代码是一个参考的实现(本文发布时有效):

var devtools = function() {};

devtools.toString = function() {
    if (this.open) {
        document.clear();
        document.write("DevTools were open! (You can close them again, but this text will stay)");
        clearInterval(interval);
    }
    this.open = true;
    return '-';
}

var interval = setInterval(() => {
    console.profile(devtools);
    console.profileEnd(devtools);
    console.clear();
}, 100);

下面的代码是一个曾经有效的方式(利用 console.log,参考 Stack Overflow):

function check() {
    var devtools = function() {};
    devtools.toString = function() {
        this.opened = true;
    };
    console.log('%c', devtools);
    if (devtools.opened) {
        document.write("This technique is broken since Chrome 77. Only included for historical reasons");
    }
}
setTimeout(check, 300);

下面的代码是另一个曾经有效的方式(利用 requestAnimationFrame,参考 Stack Overflow):

var checkStatus;
var element = new Image();

Object.defineProperty(element, 'id', {
    get: function() {
        checkStatus = true;
        throw new Error("Dev tools checker");
    }
});

requestAnimationFrame(function check() {
    console.dir(element);
    console.log(checkStatus);
    if (checkStatus) {
        document.write("DevTools were open! (You can close them again, but this text will stay)");
        console.clear();
        return;
    }
    requestAnimationFrame(check);
});

MonBreak(基于时间的复杂反调试)

工作原理

由于 debugger 语句只有在开启了 DevTools 的情况下才会停止执行代码,我们可以简单地比较执行 debugger 语句前后的时间,如果超出阈值就认为 DevTools 已被打开;不同于 TrigBreak,MonBreak 的目标不是扰乱用户,而是推断出 DevTools 的状态,因而只要触发断点一次即可。

应对方式

由于存在(可疑的)debugger 语句,与 TrigBreak 类似,可在 DevTools 里停用断点以禁用所有断点,或在 DevTools 里 debugger 语句所在的行号右键禁用这一行的断点。

参考代码

下面的代码是一个参考的实现:

addEventListener("load", () => {
    var threshold = 500;
    const measure = () => {
        const start = performance.now();
        debugger;
        const time = performance.now() - start;
        if (time > threshold) {
            document.write("<p>DevTools were open since page load</p>");
        }
    }
    setInterval(measure, 300);
});

NewBreak(基于时间的复杂反调试)

工作原理

为 MonBreak 一个更加隐蔽的变种,注意到分析者可能下断点调试,断点被命中时可以通过时间信息来区分,只要反复调用一个函数并记录时间。如果发现此函数突然花了很长的时间来执行,就很有可能是因为一个断点被命中了。

应对方式

虽然这种方法更隐蔽,但由于只检测了是否有人打开 DevTools,然后触发了一个断点,只要分析者在使用 DevTools 时根本不设置/触发断点,就没有什么效果了。

参考代码

下面的代码是一个简单的实现(仅示意,未考虑失去焦点时对 setInterval 产生的影响并使用 hasFocus 进行判断与处理):

var timeSinceLast;

addEventListener("load", () => {
    var threshold = 1000;
    const measure = () => {
        if (!timeSinceLast) {
            timeSinceLast = performance.now();
        }
        const diff = performance.now() - timeSinceLast;
        if (diff > threshold) {
            document.write("<p>A breakpoint was hit</p>");
        }
        timeSinceLast = performance.now();
    }
    setInterval(measure, 300);
});

ConSpam(基于时间的复杂反调试)

工作原理

利用浏览器关闭与打开 DevTools 时的性能差异进行判断,一个曾经有效的做法是创建许多内容较长的文本元素,并快速地在 DOM 中反复添加和删除它们;一个目前有效的做法是向控制台写大量的输出,并检查这需要多长时间。

应对方式

这个方法比较难处理;除了删除控制台对象的所有记录功能外,只能尝试本地 mitmproxy 来修改响应了。

此外这个方法在实现上也存在一定难处,如果采用了固定的阈值,具有缓慢硬件的访问者可能被误判,如果采用了可变的阈值,就要首先测量开始时的几轮时间,因而只能应对 DevTools 在页面加载后被打开,而不是一开始就被打开的情况。

参考代码

下面的代码是一个参考的实现(本文发布时有效):

var baseline;

function measure() {
    const start = performance.now();
    for (let i = 0; i < 100; i++) {
        console.log(i);
        console.clear();
    }
    const time = performance.now() - start;
    if (baseline === undefined) {
        baseline = time;
    }
    else if (time > baseline * 2) {
        document.write("DevTools were opened");
        return;
    }
    setTimeout(measure, 1000);
}

measure();

结语

A backdoor could always be cleverly disguised as a “bugdoor”.

和全文相比,还是论文作者这里说的有水平呀,学到了学到了。

原文地址:http://www.cnblogs.com/Chenrt/p/16899145.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性