Ты, вероятно, обращал внимание на забавный эффект: как только нуб выучит какой‑нибудь простецкий язык в достаточной степени, чтобы самому писать скрипты, его охватывает всепоглощающее чувство собственного величия, переходящее в желание продавать свои поделки окружающим. Этому обычно сопутствует опасение, как бы эту бесценную интеллектуальную собственность не украли. Ничего плохого в этом нет, поскольку такой страх дает работу целому сегменту программистов, пишущих защиты для скриптов разной степени упоротости.
Я уже писал статьи про подобные защиты, разработанные на JavaScript, Python, PHP и даже AutoIT. Сегодня у меня наконец‑то дошли руки до темы защиты экселевских VBA-скриптов.
Недавно я наткнулся на известный в очень узких кругах компилятор DoneEx VBA Compiler. Он позиционируется ни много ни мало, как полноценный компилятор Excel VBA в нативный код со встроенной защитой. Сразу предупреждаю: это поделие достаточно кривое — лично мне, чтобы скомпилировать на нем хоть какой‑то более‑менее работоспособный пример, понадобилось гораздо больше времени, чем разобраться, как снимать саму защиту. Однако ее описание изобилует громкими претенциозными заявлениями вроде «код VBA не может быть скопирован или восстановлен и имеет самый высокий уровень защиты от пиратства». Давай попробуем оспорить это утверждение.
К слову сказать, эти ребята еще и «компилятор» электронных книг XLS в EXE-модуль запилили, который, правда, без установленного Excel не запускается, но это совсем другая история, которой мы сегодня касаться не будем.
Итак, предположим, что нам в руки попал необычный экселевский файл. К нему прилагается одна (или две) DLL-библиотека с тем же именем, но возможны суффиксы *_64
или *_32
в зависимости от разрядности. Если натравить на них Detect It Easy, программа показывает компилятор MinGW(
, однако ординалы из них экспортируются довольно подозрительные.

Попробуем открыть защищенный документ. На экране появляется весьма раздражающее окно с бегущим ползунком, а затем выскакивает предложение о регистрации.

Пока оно висит, откроем наш любимый отладчик x64dbg и приаттачимся к процессу Excel. Принудительно прервав его выполнение, мы видим занятный стек вызовов.

Из него как на ладони видна последовательность вызовов, порождающих это окно. Вначале непосредственно из VBA-кода вызывается функция SetThisWorkbook
, экспортируемая из нашей скомпилированной библиотеки testvba1_xlsm_64.
. Эта функция вызывает функцию DummyFunc05
некоей загадочной библиотеки cbinrtl.
, отсутствующей в каталоге макроса и вообще непонятно откуда взявшейся. А уже она выкидывает окно предупреждения, реализованное через функцию DialogBoxIndirectParamW
.
При детальном рассмотрении мы обнаруживаем эту библиотеку в подкаталоге v6n3vk66ej
(название при каждом вызове случайное) временной папки Windows. Скомпилированный модуль testvba1_xlsm_64.
сохраняет ее туда из соответствующего собственного ресурса при вызове SetThisWorkbook
, а затем подчищает за собой при отработке. Собственно, скомпилированный модуль практически целиком и состоит из библиотеки cbinrtl.
, хранящейся в нем в явном незашифрованном виде, так же как и в самом модуле компилятора vbaclr4e.
, от которого ее и получает. Это дает надежду на убиение двух зайцев патчем файла testvba1_xlsm_64.
— можно патчить код и библиотеки testvba1_xlsm_64.
, и порождаемой ею библиотеки cbinrtl.
.
Попробуем это реализовать. Сразу напрашивается гипотеза, что защита сосредоточена в функции DummyFunc05
, — ведь именно ее вызов и выводит сообщение о незарегистрированной версии. Однако ее нельзя просто так взять и закоротить — Excel при этом падает с ошибкой. При ближайшем рассмотрении мы видим ее вызовы в IDA. Почему так происходит? Эта функция заполняет некую жизненно важную для программы структуру qword_62FC9180
.

Значит, придется копать вглубь кода DummyFunc05
, благо основные вызовы у нас уже размечены на стеке. Довольно быстро мы натыкаемся на развилку в коде cbinrtl.
, которая ведет к заполнению структуры qword_62FC9180
(на следующем скриншоте она обозначена как a2
, поскольку передается вторым параметром в процедуру).

Функция sub_180047A20
в незарегистрированной версии выкидывает окна c предупреждениями, однако, если скомпилировать модуль без защиты, эта функция просто возвращает 1 без лишних слов. Если в отладчике закоротить ее на return
, все тоже работает безо всяких предупреждений, но при попытке патча модуля testvba1_xlsm_64.
нас ждет сюрприз. Находим во вложенном в файл testvba1_xlsm_64.
модуле cbinrtl.
место, соответствующее sub_180047A20
, и патчим его нужным образом.

Однако патченный файл упорно отказывается загружаться, хотя, как я уже говорил, при патче непосредственно в отладчике все работает корректно. Налицо встроенный контроль целостности. Попробуем его открутить.
Первое, что бросается в глаза, — проверка 128-битного хеша SHA-1 непосредственно перед загрузкой cbinrtl.
. Обрати внимание на верхнюю часть скриншота — загрузка модуля происходит только тогда, когда функция sub_62FC28D4
дает добро.

Внутри она реализована так.

Функция sub_62FC27F7
читает весь модуль, sub_62FC2095
считает его хеш, который затем сравнивается с тестовым значением при помощи memcmp (его возвращает sub_62FC1720
). Эту проверку легко можно закоротить, однако ей дело не ограничивается. Если мы это сделаем, то модуль cbinrtl.
загружается, патченная функция DummyFunc05
корректно отрабатывает, однако в самом конце SetThisWorkbook
вылетает по эксепшену при вызове некоего безымянного callback из того же cbinrtl.
.

В непатченном варианте указатель на функцию qword_62FC90B0
содержит совершенно другой адрес, который отрабатывает нормально. Помнишь, в начале нашего повествования я упоминал некую жизненно важную структуру qword_62FC9180
, заполняемую в DummyFunc05
? Она как раз и содержит этот адрес и, если модуль пропатчен, заполняется некорректно. Разработчики решили поумничать и максимально запутать этот путь, но мы хитрее их и поэтому поищем обходную тропинку.
Нам известно, что существует как минимум еще одна проверка целостности, помимо sub_62FC28D4
, которая заново перечитывает файл cbinrtl.
(все данные, считанные функцией sub_62FC28D4
, остаются локально внутри нее). Поэтому мы сразу по отработке sub_62FC28D4
ставим точку останова на ядерную функцию ReadFile
, и — о чудо! — она тут же срабатывает на новом чтении файла cbinrtl.
. Снова смотрим на стек вызовов.

Очень интересно: выходит, на этот раз новорожденная cbinrtl.
перепроверяет сама себя на невинность. Двигаясь по стеку вызовов вверх, мы обнаруживаем саму процедуру подсчета хеша.

На скриншоте функция cbinrtl.
возвращает в регистре RAX
адрес указателя на 512-битный хеш Whirlpool от модуля cbinrtl.
(выделен в дампе). В этом случае запатчить проверку не так просто, в отличие от предыдущего случая, где хеши тупо сравнивались при помощи memcmp
. Разработчики решили заморочиться по максимуму, проделывая над полученным хешем множество мудреных манипуляций, которые в итоге и приводят к неочевидному перемешиванию данных в структуре qword_62FC9180
.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее