- Регистрация
- 9 Май 2015
- Сообщения
- 1,480
- Баллы
- 155
Сегодня мы разберемся с тем, как устроена «непробиваемая» защита VBA-скриптов в Excel, предлагаемая компилятором DoneEx VBA Compiler. Мы посмотрим, как этот инструмент компилирует макросы в нативные DLL, какие трюки использует для контроля целостности, и шаг за шагом покажем, как обходить проверки и восстанавливать исходный код из скомпилированных модулей.
Ты, вероятно, обращал внимание на забавный эффект: как только нуб выучит какой‑нибудь простецкий язык в достаточной степени, чтобы самому писать скрипты, его охватывает всепоглощающее чувство собственного величия, переходящее в желание продавать свои поделки окружающим. Этому обычно сопутствует опасение, как бы эту бесценную интеллектуальную собственность не украли. Ничего плохого в этом нет, поскольку такой страх дает работу целому сегменту программистов, пишущих защиты для скриптов разной степени упоротости.
Я уже писал статьи про подобные защиты, разработанные на JavaScript, Python, PHP и даже AutoIT. Сегодня у меня наконец‑то дошли руки до темы защиты экселевских VBA-скриптов.
Недавно я наткнулся на известный в очень узких кругах компилятор . Он позиционируется ни много ни мало, как полноценный компилятор Excel VBA в нативный код со встроенной защитой. Сразу предупреждаю: это поделие достаточно кривое — лично мне, чтобы скомпилировать на нем хоть какой‑то более‑менее работоспособный пример, понадобилось гораздо больше времени, чем разобраться, как снимать саму защиту. Однако ее описание изобилует громкими претенциозными заявлениями вроде «код VBA не может быть скопирован или восстановлен и имеет самый высокий уровень защиты от пиратства». Давай попробуем оспорить это утверждение.
К слову сказать, эти ребята еще и , который, правда, без установленного Excel не запускается, но это совсем другая история, которой мы сегодня касаться не будем.
Итак, предположим, что нам в руки попал необычный экселевский файл. К нему прилагается одна (или две) DLL-библиотека с тем же именем, но возможны суффиксы *_64 или *_32 в зависимости от разрядности. Если натравить на них Detect It Easy, программа показывает компилятор MinGW(GCC: (GNU)), однако ординалы из них экспортируются довольно подозрительные.
Попробуем открыть защищенный документ. На экране появляется весьма раздражающее окно с бегущим ползунком, а затем выскакивает предложение о регистрации.
Пока оно висит, откроем наш любимый отладчик x64dbg и приаттачимся к процессу Excel. Принудительно прервав его выполнение, мы видим занятный стек вызовов.
Из него как на ладони видна последовательность вызовов, порождающих это окно. Вначале непосредственно из VBA-кода вызывается функция SetThisWorkbook, экспортируемая из нашей скомпилированной библиотеки testvba1_xlsm_64.dll. Эта функция вызывает функцию DummyFunc05 некоей загадочной библиотеки cbinrtl.dll, отсутствующей в каталоге макроса и вообще непонятно откуда взявшейся. А уже она выкидывает окно предупреждения, реализованное через функцию DialogBoxIndirectParamW.
При детальном рассмотрении мы обнаруживаем эту библиотеку в подкаталоге v6n3vk66ej (название при каждом вызове случайное) временной папки Windows. Скомпилированный модуль testvba1_xlsm_64.dll сохраняет ее туда из соответствующего собственного ресурса при вызове SetThisWorkbook, а затем подчищает за собой при отработке. Собственно, скомпилированный модуль практически целиком и состоит из библиотеки cbinrtl.dll, хранящейся в нем в явном незашифрованном виде, так же как и в самом модуле компилятора vbaclr4e.exe, от которого ее и получает. Это дает надежду на убиение двух зайцев патчем файла testvba1_xlsm_64.dll — можно патчить код и библиотеки testvba1_xlsm_64.dll, и порождаемой ею библиотеки cbinrtl.dll.
Попробуем это реализовать. Сразу напрашивается гипотеза, что защита сосредоточена в функции DummyFunc05, — ведь именно ее вызов и выводит сообщение о незарегистрированной версии. Однако ее нельзя просто так взять и закоротить — Excel при этом падает с ошибкой. При ближайшем рассмотрении мы видим ее вызовы в IDA. Почему так происходит? Эта функция заполняет некую жизненно важную для программы структуру qword_62FC9180.
Значит, придется копать вглубь кода DummyFunc05, благо основные вызовы у нас уже размечены на стеке. Довольно быстро мы натыкаемся на развилку в коде cbinrtl.dll, которая ведет к заполнению структуры qword_62FC9180 (на следующем скриншоте она обозначена как a2, поскольку передается вторым параметром в процедуру).
Функция sub_180047A20 в незарегистрированной версии выкидывает окна c предупреждениями, однако, если скомпилировать модуль без защиты, эта функция просто возвращает 1 без лишних слов. Если в отладчике закоротить ее на return 1, все тоже работает безо всяких предупреждений, но при попытке патча модуля testvba1_xlsm_64.dll нас ждет сюрприз. Находим во вложенном в файл testvba1_xlsm_64.dll модуле cbinrtl.dll место, соответствующее sub_180047A20, и патчим его нужным образом.
Однако патченный файл упорно отказывается загружаться, хотя, как я уже говорил, при патче непосредственно в отладчике все работает корректно. Налицо встроенный контроль целостности. Попробуем его открутить.
Первое, что бросается в глаза, — проверка 128-битного хеша SHA-1 непосредственно перед загрузкой cbinrtl.dll. Обрати внимание на верхнюю часть скриншота — загрузка модуля происходит только тогда, когда функция sub_62FC28D4 дает добро.
Внутри она реализована так.
Функция sub_62FC27F7 читает весь модуль, sub_62FC2095 считает его хеш, который затем сравнивается с тестовым значением при помощи memcmp (его возвращает sub_62FC1720). Эту проверку легко можно закоротить, однако ей дело не ограничивается. Если мы это сделаем, то модуль cbinrtl.dll загружается, патченная функция DummyFunc05 корректно отрабатывает, однако в самом конце SetThisWorkbook вылетает по эксепшену при вызове некоего безымянного callback из того же cbinrtl.dll.
В непатченном варианте указатель на функцию qword_62FC90B0 содержит совершенно другой адрес, который отрабатывает нормально. Помнишь, в начале нашего повествования я упоминал некую жизненно важную структуру qword_62FC9180, заполняемую в DummyFunc05? Она как раз и содержит этот адрес и, если модуль пропатчен, заполняется некорректно. Разработчики решили поумничать и максимально запутать этот путь, но мы хитрее их и поэтому поищем обходную тропинку.
Нам известно, что существует как минимум еще одна проверка целостности, помимо sub_62FC28D4, которая заново перечитывает файл cbinrtl.dll (все данные, считанные функцией sub_62FC28D4, остаются локально внутри нее). Поэтому мы сразу по отработке sub_62FC28D4 ставим точку останова на ядерную функцию ReadFile, и — о чудо! — она тут же срабатывает на новом чтении файла cbinrtl.dll. Снова смотрим на стек вызовов.
Очень интересно: выходит, на этот раз новорожденная cbinrtl.dll перепроверяет сама себя на невинность. Двигаясь по стеку вызовов вверх, мы обнаруживаем саму процедуру подсчета хеша.
На скриншоте функция cbinrtl.7FF9985145B0 возвращает в регистре RAX адрес указателя на 512-битный хеш Whirlpool от модуля cbinrtl.dll (выделен в дампе). В этом случае запатчить проверку не так просто, в отличие от предыдущего случая, где хеши тупо сравнивались при помощи memcmp. Разработчики решили заморочиться по максимуму, проделывая над полученным хешем множество мудреных манипуляций, которые в итоге и приводят к неочевидному перемешиванию данных в структуре qword_62FC9180.
Ты, вероятно, обращал внимание на забавный эффект: как только нуб выучит какой‑нибудь простецкий язык в достаточной степени, чтобы самому писать скрипты, его охватывает всепоглощающее чувство собственного величия, переходящее в желание продавать свои поделки окружающим. Этому обычно сопутствует опасение, как бы эту бесценную интеллектуальную собственность не украли. Ничего плохого в этом нет, поскольку такой страх дает работу целому сегменту программистов, пишущих защиты для скриптов разной степени упоротости.
Я уже писал статьи про подобные защиты, разработанные на JavaScript, Python, PHP и даже AutoIT. Сегодня у меня наконец‑то дошли руки до темы защиты экселевских VBA-скриптов.
Недавно я наткнулся на известный в очень узких кругах компилятор . Он позиционируется ни много ни мало, как полноценный компилятор Excel VBA в нативный код со встроенной защитой. Сразу предупреждаю: это поделие достаточно кривое — лично мне, чтобы скомпилировать на нем хоть какой‑то более‑менее работоспособный пример, понадобилось гораздо больше времени, чем разобраться, как снимать саму защиту. Однако ее описание изобилует громкими претенциозными заявлениями вроде «код VBA не может быть скопирован или восстановлен и имеет самый высокий уровень защиты от пиратства». Давай попробуем оспорить это утверждение.
К слову сказать, эти ребята еще и , который, правда, без установленного Excel не запускается, но это совсем другая история, которой мы сегодня касаться не будем.
Итак, предположим, что нам в руки попал необычный экселевский файл. К нему прилагается одна (или две) DLL-библиотека с тем же именем, но возможны суффиксы *_64 или *_32 в зависимости от разрядности. Если натравить на них Detect It Easy, программа показывает компилятор MinGW(GCC: (GNU)), однако ординалы из них экспортируются довольно подозрительные.

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

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

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

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

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

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

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

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

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

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

На скриншоте функция cbinrtl.7FF9985145B0 возвращает в регистре RAX адрес указателя на 512-битный хеш Whirlpool от модуля cbinrtl.dll (выделен в дампе). В этом случае запатчить проверку не так просто, в отличие от предыдущего случая, где хеши тупо сравнивались при помощи memcmp. Разработчики решили заморочиться по максимуму, проделывая над полученным хешем множество мудреных манипуляций, которые в итоге и приводят к неочевидному перемешиванию данных в структуре qword_62FC9180.
Источник: