Jak ustalić nazwę procesu korzystającego z sieci

Spis treści

Konfigurując filtr pakietów iptables/nftables na Debianie zwykle nie przykładamy większej wagi do procesów, które chcą nawiązać połączenia wychodzące z naszego linux'owego hosta. Mamy przecież "skonfigurowany" firewall w łańcuchach INPUT i FORWARD i wszelkie zagrożenia z sieci nie powinny nas dotyczyć. Problem w tym, że jeśli jakiś złowrogi proces zostanie uruchomiony w naszym systemie, to jest on w stanie komunikować się ze światem zewnętrznym praktycznie bez żadnych ograniczeń za sprawą braku jakichkolwiek reguł w łańcuchu OUTPUT . Można oczywiście temu zaradzić budując zaporę sieciową na bazie cgroups , gdzie każda aplikacja będzie miała oznaczone pakiety, przez co będzie można je rozróżnić i zablokować albo przepuścić przez filter. W tym wpisie jednak nie będziemy się zajmować konstrukcją tego typu FW, tylko spróbujemy sobie odpowiedzieć na pytanie jak namierzyć proces, który komunikuje się z siecią (lub też próbuje), posiadając jedynie log iptables/nftables .

Narzędzia netstat i ss

Zwykle, gdy poszukujemy informacji na temat procesów korzystających z sieci, to sięgamy po narzędzia typu netstat albo ss . No i faktycznie są one w stanie powiedzieć nam sporo na temat aktualnie nawiązanych przez procesy połączeń.

Poniżej przykład netstat:

# netstat -napletu
...
tcp        0      0 192.168.0.135:54182     104.18.70.113:443       ESTABLISHED 1000       3406460    110338/firefox
tcp        0      0 192.168.0.135:61156     95.101.72.207:80        ESTABLISHED 1000       3419968    110338/firefox
tcp        0      0 192.168.0.135:53406     104.16.52.111:443       ESTABLISHED 1000       3405672    110338/firefox
tcp        0      0 192.168.0.135:61032     151.101.114.165:443     ESTABLISHED 1000       3402793    110338/firefox
tcp        0      0 192.168.0.135:54184     104.18.70.113:443       ESTABLISHED 1000       3406461    110338/firefox
tcp        0      0 192.168.0.135:62966     198.252.206.25:443      ESTABLISHED 1000       3341379    110338/firefox

A niżej zaś przykład ss:

# ss -tupan
...
tcp    ESTAB      0       0        192.168.0.135:54182     104.18.70.113:443     users:(("firefox",pid=110338,fd=198))
tcp    ESTAB      0       0        192.168.0.135:61156     95.101.72.207:80      users:(("firefox",pid=110338,fd=83))
tcp    ESTAB      0       0        192.168.0.135:53406     104.16.52.111:443     users:(("firefox",pid=110338,fd=211))
tcp    ESTAB      0       0        192.168.0.135:61032   151.101.114.165:443     users:(("firefox",pid=110338,fd=175))
tcp    ESTAB      0       0        192.168.0.135:54184     104.18.70.113:443     users:(("firefox",pid=110338,fd=212))
tcp    ESTAB      0       0        192.168.0.135:62966    198.252.206.25:443     users:(("firefox",pid=110338,fd=62))

W obu przypadkach mamy informacje na temat nazwy procesu oraz jego PID'u, no i oczywiście szereg danych na temat samego połączenia. W tym przypadku jest wszystko oczywiste i odnalezienie szukanego procesu nie nastręcza żadnych trudności.

Co jednak w przypadku, gdy proces nawiązuje lub próbuje nawiązać połączenie i bardzo szybko się sam unicestwi? W tych listingach wyżej takiego procesu już nie zobaczymy. Jak zatem ustalić, które procesy w naszym systemie próbują się łączyć z siecią?

Logi iptables/nftables

Gdy nie jesteśmy pewni, czy cały ruch sieciowy wychodzący z naszego linux'a został odpowiednio sklasyfikowany przez dodane reguły iptables/nftables , to możemy wrzucić do filtra regułkę logującą pakiety i zobaczyć czy coś zostanie zalogowane. Poniżej jest przykładowa reguła dla nftables :

add rule inet filter OUTPUT limit rate 30/minute burst 1 packets log flags all prefix "* OUTPUT * " counter

Na razie nam nie zależy na tym, by cokolwiek jeszcze blokować i lepiej póki co jeszcze tego nie robić. Mając dodaną regułę, zaglądamy do logu systemowego. W tym przypadku został zalogowany poniższy komunikat:

Feb 07 18:42:35 morfikownia kernel: * IPTABLES:OUTPUT * IN= OUT=bond0 SRC=192.168.0.135
DST=104.81.106.31 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=2989 DF PROTO=TCP SPT=63798
DPT=443 SEQ=850951390 ACK=0 WINDOW=64240 RES=0x00 SYN URGP=0 OPT
(020405B40402080AA8F3EBDA0000000001030309) UID=1000 GID=1000

To co się rzuca od razu w oczy, to fakt, że proces, który wysłał ten pakiet, miał UID=1000 i GID=1000 . No to zawęża nieco krąg poszukiwań ale wciąż odnalezienie tego procesu na podstawie powyższego komunikatu nie jest zbytnio możliwe.

Narzędzie auditd

Władając informacją na temat czasu zalogowania tego pakietu, można odszukać z dużą dokładnością proces, który chciał ten pakiet wysłać. Potrzebne nam jednak będzie dodatkowe oprogramowanie, które musimy zainstalować w swoim systemie. W Debianie instalujemy paczkę auditd . Zawiera ona demona audytu, który jest w stanie zalogować dosłownie wszystko. My nie będziemy logować co popadnie, a jedynie skupimy się na zalogowaniu wywołań systemowych typu connect .

Wszystko czego nam teraz trzeba to reguły, która zaloguje pożądane przez nas wywołanie systemowe. Tą regułę możemy na sztywno dodać do pliku /etc/audit/rules.d/audit.rules lub też stosowne opcje możemy podać w auditctl . Gdy dodajemy reguły do pliku, to trzeba przeładować demona. Poniżej jest reguła, która zaloguje wywołania connect :

# auditctl -a exit,always -F arch=b64 -S connect -k MYCONNECT

Wartość parametru -k może być dowolna. Jest to w zasadzie nic innego jak TAG, który oznaczy wszystkie zalogowane komunikaty tym co sobie tutaj ustawimy -- to ma więcej sensu, gdy mamy nieco więcej reguł. Aktualnie załadowane reguły można podejrzeć w poniższy sposób:

# auditctl -l
-a always,exit -F arch=b64 -S connect -F key=MYCONNECT

Wszystkie komunikaty audytu są logowane do pliku /var/log/audit/audit.log .

Czasami może się zdarzyć tak, że jakiś proces będzie próbował nawiązywać połączenia ale w logu audytu żadnych informacji na jego temat nie znajdziemy. Być może trzeba będzie dodać również wywołania socket i bind do tej powyższej reguły:

auditctl -a exit,always -F arch=b64 -S socket,connect,bind -k MYCONNECT

Logi w czytelnej dla człowieka formie (ausearch)

Gdy zajrzy się w plik /var/log/audit/audit.log , to informacje, które tam się znajdują nie są zbyt zrozumiałe czy też czytelne dla człowieka. Można oczywiście ręcznie zdekodować te logi ale zamiast się trudzić, to lepiej skorzystać z narzędzia ausearch . Jeśli mamy do czynienia z całą masą wywołań systemowych, to dobrze jest określić --start i --end podając w nich czas, który nas interesuje:

# cat /var/log/audit/audit.log | ausearch -i
...
----
type=PROCTITLE msg=audit(07/02/19 18:42:35.345:98867) : proctitle=wget --quiet -U firefox -o /dev/null -O /home/morfik/.conky/...
type=PATH msg=audit(07/02/19 18:42:35.345:98867) : item=0 name=/var/run/nscd/socket nametype=UNKNOWN cap_fp=none cap_fi=none cap_fe=0 cap_fver=0
type=CWD msg=audit(07/02/19 18:42:35.345:98867) : cwd=/media/Android/
type=SOCKADDR msg=audit(07/02/19 18:42:35.345:98867) : saddr={ fam=local path=/var/run/nscd/socket }
type=SYSCALL msg=audit(07/02/19 18:42:35.345:98867) : arch=x86_64 syscall=connect success=no exit=ENOENT(No such file or directory) a0=0x5 a1=0x7ffd307b3a50 a2=0x6e a3=0x6 items=1 ppid=68429 pid=68753 auid=morfik uid=morfik gid=morfik euid=morfik suid=morfik fsuid=morfik egid=morfik sgid=morfik fsgid=morfik tty=pts11 ses=19 comm=wget exe=/usr/bin/wget subj==conky (enforce) key=MYCONNECT
----
type=PROCTITLE msg=audit(07/02/19 18:42:35.345:98868) : proctitle=wget --quiet -U firefox -o /dev/null -O /home/morfik/.conky/...
type=PATH msg=audit(07/02/19 18:42:35.345:98868) : item=0 name=/var/run/nscd/socket nametype=UNKNOWN cap_fp=none cap_fi=none cap_fe=0 cap_fver=0
type=CWD msg=audit(07/02/19 18:42:35.345:98868) : cwd=/media/Android/
type=SOCKADDR msg=audit(07/02/19 18:42:35.345:98868) : saddr={ fam=local path=/var/run/nscd/socket }
type=SYSCALL msg=audit(07/02/19 18:42:35.345:98868) : arch=x86_64 syscall=connect success=no exit=ENOENT(No such file or directory) a0=0x5 a1=0x7ffd307b3c10 a2=0x6e a3=0x6 items=1 ppid=68429 pid=68753 auid=morfik uid=morfik gid=morfik euid=morfik suid=morfik fsuid=morfik egid=morfik sgid=morfik fsgid=morfik tty=pts11 ses=19 comm=wget exe=/usr/bin/wget subj==conky (enforce) key=MYCONNECT
----
type=PROCTITLE msg=audit(07/02/19 18:42:35.347:98869) : proctitle=wget --quiet -U firefox -o /dev/null -O /home/morfik/.conky/...
type=SOCKADDR msg=audit(07/02/19 18:42:35.347:98869) : saddr={ fam=inet laddr=127.0.2.1 lport=53 }
type=SYSCALL msg=audit(07/02/19 18:42:35.347:98869) : arch=x86_64 syscall=connect success=yes exit=0 a0=0x5 a1=0x7e5745179914 a2=0x10 a3=0x55c4af2df010 items=0 ppid=68429 pid=68753 auid=morfik uid=morfik gid=morfik euid=morfik suid=morfik fsuid=morfik egid=morfik sgid=morfik fsgid=morfik tty=pts11 ses=19 comm=wget exe=/usr/bin/wget subj==conky (enforce) key=MYCONNECT
----
type=PROCTITLE msg=audit(07/02/19 18:42:35.348:98875) : proctitle=wget --quiet -U firefox -o /dev/null -O /home/morfik/.conky/...
type=SOCKADDR msg=audit(07/02/19 18:42:35.348:98875) : saddr={ fam=inet laddr=104.81.106.31 lport=443 }
type=SYSCALL msg=audit(07/02/19 18:42:35.348:98875) : arch=x86_64 syscall=connect success=yes exit=0 a0=0x5 a1=0x7ffd307b4550 a2=0x10 a3=0x0 items=0 ppid=68429 pid=68753 auid=morfik uid=morfik gid=morfik euid=morfik suid=morfik fsuid=morfik egid=morfik sgid=morfik fsgid=morfik tty=pts11 ses=19 comm=wget exe=/usr/bin/wget subj==conky (enforce) key=MYCONNECT

Z tego logu wyżej wynika, że conky (oprofilowany przez AppArmor) wywołał sobie wget , który próbował połączyć się z adresem 104.81.106.31 na porcie 443 ale wcześniej posłał zapytanie DNS do dnscrypt-proxy , który nasłuchuje na adresie 127.0.2.1 i na porcie 53 . Komunikacja DNS została przepuszczona wcześniej na zaporze sieciowej ale brakło stosownej reguły od conky/wget . W podobny sposób można namierzyć praktycznie każdy inny proces, który z jakiegoś powodu próbuje się komunikować ze światem zewnętrznym, a mając informacje o procesie, w tym też i ścieżkę do pliku binarnego, to możemy już sobie ten proces zablokować lub przepuścić na zaporze bez większego problemu.

Ustalenie nazwy procesu i numeru PID via FTrace (kprobes)

Inną metodą, która jest w stanie nam pomóc w ustaleniu jaki proces chce komunikować się z siecią jest FTrace (kprobes), który korzysta z systemu plików tracefs montowanego w /sys/kernel/tracing/ (we wersjach kernela do 4.1, był wykorzystywany system plików debugfs montowany w /sys/kernel/debug/tracing/ ) . Możemy nakazać kernelowi by logował każde nowe połączenie, które dowolny program będzie otwierał, przykładowo:

# echo 'p:m security_socket_connect' >> /sys/kernel/tracing/kprobe_events
# echo 1 > /sys/kernel/tracing/events/kprobes/m/enable
# cat /sys/kernel/tracing/trace_pipe

By ten mechanizm wyłączyć, wpisujemy w terminal poniższe polecenia:

# echo 0 > /sys/kernel/tracing/events/kprobes/m/enable
# echo '-:kprobes/m' >>  /sys/kernel/tracing/kprobe_events
Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.