• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Декомпилируем и деобфусцируем скрипт, снимаем лимиты без пересборки проекта

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,167
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Давай разберемся, как вытащить исходники из «скомпилированного» скрипта AutoIt, обойти обфускацию и снять триальные ограничения без пересборки проекта. Я покажу, как работает декомпилятор myAutToExe, где скрыты строки и как подловить ключевую переменную, чтобы обмануть проверку регистрации, — по шагам, от реверса до финального патча.
Сегодня мы будем сочетать приятное с полезным, а именно погрузимся в две наши любимые темы — разные экзотические интерпретаторы и деобфускация. Я уже достаточно злословил на тему того, сколько в последнее время понапридумывали средств разработки для чайников, чтобы каждый школьник после просмотра ознакомительного тиктоковского ролика мог закодить свою первую программу.

Наверное, ты удивился, почему я до сих пор не упоминал такое распространенное условно‑бесплатное поделие, как Для просмотра ссылки Войди или Зарегистрируйся? Судя по всему, он задумывался как мечта любой дрессированной обезьяны — ничего не надо программировать, просто вози мышкой по экрану и тыкай по кнопкам, все твои действия автоматически скриптуются, причем совершенно безвозмездно, то есть даром. Для тех, кто чуть поумнее, наскоро придумали урезанную версию бейсика для добавления в скрипты простенькой логики.


Дальше, по мере роста популярности технологии, ее создатели пошли по накатанной схеме — придумали механизм компоновки из простецких текстовых скриптов исполняемых файлов, и вот уже целые коммерческие проекты пишутся исключительно на AutoIt. Причем изначально в политике конторы сохранялось некое фриварно‑опенсорсное фрондерство: пакет, помимо «компилятора» Aut2Exe, преобразовывающего скрипт в исполняемый модуль, содержал «декомпилятор», проворачивающий фарш назад, — Exe2Aut.

Однако с версии 3.2.5.1 эти вредные веяния упразднили и теперь грозят всяческими карами наглецам, посмевшим заикнуться Для просмотра ссылки Войди или Зарегистрируйся. Конечно же, угрозами дело не ограничилось — была предпринята и программная защита исходного кода (местами довольно забавная): прекомпиляция исходников в байт‑код, который потом упаковывали, зашифровывали и даже навешивали сверху UPX, а кроме того, использовали весьма примитивную обфускацию. Естественно, такая защита эффективна исключительно от честных людей, точнее, от школьников, незнакомых с основами реверс‑инжиниринга.

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

Для просмотра ссылки Войди или Зарегистрируйся
Exeinfo PE более конкретно называет версию — AutoIt v3.3.13.15. Пакет работает, но без регистрации существует ряд раздражающих ограничений: при определенных действиях программа выдает сообщения вида «Unregistered version can not ..., to register?» с предложением, понятное дело, зарегистрироваться.

Для просмотра ссылки Войди или Зарегистрируйся
Лицензионный ключ программа запрашивает на сайте разработчика, там же этот ключ и валидирует, что дополнительно характеризует авторов с не очень хорошей стороны. Раз так, попробуем сами вытащить исходники из исполняемого файла. Поскольку версия нашего AutoIt выше 3.2.5.1, штатного декомпилятора для нее не существует.

По счастью, имеются и сторонние декомпиляторы, и деобфускаторы AutoIt, и Exeinfo PE даже указывает нам на них: это Для просмотра ссылки Войди или Зарегистрируйся и AU3Stripper v19.x 2019 or myAutToExe 2.15 CW2K@gmx.de.

Первая ссылка уже мертва, а со второй явно что‑то не то, однако проект myAutToExe вполне существует и поддерживается Для просмотра ссылки Войди или Зарегистрируйся. Можно нагуглить еще, к примеру, питоновский скрипт Для просмотра ссылки Войди или Зарегистрируйся, но нам вполне подходит и myAutToExe — он самый актуальный, да еще и со множеством заявленных дополнительных фич, поэтому им и воспользуемся.

Действительно, этот инструмент восстановил нам исходный au3-файл, сэкономив кучу времени и нервов на разбор виртуальной машины и системы команд AutoIt, которых мы на этот раз касаться не будем. Возможно, у нас когда‑нибудь появится задача, для которой это будет необходимо, но пока что для дальнейших действий вполне достаточно и восстановленного кода. Желающие могут самостоятельно углубиться в эту тему, более детально исследовав исходники приведенных выше гитхабовских проектов.

У нас же, даже несмотря на наличие восстановленного кода, веселье только начинается. При первом взгляде становится очевидно, что полученный код совершенно нечитаем.

Для просмотра ссылки Войди или Зарегистрируйся
Начисто отсутствуют имена идентификаторов, численные и строковые константы. Судя по обилию функций EXECUTE(), вероятно, зашифрованы особо критичные фрагменты кода — налицо явная обфускация с целью осложнить нам жизнь. Причем обфускация какая‑то самописно‑кастомная, поскольку в использованном нами myAutToExe деобфускация известных инструментов заявлена прямо «из коробки». В readme.txt даже перечислен список поддерживаемых обфускаторов:

Код:
Supported Obfuscators:
'Jos van der Zande AutoIt3 Source Obfuscator v1.0.14 [June 16, 2007]' ,
'Jos van der Zande AutoIt3 Source Obfuscator v1.0.15 [July 1, 2007]' ,
'Jos van der Zande AutoIt3 Source Obfuscator v1.0.20 [Sept 8, 2007]' ,
'Jos van der Zande AutoIt3 Source Obfuscator v1.0.22 [Oct 18, 2007]' ,
'Jos van der Zande AutoIt3 Source Obfuscator v1.0.24 [Feb 15, 2008]' ,
'EncodeIt 2.0' and
'Chr() string encode'

Причем содержащиеся в примерах обфусцированные файлы этим самым Jos van der Zande AutoIt3 Source Obfuscator до боли напоминают наш код, но что‑то неуловимое мешает myAutToExe корректно его детектировать и деобфусцировать. Не будем искать ответ на вопрос, что именно и как починить myAutToExe, — у нас сейчас есть конкретная цель, и нет смысла отвлекаться на побочные квесты. Попробуем придать программе читабельности своими руками.

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

Вдобавок умные люди уже давно занимаются делом деобфускации скриптов AutoIt и Для просмотра ссылки Войди или Зарегистрируйся об этом Для просмотра ссылки Войди или Зарегистрируйся. В общем, как говорится, глаза боятся, а руки делают.

Начнем с восстановления текстовых строк. По обилию в восстановленном коде присваиваний выражений вида A2E00001A18($OS[...]) можно предположить, что строковые константы каким‑то образом закодированы через эти функции и массив. Находим функцию A2E00001A18 в самом конце кода:

Код:
FUNC A2E00001A18($A2E00001A18)
  LOCAL $A2E00001A18_
  FOR $X=1 TO STRINGLEN($A2E00001A18)STEP 2
    $A2E00001A18_&=CHR(DEC(STRINGMID($A2E00001A18,$X,2)))
  NEXT
  RETURN $A2E00001A18_
ENDFUNC

Ничего сложного, типичная криптография для младших школьников: каждый символ закодирован двумя Hex-символами своего 8-битного шестнадцатеричного кода. Осталось понять, как формируется огромный (несколько тысяч элементов) массив исходных строк для этой функции $OS[]. Если глянуть на предыдущий скриншот, то становится очевидно, что его заполняет функция A2E00001A18_(), тоже расположенная в конце кода.

Для просмотра ссылки Войди или Зарегистрируйся

В ней также нет ничего сложного: она зачем‑то целых пять раз подряд сохраняет в файл временного каталога со случайным названием (генерируемым функцией a2e00001a18x_()) огромный массив текстовых данных, захардкоженный в предшествующей функции _igrf4d27dqc(). Немного смущают вот эти «зашифрованные» строки кода:

Global $a2e00001a18, $os = Execute(BinaryToString("0x457865637574652842696E617279746F737472696E672827...
И вот такие:

Execute(BinaryToString("0x457865637574652842696E617279746F737472696E672827307834353738363536333735373...
Первая из них заполняет искомый массив $OS, но, судя по использованию функции BinaryToString(), других криптоалгоритмов автор обфускатора пока не выучил, поэтому пишем простенькую программку на шарпе и раскодируем первую строку:

Код:
string newline = "";
for (int j = 0; j < sline.Length; j += 2)
{
    string charcode = sline.Substring(j, 2);
    try
    {
        byte bt = Convert.ToByte(charcode, 16);
        newline += (char)(bt);
    }
    catch (Exception e)
    { break; }
}

На выходе вместо нормального кода мы снова видим конструкцию Execute(BinaryToString("0x..."), под ней — еще одну такую же, из чего делаем вывод, что разработчик мало того что лишен фантазии, так еще и очень упорный. Однако мы все равно упорнее и на пятом или шестом шаге видим банальное чтение из свежесохраненного текстового файла, а затем — парсинг прочитанного на строки с разделителем I9O. Вторая строка еще проще: там под всеми BinaryToString скрывается закрытие файла, можно было и не париться с раскодировкой.

В итоге для получения массива исходных констант, а это именно он, мы всего‑навсего лепим из строк $CWB73PD1HFM исходный файл, раскодируем его, парсим с разделителем на массив строк, которые снова раскодируем. Затем в исходном скрипте в цикле делаем следующую замену:

script = script.Replace("A2E00001A18($OS[" + (i+1) + "]", """+newline+""");
На выходе получаем более‑менее читаемый код, по которому уже можно вести поиск строковых констант. Поэтому приступим к следующему этапу.

Даже не будем связываться с генерацией License code — серверную валидацию нам не победить, поэтому попробуем побороться с триальными ограничениями. Для начала ищем первое попавшееся сообщение о триальной лимитации:

GLOBAL $A020E505234="Unregistered version can not run the script by double-clicking, to register?")
По‑хорошему надо бы сделать еще одну серию замен имен переменных присвоенными константами, ну да и так сойдет. Находим первое использование $A020E505234:

Код:
...
$A2626105430=A1301903946()
IF $A2626105430<>NUMBER($A5FFDF04D54)THEN
  IF MSGBOX(NUMBER($A420E005246)+NUMBER($A0B0E102B48)+NUMBER($A130E204A06)+NUMBER($A5E0E301024),$A5E0E40554C,$A020E505234)=NUMBER($A360E600A00)THEN
    SHELLEXECUTE($A2F0E703700)
    EXIT
  ELSE
    EXIT
  ENDIF
...

Поиском по коду находим константу, с которой сравнивается $A2626105430:

$A5FFDF04D54=" 1 "
Вся конструкция напоминает проверку на регистрацию, которую выполняет функция A1301903946(), в зарегистрированной версии она возвращает 1. Точно так же поиском по коду мы находим функцию A1301903946 и ветку кода, которая исполняется в случае отсутствия правильной регистрации.

Для просмотра ссылки Войди или Зарегистрируйся

А вот теперь давай сделаем паузу и подумаем, куда мы пришли и что нам делать дальше. С одной стороны, мы вроде как задачу выполнили — нашли место для патча, ведь достаточно заменить выделенную строку return 1, и программа будет вести себя как навечно зарегистрированная.

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

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

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

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

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

К примеру, для того чтобы функция A1301903946() возвращала 1, нам достаточно поменять значение переменной $A3CC582494F с 0 на 1. В исходном коде переменной $A3CC582494F присваивается значение A2E00001A18($OS[9672]). Для модификации этого значения достаточно поменять элемент массива $OS[9672].

Легко проверить, что этот элемент в остальном коде больше нигде не используется (свидетельство того, что автор обфускатора прогулял занятие, посвященное оптимизации кода, или еще не доучился до него). То есть для регистрации программы нам достаточно в момент сохранения или загрузки файла, содержащего все константы, поменять в нем 9672-e значение с 0 на 1.

Попробуем это реализовать.

Для начала найдем место в коде программы, где выгружаемый файл констант полностью загружен и готов к патчу. Для этого нам все‑таки придется немного попользовать наш любимый отладчик x64dbg. Упомянутую процедуру мы проделывали в предыдущих статьях много раз — ставим точку останова на kernel32-функцию ReadFile и отслеживаем, какие именно данные читаются этой функцией. По счастью, наша программа читает совсем немного, и буквально через несколько вызовов мы натыкаемся на чтение файла констант порциями по 0x10000 байт.

Поднявшись чуть выше по стеку вызовов, мы находим место, где файл уже загружен полностью, и указатель на начало буфера его загрузки находится в [ebp+8].

Для просмотра ссылки Войди или Зарегистрируйся

Теперь надо разобраться, что именно нужно править в этом буфере. Вспоминаем, что данные в нашем файле представляют собой массив закодированных строк с разделителем I9O. Берем слитый из временного каталога файл констант и ищем в нем 9671-е вхождение разделителя при помощи простенькой программы на шарпе:

Код:
public static int IndexOfNth(string str, string value, int n)
{

    int startIndex = 0;
    int index = 0;

    for (int i = 0; i < n; i++)
    {
        index = str.IndexOf(value, startIndex);

        if (index == -1)
        {
            return -1;
        }

        startIndex = index + value.Length;
    }

    return index;
}
...
int pos = IndexOfNth(strings, "I9O", 9671);

Искомая позиция в файле выглядит так.

Для просмотра ссылки Войди или Зарегистрируйся

Идущая за ним последовательность 203020 представляет собой закодированную строковую константу 0, которая загружается в $OS[9672]. То есть, для того чтобы там стало значение 1, нам надо поправить байт по смещению 0x24972 c 0x30 на 0x31. Места в коде программы для этого, разумеется, нет, поэтому ищем в конце секции кода свободный кусок и делаем на него переход.

Для просмотра ссылки Войди или Зарегистрируйся

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

Для просмотра ссылки Войди или Зарегистрируйся

Запускаем программу — действительно, в окне регистрации она выглядит как зарегистрированная, и даже лицензионных ограничений нет.

Для просмотра ссылки Войди или Зарегистрируйся

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

С другой стороны, кто знает, где и когда нам может пригодиться полученный опыт? Возможно, при нынешних тенденциях развития кодинга когда‑нибудь через несколько лет AutoIt эволюционирует в мощную коммерческую среду разработки, вытесняющую нынешние низкоуровневые языки программирования, так же, как в свое время поднялись какие‑нибудь потенциально узкоориентированные Python и JavaScript.
 
Activity
So far there's no one here
Сверху Снизу