Что такое обфусцированный (нечитаемый) код в 1С и как его расшифровать?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Каждый разработчик 1С рано или поздно сталкивается с ситуацией, когда при открытии модуля внешней обработки или конфигурации вместо привычного и стройного программного кода открывается хаотичный набор символов, подчеркиваний, странных циклов и переходов goto. Первая реакция — это недоумение или подозрение на повреждение файла. Однако на самом деле перед нами классический пример обфусцированного (запутанного) кода.

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

Зачем нужна обфускация кода в 1С?

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

  1. Защита интеллектуальной собственности. Разработчик хочет защитить свои уникальные алгоритмы от несанкционированного копирования и тиражирования.
  2. Временная защита («до оплаты»). Часто фрилансеры передают заказчику рабочую обработку с обфусцированным кодом в качестве гарантии оплаты. После получения финального расчета код заменяется на открытый оригинал.
  3. Борьба с неквалифицированным вмешательством. Запутанный код снижает вероятность того, что сторонний программист или сам пользователь попытается «подправить» логику работы критически важного механизма, нарушив работоспособность системы.

Важно понимать, что обфускация не шифрует код в бинарный вид. Код остается текстовым, и платформа 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;

Проанализируем основные приемы, которые применил автор (или специализированная программа-обфускатор) для запутывания:

  1. Визуально схожие имена переменных. Использование имен вида _0, a_, _o__, __o__, ___o_, p_, p__ основано на оптической схожести символа подчеркивания и букв «о» (как латинских, так и русских). Это делает практически невозможным быстрое чтение кода «глазами» и усложняет автозамену.
  2. Скрытие строковых литералов (динамическое декодирование строк). Вместо явного объявления строк (например, "Привет" или "Пароль"), обфускатор динамически вычисляет каждый символ строки при помощи математических формул со сдвигом по таблице символов Unicode.
  3. Фиктивные циклы (Dummy Loops). Конструкция Для p=1 по 1 Цикл ... КонецЦикла; выполняет ровно одну итерацию. Она введена исключительно для усложнения структуры и обхода простейших статических анализаторов.
  4. Запутывание потока управления (Control Flow Obfuscation). Использование меток ~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-1)*4+1 = 1. Длина подстроки равна 4. Следовательно, выражение сред("1063", 1, 4) вернет нам строку "1063".
  2. Преобразуем полученную строку в число: число("1063") возвращает число 1063.
  3. Теперь разберем вычитаемую часть: кодсимвола("8", 1). Данная функция возвращает числовой код Unicode для первого символа строки "8". Код символа "8" в кодировке Windows/Unicode равен 56.
  4. Далее применяется функция символ(56), которая преобразует код обратно в символ. Мы получаем строковое представление цифры "8".
  5. Функция число("8") возвращает число 8.
  6. Вычитаем из левой части правую: 1063 - 8 = 1055.
  7. Функция символ(1055) возвращает символ Unicode с кодом 1055. В таблице символов Unicode код 1055 соответствует русской прописной букве «П».

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

Теперь проанализируем второй аналогичный цикл:


для p__=1 по 1 цикл 
    p_=p_+символ(число(сред("0085",(p__-1)*4+1,4))-число(символ(кодсимвола("5",p__))));
конеццикла;

Подставим p__ = 1:

  1. Выражение сред("0085", 1, 4) возвращает строковое значение "0085".
  2. Преобразование число("0085") дает число 85.
  3. Вычисляем вычитаемое: кодсимвола("5", 1) возвращает 53 (код символа "5").
  4. символ(53) возвращает строку "5".
  5. число("5") преобразует строку в число 5.
  6. Вычитаем: 85 - 5 = 80.
  7. Функция символ(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;

Сделаем следующие логические подстановки:

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


// Выполняем первичную замену в исходной строке
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, СимволПоиска1, СимволЗамены1);

СимволП = "П";
СимволР = "P"; // латинская P

// Инициализируем служебную переменную y__ (в исходном коде y__ = 0)
СлужебныйФлаг = 0; 

// Заменяем в строке русскую "П" на латинскую "P"
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, СимволП, СимволР);

Как мы видим, за устрашающим набором символов скрывался простейший алгоритм замены символов в строке при помощи стандартной функции 1С СтрЗаменить().

Как автоматизировать деобфускацию больших модулей?

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

  1. Использование функции Выполнить(). Можно написать собственную небольшую обработку на 1С, которая будет считывать текст обфусцированного модуля как строку, находить регулярными выражениями математические блоки вида символ(число(...)...), передавать их в функцию Выполнить() и автоматически заменять исходные формулы на полученные строковые литералы.
  2. Специализированные деобфускаторы. Существуют готовые скрипты и парсеры (например, написанные на языках Python или Go), которые умеют строить AST-дерево (абстрактное синтаксическое дерево) кода 1С, оптимизировать фиктивные циклы Для ... по 1, убирать пустые переходы goto и переименовывать переменные по порядку (например, Переменная1, Переменная2).

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

← На главную