Ukrywamy nazwę procesu i jego parametry za pomocą programu zapper
Napisał: Patryk Krawaczyński
Dzisiaj w Bezpieczeństwo, Pen Test Brak komentarzy. (artykuł nr 940, ilość słów: 2302)
Z
apper to mały program, który za pomocą ptrace() manipuluje stosem wektora pomocniczego (ang. auxiliary vector) formatu ELF (ang. Executable and Linkable Format), który posiada format tablicy w postaci par: „klucz – wartość” i jest przekazywany z jądra systemu do procesu w przestrzeni użytkownika, gdy program jest uruchamiany za pomocą funkcji systemowej execve(). Celem wspomnianego wektora (nazywanego również tablicą pomocniczą) jest dostarczenie programowi dodatkowych informacji konfiguracyjnych i środowiskowych, które są niezbędne lub przydatne dla programu w trakcie jego działania (np. jak argumenty linii poleceń czy zmienne środowiskowe – argc, argv, envp).
Zapper przechwytuje moment, gdy jądro systemu (ang. kernel) przekazuje opcje uruchamianego programu (za pomocą wspomnianego execve()): przenosi oryginalne opcje programu do nowej lokalizacji w pamięci, a następnie niszczy starą. Z perspektywy jądra (i pakietu narzędzi systemowych korzystających z procps) opcje programu przestają istnieją istnieć. Następnie zapper „naprawia” wskaźniki w tablicy pomocniczej programu i oddaje wykonanie z powrotem uruchamianemu programowi (za pomocą PTRACE_CONT). Wykonywany program przez zapper jest później śledzony tylko pod kątem wszelkich, dalszych wywołań typu fork() lub execve() (aby powtórzyć sztuczkę od nowa dla wątków i procesów potomnych). Według autora ma to prawie zerowy (tylko 00.1% narzutu) wpływ na wydajność działania programu dzięki zastosowaniu kilku przydatnych funkcji ptrace: śledzimy tylko dwa rodzaje wywołań systemowych (fork() i execve()).
Inne zalety programu zapper to: brak wymagań uprawnień administracyjnych; działanie na statycznych plikach binarnych (np. rust i golang); możliwość uruchomienia procesu pod dowolnym (niezajętym) identyfikatorem. Przykład działania:
agresor@darkstar:~$ wget -O zapper https://github.com/.../zapper-linux-$(uname -m)
agresor@darkstar:~$ chmod +x zapper
agresor@darkstar:~$ ps x
PID TTY STAT TIME COMMAND
31384 pts/0 R+ 0:00 ps x
3620444 ? Ss 0:00 /usr/lib/systemd/systemd --user
3620445 ? S 0:00 (sd-pam)
3620475 ? S 0:02 sshd: agresor@pts/0
3620476 pts/0 Ss 0:00 -bash
Uruchomimy teraz proces sleep 3600, jako fałszywy wątek jądra [kworker/u10:0] nadając mu PID: 31337:
agresor@darkstar:~$ (./zapper -n31337 -a '[kworker/u10:0]' sleep 3600 &>/dev/null &)
agresor@darkstar:~$ ps x
PID TTY STAT TIME COMMAND
31386 pts/0 S 0:00 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31387 pts/0 D 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31388 pts/0 R 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31390 pts/0 R 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31393 pts/0 D 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31395 pts/0 R 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31398 pts/0 D 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31400 pts/0 R 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
31401 pts/0 D 0:01 [kworker/u10:0] -n31337 -a [kworker/u10:0] sleep 3600
545989 pts/0 R+ 0:00 ps x
3620444 ? Ss 0:00 /usr/lib/systemd/systemd --user
3620445 ? S 0:00 (sd-pam)
3620475 ? S 0:02 sshd: agresor@pts/0
3620476 pts/0 Ss 0:00 -bash
Na pierwszy rzut oka wygląda, jakby coś nam nie do końca zadziałało z ukryciem procesu. Dzieje się tak, ponieważ system Linux przydziela nowy PID (identyfikator procesu) każdemu nowemu wątkowi w kolejności sekwencyjnej, aż do największego możliwego numeru wynoszącego: 4194304 (2^22) – kernel.pid_max. Następnie zaczyna ponownie od numeru 300 (lub 1, w zależności od środowiska). Dlatego zapper musi iterować przez wszystkie (2^22) możliwe wartości numeryczne (w ciągu kilku sekund), aż napotka docelowy PID-1. W tym celu rozwidla / forkuje się na 8+ procesów i je zabija, aby przekręcić licznik systemu. Po chwili wzmożonej pracy CPU otrzymujemy finalny wynik:
agresor@darkstar:~$ ps x
PID TTY STAT TIME COMMAND
31337 pts/0 S 0:00 [kworker/u10:0]
31355 pts/0 R+ 0:00 ps x
3620444 ? Ss 0:00 /usr/lib/systemd/systemd --user
3620445 ? S 0:00 (sd-pam)
3620475 ? S 0:02 sshd: agresor@pts/0
3620476 pts/0 Ss 0:00 -bash
Możemy też uruchomić proces, którego wątki również zostaną zamaskowane (-f):
agresor@darkstar:~$ ./zapper -f -a '[kworker/u11:1]' screen -S agresor
agresor@darkstar:~$ ps x
PID TTY STAT TIME COMMAND
31337 pts/0 S 0:00 [kworker/u10:0]
31399 pts/0 S+ 0:00 [kworker/u11:1]
31400 pts/0 S+ 0:00 [kworker/u11:1]
31401 ? Ss 0:00 [kworker/u11:1]
31402 pts/1 Ss 0:00 [kworker/u11:1]
31409 pts/1 R+ 0:00 [kworker/u11:1]
3620444 ? Ss 0:00 /usr/lib/systemd/systemd --user
3620445 ? S 0:00 (sd-pam)
3620475 ? S 0:04 sshd: agresor@pts/0
3620476 pts/0 Ss 0:00 -bash
Oczywiście wyłączenie ptrace w systemie uniemożliwia poprawne działanie programu:
root@darkstar:~# sysctl -w kernel.yama.ptrace_scope=3
agresor@darkstar:~$ ps x
PID TTY STAT TIME COMMAND
31455 pts/0 S+ 0:00 [kworker/u11:1] -f -a [kworker/u11:1] screen -S agresor
31456 pts/0 S+ 0:00 screen -S agresor
31457 ? Ss 0:00 SCREEN -S agresor
31458 pts/1 Ss 0:00 /bin/bash
31470 pts/1 R+ 0:00 ps x
3620444 ? Ss 0:00 /usr/lib/systemd/systemd --user
3620445 ? S 0:00 (sd-pam)
3620475 ? S 0:05 sshd: agresor@pts/0
3620476 pts/0 Ss 0:00 -bash
Program również nie jest odporny na szukania niespójności w systemie plików procfs. Dodatkowo warto wiedzieć, że procfs także udostępnia informacje o wektorze pomocniczym (tak jak glibc za pomocą zmiennej LD_SHOW_AUXV=1 przy uruchamianiu programu) w ścieżce: /proc/$PID/auxv. Jeśli teraz za pomocą programu zapper został ukryty proces o ID: 1659 w systemie:
agresor@darkstar:~$ ps x
PID TTY STAT TIME COMMAND
1613 ? Ss 0:00 /usr/lib/systemd/systemd --user
1614 ? S 0:00 (sd-pam)
1643 ? S 0:00 sshd: agresor@pts/0
1644 pts/0 Ss 0:00 -bash
1659 pts/0 S 0:00 redis --port 3129
1662 pts/0 R+ 0:00 ps x
Możemy sprawdzić i odczytać zawartość jego wektora pomocniczego, w celu uzyskania prawdziwej nazwy pliku:
agresor@darkstar:~$ od -t d8 /proc/1659/auxv 0000000 33 140725435932672 0000020 51 1776 0000040 16 395049983 0000060 6 4096 0000100 17 100 0000120 3 102138210017344 0000140 4 56 0000160 5 13 0000200 7 134200643850240 0000220 8 0 0000240 9 102138210021568 0000260 11 1000 0000300 12 1000 0000320 13 1000 0000340 14 1000 0000360 23 0 0000400 25 140725435594489 0000420 26 2 0000440 31 140725435600878 0000460 15 140725435594505 0000500 27 28 0000520 28 32 0000540 0 0 0000560
Znaczenie poszczególnych wartości numerycznych dla drugiej kolumny znajdziemy w pliku /usr/include/linux/auxvec.h. Nas interesuje wartość wskaźnika trzeciej kolumny, dla której kluczem jest: 31 (AT_EXECFN – ang. File name of executable), czyli wartość: 140725435600878. Możemy ją odczytać prosto z procesu za pomocą GNU Debuggera (gdb):
agresor@darkstar:~$ gdb -p 1659 --batch -ex "x/s 140725435600878" 2>/dev/null 0x7ffd31998fee: "./malware" agresor@darkstar:~$ gdb -p 1659 --batch -ex "info auxv" 2>/dev/null | grep AT_EXECFN 31 AT_EXECFN File name of executable 0x7ffd31998fee "./malware"
Jak widzimy to nie proces redis, tylko malware kryje się pod tym ID.
Więcej informacji: zapper, How does Linux start a process, About ELF Auxiliary Vectors, getauxval() and the auxiliary vector
Poprzedni wpis Brak nowszych postów

