Flaga HANDLE_FLAG_PROTECT_FROM_CLOSE informuje system, że nie powinien pozwalać na zamknięcie danego uchwytu:
SetHandleInformation(hObj, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
CloseHandle(hObj); // Wzbudzany jest wyjątek
Gdy aplikacja uruchomiona jest pod kontrolą debugera, jeśli wątek będzie próbował zamknąć chroniony uchwyt, CloseHandle wzbudzi wyjątek. Poza kontrolą debugera, CloseHandle po prostu zwróci FALSE
Sądzę że każdemu kto zetknął się z problemem zabezpieczenia aplikacji przed RE (czy to od strony zabezpieczającego, czy też od strony odbezpieczającego) zapaliła się teraz w głowie lampka oznaczona 'detekcja debugera'.
Taak, ten trik (o który, przyznaje się bez bicia, wcześniej nie wiedziałem) może zostać wykorzystany aby wykryć debugger - wystarczy stworzyć jakiś exception handler, stworzyć jakiś obiekt jądra (np. semafor za pomocą CreateSemaphore), i wykonać dwie zacytowane wyżej linijki kodu. Jeżeli zostanie wykonany exception handler - jesteśmy w debuggerze, a jeżeli nie zostanie wykonany, to w nim nie jesteśmy (exception to C0000235 aka HANDLE_NOT_CLOSABLE).
Dodam że metodę tą Peter Ferrie uwzględnił w swoim papierku Anti-Unpacker tricks - Part One (Part Two ma wyjść w tym miesiącu).
Co ciekawe, okazuje się że CloseHandle zachowuje się w podobny sposób również w sytuacji gdy otrzyma nieprawidłowy handle (totalnie nieprawdidłowy, lub zostanie wywołane dwa razy dla prawidłowego uchwytu) - jest debugger - jest exception (INVALID_HANDLE btw), nie ma debuggera - nie ma exceptiona (warto rzucić okiem tutaj).
Dlaczego tak się dzieje? Skorzystajmy z dobrodziejstwa Windows Research Kernel, i zajrzyjmy do wnętrza syscalla NtClose, a konkretniej do wnętrza funkcji ObpCloseHandle (base\ntos\ob\obclose.c) która implementuje zamykanie uchwytów do obiektów jądra. Konkretnie interesuje nas następujący fragment (który dotyczy wyjątku INVALID_HANDLE, kod odpowiedzialny za drugi wspomniany wyjątek jest identyczny):
if ((NtGlobalFlag & FLG_ENABLE_CLOSE_EXCEPTIONS) ||
(CurrentProcess->DebugPort != NULL) ||
(ObjectTable->DebugInfo != NULL)) {
if (!KeIsAttachedProcess()) {
return KeRaiseUserException (STATUS_INVALID_HANDLE);
} else {
return STATUS_INVALID_HANDLE;
}
}
[...]
Status = STATUS_INVALID_HANDLE;
[...]
return Status;
Jak widać sprawdzane są cztery rzeczy:
1) NtGlobalFlag na obecność flagi FLG_ENABLE_CLOSE_EXCEPTIONS
2) Czy obecny proces ma podpięty debugger (->DebugPort != NULL)
3) Czy pole DebugInfo w ObjectTable obecnego procesu nie jest puste
4) KeIsAttachedProcess() zwróci FALSE
Jeżeli któryś z warunków 1-3 jest spełniony, oraz jest spełniony warunek 4rty, zostanie rzucony exception INVALID_HANDLE. W innym wypadku, funkcja po prostu zwróci INVALID_HANDLE w wartości.
Przeanalizujmy po kolei powyższe punkty, zwracając uwagę na rzeczy które pozwolą nam zataić obecność debuggera.
W punkcie pierwszym analizowana jest flaga FLG_ENABLE_CLOSE_EXCEPTIONS.
#if DBG
NtGlobalFlag |= FLG_ENABLE_CLOSE_EXCEPTIONS |
FLG_ENABLE_KDEBUG_SYMBOL_LOAD;
#endif
Jak się okazuje, flaga ta ustawiana jest we wszystkich debug-buldach jądra Windowsa, i szczerze mówiąc bez wykorzystania drivera, za dużo nie możemy z nią zrobić (możemy ją pobrać - RtlGetNtGlobalFlags, ale nie ustawić). Upewnijmy się więc że nie mamy debug-builda jądra, lub naklepmy krótki sterownik, i już tą flagą nie musimy się przejmować.
Punkt drugi mówi o DebugPort w EPROCESS. Kojarzycie PEB i pole BeingDebugged? To user-land flaga ustawiana na podstawie DebugPort (base\ntos\mm\procsup.c):
PebBase->BeingDebugged = (BOOLEAN)(TargetProcess->DebugPort != NULL ? TRUE : FALSE);
Szczerze mówiąc, jeśli chcemy żeby debugger działał, to z tą flagą nic nie zrobimy (btw, można wartość tego pola pobrać z user-land za pomocą funkcji NtQueryInformationProcess z parametrem ProcessDebugPort - ot taka kolejna znana (mi od dzisiaj) metoda na detekcje debuggera).
Punkt trzeci - DebugInfo w ObjectTable. Pole opisane jako w następujący sposób:
//
// Debug info. Only allocated if we are debugging handles
//
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
Debugowanie uchwytów, a w zasadzie trace'owanie uchtywów, to zapisywanie dodatkowych informacji o handle'ach. Ów handle tracing można włączyć lub wyłączyć za pomocą funkcji NtSetInformationProcess z parametrem ProcessHandleTracing.
Ostatnia rzecz, w zasadzie najważniejsza, to KeIsAttachedProcess, a w zasadzie KiIsAttachedProcess, to makro zwracające wynik poniższego wyrażenia logicznego:
(KeGetCurrentThread()->ApcStateIndex == AttachedApcEnvironment)
Wg. opisu funkcji KeIsAttachedProcess (które korzysta z powyższego makra), wartość TRUE jest zwracana jeżeli wątek w którego kontekście rozpatrywane jest wywołanie należy do jakiegoś procesu. Z tym chyba za wiele nie zrobimy.
Jak widać za dużo w prosty sposób nie zdziałamy. Jak więc ukryć debugger?
By the way...
There are more blog posts you might like on my company's blog: https://hexarcana.ch/b/
Rozwiązanie 1.
Tworzymy driver który patchuje ObpCloseHandle, tak aby ten nigdy nie rzucał wyjątkami.
Rozwiązanie 2.
W debugerze nie przekazujemy exceptiona do aplikacji - zamiast tego przenosimy grzecznie EIP za wywołanie NtClose/CloseHandle i ustawiamy EAX na FALSE (0).
Rozwiązanie 3.
Tworzymy hooka na NtClose/CloseHandle który dodaje dodatkowe sprawdzanie wyjątków.
Oczywiście rozwiązanie 2 i 3 jest stosunkowo łatwe do wykrycia, a rozwiązanie 1 trudne w implementacji (dla osób które nie bawiły się w kernel-land). Niemniej jednak powyższe metody wystarczają ;>
Huh... I pomyśleć że taki długi post mi wyszedł o takiej prostej funkcji...
Comments:
A tak BeTeWu to wolno spytać skąd masz dostęp do WRK? Z pracy czy jakoś inaczej?
Aaa wolno zapytać ;> Z pracy mam ;>
Add a comment: