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

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

Пример хука
При­мер хука

Здесь EDR пос­тавил хук на NtAllocateVirtualMemory(). Эта фун­кция будет пос­ледней в User Mode, она вызыва­ется лишь для ини­циали­зации сис­темно­го вызова и выделе­ния памяти путем обра­щения к ядру. В сто­ковой кон­фигура­ции, ког­да хука нет, никаких безус­ловных jmp-перехо­дов быть не дол­жно. Тут мы видим иную ситу­ацию: переход как раз таки есть, поток управле­ния отда­ется непонят­но кому и непонят­но куда. Поэто­му нам как ата­кующим, да и прос­то что­бы укло­нить­ся от обна­руже­ния, нуж­на опе­рация анху­кин­га, которая сни­мет этот хук, и, как следс­твие, средс­тво защиты потеря­ет кон­троль над потоком выпол­нения прог­раммы.

От­мечу лишь, что подоб­ный спо­соб обхо­да хуков — один из мно­жес­тва. Мож­но, нап­ример, совер­шать Direct- и Indirect-сис­колы, но сто­ит пом­нить, что получит­ся обой­ти толь­ко хуки, которые сто­ят в User Mode. Если средс­тво защиты при­меня­ет хуки Kernel Mode (нап­ример, SSDT Hooking), то подоб­ные методы ока­жут­ся бес­полез­ны. На будущее: SSDT — это спе­циаль­ная таб­лица, бла­года­ря которой сопос­тавля­ются сис­кол и дей­ствие ядра Windows. Есть, конеч­но, Kernel Patch Protection, который меша­ет уста­нав­ливать подоб­ные хуки, но это уже сов­сем дру­гая исто­рия.

В статье я рас­смот­рю наибо­лее популяр­ные спо­собы сня­тия хуков, от прос­того к слож­ному.

warning

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

 

Снятие хука через чтение библиотеки с диска

Этот метод мож­но счи­тать одним из самых прос­тых. Он осно­ван на том, что биб­лиоте­ка ntdll.dll под­гру­жает­ся в память так же, как находит­ся на дис­ке. При­чем хуки уста­нов­лены непос­редс­твен­но в памяти, на дис­ке образ девс­твен­но чист. Поэто­му мы дол­жны будем лишь счи­тать биб­лиоте­ку с дис­ка, дос­тать из нее PE-сек­цию .text (в ней находит­ся код), а пос­ле переза­писать сек­цию .text хук­нутой биб­лиоте­ки сек­цией, счи­тан­ной с дис­ка.

Алгоритм снятия хука
Ал­горитм сня­тия хука

Мы будем исполь­зовать фун­кции ReadFile() и MapViewOfFile(), и EDR может отсле­живать их, поэто­му есть риск, что наша ntdll.dll, заг­ружен­ная с дис­ка, будет изме­нена при попыт­ке под­гру­зить ее содер­жимое в прог­рамму. Поэто­му при­дет­ся исполь­зовать иной спо­соб сня­тия хука, нап­ример тащить ntdll.dll с неко­его уда­лен­ного сер­вера. Этот алго­ритм реали­зуем поз­же. За идею боль­шое спа­сибо Раль­фу.

Итак, сна­чала нуж­но счи­тать содер­жимое биб­лиоте­ки ntdll.dll. Нач­нем со стан­дар­тной фун­кции ReadFile(). По умол­чанию ntdll.dll лежит в сис­темной пап­ке \Windows\System32. Пред­лагаю соз­дать фун­кцию, которая будет воз­вра­щать буфер с содер­жимым ntdll.dll.

#define NTDLL "NTDLL.DLL"
BOOL ReadNtdllFromDisk(OUT PVOID* ppNtdllBuf) {
CHAR cWinPath[MAX_PATH / 2] = { 0 };
CHAR cNtdllPath[MAX_PATH] = { 0 };
HANDLE hFile = NULL;
DWORD dwNumberOfBytesRead = NULL, dwFileLen = NULL;
PVOID pNtdllBuffer = NULL;
if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {
printf("[!] GetWindowsDirectoryA Failed With Error : %d \n", GetLastError());
goto EndOfFunc;
}
sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL);
hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileA Failed With Error : %d \n", GetLastError());
goto EndOfFunc;
}
dwFileLen = GetFileSize(hFile, NULL);
pNtdllBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileLen);
if (!ReadFile(hFile, pNtdllBuffer, dwFileLen, &dwNumberOfBytesRead, NULL) || dwFileLen != dwNumberOfBytesRead) {
printf("[!] ReadFile Failed With Error : %d \n", GetLastError());
printf("[i] Read %d of %d Bytes \n", dwNumberOfBytesRead, dwFileLen);
goto EndOfFunc;
}
*ppNtdllBuf = pNtdllBuffer;
EndOfFunc:
if (hFile)
CloseHandle(hFile);
if (*ppNtdllBuf == NULL)
return FALSE;
else
return TRUE;
}

Ос­тает­ся про­верить, что наш код вер­но работа­ет. Если ты пишешь в Visual Studio, то откры­вай пункт «Отладка → Парамет­ры» и ставь две галоч­ки, что­бы мож­но было видеть содер­жимое памяти.

Включение показа содержимого памяти
Вклю­чение показа содер­жимого памяти

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии