Каждый разработчик 1С рано или поздно сталкивается с ситуацией, когда при открытии модуля внешней обработки или конфигурации вместо привычного и стройного программного кода открывается хаотичный набор символов, подчеркиваний, странных циклов и переходов goto. Первая реакция — это недоумение или подозрение на повреждение файла. Однако на самом деле перед нами классический пример обфусцированного (запутанного) кода.
В этой статье мы подробно разберем, для чего применяется обфускация в среде 1С, детально проанализируем математический алгоритм, который скрывается за нечитаемым кодом из примера, и по шагам восстановим исходный человекочитаемый текст программы.
Обфускация — это процедура приведения исходного кода программы к виду, который сохраняет полную работоспособность для платформы (компьютера), но становится крайне сложным для анализа, понимания и модификации человеком. Основные цели обфускации в мире 1С:
Важно понимать, что обфускация не шифрует код в бинарный вид. Код остается текстовым, и платформа 1С компилирует его стандартным образом. Следовательно, теоретически любой обфусцированный код можно восстановить до исходного логического состояния.
Давайте подробно рассмотрим фрагмент кода, который вызвал вопросы:
_0=СтрЗаменить(a_,_o__,__o__); a_=_0; _0=""; ___o_=_0;
для p=1 по 1 цикл
___o_=___o_+символ(число(сред("1063",(p-1)*4+1,4))-число(символ(кодсимвола("8",p))));
конеццикла;
_0=""; p_=_0;
для p__=1 по 1 цикл
p_=p_+символ(число(сред("0085",(p__-1)*4+1,4))-число(символ(кодсимвола("5",p__))));
конеццикла;
~87:; _0=0; y__=_0; goto ~88;
~88:; _0=СтрЗаменить(a_,___o_,p_); a_=_0; goto ~90;
Проанализируем основные приемы, которые применил автор (или специализированная программа-обфускатор) для запутывания:
_0, a_, _o__, __o__, ___o_, p_, p__ основано на оптической схожести символа подчеркивания и букв «о» (как латинских, так и русских). Это делает практически невозможным быстрое чтение кода «глазами» и усложняет автозамену.Для p=1 по 1 Цикл ... КонецЦикла; выполняет ровно одну итерацию. Она введена исключительно для усложнения структуры и обхода простейших статических анализаторов.~87:;, ~88:; и операторов безусловного перехода goto искусственно разрывает линейную структуру программы. Декомпиляторам и человеку становится трудно понять реальную последовательность выполнения команд.Давайте выясним, как программа «собирает» зашифрованные строки. Проанализируем первый цикл:
___o_ = "";
для p=1 по 1 цикл
___o_=___o_+символ(число(сред("1063",(p-1)*4+1,4))-число(символ(кодсимвола("8",p))));
конеццикла;
Поскольку цикл выполняется ровно один раз при p = 1, мы можем избавиться от цикла и подставить значение переменной напрямую:
___o_ = символ(число(сред("1063", (1-1)*4+1, 4)) - число(символ(кодсимвола("8", 1))));
Разберем это выражение изнутри наружу по шагам:
Сред() для выделения подстроки из шифра: (1-1)*4+1 = 1. Длина подстроки равна 4. Следовательно, выражение сред("1063", 1, 4) вернет нам строку "1063".число("1063") возвращает число 1063.кодсимвола("8", 1). Данная функция возвращает числовой код Unicode для первого символа строки "8". Код символа "8" в кодировке Windows/Unicode равен 56.символ(56), которая преобразует код обратно в символ. Мы получаем строковое представление цифры "8".число("8") возвращает число 8.1063 - 8 = 1055.символ(1055) возвращает символ Unicode с кодом 1055. В таблице символов Unicode код 1055 соответствует русской прописной букве «П».В итоге, в результате выполнения этого сложного нагромождения функций переменная ___o_ принимает значение "П".
Теперь проанализируем второй аналогичный цикл:
для p__=1 по 1 цикл
p_=p_+символ(число(сред("0085",(p__-1)*4+1,4))-число(символ(кодсимвола("5",p__))));
конеццикла;
Подставим p__ = 1:
сред("0085", 1, 4) возвращает строковое значение "0085".число("0085") дает число 85.кодсимвола("5", 1) возвращает 53 (код символа "5").символ(53) возвращает строку "5".число("5") преобразует строку в число 5.85 - 5 = 80.символ(80) возвращает символ Unicode с кодом 80. Это латинская прописная буква «P» (Пи).Таким образом, переменная p_ после выполнения цикла принимает значение "P".
Если продолжить деобфускацию следующих участков кода, аналогичным образом мы расшифруем и остальные символы, из которых в итоге будет сложено осмысленное слово или строка (например, приветствие или команда).
Давайте выполним ручную деобфускацию предоставленного фрагмента, заменив запутанные переменные на осмысленные имена, а вычисляемые выражения — на константы. Проанализируем исходный фрагмент:
_0=СтрЗаменить(a_,_o__,__o__); a_=_0; _0=""; ___o_=_0;
// ... здесь были расшифрованные нами циклы ...
~87:; _0=0; y__=_0; goto ~88;
~88:; _0=СтрЗаменить(a_,___o_,p_); a_=_0; goto ~90;
Сделаем следующие логические подстановки:
a_ — это исходная обрабатываемая строка. Назовем ее ОбрабатываемаяСтрока._o__ и __o__ — это символы поиска и автозамены. Назовем их СимволПоиска1 и СимволЗамены1._0, которая используется для временного хранения результатов, будем присваивать значения напрямую.___o_, значение которой мы вычислили как "П", заменим прямой строковой константой.p_, значение которой мы вычислили как латинскую "P", также заменим константой.goto и неиспользуемые метки, выстроив код линейно.В результате рефакторинга наш код принимает следующий аккуратный и абсолютно понятный вид:
// Выполняем первичную замену в исходной строке
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, СимволПоиска1, СимволЗамены1);
СимволП = "П";
СимволР = "P"; // латинская P
// Инициализируем служебную переменную y__ (в исходном коде y__ = 0)
СлужебныйФлаг = 0;
// Заменяем в строке русскую "П" на латинскую "P"
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, СимволП, СимволР);
Как мы видим, за устрашающим набором символов скрывался простейший алгоритм замены символов в строке при помощи стандартной функции 1С СтрЗаменить().
Если обфусцированный модуль содержит тысячи строк, ручной разбор каждого символа займет недели. Для автоматизации процесса деобфускации разработчики используют специализированные подходы:
Выполнить(). Можно написать собственную небольшую обработку на 1С, которая будет считывать текст обфусцированного модуля как строку, находить регулярными выражениями математические блоки вида символ(число(...)...), передавать их в функцию Выполнить() и автоматически заменять исходные формулы на полученные строковые литералы.Для ... по 1, убирать пустые переходы goto и переименовывать переменные по порядку (например, Переменная1, Переменная2).Несмотря на то, что полностью восстановить оригинальные имена переменных и комментарии автора невозможно (эта информация бесследно стирается при обфускации), автоматический анализ позволяет вернуть коду структуру, пригодную для чтения и дальнейшей поддержки.