Уроки Разработка вирусов-27.Кунгфу-2.Изучаем API Hooking


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 094
Репутация
8 213
1696085295787.png


Введение

API Hooking — это техника, используемая для перехвата и изменения поведения функции API. Она часто используется для отладки, обратного проектирования и взлома игр.
API Hooking позволяет заменять оригинальную реализацию функции API собственной версией, которая выполняет некоторые дополнительные действия до или после вызова оригинальной функции. Это позволяет изменять поведение программы без изменения её исходного кода.

Трамплины

Классический способ реализации API-хуков осуществляется с помощью трамплинов.
Трамплин — это шеллкод, который используется для изменения пути выполнения кода путем перехода на другой конкретный адрес в адресном пространстве процесса. Шеллкод трамплина вставляется в начало функции, в результате чего функция становится "подцепленной". Когда вызывается подцепленная функция, вместо нее активируется шеллкод трамплина, и поток выполнения передается и изменяется на другой адрес, что приводит к выполнению другой функции.

1696085471579.png


Встраиваемый Хук

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

1696085543859.png


API-хук выполняется некоторыми средствами безопасности, чтобы позволить им более тщательно изучать часто злоупотребляемые функции. Об этом будет подробнее рассказано далее.

Зачем API-Хук

Хотя API-хук в основном используется для анализа вредоносного ПО и целей отладки, его можно использовать при разработке вредоносного ПО по следующим причинам:

Сбор конфиденциальной информации или данных (например, учетные данные).

Изменение или перехват вызовов функций в злонамеренных целях.

Обход мер безопасности путем изменения поведения операционной системы или программы (например, AMSI, ETW).

Реализация Хука

Существует много способов реализации API-хука, один из способов — через открытые библиотеки, такие как библиотека и . Еще один, более ограниченный способ, — использование API Windows, предназначенных для выполнения API-хука (хотя с ограниченными возможностями).

Далее в статье будут продемонстрированы как Detours, так и Minhook.

Кроме того, будут использованы API Windows, чтобы увидеть, что они могут предложить.

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

Итак начнём.)

API Hooking -

Библиотека Detours для хука, разработанная Microsoft Research, позволяет перехватывать и перенаправлять вызовы функций в Windows.
Библиотека перенаправляет вызовы определенных функций к пользовательской заменяющей функции, которая может затем выполнять дополнительные задачи или изменять поведение исходной функции.
Detours обычно используется с программами на C/C++ и может быть использована как с 32-битными, так и с 64-битными приложениями.

Страница wiki библиотеки доступна

Транзакции

Библиотека Detours заменяет первые несколько инструкций целевой функции, то есть функции, к которой применяется хук, безусловным переходом к пользовательской функции обходного пути, которая будет выполнена вместо неё.
Термин "безусловный переход" также называется "трамплином".

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

Использование библиотеки Detours

Для использования функций библиотеки Detours необходимо загрузить и скомпилировать репозиторий Detours, чтобы получить необходимые статические файлы библиотеки (.lib). Кроме того, следует включить заголовочный файл detours.h, что объясняется в разделе "Использование Detours" на вики Detours.

Для дополнительной помощи в добавлении .lib файлов в проект ознакомьтесь

32-битная vs 64-битная библиотека Detours

Предоставленный в этой статье код содержит препроцессорный код, который определяет, какую версию файла .lib Detours следует включить, в зависимости от архитектуры используемой машины. Для этого используются макросы _M_X64 и _M_IX86. Эти макросы определены компилятором для указания, работает ли машина на 64-битной или 32-битной версии Windows. Препроцессорный код выглядит следующим образом:
C:
// Если компилируется как 64-бит
#ifdef _M_X64
#pragma comment (lib, "detoursx64.lib")
#endif // _M_X64
// Если компилируется как 32-бит
#ifdef _M_IX86
#pragma comment (lib, "detoursx86.lib")
#endif // _M_IX86

#ifdef _M_X64 проверяет, определен ли макрос _M_X64, и если он определен, следующий за ним код будет включен в компиляцию. Если он не определен, код будет проигнорирован. Аналогично, #ifdef _M_IX86 проверяет, определен ли макрос _M_IX86, и если он определен, следующий за ним код будет включен в компиляцию. #pragma comment (lib, "detoursx64.lib") используется для связывания библиотеки detoursx64.lib во время компиляции для 64-битных систем, и #pragma comment (lib, "detoursx86.lib") используется для связывания библиотеки detoursx86.lib во время компиляции для 32-битных систем.

Файлы detoursx64.lib и detoursx86.lib создаются при компиляции библиотеки Detours, detoursx64.lib создается при компиляции библиотеки Detours как 64-битный проект, таким же образом файл detoursx86.lib создается при компиляции библиотеки Detours как 32-битный проект.

Функции API Detours

При использовании любого метода хуков первым шагом всегда является получение адреса функции WinAPI для хука. Адрес функции требуется для определения, где будут размещены инструкции перехода. В этом модуле функция MessageBoxA будет использоваться как функция для хука.

Ниже перечислены функции API, которые предлагает библиотека Detours:
  • DetourTransactionBegin - начать новую транзакцию для установки или удаления обходных путей. Эту функцию следует вызывать первой при установке и удалении хука.
  • DetourUpdateThread - обновить текущую транзакцию. Это используется библиотекой Detours для включения потока в текущую транзакцию.
  • DetourAttach - установить хук на целевую функцию в текущей транзакции. Это не будет зафиксировано, пока не будет вызвана функция DetourTransactionCommit.
  • DetourDetach - удалить хук с целевой функции в текущей транзакции. Это не будет зафиксировано, пока не будет вызвана функция DetourTransactionCommit.
  • DetourTransactionCommit - подтвердить текущую транзакцию для установки или удаления обходных путей. Возвращаемое значение функций выше - это значение LONG, которое используется для понимания результата выполнения функции. API Detours вернет NO_ERROR, которое равно 0, если она завершится успешно, и ненулевое значение в случае ошибки. Ненулевое значение может быть использовано как код ошибки для целей отладки.
Замена подцепленного API

Следующим шагом является создание функции для замены подцепленного API. Функция замены должна иметь тот же тип данных и, по желанию, принимать те же параметры. Это позволяет проверять или изменять значения параметров. Например, следующая функция может быть использована в качестве функции обходного пути для MessageBoxA, что позволяет проверять исходные значения параметров.

Код:
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // мы можем проверять hWnd - lpText - lpCaption - параметры uType
}

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

Проблема бесконечного цикла

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

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

C:
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // Вывод оригинальных значений параметров
  printf("Оригинальный параметр lpText    : %s\n", lpText);
  printf("Оригинальный параметр lpCaption : %s\n", lpCaption);

  // НЕ ДЕЛАЙТЕ ТАК
  // Изменение значений параметров
  return MessageBoxA(hWnd, "другой lpText", "другой lpCaption", uType); // Вызов MessageBoxA (этот хук активен)
}

Решение 1 - Глобальный указатель на оригинальную функцию

Библиотека Detours может решить эту проблему, сохранив указатель на исходную функцию перед ее подцеплением. Этот указатель можно сохранить в глобальной переменной и вызвать вместо подцепленной функции в функции обходного пути.

C:
// Используется как не подцепленная MessageBoxA в `MyMessageBoxA`
fnMessageBoxA g_pMessageBoxA = MessageBoxA;

INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // Вывод оригинальных значений параметров
  printf("Оригинальный параметр lpText    : %s\n", lpText);
  printf("Оригинальный параметр lpCaption : %s\n", lpCaption);

  // Изменение значений параметров
  // Вызов не подцепленной MessageBoxA
  return g_pMessageBoxA(hWnd, "другой lpText", "другой lpCaption", uType);
}

Решение 2 - Использование другого API

Еще одно более общее решение, которое стоит упомянуть, заключается в вызове другой не подцепленной функции, которая имеет такую же функциональность, как и подцепленная функция. Например, MessageBoxA и MessageBoxW, VirtualAlloc и VirtualAllocEx.

C:
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // Вывод оригинальных значений параметров
  printf("Оригинальный параметр lpText    : %s\n", lpText);
  printf("Оригинальный параметр lpCaption : %s\n", lpCaption);

  // Изменение значений параметров
  return MessageBoxW(hWnd, L"другой lpText", L"другой lpCaption", uType);
}

Процедура хука Detours

Как было объяснено ранее, библиотека Detours работает с использованием транзакций, поэтому для установки хука на функцию API необходимо создать транзакцию, представить действие (установка/удаление хука) для транзакции, а затем подтвердить транзакцию. Приведенный ниже фрагмент кода выполняет эти шаги.

C:
// Используется как не подцепленная MessageBoxA в `MyMessageBoxA`
// И используется в `DetourAttach` & `DetourDetach`
fnMessageBoxA g_pMessageBoxA = MessageBoxA;

// Функция, которая будет запускаться вместо MessageBoxA при активации хука
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {

    printf("[+] Оригинальные параметры : \n");
    printf("\t - lpText    : %s\n", lpText);
    printf("\t - lpCaption    : %s\n", lpCaption);

    return g_pMessageBoxA(hWnd, "другой lpText", "другой lpCaption", uType);
}

BOOL InstallHook() {

    DWORD    dwDetoursErr = NULL;

      // Создание транзакции и её обновление
    if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {
        printf("[!] DetourTransactionBegin завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

    if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {
        printf("[!] DetourUpdateThread завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

      // Запуск MyMessageBoxA вместо g_pMessageBoxA, который является MessageBoxA
    if ((dwDetoursErr = DetourAttach((PVOID)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {
        printf("[!] DetourAttach завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

      // Фактическая установка хука происходит после `DetourTransactionCommit` - подтверждение транзакции
    if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {
        printf("[!] DetourTransactionCommit завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

    return TRUE;
}

Процедура удаления хука Detours

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

C:
// Используется как не подцепленная MessageBoxA в `MyMessageBoxA`
// И используется в `DetourAttach` & `DetourDetach`
fnMessageBoxA g_pMessageBoxA = MessageBoxA;

// Функция, которая будет запускаться вместо MessageBoxA при активации хука
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {

    printf("[+] Оригинальные параметры : \n");
    printf("\t - lpText    : %s\n", lpText);
    printf("\t - lpCaption    : %s\n", lpCaption);

    return g_pMessageBoxA(hWnd, "другой lpText", "другой lpCaption", uType);
}

BOOL Unhook() {

    DWORD    dwDetoursErr = NULL;

      // Создание транзакции и её обновление
    if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {
        printf("[!] DetourTransactionBegin завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

    if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {
        printf("[!] DetourUpdateThread завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

      // Удаление хука из MessageBoxA
    if ((dwDetoursErr = DetourDetach((PVOID)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {
        printf("[!] DetourDetach завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

      // Фактическое удаление хука происходит после `DetourTransactionCommit` - подтверждение транзакции
    if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {
        printf("[!] DetourTransactionCommit завершилась ошибкой с кодом : %d \n", dwDetoursErr);
        return FALSE;
    }

    return TRUE;
}

Основная функция

Ранее показанные процедуры установки и удаления хука не включают в себя основную функцию. Основная функция представлена ниже, в ней просто вызываются подцепленные и не подцепленные версии MessageBoxA.

C:
int main() {

    // Будет выполнено - не подцеплено
    MessageBoxA(NULL, "Что вы думаете о разработке вредоносных программ?", "Оригинальное сообщение", MB_OK | MB_ICONQUESTION);

//------------------------------------------------------------------
    //  Установка хука
    if (!InstallHook())
        return -1;

//------------------------------------------------------------------
    // Не будет выполнено - будет запущена функция MyMessageBoxA вместо этого
    MessageBoxA(NULL, "Разработка вредоносных программ - это плохо", "Оригинальное сообщение", MB_OK | MB_ICONWARNING);

//------------------------------------------------------------------
    //  Удаление хука
    if (!Unhook())
        return -1;

//------------------------------------------------------------------
    //  Будет выполнено - хук удален
    MessageBoxA(NULL, "Снова обычное сообщение", "Оригинальное сообщение", MB_OK | MB_ICONINFORMATION);

      return 0;
}

Демо

MessageBoxA (Unhooked)

1696086887849.png


MessageBoxA (Hooked)

1696086912282.png


MessageBoxA (Unhooked)

1696086971707.png


Библиотека Minhook


- это библиотека для хукинга, написанная на C, которая может быть использована для достижения API-хукинга. Она совместима как с 32-битными, так и с 64-битными приложениями на Windows и использует ассемблер x86/x64 для внутреннего хукинга, аналогично библиотеке Detours. По сравнению с другими библиотеками хукинга, MinHook проще и предлагает легковесные API, что делает работу с ней проще.

Использование библиотеки Minhook

Как и библиотека Detours, библиотека Minhook требует статический .lib файл и заголовочный файл MinHook.h, которые должны быть включены в проект Visual Studio.

Функции API Minhook

Библиотека Minhook работает, инициализируя структуру, которая содержит необходимую информацию, необходимую для установки или удаления хука.
Это делается через API MH_Initialize, который инициализирует структуру HOOK_ENTRY в библиотеке. Затем используется функция MH_CreateHook для создания хуков, и MH_EnableHook используется для их активации. MH_DisableHook используется для удаления хуков, и, наконец, MH_Uninitialize используется для очистки инициализированной структуры.

Функции перечислены ниже для удобства:

MH_Initialize - Инициализация структуры HOOK_ENTRY.

MH_CreateHook - Создание хуков.

MH_EnableHook - Активация созданных хуков.

MH_DisableHook - Удаление хуков.

MH_Uninitialize - Очистка инициализированной структуры.

API Minhook возвращает значение MH_STATUS, которое является пользовательским перечислением, расположенным в Minhook.h. Возвращаемый тип данных MH_STATUS указывает код ошибки указанной функции. Значение MH_OK, которое равно 0, возвращается, если функция выполняется успешно, и ненулевое значение возвращается в случае ошибки.

Стоит отметить, что функции MH_Initialize и MH_Uninitialize следует вызывать только один раз, в начале и в конце программы соответственно.

Функция подмены (которая будет вызываться вместо API)

Здесь тот же пример API MessageBoxA из предыдущего модуля, который будет подменяться и изменяться для выполнения другого сообщения.

C:
fnMessageBoxA g_pMessageBoxA = NULL;

INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
printf("[+] Оригинальные параметры : \n");
printf("\t - lpText    : %s\n", lpText);
printf("\t - lpCaption    : %s\n", lpCaption);

return g_pMessageBoxA(hWnd, "Разный lpText", "Разный lpCaption", uType);
}

Обратите внимание, что глобальная переменная g_pMessageBoxA используется для запуска окна сообщений, где g_pMessageBoxA - это указатель на оригинальный, не подмененный API MessageBoxA. Ей присваивается значение NULL, потому что вызов API Minhook MH_CreateHook инициализирует его для использования, в отличие от библиотеки Detours, где g_pMessageBoxA устанавливается вручную. Это делается для предотвращения возникновения проблемы цикла хукинга, о которой говорилось в предыдущем модуле.

Процедура хукинга Minhook

Как упоминалось ранее, чтобы подменить конкретный API с помощью Minhook, сначала необходимо выполнить функцию MH_Initialize. Затем можно создать хуки с помощью MH_CreateHook и активировать их с помощью MH_EnableHook.

C:
BOOL InstallHook() {

DWORD     dwMinHookErr = NULL;

if ((dwMinHookErr = MH_Initialize()) != MH_OK) {
    printf("[!] MH_Initialize завершился с ошибкой : %d \n", dwMinHookErr);
    return FALSE;
}

// Установка хука на MessageBoxA, чтобы запускать MyMessageBoxA вместо него
// g_pMessageBoxA будет указателем на оригинальную функцию MessageBoxA
if ((dwMinHookErr = MH_CreateHook(&MessageBoxA, &MyMessageBoxA, &g_pMessageBoxA)) != MH_OK) {
    printf("[!] MH_CreateHook завершился с ошибкой : %d \n", dwMinHookErr);
    return FALSE;
}

// Активация хука на MessageBoxA
if ((dwMinHookErr = MH_EnableHook(&MessageBoxA)) != MH_OK) {
    printf("[!] MH_EnableHook завершился с ошибкой : %d \n", dwMinHookErr);
    return -1;
}

return TRUE;
}

Процедура отмены хукинга Minhook

В отличие от библиотеки Detours, библиотека Minhook не требует использования транзакций. Вместо этого, чтобы удалить хук, единственное требование - это запустить API MH_DisableHook с адресом подмененной функции. Вызов MH_Uninitialize необязателен, но он очищает структуру, инициализированную предыдущим вызовом MH_Initialize.

C:
BOOL Unhook() {

DWORD     dwMinHookErr = NULL;

if ((dwMinHookErr = MH_DisableHook(&MessageBoxA)) != MH_OK) {
    printf("[!] MH_DisableHook завершился с ошибкой : %d \n", dwMinHookErr);
    return -1;
}

if ((dwMinHookErr = MH_Uninitialize()) != MH_OK) {
    printf("[!] MH_Uninitialize завершился с ошибкой : %d \n", dwMinHookErr);
    return -1;
}
}

Главная функция

Ранее показанные процедуры хукинга и отмены хукинга не включают главную функцию. Главная функция представлена ниже, она просто вызывает подмененные и не подмененные версии MessageBoxA.

C:
int main() {

// будет выполняться
MessageBoxA(NULL, "Что вы думаете о разработке вредоносных программ?", "Оригинальное сообщение", MB_OK | MB_ICONQUESTION);

// подмена
if (!InstallHook())
    return -1;

// не будет выполняться - подменено
MessageBoxA(NULL, "Разработка вредоносных программ - это плохо", "Оригинальное сообщение", MB_OK | MB_ICONWARNING);

// отмена подмены
if (!Unhook())
    return -1;

// будет выполняться - хук отключен
MessageBoxA(NULL, "Снова обычное сообщение", "Оригинальное сообщение", MB_OK | MB_ICONINFORMATION);

return 0;
}

API Hooking - Собственный код

Введение


До сих пор для реализации API-хукинга использовались открытые библиотеки. Однако основная проблема этого подхода заключается в том, что исходный код этих библиотек доступен публично, что позволяет исследователям в области безопасности и поставщикам продуктов безопасности создавать IoC(Indicators of compromise). По этой причине в этом модуле API-хукинг будет реализован вручную, хотя и не так изощренно, как в ранее продемонстрированных библиотеках, но достаточно для достижения желаемого результата без IoC.

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

Создание трамплинного шеллкода

Один из способов установки хука на функцию - перезаписать её первые несколько инструкций новыми. Эти новые инструкции - это трамплин, который отвечает за изменение потока выполнения функции на заменяющую функцию. Типичным трамплином является небольшой jmp-шеллкод, который выполняет инструкцию jmp к адресу функции, которая должна быть выполнена. Чтобы выполнить инструкцию jmp, адрес, к которому нужно перейти, должен быть сохранен в регистре. В представленном примере регистром будет eax на 32-битном процессоре и r10 на 64-битном процессоре. Инструкция mov будет использоваться для сохранения адреса в этих регистрах.

Этого достаточно для трамплина: инструкции mov и jmp. Глубокое погружение в то, как используются эти инструкции, не является целью этой статьи. Если вы хотите узнать больше, то можно посетить felixcloutier.com/x86/mov и felixcloutier.com/x86/jmp для получения дополнительной информации.

64-битный Jump Shellcode

64-битный jump-шеллкод должен выглядеть следующим образом:

C:
mov r10, pAddress
jmp r10

Где pAddress - это адрес функции, к которой нужно перейти (например, 0x0000FFFEC32A300). Чтобы использовать эти инструкции в коде, их сначала нужно преобразовать в опкоды.

C:
0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pAddress
0x41, 0xFF, 0xE2                                            // jmp r10

32-битный Jump Shellcode А версия для 32-бит:

C:
mov eax, pAddress
jmp eax

Также нужно преобразовать инструкции в опкоды.

C:
0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, pAddress
0xFF, 0xE0                        // jmp eax

Следует отметить, что pAddress представлено как NULL, что объясняет последовательность 0x00. Эти опкоды 0x00 являются заполнителями и будут перезаписаны во время выполнения.

Получение pAddress


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

C:
//64-битное пропатчивание
uint8_t        uTrampoline[] = {
            0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pFunctionToRun
            0x41, 0xFF, 0xE2                                            // jmp r10
};
uint64_t uPatch = (uint64_t)pAddress;
memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch)); // копирование адреса в смещение '2' в uTrampoline

C:
//32-битное пропатчивание
uint8_t        uTrampoline[] = {
       0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, pFunctionToRun
       0xFF, 0xE0                        // jmp eax
};
uint32_t uPatch = (uint32_t)pAddress;
memcpy(&uTrampoline[1], &uPatch, sizeof(uPatch)); // копирование адреса в смещение '1' в uTrampoline

Как упоминалось ранее, pAddress - это адрес функции, к которой нужно перейти. Типы данных uint32_t и uint64_t используются для обеспечения правильного количества байтов адреса, то есть 4 байта для 32-битных машин и 8 байтов для 64-битных машин. uint32_t имеет размер 4 байта, а uint64_t - 8 байтов. Затем memcpy поместит адрес в трамплин, перезаписывая байты-заполнители 0x00.

Запись трамплина

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

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

C:
// Изменение разрешений памяти в 'pFunctionToHook' на PAGE_EXECUTE_READWRITE
if (!VirtualProtect(pFunctionToHook, sizeof(uTrampoline), PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
    return FALSE;
}
// Копирование трамплинового шеллкода в 'pFunctionToHook'
memcpy(pFunctionToHook, uTrampoline, sizeof(uTrampoline));

Где pFunctionToHook - это адрес функции для установки хука, а uTrampoline - это jmp-шеллкод.

Отключение хука

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

C:
memcpy(pFunctionToHook, pOriginalBytes, sizeof(pOriginalBytes));

Где pFunctionToHook - это адрес захваченной функции, а pOriginalBytes - это буфер, который содержит оригинальные байты функции, которые должны были быть сохранены до установки хука, и это можно сделать с помощью вызова memcpy. Размер буфера pOriginalBytes должен быть таким же, как размер трамплинового шеллкода, таким образом, будет перезаписан только шеллкод. Наконец, рекомендуется восстановить разрешения памяти, что можно сделать с помощью приведенного ниже кода.

C:
if (!VirtualProtect(pFunctionToHook, sizeof(uTrampoline), dwOldProtection, &dwOldProtection)) {
    return FALSE;
}

Где dwOldProtection - это старое разрешение памяти, возвращаемое первым вызовом VirtualProtect.

Структура HookSt

Чтобы упростить реализацию, была создана структура HookSt. Эта структура будет содержать необходимую информацию для установки и отключения хука на определенную функцию. Значение TRAMPOLINE_SIZE установлено на 13, если программа компилируется как 64-битное приложение, и установлено на 7, если программа компилируется в режиме 32-бит. Значения 13 и 7 - это размеры трамплинового шеллкода, указанные в переменной uTrampoline, представленной ранее, для 64-битных и 32-битных систем соответственно.

C:
typedef struct _HookSt{

    PVOID    pFunctionToHook;                  // адрес функции для установки хука
    PVOID    pFunctionToRun;                   // адрес функции для выполнения вместо
    BYTE    pOriginalBytes[TRAMPOLINE_SIZE];  // буфер для сохранения некоторых оригинальных байтов (необходим для очистки)
    DWORD    dwOldProtection;                  // сохраняет старое разрешение памяти по адресу "функции для установки хука" (необходимо для очистки)

}HookSt, *PHookSt;

Установка значения TRAMPOLINE_SIZE выполняется с помощью следующего препроцессорного кода:

C:
// если компиляция как 64-бит
#ifdef _M_X64
#define TRAMPOLINE_SIZE        13
#endif // _M_X64
// если компиляция как 32-бит
#ifdef _M_IX86
#define TRAMPOLINE_SIZE        7
#endif // _M_IX86

Установка хуков

Следующая функция использует HookSt для установки хуков.

C:
BOOL InstallHook (IN PHookSt Hook) {

#ifdef _M_X64
// 64-битный трамплин
    uint8_t    uTrampoline [] = {
            0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pFunctionToRun
            0x41, 0xFF, 0xE2                                            // jmp r10
    };

    // Пропатчивание шеллкода адресом для перехода (pFunctionToRun)
    uint64_t uPatch = (uint64_t)(Hook->pFunctionToRun);
    // Копирование адреса функции для перехода в смещение '2' в uTrampoline
    memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch));
#endif // _M_X64

#ifdef _M_IX86
// 32-битный трамплин
    uint8_t    uTrampoline[] = {
       0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, pFunctionToRun
       0xFF, 0xE0                        // jmp eax
    };

    // Пропатчивание шеллкода адресом для перехода (pFunctionToRun)
    uint32_t uPatch = (uint32_t)(Hook->pFunctionToRun);
    // Копирование адреса функции для перехода в смещение '1' в uTrampoline
    memcpy(&uTrampoline[1], &uPatch, sizeof(uPatch));
#endif // _M_IX86

    // Размещение трамплинной функции - установка хука
    memcpy(Hook->pFunctionToHook, uTrampoline, sizeof(uTrampoline));

    return TRUE;
}

Снятие хуков

Функция ниже использует HookSt для снятия хуков.

C:
BOOL RemoveHook (IN PHookSt Hook) {

    DWORD    dwOldProtection        = NULL;

    // Копирование оригинальных байтов назад
    memcpy(Hook->pFunctionToHook, Hook->pOriginalBytes, TRAMPOLINE_SIZE);
    // Очистка нашего буфера
    memset(Hook->pOriginalBytes, '\0', TRAMPOLINE_SIZE);
    // Установка старого разрешения памяти обратно в то, что было до установки хука
    if (!VirtualProtect(Hook->pFunctionToHook, TRAMPOLINE_SIZE, Hook->dwOldProtection, &dwOldProtection)) {
        printf("[!] VirtualProtect Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    // Установка всех значений в null
    Hook->pFunctionToHook   = NULL;
    Hook->pFunctionToRun    = NULL;
    Hook->dwOldProtection   = NULL;

    return TRUE;
}

Заполнение структуры HookSt

Функция InitializeHookStruct используется для заполнения структуры HookSt необходимой информацией для установки хука.

C:
BOOL InitializeHookStruct(IN PVOID pFunctionToHook, IN PVOID pFunctionToRun, OUT PHookSt Hook) {

    // Заполнение структуры
    Hook->pFunctionToHook   = pFunctionToHook;
    Hook->pFunctionToRun    = pFunctionToRun;

    // Сохранение оригинальных байтов того же размера, который мы будем перезаписывать (то есть TRAMPOLINE_SIZE)
    // Это делается для возможности выполнения очистки при завершении
    memcpy(Hook->pOriginalBytes, pFunctionToHook, TRAMPOLINE_SIZE);

    // Изменение разрешения на RWX, чтобы можно было изменять байты
    // Мы сохраняем старое разрешение в структуре (для последующего восстановления)
    if (!VirtualProtect(pFunctionToHook, TRAMPOLINE_SIZE, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection)) {
        printf("[!] VirtualProtect Failed With Error : %d \n", GetLastError());
        return FALSE;
    }

    return TRUE;
}

Основная функция

Основная функция ниже вызывает ранее продемонстрированные функции и устанавливает хук на WinAPI MessageBoxA.

C:
int main() {

    // Инициализация структуры (необходима до установки/снятия хука)
    HookSt st = { 0 };

    if (!InitializeHookStruct(&MessageBoxA, &MyMessageBoxA, &st)) {
        return -1;
    }

    // будет запущено
    MessageBoxA(NULL, "Что вы думаете о разработке вредоносного ПО?", "Оригинальное сообщение", MB_OK | MB_ICONQUESTION);

    // установка хука
    if (!InstallHook(&st)) {
        return -1;
    }

    // не будет запущено - с хуком
    MessageBoxA(NULL, "Разработка вредоносного ПО - это плохо", "Оригинальное сообщение", MB_OK | MB_ICONWARNING);

    // снятие хука
    if (!RemoveHook(&st)) {
        return -1;
    }

    // будет запущено - хук отключен
    MessageBoxA(NULL, "Обычное сообщение снова", "Оригинальное сообщение", MB_OK | MB_ICONINFORMATION);

    return 0;
}

Перехват API - Использование API Windows

Вызов WinAPI SetWindowsHookEx является альтернативным методом перехвата API. Он преимущественно используется для отслеживания определенных типов системных событий, что отличается от техник, используемых в предыдущих модулях, так как SetWindowsHookExW/A не изменяет функциональность функции, а вместо этого выполняет обратный вызов функции каждый раз, когда происходит определенное событие.

Типы событий ограничены теми, что предоставляет Windows.

Использование SetWindowsHookEx

WinAPI SetWindowsHookExW показан ниже.

Код:
HHOOK SetWindowsHookExW(
  [вход] int       idHook,      // Тип процедуры перехвата, который будет установлен
  [вход] HOOKPROC  lpfn,        // Указатель на процедуру перехвата (функцию для выполнения)
  [вход] HINSTANCE hmod,        // Дескриптор DLL, содержащей процедуру перехвата (это оставляют как NULL)
  [вход] DWORD     dwThreadId   // Id потока, с которым будет ассоциирована процедура перехвата (это оставляют как NULL)
);

idHook - Событие, которое будет отслеживаться. Например, флаг WH_KEYBOARD_LL используется для отслеживания сообщений нажатия клавиш, которые могут действовать как кейлоггер.
Обратите внимание, что использование SetWindowsHookEx для кейлоггинга - старый трюк. В этой статье будет использоваться флаг WH_MOUSE_LL для отслеживания кликов мыши.

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

Функция обратного вызова

Функция обратного вызова должна быть типа HOOKPROC, который показан ниже.

C:
typedef LRESULT (CALLBACK* HOOKPROC)(int nCode, WPARAM wParam, LPARAM lParam);

Таким образом, функция обратного вызова должна быть определена, как функция ниже.

C:
LRESULT HookCallbackFunc(int nCode, WPARAM wParam, LPARAM lParam){
  // код функции
}

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

Функция обратного вызова обновлена, чтобы включать в себя CallNextHookEx.

C:
LRESULT HookCallbackFunc(int nCode, WPARAM wParam, LPARAM lParam){
  // Код функции

  return CallNextHookEx(NULL, nCode, wParam, lParam);
}

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

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

C:
LRESULT HookCallbackFunc(int nCode, WPARAM wParam, LPARAM lParam){

    if (wParam == WM_LBUTTONDOWN){
        printf("[ # ] Левый клик мыши \n");
    }

    if (wParam == WM_RBUTTONDOWN) {
        printf("[ # ] Правый клик мыши \n");
    }

    if (wParam == WM_MBUTTONDOWN) {
        printf("[ # ] Средний клик мыши \n");
    }

  return CallNextHookEx(NULL, nCode, wParam, lParam);
}

Обработка сообщений

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

C:
// Функция обратного вызова, которая будет выполняться каждый раз, когда пользователь кликает кнопкой мыши
LRESULT HookCallback(int nCode, WPARAM wParam, LPARAM lParam){

    if (wParam == WM_LBUTTONDOWN){
        printf("[ # ] Левый клик мыши \n");
    }

    if (wParam == WM_RBUTTONDOWN) {
        printf("[ # ] Правый клик мыши \n");
    }

    if (wParam == WM_MBUTTONDOWN) {
        printf("[ # ] Средний клик мыши \n");
    }

    // переход к следующему перехвату в цепочке перехватов
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}


BOOL MouseClicksLogger(){

    // Установка перехвата
    HHOOK hMouseHook = SetWindowsHookExW(
        WH_MOUSE_LL,
        (HOOKPROC)HookCallback,
        NULL,
        NULL
    );
    if (!hMouseHook) {
        printf("[!] SetWindowsHookExW не удалось с ошибкой : %d \n", GetLastError());
        return FALSE;
    }

    // Поддержание работы потока
    while(1){

    }

    return TRUE;
}


int main() {

    HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MouseClicksLogger, NULL, NULL, NULL);
    if (hThread)
        WaitForSingleObject(hThread, 10000); // Отслеживание кликов мыши в течение 10 секунд

    return 0;
}

Улучшение реализации

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

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

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

C:
// Функция обратного вызова, которая будет выполняться каждый раз, когда пользователь кликнет кнопкой мыши
LRESULT HookCallback(int nCode, WPARAM wParam, LPARAM lParam){

    if (wParam == WM_LBUTTONDOWN){
        printf("[ # ] Левый клик мыши \n");
    }

    if (wParam == WM_RBUTTONDOWN) {
        printf("[ # ] Правый клик мыши \n");
    }

    if (wParam == WM_MBUTTONDOWN) {
        printf("[ # ] Средний клик мыши \n");
    }

    // Переход к следующему перехвату в цепочке перехватов
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}


BOOL MouseClicksLogger(){

    MSG         Msg         = { 0 };

    // Установка перехвата
    HHOOK hMouseHook = SetWindowsHookExW(
        WH_MOUSE_LL,
        (HOOKPROC)HookCallback,
        NULL,
        NULL
    );
    if (!hMouseHook) {
        printf("[!] SetWindowsHookExW не удалось с ошибкой : %d \n", GetLastError());
        return FALSE;
    }

    // Обработка необработанных событий
    while (GetMessageW(&Msg, NULL, NULL, NULL)) {
        DefWindowProcW(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
    }

    return TRUE;
}


int main() {

    HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MouseClicksLogger, NULL, NULL, NULL);
    if (hThread)
        WaitForSingleObject(hThread, 10000); // Отслеживание кликов мыши в течение 10 секунд

    return 0;
}

Удаление перехватов

Чтобы удалить любой перехват, установленный функцией SetWindowsHookEx, должен быть вызван WinAPI UnhookWindowsHookEx.
UnhookWindowsHookEx принимает только дескриптор перехвата для удаления.

Код перехвата SetWindowsHookEx

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

C:
// Глобальная переменная дескриптора перехвата
HHOOK g_hMouseHook      = NULL;

// Функция обратного вызова, которая будет выполняться каждый раз, когда пользователь кликнет кнопкой мыши
LRESULT HookCallback(int nCode, WPARAM wParam, LPARAM lParam){

    if (wParam == WM_LBUTTONDOWN){
        printf("[ # ] Левый клик мыши \n");
    }

    if (wParam == WM_RBUTTONDOWN) {
        printf("[ # ] Правый клик мыши \n");
    }

    if (wParam == WM_MBUTTONDOWN) {
        printf("[ # ] Средний клик мыши \n");
    }

    // Переход к следующему перехвату в цепочке перехватов
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}


BOOL MouseClicksLogger(){

    MSG         Msg         = { 0 };

    // Установка перехвата
    g_hMouseHook = SetWindowsHookExW(
        WH_MOUSE_LL,
        (HOOKPROC)HookCallback,
        NULL,
        NULL
    );
    if (!g_hMouseHook) {
        printf("[!] SetWindowsHookExW не удалось с ошибкой : %d \n", GetLastError());
        return FALSE;
    }

    // Обработка необработанных событий
    while (GetMessageW(&Msg, NULL, NULL, NULL)) {
        DefWindowProcW(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
    }

    return TRUE;
}


int main() {

    HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MouseClicksLogger, NULL, NULL, NULL);
    if (hThread)
        WaitForSingleObject(hThread, 10000); // Отслеживание кликов мыши в течение 10 секунд

    // Отключение перехвата
    if (g_hMouseHook && !UnhookWindowsHookEx(g_hMouseHook)) {
        printf("[!] UnhookWindowsHookEx не удалось с ошибкой : %d \n", GetLastError());
    }
    return 0;
}
 
Автор темы Похожие темы Форум Ответы Дата
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 6
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 5
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 3
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-34.Обход Windows defender
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-32.Открываем врата ада
Уроки Разработка вирусов-31.Обход виртуальных машин
Уроки Разработка вирусов-30.Черпаем силы в антиотладке
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка вирусов-28. Предельная техника. Разборка с сисколами
Уроки Разработка вирусов-26. Изучаем кунгфу-1. Скрытие таблицы импорта
Уроки Разработка вирусов-25. Скрытие строк
Уроки Разработка вирусов-24. Изучаем технику Spoofing
Уроки Разработка вирусов-23. Контроль выполнения полезной нагрузки
Уроки Разработка вирусов-22.Изучаем технику Stomping Injection
Уроки Разработка вирусов-21.Инъекция отображаемой памяти
Уроки Разработка вирусов-20.Вызов кода через функции обратного вызова
Уроки Разработка вирусов-19.Изучаем технику APC Injection
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка вирусов-17.Изучаем технику Thread Hijacking
Уроки Разработка вирусов-16.Разборка с цифровой подписью зверька
Уроки Разработка вирусов-15. Прячем Payload в реестре
Верх Низ