Как написать неплохой обфускатор

13 октября 2010

Для тех кто не в теме, обфускация — это когда у нас есть код программы, и мы хотим сделать этот код нечитаемым. Как правило, обфусцировать (или все-таки обфускировать?) пытаются разные скрипты (на javascript, php и тд), потому что если программа написана на компилируемом языке, можно не извращаться и распространять только бинарники. Производить обфускацию руками долго и неприятно, потому пишутся специальные программы — обфускаторы. Дальше речь пойдет о том, как их собственно делают.

В некоторых блогах пишут мол «смотрите — берем код на javascript, делаем escape(), а потом eval(unescape()), обфускатор готов!». Код конечно будет нечитаемым, вот только расшифровать его можно будет за две минуты. Притом мы получим исходный код в том состоянии, в каком он был до обфускации — с комментариями, отступами и тд.

В «нормальном» обфускаторе, который на выходе выдает в 99% случаев рабочий код, должен быть реализован полноценный синтаксический анализатор нужного нам языка программирования. Задача эта не то, чтобы нерешаемая, но можно пойти и более короткой дорогой, воспользовавшись регулярными выражениями. Это потребует от нас кое-какой дисциплины во время написания кода шифруемого приложения, зато весь обфускатор уложится в 20 строк.

Итак, пусть у нас есть код на JavaScript (можете взять его из этой или этой заметки). Что нужно с ним делать, чтобы он стал нечитаемым? В первую очередь, очевидно, избавиться от всех комментариев, отступов, лишний пробелов и так далее. Вот функции, выполняющие эти действия:

sub js_quick_compress {
  my($str) = @_;
  # многострочные комментарии
  $str =~ s/\/\*.*?\*\///gs;
  # однострочные комментарии
  $str =~ s/\/\/[^\r\n]+//gs;
 
  # удаляем лишние пробелы и переносы строк
  $str =~ s/%/%percent;/g;
  $str =~ s/("[^"]*?")/hide_spaces($1)/gse;
  $str =~ s/('[^']*?')/hide_spaces($1)/gse;
  $str =~ s/(var|function|return|new) /$1%space;/g;
  $str =~ s/\s//gs;
  $str =~ s/%space;/ /gs;
  $str =~ s/%percent;/%/gs;

  return $str;
}

sub hide_spaces {
  my ($str) = @_;
  $str =~ s/ /%space;/gs;
  return $str;
}

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

Потому мы поступаем следующим образом. Сначала находим в коде все знаки процента и заменяем их на строки «%percent;». Затем находим пробелы везде, где они должны находится — после слов var или function, в строках и так далее. Эти пробела заменяются на «%space;». Затем мы выдираем все оставшиеся пробелы, после чего заменяем все строки «%space;» на пробелы, а «%percent;» на проценты. Надеюсь, не нужно объяснять, зачем нужны такие сложности с временным кодированием пробелов и символа процента.

С комментариями разобрались. Чтобы сделать код еще более запутанным, нужно имена всех переменных, функций и аргументов функций заменить на что-то вроде aa, ab, ac. Но вот проблема — найти переменные и функции в коде будет посложнее, чем комментарии. Кроме того, вдруг какие-то переменные или функции мы не имеем права переименовывать? Например, они могут использоваться в коде, который писал другой программист. Или вовсе находится не в js-файле, а в html-страничке.

Решить эту проблему можно, давая функциям и переменным, подлежащим обфускации, имена, начинающиеся с определенного префикса. Например, «obf_» или «{PREFIX}». Да, на плечи бедного программиста ложится дополнительная работа, зато, повторюсь, не нужно писать синтаксический анализатор, да и обфускатор получается более гибким.

Учитывая вышесказанное, код обфускатора будет следующим:

use strict;
use List::Util qw/shuffle/;
use List::MoreUtils qw/uniq/;
use constant DEBUG => 0;

my $code;

# ...

if(!DEBUG) {
  $code =~ s/(\/\*\*\* $js_compress_begin \*\*\*\/.*?\/\*\*\* $js_compress_end \*\*\*\/)/js_quick_compress($1)/gsieo;

  # удаляем лишние комменты в HTML (кроме if IE)
  $code =~ s/<!--[^\[].*?-->//gs;
 
  # получаем имена всех переменных и функций с помощью префикса
  # также делаем замены в html-коде
  my @vars = $report =~ /(\{PREFIX\}[a-zA-Z0-9\_]+)/g;
  @vars = shuffle uniq @vars;
  my $new_name = "aa";
  for my $i(0..$#vars) {
    my $old_name = quotemeta($vars[$i]);
    $code =~ s/$old_name/$new_name/g;
    $new_name++;
  }
}

print $code;

Вы спросите, что это за js_compress_begin? Я решил, что удобнее будет подвергать обфускации не весь код, а только тот, что находится между /*** js_compress_begin ***/ и /*** js_compress_end ***/. Обфускатор может принимать на вход как чистый JavaScript, так и HTML+JavaScript вперемешку. Все остальное я, кажется, уже рассказал.

С программами на других языках программирования, я надеюсь, вы разберетесь своими силами. Поскольку большинство из них унаследовали свой синтаксис у языка Си (вот не мог Python не понтонутся, да?), принцип будет тот же. Хочу обратить ваше внимание на то, что в отличие от «нормального» обфускатора, описанный выше не может нормально обрабатывать код вроде такого:

  var mystr = "abc /* def */ cba"; // строчка def будет вырезана

В общем, главное — следить за именами переменных и помнить о моментах, вроде приведенного выше. Кстати, приятный побочный эффект от применения обфускатора заключается в том, что кода становится меньше. Собственно, передо мной в первую очередь стояла задача уменьшить объем кода, а уже потом сделать его нечитаемым. Если ваш проект страдает от лишнего веса .js-файлов, подумайте о том, чтобы прогнать их через обфускатор. См также мой пост про mod_gzip.

Если у вас есть желание доработать приведенный скрипт, можете добавить в него шифрование строковых и числовых данных. Числа шифровать легко — вместо 123 пишем 321-198, вместо 456 подставляем (1806 % 789)*2 и так далее. Чем больше операций, тем лучше. Со строками чуть интереснее. Нужно написать генератор функций шифрования и дешифрования строк. Генерируем штук 100 таких функций и делаем в коде замену «aaa» на obf_decrypt01(’9fa341′), «bbb» на obf_decrypt34(2182,6335,19354) и так далее.

Если у вас есть свои идеи по поводу обфускации кода, буду рад ознакомиться с ними в комментариях. Спасибо за внимание!

Дополнение: Набрел случайно на интересный онлайн-обфускатор JavaScript кода. Например, строчку

alert("Hello, JavaScript" );

он превращает вот в такую тарабарщину:

$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"") ...

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

Дополнение: Обратите также внимание на Closure Compiler — приложение и онлайн-версию.

Метки: , .


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