Советы по предотвращению отладки JavaScript

JavaScript

слова, написанные впереди

До этого я изучал антиотладочные методы, связанные с JavaScript. Но когда я поискал соответствующую информацию в Интернете, то обнаружил, что статей в Интернете не так много, а если и есть, то очень неполные. Итак, в этой статье я намерен рассказать вам о методах предотвращения отладки JavaScript. Стоит отметить, что некоторые из этих методов широко использовались киберпреступниками в вредоносных программах.

aa.png


Для JavaScript вам нужно всего лишь потратить немного времени на отладку и профилирование, и вы сможете понять функциональную логику фрагментов кода JavaScript. И то, о чем мы говорим, может немного усложнить задачу тем, кто хочет проанализировать ваш код JavaScript. Однако наша технология не имеет ничего общего с обфускацией кода, мы в основном сосредоточены на том, как затруднить активную отладку кода.

Технические методы, которые будут представлены в этой статье, примерно следующие:

1. Обнаружение неизвестных сред выполнения (наш код хочет выполняться только в браузере);

2. Обнаружение средств отладки (таких как DevTools);

3. Контроль целостности кода;

4. Контроль целостности потока;

5. Антисимуляция;

Короче говоря, если мы обнаружим что-то «необычное», поток программы изменится, перескочив на поддельный блок кода и «скрыв» настоящий функциональный код.

1. Переопределение функции

Это один из самых основных и наиболее часто используемых методов предотвращения отладки кода. В JavaScript мы можем переопределить функции, используемые для сбора информации. Например, функцию console.log() можно использовать для сбора информации, такой как функции и переменные, и отображения ее в консоли. Если мы переопределим эту функцию, мы сможем изменить ее поведение и скрыть определенную информацию или показать поддельную информацию.

Мы можем запустить эту функцию прямо в DevTools, чтобы увидеть, что она делает:

console.log("HelloWorld");
var fake = function() {};
window['console']['log']= fake;
console.log("Youcan't see me!");

После запуска мы увидим:

VM48:1 Hello World

Вы обнаружите, что вторая информация не отображается, потому что мы переопределили эту функцию, т.е. «отключили» ее исходные функции. Но мы также можем позволить ему отображать поддельную информацию. Например это:

console.log("Normalfunction");
//First we save a reference to the original console.log function
var original = window['console']['log'];
//Next we create our fake function
//Basicly we check the argument and if match we call original function with otherparam.
// If there is no match pass the argument to the original function
var fake = function(argument) {
    if (argument === "Ka0labs") {
        original("Spoofed!");
    } else {
        original(argument);
    }
}
// We redefine now console.log as our fake function
window['console']['log']= fake;
//Then we call console.log with any argument
console.log("Thisis unaltered");
//Now we should see other text in console different to "Ka0labs"
console.log("Ka0labs");
//Aaaand everything still OK
console.log("Byebye!");

Если все пойдет хорошо:

Normal function
VM117:11 This is unaltered
VM117:9 Spoofed!
VM117:11 Bye bye!

На самом деле, чтобы контролировать, как выполняется код, мы также можем изменять функцию функции более умным способом. Например, мы можем построить фрагмент на основе вышеуказанного кода и переопределить функцию Eval. Мы можем пройти код JavaScript в функцию Eval, и код будет оценен и выполнен. Если мы переопределим эту функцию, мы можем запустить другой код:

//Just a normal eval
 
eval("console.log('1337')");
 
//Now we repat the process...
 
var original = eval;
 
var fake = function(argument) {
 
    // If the code to be evaluated contains1337...
 
    if (argument.indexOf("1337") !==-1) {
 
        // ... we just execute a different code
 
        original("for (i = 0; i < 10;i++) { console.log(i);}");
 
    }
 
    else {
 
        original(argument);
 
    }
 
}
 
eval= fake;
 
eval("console.log('Weshould see this...')");
 
//Now we should see the execution of a for loop instead of what is expected
 
eval("console.log('Too1337 for you!')");

Результаты приведены ниже:

1337
VM146:1We should see this…
VM147:10
VM147:11
VM147:12
VM147:13
VM147:14
VM147:15
VM147:16
VM147:17
VM147:18
VM147:19

Как было сказано ранее, хотя этот метод очень умен, он также является очень простым и распространенным методом, поэтому его относительно легко обнаружить.

2. Точка останова

Чтобы помочь нам понять функцию кода, инструменты отладки JavaScript (такие как DevTools) могут предотвращать выполнение кода сценария, устанавливая точки останова, а точки останова также являются самыми основными в отладке кода.

Если вы изучали отладчики или архитектуру x86, вы, вероятно, знакомы с инструкцией 0xCC. В JavaScript у нас есть похожая директива, называемая отладчиком. Когда мы объявим функцию отладчика в коде, код скрипта перестанет выполняться по команде отладчика. Например:

console.log("Seeme!");
debugger;
console.log("Seeme!");

Многие коммерческие продукты определяют в своем коде бесконечный цикл директив отладчика, но некоторые браузеры блокируют этот код, а другие нет. Основная цель этого подхода — раздражать людей, которые хотят отладить ваш код, потому что бесконечный цикл означает, что код продолжает появляться с вопросом, хотите ли вы продолжить выполнение кода скрипта:

setTimeout(function(){while (true) {eval("debugger")


3. Разница во времени

Это метод защиты от отладки, основанный на времени, заимствованный из традиционных методов защиты от реверса. Когда сценарий выполняется в инструментальной среде, такой как DevTools, скорость выполнения будет очень низкой (в течение длительного времени), поэтому по времени выполнения мы можем судить о том, отлаживается ли сценарий в данный момент. Например, мы можем сделать это, измерив время выполнения между двумя заданными точками в коде, а затем использовать это значение в качестве эталона, если время выполнения превышает это значение, сценарий в данный момент выполняется в отладчике.

Демонстрационный код выглядит следующим образом:

set Interval(function(){
  var startTime = performance.now(), check,diff;
  for (check = 0; check < 1000; check++){
    console.log(check);
    console.clear();
  }
  diff = performance.now() - startTime;
  if (diff > 200){
    alert("Debugger detected!");
  }
},500);


В-четвертых, обнаружение DevTools (Chrome)

Этот метод использует атрибут id в элементе div, и когда элемент div отправляется на консоль (например, console.log(div)), браузер автоматически попытается получить в нем идентификатор элемента. Если код вызывает метод получения после вызова console.log, консоль в данный момент работает.

Простой код проверки концепции выглядит следующим образом:

let div = document.createElement('div');
let loop = setInterval(() => {
    console.log(div);
    console.clear();
});
Object.defineProperty(div,"id", {get: () => {
    clearInterval(loop);
    alert("Dev Tools detected!");
}});


5. Неявный контроль целостности потока

Когда мы пытаемся деобфусцировать код, мы сначала пытаемся переименовать некоторые функции или переменные, но в JavaScript мы можем определить, было ли изменено имя функции, или мы можем получить исходное имя непосредственно из трассировки стека или последовательности вызовов.

arguments.callee.caller может помочь нам создать трассировку стека для хранения ранее выполненных функций.Демонстрационный код выглядит следующим образом:

function getCallStack() {
    var stack = "#", total = 0, fn =arguments.callee;
    while ( (fn = fn.caller) ) {
        stack = stack + "" +fn.name;
        total++
    }
    return stack
}
function test1() {
    console.log(getCallStack());
}
function test2() {
    test1();
}
function test3() {
    test2();
}
function test4() {
    test3();
}
test4();

Примечание. Чем более запутан исходный код, тем лучше работает этот метод.

6. Прокси-объект

Прокси-объекты — безусловно, самый полезный инструмент в JavaScript.Такие объекты могут помочь нам понять другие объекты в нашем коде, в том числе изменить их поведение и активировать действия объекта в определенных контекстах. Например, мы могли бы создать объект elemen и отслеживать каждый вызов document.createElement, а затем регистрировать соответствующую информацию:

const handler = { // Our hook to keep the track
    apply: function (target, thisArg, args){
        console.log("Intercepted a call tocreateElement with args: " + args);
        return target.apply(thisArg, args)
    }
}
 
document.createElement= new Proxy(document.createElement, handler) // Create our proxy object withour hook ready to intercept
document.createElement('div');

Затем мы можем зарегистрировать соответствующие параметры и информацию в консоли:

VM64:3 Intercepted a call to createElement with args: div

Мы можем использовать эту информацию и отладку кода, перехватываю некоторые определенные функции, но основной целью данной статьи является введение методов антиподнятия, так как мы обнаруживаем, следует ли использовать прокси-объект «Другое»? На самом деле, это игра «CAT and Mouse», например, мы можем использовать один и тот же фрагмент кода, а затем попытаться вызвать метод ToString и завершить исключение:

//Call a "virgin" createElement:
try {
    document.createElement.toString();
}catch(e){
    console.log("I saw your proxy!");
}

Сообщение следующего содержания:

"function createElement() { [native code] }"

Но когда мы используем прокси:

//Then apply the hook
consthandler = {
    apply: function (target, thisArg, args){
        console.log("Intercepted a call tocreateElement with args: " + args);
        return target.apply(thisArg, args)
    }
}
document.createElement= new Proxy(document.createElement, handler);
 
//Callour not-so-virgin-after-that-party createElement
try {
    document.createElement.toString();
}catch(e) {
    console.log("I saw your proxy!");
}

Правильно, мы действительно можем обнаружить прокси:

VM391:13 I saw your proxy!

Мы также можем добавить метод toString:

const handler = {
    apply: function (target, thisArg, args){
        console.log("Intercepted a call tocreateElement with args: " + args);
        return target.apply(thisArg, args)
    }
}
document.createElement= new Proxy(document.createElement, handler);
document.createElement= Function.prototype.toString.bind(document.createElement); //Add toString
//Callour not-so-virgin-after-that-party createElement
try {
    document.createElement.toString();
}catch(e) {
    console.log("I saw your proxy!");
}

Теперь мы не можем его обнаружить:

"function createElement() { [native code] }"

Как я уже сказал, это игра в кошки-мышки.

Суммировать

Я надеюсь, что эти советы, которые я собрал, могут быть вам полезны.Если у вас есть лучшие советы, чтобы поделиться с вами, вы можете оставить сообщение в области комментариев под статьей или написать мне в Твиттере (@TheXC3LL).