Версия 1.0.1
Работа программиста реализующего код защиты как правило является достаточно увлекательным занятием. Но правда сам интерес начинает проявляться только после того, как ваш продукт был в первый (или очередной) раз взломан :)
В этот момент у вас на самом деле появляется "работа", которая заключается в анализе результатов работы реверсера и построению алгоритма противодействия.
Это как война на баррикадах, сначала мы их, потом они нас и на новый круг :)
Анализируя методы взлома вашего ПО вы, во первых, учитесь и учит вас именно реверсер, за что ему, как минимум, стоит сказать спасибо. Он таким своеобразным образом делится с вами знаниями, которые вы не должны упускать.
Немножко поделюсь и я своими наработками с обоих сторон баррикад :)
А именно способом ухода от отладчика через срыв стека.
А именно способом ухода от отладчика через срыв стека.
В момент анализа стороннего ПО мы работаем как правило со отладчиком. Одним из главных элементов отладки является наличие валидного стэка отлаживаемой нити. На основании стэка нити отладчик (а если ему не под силу, то дополнительные скрипты) достаточно подробно строят стэк вызовов процедур, на базе которого происходит часть анализа кода. На стэке лежат параметры вызова функций. На том-же стэке хранятся SEH фреймы. Ни один вызов функции не обходится без стэка. Собственно стэк наше все :) (Глава 6. Базовые сведения о потоках)
В случае если вы каким либо образом обнаружили отладчик, то не стоит сразу делать TerminateProcess(), его отловят и вернут как было. Если сломать "механизм стэка", то отладка вашего приложения в большинстве случаев станет не возможна. А отловить код поломки уже на порядок сложнее, чем перехватить пресловутый Halt или TerminateProcess.
Смысл стрыва стека сводится к генерации необрабатываемого исключения PAGE_FAULT, после чего процесс можно только закрыть. Дальнейшая его отладка бесперспективна.
Здесь я приведу четыре классических варианта срыва стэка:
1. Что есть стэк с точки зрения программиста в конкретной точке кода?
Это два регистра, EBP - Base Pointer и ESP - Stack Pointer.
Изменив эти два значения на любые произвольные, мы разрушим стэк::
asm2. Стек всегда растет вниз, в то время как сверху остаются некие данные в том числе и адреса возврата и SEH фреймы. Все эти данные доступны для чтения и модификации. Удалив их мы разрушим стэк.
mov ebp, 0
mov esp, 0
end;
procedure _FillChar(var Dest; count: Integer; Value: Byte); asm { ->EAX Pointer to destination } { EDX count } { CL value } PUSH EDI MOV EDI,EAX { Point EDI to destination } MOV CH,CL { Fill EAX with value repeated 4 times } MOV EAX,ECX SHL EAX,16 MOV AX,CX MOV ECX,EDX SAR ECX,2 JS @@exit REP STOSD { Fill count DIV 4 dwords } MOV ECX,EDX AND ECX,3 REP STOSB { Fill count MOD 4 bytes } @@exit: POP EDI end; procedure TForm1.btnKillUpStackClick(Sender: TObject); var P: Pointer; begin _FillChar(P, MaxInt, 0); end;
Здесь используется реализация FillChar из Delphi 7. В более старших версиях данная продцедура выполнена несколько другим способом и не дает необходимого эффекта.
3. Границы стека всегда обрамлены страницами с флагом PAGE_GUARD. Это можно наглядно увидеть в данном примере. Механизм работы данного флага следующий, при обращении к участку памяти с данным флагом срабатывает исключение Access Violation и данный флаг снимается. После чего при повторном обращении к этому участку срабатывает PAGE_FAULT.
3. Границы стека всегда обрамлены страницами с флагом PAGE_GUARD. Это можно наглядно увидеть в данном примере. Механизм работы данного флага следующий, при обращении к участку памяти с данным флагом срабатывает исключение Access Violation и данный флаг снимается. После чего при повторном обращении к этому участку срабатывает PAGE_FAULT.
В данном коде используется локальный статический массив для ускорения переполнения стека.
procedure TForm1.btnKillStackOnGuardClick(Sender: TObject); procedure T; var HugeBuff: array [0..10000] of DWORD; begin if HugeBuff[0] <> HugeBuff[10000] then Inc(HugeBuff[0]); T; end; begin try T; except T; end; end;
4. Срыв стека на рекурсивном вызове SEH фрейма. Логика данных фреймов проста, после установки они обрабатывают все исключения до тех пор, пока не будут сняты. В Delphi они представлены в частично обрезанном виде в качестве оберток try..finally/except. Идея заключается в том, что после установки SEH фрейма мы не производим его удаления и в нем-же генерируем ошибку, заставляя рекурсивно вызывать самого себя. В результате мы имеем переполнение стека, плавно перерастающее в PAGE_FAULT.
procedure TForm1.btnKillStackOnSEHClick(Sender: TObject); begin asm lea eax, @KillStack push eax push dword ptr [fs:0] mov [fs:0], esp xor eax, eax mov eax, [eax] @KillStack: mov eax, 0 call eax end; end;
Пошагово трассировать всю вашу программу никто никогда не будет, поэтому разместив несколько данных ловушек вы достаточно осложните жизнь реверсеру, которому придется раз за разом перезапускать процесс отладки с целью локализовать место срыва стэка.
Подсветка кода выполнена при помощи: http://highlight.hohli.com/
>Смысл стрыва стека сводится к генерации необрабатываемого исключения PAGE_FAULT, после чего процесс можно только закрыть. Дальнейшая его отладка бесперспективна.
ОтветитьУдалитьНе понятно, зачем нацеливаться на #PF, и мучать SEH, если вполне достаточно просто разрушить стэк: если адрес возврата будет соответствовать другой функции (например для делфи функции инициализации модуля - там частенько настройки читаются), то отлаживать это будет тоже весьма печально.
Любое разрушение стека приведет к ошибке, но иногда это можно побороть...
УдалитьБольшое спасибо Администратору за предоставленную статью. Теперь у меня 333 проверки на каждой кнопке, 33333 рекурсии и куча дополнительного защитного кода. Крякерам и реверсерам придется постараться =)
ОтветитьУдалитьНепонятен смысл комментария. Разве в статье где-то говорится про 333 проверки? По всей видимости нет, в статье описывается способ ухода от отладчика - не более того, а уж как это применять на практике должен решать сам программист.
УдалитьРазличных защитных трюков в данном блоге описано более чем предостаточно, и этот вариант является только одним из многих.