Защита вашего JS-кода - одна из самых важных задач, когда вы должны предоставить свой код пользователям. Тут на помощь приходит обфускатор JavaScript, такой как aaencode. Сегодня я собираюсь проанализировать обфусцированный код, чтобы узнать больше о том, как выполняется обфусцированный код, и в то же время получить больше знаний о типе данных JS.

Что такое aaencode

Возможно, вы не знакомы с этим именем, поэтому позвольте мне показать вам, что вы упустили. Обфускатор под названием aaencode - один из самых крутых обфускаторов JS, созданных Йосуке Хасегава. В отличие от других обфускаторов, в то время как запутанный код других обфускаторов дает вам ощущение решаемого кода, aaencode делает его настолько трудным для чтения, что, когда вы смотрите на него, вы даже не думаете, что это рабочий JS-код.

Вот доказательство, давайте попробуем запутать простую строчку кода.

alert("hi")

Как это выглядит в aaencode

゚ω゚ノ= /`m´)ノ ~┻━┻   //*´∇`*/ ['_']; o=(゚ー゚)  =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

Вы не поверите, но приведенный выше код работает отлично, вы можете попробовать выполнить его в консоли браузера и ознакомиться с ним, поскольку этот фрагмент запутанного кода вот-вот сломается.

Пусть украшают нашу золушку

Чтобы повысить удобочитаемость, я использовал чрезвычайно полезный веб-сайт под названием jsbeautifier, чтобы несколько деобфускировать код. После использования jsbeautifier читаемость улучшилась лишь незначительно, так как все переменные являются символами UTF8.

Сумасшедший, правда? Эта функция JS чрезвычайно полезна для усложнения кода. По сути, если вы используете символ UTF8 для именования своих переменных, то эта строка кода ниже рассматривается как действительный JS.

var ಠ_ಠ = "I'm done with JS"; 
alert(ಠ_ಠ);

Так как же решить эту проблему? Есть только один способ - заменить все имена переменных UTF8 буквами английского алфавита. Этот процесс займет некоторое время, но с заменой всех функций возвышенного текста я смог завершить задачу всего за несколько минут.

Погрузитесь в мир кода

Давайте сначала посмотрим на первые 10 строк.

a = /`m´)ノ ~┻━┻   / /*´∇`*/ ['_'];
o = (b) = _ = 3;
c = (d) = (b) - (b);
(e) = (d) = (o ^ _ ^ o) / (o ^ _ ^ o);
(e) = {
    d: '_',
    a: ((a == 3) + '_')[d],
    h: (a + '_')[o ^ _ ^ o - (d)],
    i: ((b == 3) + '_')[b]
};

Прежде всего, мы видим, что код присваивает переменной «a» некоторые символы темной магии. Однако, несмотря на его странный вид, его значение на самом деле просто undefined, потому что значение a состоит из 3 частей.

/`m´)ノ ~┻━┻   / -> regular expression
/*´∇`*/ -> comment
['_'] -> object property selector

Таким образом, значение a - это значение свойства _ в регулярном выражении, и поскольку свойство _ не определено, оно вернет undefined.

Затем значение o, b и _ устанавливается на 3. После этого создаются переменные c и d, которым присваивается значение b-b, равное 3-3 = 0. Затем переменная d делится своим значением с e, которая является вновь созданной переменной, и обе они содержат новое значение, которое представляет собой улыбающееся лицо, разделенное на улыбающееся лицо.

Ха-ха, на самом деле это не улыбающееся лицо, это комбинация переменных o и _ с использованием оператора xor и оператора разделения. Фактическая ценность улыбки составляет.

(o ^ _ ^ o) = (3 ^ 3 ^ 3) = 3

Таким образом, мы можем вычислить значение e, и d теперь равно 3 / 3 = 1.

Затем e изменил свое значение на объект, это означает, что только d теперь несут значение 1. Значение e теперь легко определить, поскольку теперь это просто базовый JavaScript.

(e) = {
    d: '_',
    a: ((a == 3) + '_')[d],
    h: (a + '_')[o ^ _ ^ o - (d)],
    i: ((b == 3) + '_')[b]
};

Давайте посмотрим на свойство a, его значение ((a == 3) + '_')[d]. Теперь мы все знаем, что a равно undefined, поэтому a==3 обязательно вернет false, затем false объединяется со строкой _, которая изменяет значение на false_, и после этого берется индекс d строки, который является индексом 1 строки. , это означает, что свойство a теперь равно false_[1] = 'a'.

То же самое с h, собственность, у нас будет

(a + '_')[o ^ _ ^ o - (d)] = 'undefined_'[3 - 1] = 'd'

и i свойство будет иметь стоимость

((b == 3) + '_')[b] = (true + '_')[3] = 'true_'[3] = 'e'

Давайте перепишем наш код с новым значением, которое мы успешно вычислили.

a = undefined;
o = (b) = _ = 3;
c = (d) = 0;
(e) = (d) = 1;
(e) = {
    d: '_',
    a: 'a',
    h: 'd',
    i: 'e'
};

Переходите к следующим трем строкам.

(e)[d] = ((a == 3) + '_')[c ^ _ ^ o];
(e)['c'] = ((e) + '_')[(b) + (b) - (d)];
(e)['o'] = ((e) + '_')[d];

Кажется, что первая строка в этих трех строках присваивает новое свойство объектной переменной e. Поскольку d равно 1 и в e нет свойства с именем 1, поэтому JavaScript создаст новое свойство, если вы присвоите какое-то значение неопределенному свойству. Это означает, что e теперь владеет другим свойством со значением 'false_'[0 ^ 3 ^ 3] = 'f'.

То же самое произойдет и со следующими двумя строками. Но в этих двух строках появилось что-то другое. Они соединяют e с '_'? Как объект может сочетаться с персонажем? Ну, если вы попытаетесь объединить объект с символом или строкой, JavaScript не вернет никакой ошибки, но вместо этого он вызовет метод toString как объекта, так и строки, а затем вернет строку результата после добавления этих двух строк, полученных из toString вместе.

Вот результат.

Таким образом, значение последних 2 строк будет

(e)['c'] = ((e) + '_')[(b) + (b) - (d)] = '[object Object]_'[5] = 'c'
(e)['o'] = ((e) + '_')[d] = '[object Object]_'[1] = 'o';

Снова позвольте перезаписать переменную e с новым добавленным свойством.

Сделай перерыв!

Теперь вам следует сделать перерыв, потому что мы только что закончили работу со стадией 1, и вы не переживете стадию 2, если не сделаете перерыв. Кто сказал, что JavaScript проще вычислений?

Более сложный код

Теперь мы перейдем к самой сложной части нашего процесса анализа. Вот часть остальной части нашего запутанного кода, и в этом разделе мы проанализируем часть от начала кода до строки 22.

Начнем с первой строки

(g) = (e)['c'] + (e)['o'] + (a + '_')[d] + ((a == 3) + '_')[b] + ((e) + '_')[(b) + (b)] + ((b == 3) + '_')[d] + ((b == 3) + '_')[(b) - (d)] + (e)['c'] + ((e) + '_')[(b) + (b)] + (e)['o'] + ((b == 3) + '_')[d];

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

(g) = 
 (e)['c'] + 
 (e)['o'] + 
 (a + '_')[d] + 
 ((a == 3) + '_')[b] + 
 ((e) + '_')[(b) + (b)] + 
 ((b == 3) + '_')[d] + 
 ((b == 3) + '_')[(b) - (d)] + 
 (e)['c'] + ((e) + '_')[(b) + (b)] + 
 (e)['o'] + ((b == 3) + '_')[d];

Похоже, этот оператор пытается построить строку, потому что мы видим, что первое, что они объединяют, - это e['c'], который является символом 'c'. Таким образом, мы можем заменить все переменные и получить такой результат.

(g) = 
 (e)['c'] +                             // 'c'
 (e)['o'] +                             // 'o'
 (a + '_')[d] +                         // 'n'
 ((a == 3) + '_')[b] +                  // 's'
 ((e) + '_')[(b) + (b)] +               // 't'
 ((b == 3) + '_')[d] +                  // 'r'
 ((b == 3) + '_')[(b) - (d)] +          // 'u'
 (e)['c'] +                             // 'c'
 ((e) + '_')[(b) + (b)] +               // 't'
 (e)['o'] +                             // 'o'
 ((b == 3) + '_')[d];                   // 'r'
// 'c' + 'o' + 'n' + 's' + 't' + 'r' + 'u' + 'c' + 't' + 'o' + 'r'
// 'constructor'

Таким образом, эта строка кода просто создает перемешивающий 'constructor', а затем назначает его новой переменной с именем 'g'.

Переходим к следующему утверждению, у нас очень короткая, но интересная строка кода.

(e)['_'] = (o ^ _ ^ o)[g][g];

Итак, мы знаем, что свойство '_' не существует в переменной объекта e, поэтому мы можем сделать вывод, что эта строка кода создает и присваивает новое значение этому свойству. Но это странное значение, потому что мы уже проанализировали ранее, что улыбающееся лицо вернет 3, а g будет равно 'constructor'. Таким образом, истинное значение нашей линии равно

(e)['_'] = 3['constructor']['constructor']

Так что же конструктор? По этой ссылке object['constructor'] будет

Возвращает ссылку на функцию-конструктор Object, создавшую объект-экземпляр. Обратите внимание, что значение этого свойства является ссылкой на саму функцию, а не на строку, содержащую имя функции. Значение доступно только для чтения для примитивных значений, таких как 1, true и "test".

Таким образом, вы можете использовать object['constructor'] для создания нового экземпляра этого типа объекта.

Возникает интригующий вопрос: «Что такое конструктор конструктора объекта?»

Каждый конструктор класса в JavaScript - это функция.

Таким образом, конструктор конструктора объекта является конструктором функции, а каждая функция в JavaScript является объектом Function. См. Первое предложение в разделе описания на этом сайте, чтобы узнать больше.

Объект Function является глобальным объектом и согласно MDN web docs

Function конструктор создает новый объект Function. Вызов конструктора напрямую может создавать функции динамически, но страдает такими же проблемами безопасности и производительности, как eval.

При дальнейшем чтении вы обнаружите, что это немного похоже на функцию eval, за исключением того, что она создает анонимную функцию, но не вызывает ее.

Вернемся к основной причине, по которой мы проводим все эти исследования, - нашей короткой строчке кода.

(e)['_'] = (o ^ _ ^ o)[g][g];

Это означает, что свойство _ объектной переменной e теперь содержит ссылку на конструктор объекта Function.

Наша следующая строка кода немного короче первой, но не волнуйтесь, это просто еще один код построения строки.

(f) = ((b == 3) + '_')[d] + (e).i + ((e) + '_')[(b) + (b)] + ((b == 3) + '_')[o ^ _ ^ o - d] + ((b == 3) + '_')[d] + (a + '_')[d];

Конечный результат будет

(f) = 
 ((b == 3) + '_')[d] +                       // 'r'
 (e).i +                                     // 'e'
 ((e) + '_')[(b) + (b)] +                    // 't'
 ((b == 3) + '_')[o ^ _ ^ o - d] +           // 'u'
 ((b == 3) + '_')[d] +                       // 'r'
 (a + '_')[d];                               // 'n'
// 'r' + 'e' + 't' + 'u' + 'r' + 'n'
// 'return'

Итак, теперь мы создали новую переменную с именем f со строкой 'return' внутри нее.

Последние пять строк нашего кода этапа 2.

(b) += (d);
(e)[f] = '\\';
(e).j = (e + b)[o ^ _ ^ o - (d)];
(obo) = (a + '_')[c ^ _ ^ o];
(e)[g] = '\"';

Понятно, что первая строка увеличит переменную b на значение d, которое равно 1. Теперь b будет содержать значение 4.

Следующая строка примет строку, полученную нами на предыдущем шаге, которая 'return' будет использоваться в качестве объекта свойства e, и ее значение будет строкой '\\' или, точнее, символом \.

В этой строке код продолжает добавлять еще одно свойство к объекту e, и его значение равно

(e).j = (e + b)[o ^ _ ^ o - (d)]; // -> (e).j = '[object Object]4'[2] = 'b'

Затем он создает новую переменную с именем obo для хранения символа 'u'.

(obo) = (a + '_')[c ^ _ ^ o]; // -> (obo) = 'undefined_'[0] = 'u'

Затем e продолжает добавляться новое свойство в этой строке.

(e)[g] = '\"'; // -> (e)['constructor'] = '\"'

И это конец нашего этапа 2. Теперь мы можем переписать наш код, и вот как он выглядит после перезаписи.

Последняя стоящая линия

Сейчас мы находимся на завершающей стадии нашего процесса анализа. Только одна последняя строка кода, которая также является самой важной строкой из всех.

(e)['_']((e)['_'](f + (e)[g] + (e)[f] + (d) + (b) + (d) + (e)[f] + (d) + ((b) + (d)) + (b) + (e)[f] + (d) + (b) + ((b) + (d)) + (e)[f] + (d) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + ((o ^ _ ^ o) - (d)) + (e)[f] + (d) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + (b) + (e)[f] + ((b) + (d)) + (c ^ _ ^ o) + (e)[f] + (b) + ((o ^ _ ^ o) - (d)) + (e)[f] + (d) + ((b) + (d)) + (c ^ _ ^ o) + (e)[f] + (d) + ((b) + (d)) + (d) + (e)[f] + (b) + ((o ^ _ ^ o) - (d)) + (e)[f] + ((b) + (d)) + (d) + (e)[g])(d))('_');

Для начала разделим их на части.

(e)['_'](
 (e)['_'](
  f + (e)[g] + (e)[f] + (d) + (b) + (d) + (e)[f] + (d) + ((b) + (d)) + (b) + (e)[f] + (d) + (b) + ((b) + (d)) + (e)[f] + (d) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + ((o ^ _ ^ o) - (d)) + (e)[f] + (d) + ((o ^ _ ^ o) + (o ^ _ ^ o)) + (b) + (e)[f] + ((b) + (d)) + (c ^ _ ^ o) + (e)[f] + (b) + ((o ^ _ ^ o) - (d)) + (e)[f] + (d) + ((b) + (d)) + (c ^ _ ^ o) + (e)[f] + (d) + ((b) + (d)) + (d) + (e)[f] + (b) + ((o ^ _ ^ o) - (d)) + (e)[f] + ((b) + (d)) + (d) + (e)[g]
  )(d))
('_');

Как видите, e['_'] оборачивает весь код внутри себя, а e['_'] - это конструктор объекта Function, поэтому мы знаем, что эта строка пытается динамически создать функцию и выполнить ее. Мы также знаем, что конструктор объекта Function принимает строку, чтобы использовать ее в качестве тела функции, поэтому код внутри него должен строить строку. Используя консоль браузера, мы видим, что внутренний код строит строку

return"\141\154\145\162\164\50\42\150\151\42\51"

Что значит "\141\154\145\162\50\42\150\151\42\51"?

На самом деле это восьмеричная escape-строка. Вставив его прямо в консоль, мы можем получить его истинное значение, которое

alert("hi")

Давайте перестроим код

(e)['_']( (e)['_']( "return \"alert(\"hi\")\" ) (d)) ('_');

Мы можем переписать внутреннюю функцию на эту

(function(){ return "alert(\"hi\")"; })(d)

Таким образом, после того, как функция построена, она вызывается с параметром d, и строка alert(\"hi\") будет передана конструктору outter Function, который создаст функцию и будет использовать эту строку в качестве тела функции. После создания функции она вызовет функцию с использованием символа '_', и именно так выполняется наш код.

Дело закрыто

Так работает обфускатор aaencode JS, и на его анализ уйдет много времени, однако нет ничего невозможного. Кроме того, я хочу поблагодарить Йосуке Хасегаву за создание этого удивительного обфускатора, и я надеюсь, что после прочтения вы, ребята, найдете несколько интересных вещей, которые можно сделать с JS, и, пожалуйста, оставьте комментарий, если у вас есть какие-либо вопросы или комплименты :))

До свидания и счастливого Рождества.