Обновить

Защита программ без IF: ретро-анализ библиотеки TViorProtect (Delphi 7)

Уровень сложностиСредний
Время на прочтение15 мин
Охват и читатели6.7K
Всего голосов 10: ↑10 и ↓0+12
Комментарии5

Комментарии 5

Идея очень хороша, если бы не одна проблема... Все эти манипуляции с памятью процесса (смена rw на rx, например), запуском подпроцессов, множественные GetProcAddress, и прочие "вредоносные паттерны активности" вызовут панику любого приличного антивируса. Пакеры и распаковка исполняемых файлов в темп это вообще дикий ахтунг. 10 лет назад такие штуки еще худо-бедно прокатывали, сейчас даже банальный демон-обновлятор для софта (проверить наличие версии, скачать, проверить подпись, убить старый, запустить новый) и тот время от времени попадает в немилость антивиря.

сейчас особо и не нужна подобная привязка, все на сервере. Можно даже за содержимое frontend не переживать если server-side rendering

В большинстве случаев функции проверки возвращали дискретный результат (логическое “истина” или “ложь”)

Ибо в большинстве случаев и не нужно больше. Кто всерьез захочет сломать - сломает даже Denuvo. А кто попытается наугад поменять пару файлов и через 15 минут устанет - тем и такого хватит. Принцип чем больше инвестиций - тем меньше возврата.

Дата последнего запуска скрыто сохраняется в системной директории Windows в специальном формате (количество символов пробела в файле соответствует дате

  1. палится через тот же FileMon

  2. какой там MAX_PATH ?

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

> asm call Pointer(l); end;

Почему не JMP или PUSH + RET ?
Так ваша команда-убийца видна в стеке. Осталось проверить в отладчике последовательность вызовов, поставить брейкпойнт и перезапустить

>  Наличие в IAT таких функций, как “DeviceIoControl”, “GetVolumeInformation” или “EnumWindows”, однозначно указывает на механизмы привязки ... находит их адреса в оперативной памяти непосредственно во время выполнения.

Если список подозрительных функций есть, то можно поставить брейкпойнты и увидеть вызовы, не важно статическая или динамическая привязка (early/late binding). Более того, сам факт вызова БЕЗ нормального импорта сам по себе "однозначно указывает на наличие защиты". Костёр в ночи. Нормальная программа вызывала бы не пряча, в голову не пришло бы, значит этот вызов, которые прошёл мимо IAT, точно вызов от защиты. Вы по сути пометили все вызовы, какие идут от скучного RTL/VCL, а какие от интересной защиты. Одна из сложных проблем при отладке/взломе - найти среди тысячи вызовов в секунду 2-3 нужных - решена вами.

> “BITMAP_KFC” и “BITMAP_SRV2”. Использование имен, характерных для графических ресурсов

Точно, характерных? я бы наоборот стойку сделал на такое навязчивое "поверь, что я BITMAP"
Открываем в 7-zip архив C:\RAD Studio\9.0\bin\vcl160.bpl\.rsrc\1033\BITMAP\ - и видим
* совсем другие шаблоны названий
* совсем другую папку, вовсе не RCDATA

Вы якобы положили картинки - с ненормальным названием в ненормальное место. Вы их наоборот, выпятили наружу как что-то аномальное и подозрительное.

Если хочется спрятать DLL внутри картинки - так и стоило наверное сделать. Внутрь нормального BMP по смещению положить DLL вместо байтов картинки. Назвать её типа TMYSUPERBUTTON (использовать шаблон VCL, название UpperCase(ClassName), прятать листья в лесу).

Но еще лучше просто положить ваши DLL на TDataModule или TForm, тогда в списке ресурсов их вообще не будет видно. Причем даже писать ничего не надо, готовый компонент есть в RxLib/JediVCL

> Декодированный файл записывается во временную директорию ("%TEMP%") под динамически сгенерированным именем.

...что ловится в FileMon и ProcExp - вы только что показали взломщику, что именно надо разглядеть с лупой. Дальше, вероятно, можно будет сделать "табличный эмулятор" как для старых HASP: подставить DLL-прокси, которые будет логировать параметры и результаты функций. А потом сделать "попугая-отвечалку", который будет давать правильные ответы не понимая их смысла (да и зачем)...

...а вот у легальных покупателей могут появиться проблемы, если у них админы запретили исполняемые файлы/библиотеки в %TEMP%

> принудительное завершение работы ОС WinExit(EWX_SHUTDOWN or EWX_FORCE)

Ё моё... так хорошо начали, что реакцию защиты надо маскировать под случайные ошибки вычислений...
...и сделали ещё один костёр в ночи. Вешаем брейкпойнт ан WinExit, провоцируем срабатывания, и в готовом виде получаем название вредоносных атомов. Останется только следить за этими атомами и сбрасывать их значение например 4 раза в секунду...

Если уж вам хочется "убить винду" - хотя IMHO мстительность плохая черта для защиты - почему не использовать хорошо известную ошибку VCL - утечку атомов? создайте вы стопиццот атомов, чтобы таблица переполнилась и до перезагрузки ни одна другая VCL-программа запуститься не могла.

> аналоги стандартных функций ... “StrToInt64VP”

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

> к падению программы в случайных местах.

...а, так вы понимаете, вы сознательно помогаете конкурентам делать вам чёрный пиар? Ну... ок.

Сама-то идея подмены RTL неплохая. Я бы например удалил вообще функцию FreeMem, после чего программа бы проработала несколько десятков минут и умерла по нехватке памяти. Но до внезапной смерти бы работала корректно.

Функция “RedirectCall” модифицирует начало функции-заглушки в памяти

Осталось поставить брейкпойнт на VirtualProtect и снять трассу. В начале трассы будут исправления ошибок в библиотеках (RTL/VCL), а вот в конце как раз вызовы защиты. Причём, что приятно, опять сработает подход попугая с магнитофоном. При восстановлении RTL правильные значения будут всегда одни и те же. Останется только самому их записать, не дожидаясь вашей защиты, например написав прокси для любой общесистемной DLL и положив рядом с вашей программой.

> if ViorProtect1.Value99FF32 <> "99FF32" then PerformCriticalError;

Но это же та самая "очевидная точка принятия решения", против которой вы выступали в начале статьи. Особенно предполагая, что пользователи вашей библиотеки не будут забивать себе голову вопросами защиты, посчитав, что вы это сделали за них. То есть, их PerformCriticalError; скорее всего будет достаточно очевидной, чтобы понять откуда она вылезла, найти этот if, найти по нему правильное значение и... и в обход вашей защиты его записать куда надо. Либо поставить брейкпойнт на само ваше свойство и найти все места, которые его читают и пропатчить их NOP'ами. Или и то и другое. Дополнительный бонус, что вы выбрали для проверояных значений тип LongString. То есть существует один единственный адрес литерала "99FF32", к которому обращаются ВСЕ места с проверкой. Тут опять, хоть брейкпойнт на него ставь, хоть статически ищи в бинарнике все упоминания этого адреса. Почему не double с плавающим значением, которое нужно проверять с какой-то точностью? или вообще не три значения, double 1, dobule 2, epsylon?
И ладно бы это была сложная мысль, но вы выше для временных меток сами примерно так делали, то есть уже знает эту идею.

>  основным фактором раздражения для них становилось постоянное принудительное завершение работы операционной системы

То есть взломщик не смог заблокировать выключение ОС штатным API ? Тут же даже патчить ничего не надо, надо просто у пользователя забрать права доступа к перезщагрузке. Ну или не у пользователя, а у вашей программы.

Простите, но.... Это определённо характеризует ваших исследователей. Мне кажется, чтобы они заскучали и сдались хватило бы многократно более простой и детерминированной защиты. У которой было бы меньше шансов на ложное срабатывание и создание как плавающих непредсказуемых проблем для тех-поддержки, так и дурной славы среди потенциальных покупателей.

Спасибо за подробный комментарий, со многими пунктами согласен. Но стоит учитывать что эта либа использовалась более 15 лет назад.

Про функции и IAT:

Я как раз таки избавился от списка имен в бинарнике путем шифрования названий, поэтому статический анализ был невозможен. Отладка тоже была осложнена и заминирована таймингами через RoutineAdress. Любая пауза на брейкпоинте вела к вычислению неверного адреса и падению. Про "костер" в IAT согласен, но это сознательный выбор против автоматического анализа и импорт-реконструкторов. Скрыть защиту от профи нельзя, но можно заставить его тратить время на рутину.

Про картинки и внешние EXE:

Внешние модули (KFC и SRV2) сами по себе ничего не значили без контекста обмена данными с основным процессом. Плюс вынос в отдельные EXE позволял разделять права доступа через ImpersonateLog, когда регистратору нужны были права админа, а программе нет.

Про функции типа StrToInt64VP:

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

Но это же та самая "очевидная точка принятия решения", против которой вы выступали в начале статьи.

Это не основной рубеж, а дополнительный проверочный контур.

Я как раз таки избавился от списка имен в бинарнике путем шифрования названий

И VCL 7 сама по себе не вызывает EnumWindows, никак и нигде? Возможно, но трудно поверить. Или вы вместе с защитой еще и пересобранные кастомные BPL поставляли, чтобы VCL тоже шла в обход IAT?..

Если нет, то именно в этом засветка и будет, одна и та же функция запускается И через IAT, И мимо него. Вам тогда нужно обеспечить, чтобы ВСЕ библиотеки и модули защищаемой программы всегда вызывали эту функцию только через ваш шлюз и никак иначе.

Так-то принципиально сложного в самом доступе без IAT ничего нет, конечно, просто нудно это весьма. Вот сейчас в одном легаси просто нельзя включать темы Windows, там что-то так наворотили по принципу окно-в-окне-в-окне с частичными перехватами и модификациями сообщений - что попытка включить темы или Z-Order приводит к неработоспособности сразу. А TaskDialog хочется. Но формально его нет, GetProcAddress проверяет включены ли темы, и если нет наказывает "невидимостью" этих функций. Ну что же, пришлось самому найти их в памяти...

Внешние модули (KFC и SRV2) сами по себе ничего не значили без контекста обмена данными с основным процессом

Не согласен.

1. вы защищаете их существование, но я про него ни слова не говорил. Я критиковал КАК они хранятся, и как запускаются. Если бы они хранились в DFM какой-нибудь редко-используемой непримечательной формы и запускались прямо из памяти - слова бы не сказал.

2.Но раз уж вы их сделали - то вы тем самым создали внешний plugin api, который осталось отреверсить и написать свой плагин. Просто посмотреть на прологи и эпилоги функций и воткнуть туда логгеры. Вы потратили много усилий, чтобы "спрятать деревья в лесу", а потом просто взяли и вырубили в нём просеки с табличками. Вот здесь те функции, которые надо посмотреть и написать подмену! Фактически такие DLL должны быть сами зашифрованы и покрыты защитами. Но поскольку они и есть защита - получил pkunzip.zip При всём очаровании модульности IMHO защита должна собираться в монолит. На уровне исходников комбинировать раздельные части - сколько угодно, но сборка в один единый модуль, у которого нет явной "дороги через границу", где можно сначала записать обмен, а потом его воспроизвести. При этом контекст совсем не нужен, нужен массив. Табличный эмулятор. Нужно просто знать правильные ответ на самые вероятные сочетания входных параметров :-)

Легальный пользователь с этим никогда не сталкивался.

  1. Защита тоже программа и может содержать ошибки. Вы же не захотите, чтобы у вас под кроватью бомба лежала, даже если я пообещаю, что детонатор никогда не сработает, потому что мы друзья?

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

  3. Легальный пользователь часто перед покупкой - собирает отзывы про товар, про про программу. В гугле, на форумах... И там будут отзывы - с конкретными примерами - что программа просто считать не умеет. Всё понятно, спасибо неравнодушным за предупреждение, пошли искать альтернативы. Нет, конечно, если вы монополист...

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации