Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

3204

.pdf
Скачиваний:
9
Добавлен:
15.11.2022
Размер:
3.44 Mб
Скачать

GetExitCodeThread (hThread, &a); CloseHandle (hThread);

- вызов функции через пул потоков worker thread: s.х = x;

s.y = у; s.z = z; s.ret = &a;

RtlQueueWorkltem (Function, &s, 0); WaitForSingleObject (hEvent, TIMEOUT);

Функция Function перед возвратом управления должна сделать системный вызов

SetEvent (hEvent);

- вызов функции через пул потоков wait thread: s.х = х;

s.y = у; s.z = z; s.ret = &a;

RtlRegisterWait (ShWait, hEventl, Function, &s,

TIMEOUT1, 0); SetEvent (hEventl);

WaitForSingleObject (hEvent2, TIMEOUT2);

Функция Function перед возвратом управления должна сделать системный вызов

SetEvent (hEvent2);

- вызов функции через передачу некоторому окну нестандартного сообщения:

s.х = х; s.y = у; s.z = z;

а = SendMessage (hwnd, WM_USER + HIDDEN_CALLS_GATE, Function, &s);

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

91

встретиться в параметрах обычных сообщений Windows;

-вызов функции по таймеру (предполагается, что текущий поток программы не создает никаких окон, g_s – глобальная структура):

s.х = х; s.y = у; s.z = z; s.ret = &а;

GetMessage (&msg, NULL, 0, 0); DispatchMessage (&msg); KillTimer (n);

-вызов функции через перечисление дочерних окон окна, содер жащего единственное дочернее окно:

s.х = х;

s.y = у; s.z = z; s.ret = &а;

EnumChildWindows (hwndWithSingleChild, Function, &s);

- вызов функции через перечисление главных окон программы, имеющей единственное главное окно:

s.х = х;

 

s.y = у;

 

s.z = z;

 

s.ret = &а;

 

EnumThreadWindows

(GetCurrentThreadld

(), Function, &s) ;

 

-вызов функции через перечисление файлов подкачки (pagefiles) системы, имеющей единственный файл подкачки (работает на чиная с Windows 2000):

s.х = х; s.y = у; s.z = z; s.ret = &а;

EnumPageFiles (Function, &s);

-вызов функции через асинхронный ввод-вывод:

92

s.х = х; s.y = у; s.z = z; s.ret = &а;

memset (&о, 0, sizeof(OVERLAPPED)); о.hEvent = &s;

ReadFileEx (hFile, p, 0, &o, Function);

Здесь hFile – любой файловый объект, открытый в асинхронном режиме; р – произвольный адрес памяти, доступный для записи. Вместо ReadFileEx можно использовать функции WriteFileEx, NtReadFile, NtWriteFile, NtDeviceloControlFile;

-использование нестандартных способов сравнения данных. На пример, вместо стандартных машинных команд сравнения (стр, test) можно использовать арифметические и логические команды (add, sub, and, or и т.д.).

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

Нестандартные обращения к функциям операционной системы

Чаще всего программа обращается к системным функциям, используя статический импорт используемых функций из системных библиотек. Однако существуют и другие способы обращения к системным функциям, например:

-динамический импорт:

pCreateFile

=

 

GetProcAddress

(GetModuleHandle

 

 

(«kernel32»),

«CreateFileW»);

hFile

=

pCreateFile

(FileName,

GENERIC_READ,

FILE_SRARE_READ,

NULL, OPEN_EXISTING, FILE

/\TTRIBUTEJSTORMAL,

93

NULL);

Если аналитик изучает дезассемблированный листинг программы с целью обнаружить место, где программа открывает файл, данный вызов не будет обнаружен аналитиком. Вместо стандартной функции GetProcAddress можно воспользоваться собственноручно написанным аналогом этой функции. От динамического анализа программы данный метод (если он не применяется совместно с другими методами) не защищает. Желательно применять динамический импорт не для всех вызовов скрываемой функции, а лишь для отдельных, наиболее важных – если аналитик видит в дизассемблированном листинге программы, скажем, 12 обращений к функции CreateFileW, аналитику трудно предположить, что в программе могут быть и другие обращения к этой же функции;

- использование более низкоуровневых системных

функций, чем обычно:

 

 

 

pNtCreateFile

=

 

GetProcAddress

(GetModuleHandle

 

 

("ntdll"),

"NtCreateFile");

Status

=

pNtCreateFile

(IphFile,

GENERIC_READ,

&FileAttributes,

&IoStatusBlock,

 

NULL,

FILE

/\TTRIBUTEJSTORMAL,

FILE_S

HARE_READ,

FILE_OPEN, 0, NULL, 0);

 

 

Если аналитик ставит точку останова на системные вызовы CreateFileA и CreateFileW, эти точки останова никогда не сработают, поскольку в данном случае программа сразу вызывает более низкоуровневую системную функцию NtCreateFile, обычно не вызываемую напрямую.

Развитием данного метода является прямая передача управления в ядро операционной системы непосредственно из защищаемой программы:

_asm {mov еах, iNtCreateFile mov edx,pKernelThunk call [edx] ret 2Ch}

Значения iNtCreateFile (индекс функции NtCreateFile в

списке внешних точек входа ядра операционной системы) и

94

pKemelThunk (адрес оперативной памяти, по которому размещается машинная команда, передающая управление в ядро операционной системы) должны быть получены программой заранее. Это можно сделать, например, просмотрев код функции NtCreateFile прямо в библиотеке ntdll. dll. Или, еще проще, можно целиком скопировать код функции NtCreateFile в другую область оперативной памяти и вызывать вместо оригинальной функции копию (в приведенном фрагменте кода содержимое ассемблеровской вставки есть не что иное, как исходный текст функции NtCreateFile):

pNtCreateFile = GetProcAddress (GetModuleHandle ("ntdll"), "NtCreateFile");

pNtCreateFileCopy = LocalAlloc (LMEM_FIXED, SOME_REASONABLE_SIZE);

mermriove (pNtCreateFileCopy, pNtCreateFile, SOME_REASONABLE_SIZE) ;

VirtualProtect (pNtCreateFileCopy, SOME_REASONABLE_S IZE, PAGE_EXECUTE_READWRITE, NULL);

Status = pNtCreateFileCopy (IphFile, GENERIC_READ, IFileAttributes, HoStatusBlock, NULL, FILE /\TTRIBUTEJSTORMAL, FI LE_S HARE_READ, FILE_OPEN, 0, NULL, 0);

Заметим, что этот способ применим лишь к тем системным функциям, машинный код которых является мобильным. Как правило, код системных функций, непосредственно передающих управление в ядро Windows, является мобильным, а код других системных функций, как правило, таковым не является;

использование собственных реализаций стандартных функций и компонент Windows. Например, защищаемая программа может включать в себя собственную реализацию стандартного оконного класса edit, использующую вместо стандартного метода GetWindowText какой-то другой метод получения введенного пользователем текста;

если в состав защищаемого программного продукта

95

входит драйвер, часть функций ядра операционной системы можно вызывать через посредничество драйвера:

Status = DeviceloControl (hDevice, STEALTH_CREATE_FILE_IOCTL_CODE, SParameters, sizeof(Parameters) , SParameters, sizeof (Parameters), &clw, NULL);

Этим методом можно защищаться не только от дизассемблеров и отладчиков, но и от мониторов;

• использование нестандартных путей реализации тех или иных системных функций. Например, текущее системное время можно узнать, создав временный файл и узнав время его создания:

void MyGetSystemTime (PSYSTEMTIME pst) {

HANDLE hFile; FILETIME ft;

 

hFile

=

CreateFile

(SOME_NAME,

GENERIC_LLRITE, 0,

 

 

 

NULL,

 

 

 

CREATE_NEW,

FILE_ATTRIBUTE_NORiiAL, NULL);

 

GetFileTime (hFile, &ft, NULL, NULL);

FileTimeToSystemTime (&ft, pst);

CloseHandle (hFile);

 

 

DeleteFile (SOMEUSUAME);}

 

модификация

таблицы

адресов импортов

программы в ходе вы полнения программы. Например:

ppGetWindowTextW

=

GetlATRecord

(hlnstance, "user32",

 

 

"GetWindowsTextW")

; ppOpenEventW =

GetlATRecord (hlnstance, "kernel32",

"OpenEventW");

 

 

VirtualProtect

(ppOpenEventW, 4, PAGE_WRITECOPY, NULL); *ppOpenEventW = *ppGetWindowTextW; OpenEvent ((DWORD) hwnd, (BOOL) String,

(int) Name));

Здесь функция GetlATRecord возвращает адрес относящейся к заданной функции записи в таблице адресов импортов заданного программного модуля (в нашем случае –

96

многократное копирование данных с места на

hlnstance). Программа заносит в запись таблицы адресов импортов, относящейся к функции OpenEventW, адрес функции GetWindowTextW. Начиная с этого момента при каждом вызове из программы функции OpenEventW на самом деле будет вызываться функция GetWindowsTextW. Данный метод можно применять не только к двум системным функциям, но и к паре функций, из которых лишь одна является системной, а другая является обычной функцией программы.

Искусственное усложнение алгоритмов обработки данных

Этот метод заключается в том, что данные, используемые защищаемыми от анализа алгоритмами, обрабатываются необычно сложным образом. Перечислим несколько способов искусственного усложнения алгоритмов данных:

место;

создание большого числа копий одних и тех же данных, из всех этих копий по назначению используется лишь одна, а остальные подвергаются ненужным «мусорным» проверкам и преобразованиям, отвлекающим на себя внимание аналитика. Желательно, чтобы эти «мусорные» проверки и преобразования были максимально похожи на те, которые пытается обнаружить аналитик. Например, если от анализа защищается алгоритм проверки регистрационного кода программы, целесообразно применить к введенному регистрационному коду несколько «подозрительных» алгоритмов: сравнить его с несколькими строками, внешне похожими на правильный регистрационный код, вычислить несколько разных хеш-функций и т.п.;

применение к данным сложных преобразований, в том числе и криптографических, например:

GenerateRSAKeyPair (pOpenKeyl, pSecretKeyl);

97

GenerateRSAKeyPair (p0penKey2, pSecretKey2);

RSAEncrypt (pCriticalData, pCriticalDataEncrypted, pOpenKeyl, pSecretKey2);

memset (pCriticalData, 0, CriticalDataSize);

RSADecrypt (pCriticalDataEncrypted, pCriticalDataCopy, pOpenKey2, pSecretKeyl);

DoSomething (pCriticalDataCopy);

разбиение алгоритма обработки данных на несколько фрагментов, передача управления между которыми осуществляется с исполь зованием методов искусственного усложнения структуры про граммы;

если при обработке данных обнаруживается ошибка, реакция на ошибку не должна быть немедленной. Программа должна отметить наличие ошибки, но отреагировать на нее лишь через некоторое время, при этом схема передачи управления в обработчик ошибки должна быть искусственно усложнена. Аналитик не должен иметь возможности быстро определить, в каком именно месте программы возникла ошибка;

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

RC6Encrypt (IsPasswordCorrect (Password) ? SomeStringl : SomeString2, pBuffer, SomeString3, DataSize);

RC6Decrypt (pBuffer, pBuffer2, SomeString3, DataSize);

Status = memcmp (pBuffer2, SomeStringl, DataSize);

Здесь вместо булевской переменной, содержащей результат про-перки пароля, в памяти программы хранится буфер с некоторыми данными, зашифрованными на одном из двух возможных ключей; • хранение данных в необычных

98

местах, например в области дополнительных данных окна или в локальном хранилище данных потока.

Выявление факта выполнения программы под отладчиком

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

из библиотеки kernel32.dll:

BOOL IsDebuggerPresent (void);

Эта функция возвращает TRUE, если программа выполняется под отладчиком использующим штатный интерфейс отладки, поддерживаемый Windows, и FALSE в противном случае. Вместо явного вызова функции IsDebuggerPresent можно непосредственно выполнить ее машинный код:

mov еах, fs:[018h] mov еах, [еах + 30h]

movzx еах, byte ptr [еах + 02]

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

Начиная с Windows ХР SP1 вместо IsDebuggerPresent может использоваться функция CheckRemoteDebuggerPresent,

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

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

п.);

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

99

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

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

• навязывание отладчику ложных точек останова: try { asm int 3;}

except(TRUE)

{;}

При прохождении данного участка кода под отладчиком происходит остановка программы на машинной команде int 3, которая интерпретируется как программная точка останова. Данный метод можно совместить с методом усложнения структуры программы, если сделать блок except не пустым, а поместить в него фрагмент реального кода программы.

Модификацией данного метода является генерация программой нефатальных исключительных ситуаций: регулярные вызовы CloseHandle с заведомо некорректным значением параметра, установка флага PAGEGUARD на активно используемые страницы оперативной памяти и т.п.;

100

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]