Защита собственных приложений

  • Для просмотра чата и остального функционала вам нужно авторизоваться или пройти регистрацию!
A

>A1RN1kE<

Автор темы
Добрый день!
В данной теме я опишу примитивные способы защиты от реверсинга собственных приложений.

Начнём с антиотладки:
1) Проверяем контрольные суммы отдельных блоков кода, для обнаружения int3 проставленных в коде.

2) Проверки на 0xCC в начале API-функций.

3) Рандомизация хардварных отладочных регистров с помощью SuspendThread/GetThreadContext/SetThreadContext или с помощью SEH в SEH-обработчике

4) Против SoftIce – перечисление загруженных драйверов EnumDeviceDrivers/GetDeviceDriverBaseName и поиск «ntice.sys» и «iceext.sys», попытка открыть устройства с помощь функции CreateFile, имя устройства зависит от ring0-отладчика, например, для SoftIce это - "\\.\NTICE"

5) Если отладчик обнаружен, то делается рандомизация регистров, прыжок на рандомную страницу. Из-за рандомизации регистра стековый фрейм будет поврежден и сложно будет найти, где был прыжок, и где осуществлялась антиотладочная проверка. Или перейти на код с кучей багов, но все-таки, рабочего, чтобы исследователь попал в большую кучу ошибок и соответственно левых SEH-фреймов.

6) Тайминговые атаки очень эффективны против отладчиков. Для замера времени необходимо использовать функцию GetTickCount(KeTickCount в ядре), для более точного измерения можно использовать функции QueryPerformanceFrequency / QueryPerformanceCounter, но чтобы они работали, им нужна соответствующая поддержка оборудования. Также, часто используют инструкцию RDTSC и как разновидность – поиск ее в системной и DLL и опосредованный ее вызов. Есть разновидность метода с RDTSC – делать RDTSC, потом генерить исключение, но без использования регистра EAX, и уже в предварительно поставленном обработчике исключения делать второй RDTSC и сравнивать со значением из CONTEXT.EAX. Не забываем при этом об использовании CPUID перед RDTSC. Разницу принимаем не больше E0000h – конечно необходимо использовать не точно это значение, а некоторый диапазон приемлемых значений – т.е. случайные числа.

7) Динамическое вычисление адреса перехода на процедуру:
Код:
push 1
call sin
xor eax,0x3334124
xor eax,0x553242444
sub eax,ecx
call eax

8 ) Иногда можно генерировать исключения с помощью некорректных операций или с помощью функции RaiseException. Справедливо можно заметить, что если исключение генерируется с помощью RaiseException, то при работе в отладчике OllyDbg не сможет передать управление на поставленный обработчик исключений – это придется делать вручную взломщику. Это позволяет запутать исследователя получить полные представление о коде, из-за постоянных остановок в процессе исследования. Саму установку SEH необходимо разбавить мусором, выстраивать многослойные ошибки.

9) Шифровать куски по мере исполнения, шифровать куски которые выполняются только один раз – для защиты от дампа. Чтобы делать расшифровку по мере исполнения, можно использовать TF.

10) Сделать, чтобы int3 бряк никогда не сработал – 1) не возвращаться из фейковых функций, которых будет нормальное количество:
Код:
push param1
push param2
..
push paramn
call function
nop
nop
.............
function:
pop eax ; адрес возврата
pop eax ; paramn
....
pop eax ; param1

jmp point_that_we_nead
............
point_that_we_nead:
..........

11) Записывать оригинальные байты на точку входа их TLS-CALLBACK или из внешних DLL, т.к. обычно на EP записывают программный бряк.

12) В качестве борьбы с плагинами для OllyDbg, рекомендуется использовать проверки на перехваченные процедуры и поиск присутствия плагинов, например, поисков DLL-файла плагина, Wnd-класса окна плагина и подобное. Например, чтобы сейчас обнаружать Phantom достаточно отсканить окна и найти в них строку Phant0m. Чтобы обнаружить последний OllyAdv надо проверить на хуки функции NtSetInformationThread и GetTickCount - но этих опций может и не быть в настройках, хотя часто кулхацкеры просто ставят все подряд галочки.

13) Записываем мусор в ненужные поля PE-файла. Olly этому будет не очень рада. LoadFlags и NumberOfRvaAndSizes очень для этого подходят.

14) Результат исполнения антиотладочного кода используем как ключ к расшифровке последующего кода для исполнения.

15) Запихиваем некоторый код в DLL, и делаем вызов LoadLibrary(), из DllMain можно исполнять код, который необходимо скрыть от посторонных глаз. Например, после исполнения LoadLibrary() исполнение последовательности инструкций прекращается вызовом ExitThread()/ret, а в DllMain() создается поток вызовом CreateThread() с адресом, принадлежащим образу исполняемого файла, а не DLL. В итоге DLL будет крошечного размера. Конечно, DLL должна создаваться динамически. Исполнять код можно как при загрузке DLL, так и при выгрузке, а также при создании/завершении потоков.

16) Пытаемся открыть CSRSS.EXE и если это получается, значит нас отлаживают. После открытия создаем поток в CSRSS, т.о. вызывая BSOD. PID CSRSS.EXE можно получить вызовом CsrGetProcessId().

17) Использование функции CheckRemoteDebuggerPresent(), которая доступна только в XP. Эту функцию можно сэмулировать вызовом NtQueryInformationProcess. Подробности можно узнать в дизассемблированном листинге функции CheckRemoteDebuggerPresent()

18 ) Процесс защищаемого приложения создается. В комбинации с другими антиотладочными приемами, мы создаем новый процесс для этого же файла, пишем переход в начале функции LdrInitializeThunk и в обработчике убираем расставленные бряки на EP, на TLS Callback, а также переинициализируем PEB.DebuggerPresent. Основная суть состоит в том, чтобы загрузить процесс раньше, чем это сделает отладчик. При этом можно начать исполнение не с EP, а любого другого участка. Также проверяем, что первый процесс был порожден вторым, для этого смотрим на, может быть, созданные флаги или проверяем тот же переход в функции LdrInitializeThunk. Этот же трюк можно проделать с DLL. И как кажется, это будет эффективней способа с EXE, т.к не надо будет порождать дополнительных процессов. Надо загрузить раньше чем отладчик остановиться на DllMain и убрать его хук, либо вообще изменить последовательность исполнения, перепрыгнув через DllMain.

19) Делать два процесса, чтобы один отлаживал другой. Отладчик в этом случае генерирует исключения, и родитель за счет них выполняется.

20) Создаем порт с помощью NtCreatePort, и делаем его отладочным - NtSetInformationProcess. Если этого сделать нельзя, то порт уже занят и нас отлаживают.

21) Очень долгая расшифровка, чтобы трейсинг, даже, если он будет работать, был очень долгим, невозможно долгим.

22) Использовать int3/int1 для расшифровки кода или для любых других жизненно-необходимых операций. Этот же метод можно использовать с целью антидамповой защиты.

23) Если защищаемое приложение загружает DLL, имя которой имеет размер больше 200 символов, то Olly упадет.

24) Вызываем ZwQueryObject с нулевым описателем и заполненной структурой OBJECT_ALL_TYPES_INFORMATION с типом объекта DebugObject. После вызова проверяем поля TotalNumberOfHandles и TotalNumberOfObjects объекта и если эти поля больше 0, то нас отлаживают.

25) Если исключение происходит в Windows XP SP>=2, Windows 2003, Windows Vista есть несколько вариантов развития событий – 1) Передача управления установленному VEH-обработчику, 2) передача управления самому ближайшему SEH-обработчику, 3) если user-обработчики не установлены, то происходит вызов UnhandledExceptionFilter из kernel32. Характер поведения UnhandledExceptionFilter зависит от того отлаживается процесс или нет. Если он отлаживается, то процесс завершается, иначе происходит вызов функции заданной программистом через SetUnhandledExceptionFilter. Исходя из вышесказанного можно делать жизненноважные манипуляции в обработчике установленном функцией SetUnhandledExceptionFilter. При решении этой проблемы можно перехватить SetUnhandledExceptionFilter, и узнать адрес обработчика. Но код SetUnhandledExceptionHandler простой и его можно сэмулировать, и найти адрес топового обработчика вручную.

26) Использование функции ZwSetInformationThread:
Код:
push 0
push 0
push 11h ;ThreadHideFromDebugger
push -2
call NtSetInformationThread

27) Породить дочерний процесс, который сделает вызов ZwDebugActiveProcess над процессом-родителем. Если вызов окажется неудачным, значит нас отлаживают.

28 ) В FS засунуть 0,1,2 или 3 – что означает селектор нулевого сегмента. При обращении, например FS:[0], должно быть исключение, а его нет, т.к. обработчик исключения адресуется через регистр FS. Из прикладного режима такие исключения ловятся только через SetUnhandledExceptionHandler, а в приатаченном отладчике подобные исключения не обрабатываются, т.е. обработчик необработанных исключений не вызывается.

29) Заполнение FS значением, например из DS, и активное использование префикса переопределения сегмента на FS может породить множество проблем исследователю.

30) При трассировке и исполнении инструкции PUSHFD отладчики прозрачно вычищают TF. Но при MOV SS,X происходит реальная потеря трассировочного прерывания. Следующий код хорошо иллюстрирует этот трюк:
Код:
MOV AX, SS
PUSHFD
MOV SS,AX
POPFD

В итоге POPFD сам же восстанавливает потертый TF. Более того(!) некоторые отладчики, находят например PUSHFD, но забывают затирать TF при REP PUSHFD При этом нельзя забывать, что дебагер, на следующей инструкции всегда выставляет TF заново, поэтому перепрыгивается только одна инструкция.

31) Перечисляем все Debug-описатели в системе и насильно закрываем их.

32) Если к процессу аттачатся, то в нем вызывается функция DbgUiRemoteBreakin. Перехватив ее можно спастить от аттача.

33) Поиск ключей в реестре OllyDebug.

34) CheckRemoteDebuggerPresent

35) Вызов AnimateWindow -
Код:
invoke WindowFromPoint,13ah,100h
invoke AnimateWindow,eax,701h,90000h

36) Проверка ключа «SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug в HKEY_LOCAL_MACHINE»

37) Использование прерывания int 2ch – под отладчиком после исполнения этого прерывания в edx будет -1, если без отладчика, то в edx будет адрес следующей инструкции.

38 ) OllyDebug не поддерживает SSE-команды, недокументированные команды типа LOADALL. Его можно перехитрить командой MOV [0],eax или подобной, т.к. его коданалайзер посчитает такую команду неправильной и будет интерпретировать ее как данные. Также можно сделать несколько CALLов на данные, трех будет достаточно, тогда аналайзер тоже лопухнется.

39) Можно изменить поток исполнения команд при выполнении WaitForSingleObjectEx. Ставим UserAPC на любой адрес для своего потока
invoke QueueUserAPC,address,-2,0
Потом хучим KiUserApcDispatcher с переходом на нужный нам код. Так, при выполнении WaitForSingleObjectEx будет переход на наш код магическим образом. Тоже можно сделать с CloseHandle на неправильном описателе и хук KiRaiseUserExceptionDispatcher. Интересно комбинировать эти трюки сделав WaitForSingleObjectEx внутри обработчика KiRaiseUserExceptionDispatcher. Обе функции

40) Устанавливаем TF например push -1/pop, после этого сразу генерим исключение нарушения доступа mov eax,[0] – или ud, то с отладчиком обработчик исключения, который был установлен, вызван не будет, а без отладчика будет вызван.

41) NtSetLdtEntries

42) аллокация памяти в нуле. NtAllocateVirtualMemory позволяет выделить память по виртуальному адресу 0 (если надо найду кодес. фишка в том, чтобы просить выделить по адресу 1 или 2, она сама его округляет до 0 и там выделяет).
олька может отображать тотолько начиная с 1, да и вообще адреса меньше 0x10000 могут легко ввести реверсера в заблуждение =)

отлично экспортируются ntdll. Эта же фича работает с функцией KiFastSystemCallRet и при исключениях.

43) При загрузке модуля (dll/exe) с именем, содержащим юникодные символы, которые с использованием текущей AnsiCodepage мэппятся в виде "?", Olly плюётся сообщением и в Memory Map сливает все секции модуля в одну (мелочь, но может раздражать).

(c) vulnes.com

Источник: http://fpteam-cheats.com/board/showthread.php?t=66109
Антиотладка - это метод защиты приложений не позволяющий (или усложняющий) его отладку (т.е. использование OllyDbg и т.п.).
Пример функции на WinApi антиотладки на Delphi:
Код:
function DebuggerPresent:boolean;
type
TDebugProc = function:boolean; stdcall;
var
Kernel32:HMODULE;
DebugProc:TDebugProc;
begin
Result:=false;
Kernel32:=GetModuleHandle('kernel32.dll');
if kernel32  0 then
begin
@DebugProc:=GetProcAddress(kernel32, 'IsDebuggerPresent');
if Assigned(DebugProc) then
Result:=DebugProc;
end;
end;
В этой функции используется WinApi функция IsDebuggerPresent.
Используем так - вешаем таймер, а в событие OnTimer пишем:
Код:
if DebuggerPresent then
Close;
Что происходит? Если функция DebuggerPresent возвратит true - то мы врубим приложение.
Компилируем тестовый проект, запускаем в Olly Dbg и нажимаем F9 (запустить приложение в режиме отладки).
Через секунду (интервал таймера по умолчанию) наше приложение вылетает - защита работает:psychotic:


C C++ дела обстоят ещё проще! Также ставим таймер и в событие OnTimer пишем
(код для С++ Builder):
Код:
if (IsDebuggerPresent())
Close();
Тестим, по умолчанию при отладке приложения должно вырубиться через секунду.
Есть ещё аналог функции IsDebuggerPresent() - CheckRemoteDebuggerPresent() - она более усовершенствованная.
Она позволяет проверять на отладку не только текущий процесс программы, но и любой другой.
Используем так:
Код:
BOOL mb = FALSE;

CheckRemoteDebuggerPresent(GetCurrentProcess(),&mb);

if (mb)
Close();
Данный метод защиты не панацея, и обходится достаточно легок, но для того, чтобы сбить с толку "кулхацкеров" хватает:psychotic:


Динамические пароли.
Если вы пишите запароленую программу, то существует проблема - если пароль слит или просто напросто найден (например реверснута программа), то защита паролем сходит на нет.
Вот например код проверки пароля, который обходится (находится сам пароль) одной ногой:
Код:
if Edit1.Text='paswd' then
//тут код, который выполняется при верном пароле
Можно усложнить нахождение пароля сравнением не самого пароля с введённой строкой, а его хеша. Т.е. если наша программа будет реверснута, то атакующий получит не сам пароль, а только его хеш, который ему придётся расшифровать.

Вот функция, подсчитывающая хеш строки для Delphi:
Код:
function md5(s: string): string;
begin
Result := '';
with TIdHashMessageDigest5.Create do
try
Result := AnsiLowerCase(AsHex(HashValue(s)));
finally
Free;
end;
end;
Функции необходимо предать строку, подвергающуюся шифровке, а сама функция возвратит хеш переданной строки.
Используем так:
Код:
Edit1.Text:=md5(edit1.Text);
Функция для подсчёта хеша строки для С++:
Код:
char * md5_noerrors(char *data,int size)
{
HCRYPTHASH hHash;
HCRYPTPROV hProv;
BYTE md5hash[16];
DWORD md5hash_size,dwSize;
static char str_hash[33];
int i;

ZeroMemory(str_hash,sizeof(str_hash));
ZeroMemory(md5hash,sizeof(md5hash));
CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0);
CryptCreateHash(hProv,CALG_MD5,0,0,&hHash);
CryptHashData(hHash,data,size,0);
dwSize=sizeof(md5hash_size);
CryptGetHashParam(hHash,HP_HASHSIZE,(BYTE *)&md5hash_size,&dwSize,0);
CryptGetHashParam(hHash,HP_HASHVAL,md5hash,&md5hash_size,0);
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);

for(i = 0 ; i < md5hash_size ; i++)
sprintf(str_hash+2*i,"%2.2x",md5hash[i]);

return str_hash;
}
Функция написана на WinApi и использует встроеную в Windows функцию.
В инклуд добавляем #include
Используем так:
Код:
Edit1->Text= md5_noerrors(Edit1->Text.c_str(),Edit1->Text.Length());
То есть, в качестве первого аргумента для функции md5_noerrors передаём сам текст из Edit1, а в качестве второго - длину Edit1.


Итак, как же сделать проверку хеша пароля, а не самого пароля?
Вот как - подсчитываем хеш нашего пароля (вообще, можно использовать "солёный" хеш, но об этом я расскажу позже), и выполняем такую проверку, код для Delphi:
Код:
if md5(Edit1.Text)='хеш вашего пароля' then
//код, выполняющийся при правильном вводе
В проверку подставляем хеш пароля и всё, работает.
Лучше всего использовать труднорасшифровываемый пароль, но расшифровка - это лишь дело времени.
Также, если пароль сольют, то всё, кабздец:psychotic:

Код проверки пароля для С++:
Код:
if (md5_noerrors(Edit1->Text.c_str(),Edit1->Text.Length())="хеш вашего пароля")
//код, выполняющийся при правильной проверке


"Солёный хеш"
Суть солёного хеша в том, что вместе с самим паролем используется "соль" - некая часть, которая добавляется к паролю и шифруется вместе с ним.
То есть происходит так:
Код:
md5(Edit1.Text+'cоль')
Смысл такой схемы - запутать атакующего.
Пример проверки хеша с солью на Delphi:
Код:
if md5(Edit1.Text+'cоль')='хеш вашего пароля с солью' then
//код, выполняющийся при правильной проверке
Также можно делать двойное хеширование, чтобы ещё больше усложнить брут хеша:
Код:
md5(md5(Edit1.Text+'cоль'))
Соответственно проверка:
Код:
if md5(md5(Edit1.Text+'cоль'))='двойной хеш вашего пароля с солью' then
//код, выполняющийся при правильном вводе
В общем, степень зашифровки пароля зависит только от вашей фантазии:psychotic:


Динамический пароль.
Динамический пароль - пароль, который можно изменить, но для его создания и использования требуется выход в сеть.
Для этого способа вам понадобится хостинг с поддержкой FTP (чтобы создать файл с паролем).
Принцип такой - через IdHttp получается txt файл (или происходит обращение к PHP скрипту методом GET) и сравнивается с паролем.
В полученном файле лучше всего иметь хеш пароля и сравнивать хеши пароля и введённой строки.
Пример кода на Delphi:
Код:
var
s:string;
begin
s:=Idhttp1.Get('хост.com/file.txt');
if md5(edit1.Text) = s then
//код, выполняющийся при правильном вводе
end;
Пример кода на С++:
Код:
AnsiString s = IdHTTP1->Get("хост.com/file.txt")
if (md5_noerrors(Edit1->Text.c_str(),Edit1->Text.Length())=s)
//код, выполняющийся при правильной проверке
Файл загружается в переменную и сравнивается с введённым текстом.
В данном методе защиты хорошо использовать обфусакцию - получение нескольких файлов с разными хешами, дабы Wpe Pro шёл лесом.
Также, можно использовать условие "если" таким образом:
Код:
var
s:string;
begin
s:=Idhttp1.Get('хост.com/non-pasword.txt');
if md5(edit1.Text) = s then
begin
end;
То есть идёт сравнение с паролем, но сравнение безполезное - это ещё один способ обфускации в этом методе.
Если пароль слили - берёте текстовик с паролем и меняете хеш пароля внутри него, не меняя имени текстовика.
Способы можно комбинировать, всё зависит от вашей фантазии.

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

Вот и всё...
Тема создана для обучения...
Особая благодарность ''freeman2103''
Спасибо за внимание...
 

Пользователи, просматривающие эту тему

Сейчас на форуме нет ни одного пользователя.