Ukrywanie Procesów W Systemach Operacyjnych – Mechanizmy, Techniki i Przykłady - Security Bez Tabu

Ukrywanie Procesów W Systemach Operacyjnych – Mechanizmy, Techniki i Przykłady

Jak system naprawdę widzi procesy i dlaczego to ważne przed analizą ukrywania

Czy da się ukryć działający proces przed administratorem systemu? Niestety tak – istnieją zaawansowane techniki pozwalające zataić obecność procesu zarówno w systemach Linux, jak i Windows. W tym artykule, omawiamy ukrywanie procesów krok po kroku: od metod działania w przestrzeni użytkownika (user-mode) po głębokie modyfikacje w jądrze systemu (kernel-mode).

Zobaczysz konkretne przykłady (w C, Bash, PowerShell), wycinki kodu rootkitów, logi z narzędzi takich jak strace, lsof, czy Volatility, a także dowiesz się, jak takie ukryte procesy wykrywać.

Jak system wyświetla listę procesów?

Zanim zagłębimy się w atakujące mechanizmy, ustalmy punkt wyjścia: w jaki sposób system operacyjny normalnie wyświetla listę procesów użytkownikowi? Jeśli zrozumiemy, jak działają polecenia typu ps czy Task Manager, łatwiej pojmiemy, jak można je oszukać.

  • Linux: Tutaj wszystko jest plikiem, nawet informacje o procesach. Każdy działający proces ma swój katalog w pseudo-systemie plików /proc, nazwany numerem PID procesu. Narzędzia typu ps po prostu czytają pliki w /proc/<PID>/ (np. stat, status) aby pobrać nazwę procesu, jego stan, zużycie pamięci itd.. Innymi słowy, lista procesów generowana jest na podstawie zawartości katalogu /proc – jeśli tam czegoś nie widać, to nie istnieje z punktu widzenia ps.
  • Windows: W jądrze Windows każdy proces jest reprezentowany przez strukturę EPROCESS, a wszystkie te struktury są połączone w listę dwukierunkową (doubly-linked list) poprzez wskaźniki ActiveProcessLinks. Gdy uruchamiasz Menedżer zadań, polecenie tasklist lub Get-Process w PowerShell, system iteruje przez tę listę EPROCESS, zbierając informacje o każdym procesie. Jeśli procesu nie ma na liście, narzędzia użytkownika również go nie pokażą.

Z powyższego wynika prosty fakt: aby ukryć proces, atakujący musi zmanipulować mechanizm pozyskiwania listy procesów. Można to osiągnąć na dwa sposoby:

  1. W przestrzeni użytkownika (user-mode) – oszukać programy wyświetlające procesy (np. ps, Task Manager) poprzez ingerencję w wywołania funkcji bibliotecznych czy API, zanim informacja dotrze do użytkownika.
  2. W przestrzeni jądra (kernel-mode) – zmodyfikować dane lub funkcje u źródła, np. usunąć proces z listy w jądrze albo podmienić funkcje systemowe, tak by „nie widziały” ukrywanego procesu.

Przyjrzyjmy się obu kategoriom, zarówno w Linuksie, jak i Windows.

Ukrywanie procesów w Linux

Techniki user-mode w Linuksie (np. LD_PRELOAD)

Wyobraź sobie, że masz dostęp roota na serwerze i chcesz ukryć proces przed ps. Zamiast modyfikować kernel, możesz zastosować sprytną sztuczkę w przestrzeni użytkownika: podmienić funkcje biblioteki standardowej używane przez programy takie jak ps. W Linuksie służy do tego zmienna środowiskowa LD_PRELOAD, pozwalająca wstrzyknąć własną bibliotekę (.so) przed załadowaniem standardowych bibliotek. W efekcie Twoje wersje funkcji mogą przechwycić wywołania zanim trafią do oryginalnej biblioteki. To analogiczne do mechanizmu AppInit_DLLs w Windows czy DYLD_INSERT_LIBRARIES w macOS.

Jaki efekt możemy osiągnąć? Na przykład możemy przechwycić wywołanie funkcji czytającej plik z /proc i ukryć tam nasz proces. Autor bloga affix pokazał to na przykładzie procesu pipewire: przechwycił funkcję read() (czytającą m.in. pliki /proc/<PID>/stat) i sprawdził, czy bufor zaczytany z pliku zawiera nazwę procesu pipewire. Jeśli tak – zwracał, że plik jest pusty (zero bajtów), oszukując ps, który myślał, że procesu brak. Poniżej fragment uproszczonego kodu w C demonstrującego tę technikę:

#define PROC_NAME "pipewire"

ssize_t read(int fd, void *buf, size_t count) {
    // Pobranie oryginalnej funkcji read
    ssize_t (*orig_read)(int, void*, size_t);
    orig_read = dlsym(RTLD_NEXT, "read");

    // Wywołanie oryginału
    ssize_t result = orig_read(fd, buf, count);

    // Sprawdzenie, czy bufor zawiera nazwę ukrywanego procesu
    if (strstr(buf, PROC_NAME)) {
        // Udajemy, że nic nie przeczytaliśmy (plik pusty)
        return 0;
    }
    return result;
}

Tak skompilowaną bibliotekę (.so) ładujemy zmienną LD_PRELOAD przed uruchomieniem ps lub – dla trwałości – wpisujemy jej ścieżkę do systemowego pliku /etc/ld.so.preload. Efekt? Proces z nazwą pipewire znika z listy ps. Oczywiście w praktyce rootkity user-mode mogą ukrywać wiele nazw procesów jednocześnie, filtrować po fragmentach nazw itp. Ważne, że nie dotykają jądra – żerują na tym, że narzędzia korzystają z bibliotek, które można podmienić.

Inna odmiana tej techniki to przechwycenie funkcji readdir() zwracającej listę plików (plikami są też katalogi z PID w /proc). Możemy np. nadpisać readdir() tak, by pomijała wpisy odpowiadające wybranym PID. W ten sposób dowolny program iterujący po /proc (w tym ps czy ls /proc) nie zobaczy ukrytych PID-ów. Istnieją gotowe implementacje – np. prosty projekt Preload Hook na GitHub pokazuje, jak czytać listę ukrywanych plików z konfiguracji i chować je w wynikach readdir().

Warto zauważyć, że metody user-mode są stosunkowo proste do wykonania (wystarczy biblioteka i zmiana zmiennej środowiskowej), ale mają ograniczenia. Działają per proces – tzn. aby oszukać ps, musimy uruchomić ps z zainfekowanym środowiskiem (LD_PRELOAD). Jeśli administrator uruchomi statycznie skompilowane narzędzie do wypisania /proc lub skorzysta z innego sposobu, może wykryć nasz proces. Dlatego bardziej zaawansowane rootkity często idą głębiej, w jądro.

Techniki kernel-mode w Linuksie (rootkity LKM)

Rootkit jądra potrafi ukryć proces dużo skuteczniej, bo działa na poziomie systemowym, niezależnie od tego, jakim narzędziem administrator szuka procesów. Najczęściej spotykane są rootkity w formie Loadable Kernel Module (LKM), czyli modułu, który można załadować do działającego jądra Linuksa. Mając uprawnienia roota (lub wykorzystując exploit na jądro), atakujący może wstrzyknąć taki moduł i od tej pory kernel będzie sprzymierzeńcem atakującego.

Jak kernel-mode rootkit ukrywa procesy? Dwie główne techniki to: podmiana funkcji systemowych (syscall hooking) albo bezpośrednia manipulacja strukturami danych jądra.

  • Hooking syscalls: Moduł może odnaleźć tablicę wskaźników na funkcje systemowe (sys_call_table) i podmienić np. wpis dla funkcji odpowiedzialnej za czytanie katalogu (getdents/getdents64). To właśnie te funkcje stoją za listowaniem zawartości folderów i są używane przez programy typu ls czy ps. Przechwytując sys_getdents w kernelu, rootkit może odfiltrować z wyników pewne wpisy – zarówno dotyczące plików, jak i procesów (katalogów PID w /proc). Przykładowy kod takiego hooka w module kernela: oryginalna funkcja jest zachowana, wywoływana, a następnie jej wynik (lista wpisów) jest przeglądany. Jeśli któryś wpis zawiera np. nazwę pliku “malicious_file” albo PID naszego ukrywanego procesu – zostaje usunięty z bufora wyników przed zwróceniem do przestrzeni użytkownika. Efekt: nawet bez żadnych specjalnych bibliotek, wszystkie narzędzia korzystające z tych syscalls dostają “ocenzurowany” obraz systemu. Współczesne rootkity LKM często używają różnych metod hookowania: modyfikacja sys_call_table to klasyka, ale na nowszych jądrach bywa utrudniona (wymaga np. wyłączenia ochrony pamięci tylko do odczytu). Dlatego spotyka się też hooking poprzez kprobes, ftrace czy ingerencję w funkcje VFS (Virtual File System) odpowiedzialne za iterację po katalogach. Niezależnie od metody – cel jest ten sam: wprowadzić własną logikę filtrującą tam, gdzie kernel zbiera listę procesów czy plików.
  • DKOM w Linuksie? W Windows za chwilę omówimy Direct Kernel Object Manipulation, czyli bezpośrednie modyfikacje struktur procesu. W Linuksie istnieje podobny koncept: proces jest reprezentowany przez strukturę task_struct, a wszystkie procesy są połączone w listę (dokładniej, każdy ma wskaźnik tasks do następnego procesu w kolejności stworzenia). Rootkit może usunąć dany task_struct z tej listy (np. poprzez wywołanie list_del_init na odpowiednim wpisie). W efekcie procesu nie będzie w pętli iterującej wszystkie procesy. Jednak to trudniejsze, bo jądro Linuksa utrzymuje wiele powiązanych list i struktur (proces jest częścią drzewka procesów, listy wszystkich zadań, różnych hash table itd.). Dlatego popularniejsze jest hooking funkcji niż ręczne modyfikowanie struktur – choć i takie próby się zdarzały w starszych rootkitach.

Przykłady: Na przestrzeni lat powstało wiele rootkitów LKM dla Linuksa, także open-source. Przykłady to Adore-Ng, Suterusu, Reptile, Diamorphine – często używane przez grupy APT i malware do ukrywania procesów miningu czy backdoorów. Np. rootkit Diamorphine (używany przez grupę TeamTNT) umożliwia ukrycie wskazanego procesu (lub wszystkich z danym UID) jednym poleceniem – jest to zaimplementowane właśnie przez hook w funkcji kernela i manipulację listą procesów. Innym ciekawym przypadkiem jest Reptile – potężny rootkit LKM z funkcją backdoora, wykryty w kampaniach malware w 2022 roku.

Narzędzia analizy i wykrywanie (Linux)

Skoro nawet kernel może kłamać na temat procesów, to jak je wykryć? Jako inżynierowie bezpieczeństwa musimy czasem wejść głębiej niż standardowe narzędzia:

  • strace – to narzędzie pozwala podejrzeć jak program uzyskuje listę procesów. Dzięki strace ps dowiemy się, że ps korzysta z plików w /proc oraz wywołuje syscalle takie jak openat, read czy getdents. Jeśli podejrzewamy rootkit, strace może też ujawnić dziwne błędy czy brak wyników tam, gdzie powinny być (np. read zwracający 0 bajtów dla konkretnego PID, co w normalnych warunkach jest mało prawdopodobne).
  • lsof, netstat – jeśli ukryty proces utrzymuje połączenie sieciowe lub otwarty plik, czasem narzędzia te mogą go zdradzić. Jednak wiele rootkitów hookuje także funkcje sieciowe (ukrywając nasłuchujące porty) i funkcje open/readdir używane przez lsof. Proste ukrycie user-mode (np. tylko przed ps) może zostać wykryte przez lsof – np. zobaczymy proces w liście otwartych plików, choć nie ma go w ps.
  • unhide – to dedykowane narzędzie forensics (dostępne m.in. w Kali) do wykrywania ukrytych procesów. Działa sprytnie, wykonując wiele testów porównawczych między różnymi źródłami informacji o procesach. Przykładowo, unhide porównuje listę procesów z /proc z listą z ps, porównuje wyniki wywołań syscalls z informacjami z biblioteki C, a nawet przeprowadza bruteforce: iteruje po możliwych PID i próbuje wywołać kill(0, PID) aby sprawdzić, czy proces istnieje, mimo że nie widać go w standardowym spisie. Rozbieżności między tymi metodami sygnalizują obecność rootkita. unhide ma tryby testów takie jak proc, sys, procfs, reverse itp. (ich opisy można znaleźć w man unhide). Jest to dość czasochłonne (pełny brute force może trwać kilka minut), ale potrafi wykryć nawet sprytniejsze rootkity. Twórcy rootkitów oczywiście znają unhide i czasem starają się ukryć także przed nim – ale to trudne, bo musieliby perfekcyjnie symulować wszystkie źródła danych systemowych jednocześnie.
  • Volatility (memory forensics) – ostateczna linia obrony to zrzut pamięci RAM i analiza off-line. Framework Volatility posiada wtyczki takie jak linux_pslist (lista procesów znana systemowi) oraz linux_psscan (przeszukiwanie surowej pamięci w poszukiwaniu struktur procesów). Ukryty proces nie pojawi się w pslist, ale zostanie znaleziony przez psscan, bo struktura task_struct wciąż istnieje w pamięci. Dla Windows jest analogiczny plugin psscan/pslist czy psxview, który potrafi zestawić różne metody i wskazać, które procesy są ukryte. To potężna metoda – rootkit działający w systemie nie może „wymazać” swoich śladów z całej pamięci, więc analiza forensic często demaskuje takie próby.

Podsumowując, w Linuksie wykrywanie ukrytych procesów sprowadza się do szukania niespójności: między /proc a narzędziami, między różnymi źródłami informacji, albo między pamięcią a tym, co pokazuje system. Administrator powinien być czujny na np. procesy widoczne w top, które nie pojawiają się w ps, albo port nasłuchujący wykazany przez netstat, za którym niby nie stoi żaden proces.

Ukrywanie procesów w Windows

W Windows także wyróżniamy podejścia user-mode i kernel-mode, choć realizacja różni się od linuksowej.

Techniki user-mode w Windows (hooking API)

Rootkit user-mode w Windows zwykle przyjmuje postać biblioteki DLL, którą złośliwy program wstrzykuje do innych procesów, by hookować wywołania API. Przykładowo, wiele programów do wyświetlania procesów korzysta z funkcji NT API NtQuerySystemInformation z parametrem SystemProcessInformation, aby pobrać listę procesów z jądra. Sprytna biblioteka może przechwycić to wywołanie i odfiltrować z zwracanej struktury informacje o wybranym procesie. Rezultat: Task Manager, Process Hacker, tasklist – wszystkie dostają okrojoną listę i nie pokazują naszego procesu.

Jak to wygląda w kodzie? Poniżej fragment uproszczonego przykładu w C++ oparty o technikę Detours (Microsoft Detours to popularna biblioteka do hookowania funkcji API w pamięci):

#define HIDE_PROC L"notepad.exe"
typedef NTSTATUS (NTAPI *NtQuerySystemInformation_t)(
    SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
NtQuerySystemInformation_t origNtQuerySysInfo;

NTSTATUS NTAPI HookedNtQuerySystemInformation(
        SYSTEM_INFORMATION_CLASS cls, PVOID buf, ULONG len, PULONG retLen) {
    // Wywołaj oryginalną funkcję
    NTSTATUS status = origNtQuerySysInfo(cls, buf, len, retLen);
    if (NT_SUCCESS(status) && cls == SystemProcessInformation) {
        // Iteruj przez listę procesów w buforze
        PSYSTEM_PROCESS_INFORMATION proc = (PSYSTEM_PROCESS_INFORMATION) buf;
        PSYSTEM_PROCESS_INFORMATION prev = NULL;
        while (proc->NextEntryOffset != 0) {
            if(proc->ImageName.Buffer && 
               wcsstr(proc->ImageName.Buffer, HIDE_PROC) != NULL) {
                // Usuwamy ten wpis procesu z listy
                if(prev) {
                    prev->NextEntryOffset += proc->NextEntryOffset;
                } else {
                    // Usuwany proces jest pierwszym na liście
                    // Przesuwamy początek bufora na następny wpis
                    (BYTE*)buf += proc->NextEntryOffset;
                }
            }
            prev = proc;
            proc = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)proc + proc->NextEntryOffset);
        }
    }
    return status;
}

Taki hook iteruje przez zwróconą listę procesów i jeśli natrafi na np. notepad.exe – wycina go z listy (modyfikując wskaźniki NextEntryOffset). Oczywiście, żeby to zadziałało, nasza DLL-ka musi zostać załadowana w procesie, który wywołuje NtQuerySystemInformation. Dlatego rootkit user-mode często injektuje się w konkretne procesy (np. w Task Managera, zanim ten pobierze listę) lub – bardziej uniwersalnie – wykorzystuje mechanizmy Windows do automatycznego ładowania DLL (wspomniany AppInit_DLLs, Hooki GUI, itp) we wszystkich procesach korzystających z określonych bibliotek.

Analogicznie można hookować wyższe poziomy, np. funkcję WinAPI EnumProcesses() z PSAPI.dll, która wewnętrznie też wywoła NtQuerySystemInformation. Można też podmienić wskaźniki vTable w COM – np. WMI używa COM do wylistowania Win32_Process, co złośliwa biblioteka może przekierować.

W przeciwieństwie do Linuksa, nie ma tu prostego odpowiednika LD_PRELOAD, ale istnieją całe frameworki do pisania rootkitów user-mode. Przykładowo publicznie dostępny rootkit r77 działa w całości w user-space, injektując się w procesy systemowe i ukrywając wskazane procesy (oraz inne artefakty) poprzez hooking API.

Ograniczenia: Rootkit user-mode jest łatwiejszy do wdrożenia (nie wymaga sterownika, działa na kontach bez dostępu do jądra), ale jest też łatwiejszy do wykrycia. Działając w przestrzeni użytkownika, zostawia ślady – np. patchuje w pamięci kod funkcji w procesie (co narzędzia mogą wykryć), albo dołącza nietypową bibliotekę do procesów. Co więcej, obejmuje tylko procesy, w których został załadowany. Administrator może użyć innego mechanizmu enumeracji procesów (np. bezpośrednio odpytując kernel debugerem) i wykryć intruza.

Techniki kernel-mode w Windows (DKOM i hooking jądra)

Najgroźniejsze rootkity windowsowe działają jako sterowniki kernel-mode. Mają one pełnię władzy nad jądrem, podobnie jak opisane wcześniej LKM w Linuksie. Dwie podstawowe metody ukrywania procesu w Windows to:

  • DKOM (Direct Kernel Object Manipulation) – czyli bezpośrednia modyfikacja struktur EPROCESS. Przypomnijmy: system utrzymuje listę wszystkich procesów poprzez powiązane struktury EPROCESS i ich pola ActiveProcessLinks. Rootkit może usunąć dany proces z tej listy, modyfikując wskaźniki w strukturach poprzedzającej i następnej tak, by ominęły nasz proces. To jak wyjęcie ogniwa z łańcucha – z punktu widzenia reszty systemu procesu nie ma. Ta technika została spopularyzowana przez słynny rootkit FU (autorstwa Jamesa Butlera i Sherri Sparks w połowie lat 2000.), który potrafił ukrywać procesy właśnie manipulując strukturami w pamięci jądra. Co ważne, takie ukrycie działa globalnieżaden proces w userland (Task Manager, wmic, ani nawet narzędzia Sysinternals) nie zobaczy delikwenta, bo wszyscy pytają o listę procesów kernela, a tam procesu brak. Minusem DKOM jest to, że nowsze wersje Windows mają mechanizmy ochronne (np. PatchGuard w 64-bitowych Windows) wykrywające lub uniemożliwiające modyfikacje krytycznych struktur jądra. Nagła zmiana listy procesów może wywołać BSOD, jeśli PatchGuard się zorientuje. Jednak niektóre rootkity potrafią to obchodzić lub działają na wersjach bez PatchGuard (np. Windows test mode, starsze systemy). W kontrolowanych warunkach eksperymentalnych można tak modyfikować kernel nawet na Windows 10, ale w praktyce malware raczej unika wprost wyłączania PatchGuarda, bo to trudne bez exploitów.
  • Hooking w jądrze (SSDT hooking) – alternatywnie rootkit może nie modyfikować struktur, a podmienić funkcje kernela odpowiedzialne za zwracanie listy procesów. W Windows odpowiada za to m.in. wewnętrzna funkcja NtQuerySystemInformation (ta sama, którą wcześniej hookowaliśmy w user-mode, ale teraz z poziomu jądra). W jądrze Windows istnieje tablica SSDT (System Service Descriptor Table) – odpowiednik linuxowego sys_call_table – trzymająca wskaźniki na funkcje obsługujące wywołania systemowe. Rootkit może przejąć wpis dla NtQuerySystemInformation, tak by, gdy jakikolwiek proces (np. Task Manager) poprosi o listę procesów, nasza funkcja w kernelu ocenzurowała wynik (np. usunęła proces o określonym PID czy nazwie z bufora przed zwróceniem). Efekt końcowy podobny do DKOM – proces niewidoczny – ale zrealizowany inaczej. Czasem rootkit filtruje też inne powiązane API, np. ZwEnumerateProcesses albo funkcje odpowiedzialne za enumerację w narzędziach bezpieczeństwa.

W odróżnieniu od DKOM, hooking SSDT jest potencjalnie mniej „inwazyjny” dla spójności struktur, ale PatchGuard również potrafi wykryć zmiany w SSDT na 64-bit Windows i wywołać BSOD. Dlatego współczesne rootkity kernelowe mogą używać jeszcze bardziej wyrafinowanych trików: hooking na poziomie hypervisora (VM rootkits), czy inline hooking z wykorzystaniem nadpisywania kilku bajtów początku funkcji (trudniejsze do wykrycia przez PG, bo nie monitoruje każdego bajtu kodu). Istnieją też techniki jak DKOM ukrywające wątki lub obiekty zombie, ale to wykracza poza nasz temat.

Wykrywanie ukrytych procesów w Windows

Rozpoznanie, że w systemie działa ukryty proces, jest trudnym zadaniem – na tym polega skuteczność rootkita. Istnieje jednak kilka wskazówek i narzędzi:

  • Volatility i pamięć fizyczna – podobnie jak na Linuksie, analiza zrzutu pamięci Windows jest najpewniejsza. Wtyczki psscan/pslist/psxview potrafią wykazać, że pewien proces istnieje w pamięci (np. znaleziono jego strukturę EPROCESS, wątki, handle) mimo że nie figuruje na liście aktywnych procesów. Jeśli Volatility pokazuje proces w psscan a brak go w pslist, to niemal pewny znak rootkita typu DKOM.
  • Narzędzia Sysinternals – standardowe narzędzia (Process Explorer, Tasklist, itp.) dadzą się oszukać, bo korzystają z tych samych API co system. Ale Sysinternals ma też RootkitRevealer (historyczne narzędzie do wykrywania rootkitów) oraz technikę porównywania informacji z różnych źródeł – podobnie jak unhide na Linuksie. Nowsze skanery antyrootkitowe (GMER, Kaspersky TDSSKiller, Microsoft Defender ATP) wykrywają np. niezgodności w SSDT, obecność niepodpisanych sterowników, zmiany w strukturach jąder. Process Explorer potrafi np. pokazać, że PID istnieje, choć proces bez nazwy – w przypadku wykrycia niespójności. W praktyce administrator Windows powinien być wyczulony na objawy typu: znikające usługi, działające porty bez procesu w netstat, czy niegasnące obciążenie CPU przypisane do „nieistniejącego” procesu.
  • Analiza spójności systemu – Można ręcznie spróbować różnych metod enumeracji: np. WMI (zapytanie Win32_Process), PowerShell Get-Process, bezpośrednio API Native via mały własny program, albo nawet debuger kernelowy (!process w WinDbg). Ukryty proces może ujawnić się w jednym z tych kanałów, jeśli rootkit nie pokrył wszystkich. Przykładowo, zdarzały się rootkity user-mode, które ukrywały procesy w Task Managerze, ale zapominały o wierszu poleceń tasklist (lub odwrotnie). Czasem też logi systemowe mogą coś zdradzić – np. proces może być ukryty, ale jeśli generuje jakiś błąd w systemie (np. wpis w logu aplikacji), to pozostaje ślad.
  • Bezpieczeństwo warstwy kernela – W kontekście prewencji i wykrywania, warto korzystać z mechanizmów takich jak Secure Boot i wymaganie podpisanych sterowników (utrudnia wczytanie niezaufanego rootkita do jądra) oraz rozwiązania EDR/AV, które monitorują integralność kernela. Nowoczesne systemy mają wbudowane zabezpieczenia (PatchGuard, Driver Signature Enforcement) – jeśli nie zostały wyłączone, znacznie podnoszą poprzeczkę atakującym. Jednak historia pokazuje, że atakujący potrafili np. korzystać z legalnie podpisanych sterowników z lukami, by wstrzyknąć swój kod do kernela (tzw. BYOVD – Bring Your Own Vulnerable Driver). Dlatego ważny jest bieżący nadzór.

Dlaczego to ma znaczenie?

Ukrywanie procesów to nie sztuczka dla zabawy – to kluczowy element zaawansowanych rootkitów i kampanii malware. Jeśli atakujący potrafi uruchomić swój kod i ukryć jego proces przed administratorem, zyskuje stałą, trudną do namierzenia obecność w systemie. Przykłady z życia wzięte? Proszę bardzo:

  • Grupy cyberprzestępcze używały rootkitów do ukrycia procesów koparek kryptowalut w chmurach (TeamTNT z rootkitem Diamorphine, Skidmap malware na Linuksie). Dzięki temu serwery kopały monero miesiącami, a administratorzy widzieli co najwyżej spadek wydajności, nie mogąc namierzyć procesu obciążającego CPU.
  • APT (Advanced Persistent Threat), jak chińska grupa Winnti (APT41), korzystały z ukrywających rootkitów (Adore-Ng, na bazie którego powstał nowszy Syslogk) w celu ukrycia backdoora w zainfekowanych firmach. Dzięki ukryciu procesu atakujący mógł zachować dostęp do systemu nawet podczas rutynowych inspekcji – żaden proces w Task Managerze nie zdradzał obecności malware.
  • Rootkity potrafią ukrywać procesy narzędzi bezpieczeństwa zainstalowanych przez atakujących. Np. keyloggery, sniffery, skanery działające po przejęciu systemu mogą być ukryte, by ofiara ich nie wykryła. Z perspektywy Blue Teamu, brak wiedzy o takich procesach oznacza, że monitoring może je pominąć, backupy będą zawierać backdoory itp.

Z punktu widzenia obrony, świadomość tych technik to must-have dla każdego admina i security inżyniera. W praktyce zalecamy:

  • Ograniczać możliwości wczytania rootkita – np. na Linuksie wyłączać nieużywane moduły kernela, stosować podpisywanie modułów i mechanizmy typu SELinux/AppArmor do kontroli kto może załadować moduł. Na Windows – nie wyłączać Driver Signature Enforcement, używać Secure Boot, regularnie aktualizować sterowniki (by usunąć podatne sterowniki mogące posłużyć do BYOVD).
  • Monitorować kluczowe zdarzenia – załadowanie niespodziewanego modułu kernela (Linux: logi kern.info, dmesg lub narzędzia typu osquery/auditd; Windows: zdarzenia systemowe to sygnał alarmowy). Podobnie jakikolwiek proces uruchamiający się z uprawnieniami SYSTEM/root, którego nie widać w normalnych listach, powinien wzbudzić czujność. W Linuksie można wykorzystać narzędzia typu Linux Kernel Integrity (LKMs) lub nawet proste skrypty cronowe porównujące ps z ls /proc.
  • Używać narzędzi wykrywających rootkity – wspomniane unhide, a także rkhunter, chkrootkit na Linuksie, oraz dedykowane skanery na Windows (GMER, TDSSKiller, Norton Power Eraser). One mają wbudowaną wiedzę o typowych symptomach rootkitów. Nie zastąpi to jednak stałego monitoringu. W środowiskach krytycznych stosujcie także systemy EDR z funkcją tamper protection, które potrafią wykryć, gdy np. proces lsass.exe nagle przestaje być widoczny w systemie – bo kto inny jak nie rootkit mógłby to spowodować?
  • Regularne analizy forensics – to już cięższy kaliber, ale warto okresowo robić skan pamięci RAM offline z użyciem Volatility lub Rekall, zwłaszcza gdy podejrzewamy, że coś jest nie tak (dziwne objawy, a antywirus milczy). Taka analiza wyciągnie na światło dzienne ukryte procesy, ukryte moduły, a nawet wskaże, które funkcje jądra zostały przehookowane (są wtyczki sprawdzające integralność SSDT, IDT itp.).

Na koniec najważniejsze: ukryty proces zawsze coś robi – może generować ruch sieciowy, obciążenie CPU/GPU, operacje dyskowe. Obserwuj wskaźniki wydajności i ruchu sieciowego. Często rootkit jest wykrywany nie bezpośrednio, a właśnie przez anomalię w zachowaniu systemu. Gdy coś się nie zgadza (np. dioda dysku mruga, ale iotop nie pokazuje procesu, CPU obciążony ale w top pusto) – to czerwony alert.

Checklist: wykrywanie i utrudnianie ukrywania procesów

  • Porównuj różne źródła informacji: zestaw wyniki ps/tasklist z innymi narzędziami (WMI, ls /proc, netstat, lsof, Volatility). Niespójności mogą wskazać ukryty proces.
  • Sprawdzaj integralność systemu: monitoruj sumy kontrolne kluczowych bibliotek (np. ntdll.dll, libc.so), czy nie zostały spatchowane w pamięci. Wykrywaj zainjektowane nietypowe moduły (np. dziwne .so w procesach lub DLL-e nieznanego pochodzenia).
  • Kontroluj moduły kernela: regularnie przeglądaj listę załadowanych modułów (lsmod, DriverView lub sc query type= driver w Windows). Niezidentyfikowany moduł może oznaczać rootkita. Rozważ zablokowanie możliwości ładowania modułów po starcie (Linux: parametry kernela, Windows: AppLocker dla sterowników).
  • Reaguj na podejrzane objawy: ukryty proces może zdradzić się zasobami. Jeśli widzisz obciążenie systemu bez widocznego procesu, zwiększone zużycie pamięci, otwarte porty bez procesu – traktuj to poważnie. Użyj narzędzi forensics, zanim zresetujesz maszynę (by nie utracić śladów z pamięci).
  • Aktualizuj i wzmacniaj system: stosuj aktualizacje zabezpieczeń, zwłaszcza kernela. Upewnij się, że w Windows działają mechanizmy takie jak PatchGuard i Secure Boot, a w Linuksie że moduły wymagają podpisu (module signature enforcement). To nie daje nietykalności, ale eliminuje część prostych ataków.
  • Trenuj swój zespół: wiedza to broń. Przeprowadź wewnętrzne warsztaty z wykrywania rootkitów – np. w kontrolowanym środowisku uruchom legitny rootkit (jest kilka edukacyjnych na GitHub) i pozwól adminom spróbować go wykryć narzędziami. Dzięki temu nabiorą intuicji, gdzie patrzeć.

Podsumowanie

Ukrywanie procesów to zaawansowana sztuczka używana przez malware, ale zrozumienie jej mechanizmów pozwala nam projektować lepsze zabezpieczenia. Omówiliśmy przykłady od prostego hacku z LD_PRELOAD po modyfikacje list EPROCESS (DKOM) i hooking syscalli. Jeśli dotarłeś aż tutaj, proponujemy praktyczny eksperyment: spróbuj w bezpiecznym labie zastosować któryś z opisanych trików. Np. napisz krótką bibliotekę hookującą readdir() w Linuksie lub użyj Detours, by ukryć proces testowy przed Tasklist. Zobacz w praktyce, jak wygląda taki ukryty proces i spróbuj go potem wykryć za pomocą Volatility albo unhide. To świetna lekcja oporności systemów – i najlepszy sposób, by przygotować się na spotkanie z prawdziwym rootkitem. Powodzenia i bądź czujny!

Bibliografia