Злые вирусы так и норовят под­гру­зить в нашу легитим­ную прог­рамму свои биб­лиоте­ки! Воз­можно ли изба­вить­ся от чужих модулей? Как пре­дот­вра­тить заг­рузку биб­лиотек или хотя бы сво­евре­мен­но о ней узнать? Давай раз­бирать­ся.

В основном Windows исполь­зует два фор­мата исполня­емых фай­лов — .exe и DLL. Пер­вые пред­став­ляют собой при­выч­ные для нас прог­раммы, а DLL — это биб­лиоте­ки с допол­нитель­ными фун­кци­ями, под­гру­жаемые исполня­емым фай­лом. Фай­лы DLL могут быть заг­ружены в про­цесс и сра­зу при запус­ке (так называ­емая ста­тичес­кая лин­ковка), и при выпол­нении (динами­чес­кая лин­ковка), то есть ког­да про­цесс уже вов­сю работа­ет и вдруг что‑то его под­талки­вает к решению заг­рузить биб­лиоте­ку.

Ни для кого не сек­рет, что про­цес­сы в Windows име­ют вир­туаль­ное адресное прос­транс­тво. Прос­транс­тво пред­став­ляет собой кусок памяти, который при­над­лежит толь­ко текуще­му про­цес­су. В этой час­ти памяти будут дан­ные толь­ко текуще­го про­цес­са. При под­груз­ке DLL ока­зыва­ется в том же самом адресном прос­транс­тве и, как следс­твие, получа­ет дос­туп ко всем дан­ным про­цес­са. Такое поведе­ние при­водит к тому, что злос­тные хакеры так и жела­ют, так и ждут под­груз­ки сво­ей вре­донос­ной биб­лиоте­ки в наш про­цесс, что­бы уста­новить хуки, сдам­пить память и вся­чес­ки изме­нить поведе­ние прог­раммы. С этим мож­но бороть­ся! Давай пос­мотрим как.

 

UpdateProcThreadAttribute

Пред­лагаю начать с наибо­лее рас­простра­нен­ных спо­собов пре­дот­вра­щения под­груз­ки DLL-биб­лиотек в про­цесс. Пер­вый вари­ант осно­ван на фун­кции UpdateProcThreadAttribute(). Для его реали­зации уста­нав­лива­ется спе­циаль­ное зна­чение:

PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY

Единс­твен­ная проб­лема — в нашу прог­рамму все‑таки смо­гут внед­рять­ся чужие либы, но толь­ко если они под­писаны в Microsoft. Так­же сто­ит отме­тить, что пре­дот­вра­щение под­груз­ки DLL будет при­менять­ся к про­цес­су, который мы запус­тим через CreateProcess(), к текуще­му про­цес­су при­менить эту фун­кцию не получит­ся.

Тем не менее есть воз­можность обой­ти это огра­ниче­ние. Наш текущий про­цесс может про­верять, был ли он запущен с парамет­ром, огра­ничи­вающим под­груз­ку DLL. Если нет, то он запус­кает копию самого себя, но уже с помощью атри­бутов, которые уста­нав­лива­ются в фун­кции UpdateProcThreadAttribute().

Итак, сна­чала напишем фун­кцию CreateProcessWithBlockDllPolicy(). Для это­го нам пот­ребу­ется лишь путь до исполня­емо­го фай­ла, который нуж­но запус­тить. Фун­кция будет воз­вра­щать PID запущен­ного про­цес­са, его хендл, а так­же хендл потока про­цес­са.

BOOL CreateProcessWithBlockDllPolicy(IN LPSTR lpProcessPath, OUT DWORD* dwProcessId, OUT HANDLE* hProcess, OUT HANDLE* hThread) {
STARTUPINFOEXA SiEx = { 0 };
PROCESS_INFORMATION Pi = { 0 };
SIZE_T sAttrSize = NULL;
if (lpProcessPath == NULL)
return FALSE;
RtlSecureZeroMemory(&SiEx, sizeof(STARTUPINFOEXA));
RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));
SiEx.StartupInfo.cb = sizeof(STARTUPINFOEXA);
SiEx.StartupInfo.dwFlags = EXTENDED_STARTUPINFO_PRESENT;
InitializeProcThreadAttributeList(NULL, 1, NULL, &sAttrSize);
LPPROC_THREAD_ATTRIBUTE_LIST pAttrBuf = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sAttrSize);
if (!InitializeProcThreadAttributeList(pAttrBuf, 1, NULL, &sAttrSize)) {
printf("[!] InitializeProcThreadAttributeList Failed With Error : %d \n", GetLastError());
return FALSE;
}
DWORD64 dwPolicy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
if (!UpdateProcThreadAttribute(pAttrBuf, NULL, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &dwPolicy, sizeof(DWORD64), NULL, NULL)) {
printf("[!] UpdateProcThreadAttribute Failed With Error : %d \n", GetLastError());
return FALSE;
}
SiEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)pAttrBuf;
if (!CreateProcessA(
NULL,
lpProcessPath,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&SiEx.StartupInfo,
&Pi)) {
printf("[!] CreateProcessA Failed With Error : %d \n", GetLastError());
return FALSE;
}
*dwProcessId = Pi.dwProcessId;
*hProcess = Pi.hProcess;
*hThread = Pi.hThread;
DeleteProcThreadAttributeList(pAttrBuf);
HeapFree(GetProcessHeap(), 0, pAttrBuf);
if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL)
return TRUE;
else
return FALSE;
}

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

EXTENDED_STARTUPINFO_PRESENT

Это даст воз­можность зап­скать про­цес­сы с нуж­ной митига­цией.

Да­лее с помощью фун­кции InitializeProcThreadAttributeList() готовим струк­туру LPPROC_THREAD_ATTRIBUTE_LIST. Ее будем ука­зывать в фун­кцию UpdateProcAttributeList(). Двой­ной вызов фун­кции стан­дартен для Windows — пер­вый вызов слу­жит для получе­ния нуж­ного раз­мера, а вто­рой для ини­циали­зации эле­мен­тов струк­туры.

За­тем с помощью фун­кции UpdateProcAttributeList() заносим необ­ходимые атри­буты в нашу струк­туру со спис­ком атри­бутов, пос­ле чего при­меня­ем ее в фун­кции CreateProcessA(), что поз­воля­ет запус­тить про­цесс с защитой от под­груз­ки левых биб­лиотек.

Как же запус­тить текущий про­цесс, при­менив эту фун­кцию? Здесь будем исполь­зовать аргу­мен­ты коман­дной стро­ки. Наш про­цесс может породить дочер­ний такой же про­цесс с митига­цией, а самого себя унич­тожить. Для это­го нам нуж­но соз­дать спе­циаль­ный STOP_ARG. Если вдруг в аргу­мен­тах коман­дной стро­ки текуще­го про­цес­са нет STOP_ARG, то он дол­жен переза­пус­тить­ся с защитой от под­груз­ки DLL.

На­ша фун­кция main() дол­жна выг­лядеть вот так:

#define LOCAL_BLOCKDLLPOLICY
#ifdef LOCAL_BLOCKDLLPOLICY
#define STOP_ARG "xakep"
#endif
int main(int argc, char* argv[]) {
DWORD dwProcessId = NULL;
HANDLE hProcess = NULL,
hThread = NULL;
#ifdef LOCAL_BLOCKDLLPOLICY
if (argc == 2 && (strcmp(argv[1], STOP_ARG) == 0)) {
printf("[+] Process Is Now Protected With The Block Dll Policy \n");
WaitForSingleObject((HANDLE)-1, INFINITE);
}
else {
printf("[!] Local Process Is Not Protected With The Block Dll Policy \n");
CHAR pcFilename[MAX_PATH * 2];
if (!GetModuleFileNameA(NULL, (LPSTR)&pcFilename, MAX_PATH * 2)) {
printf("[!] GetModuleFileNameA Failed With Error : %d \n", GetLastError());
return -1;
}
DWORD dwBufferSize = (DWORD)(lstrlenA(pcFilename) + lstrlenA(STOP_ARG) + 0xFF);
CHAR* pcBuffer = (CHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize);
if (!pcBuffer)
return FALSE;
sprintf_s(pcBuffer, dwBufferSize, "%s %s", pcFilename, STOP_ARG);
if (!CreateProcessWithBlockDllPolicy(pcBuffer, &dwProcessId, &hProcess, &hThread)) {
return -1;
}
HeapFree(GetProcessHeap(), 0, pcBuffer);
printf("[i] Process Created With Pid %d \n", dwProcessId);
}
#endif
#ifndef LOCAL_BLOCKDLLPOLICY
if (!CreateProcessWithBlockDllPolicy((LPSTR)"C:\\Windows\\System32\\RuntimeBroker.exe", &dwProcessId, &hProcess, &hThread)) {
return -1;
}
printf("[i] Process Created With Pid %d \n", dwProcessId);
#endif
return 0;
}

Сна­чала объ­явля­ются некото­рые дирек­тивы, поз­воля­ющие управлять тем, что мы хотим получить от кода. Если хотим защитить собс­твен­ную прог­рамму, то оставля­ем define LOCAL_BLOCKDLLPOLICY. Если хотим запус­тить иной про­цесс с защитой от под­груз­ки DLL, то уби­раем это поле.

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

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

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

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

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

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии