Śledzenie Linuxa za pomocą Syscalltracker'a

ArticleCategory:

KernelCorner

AuthorImage:[Here we need a little image from you]

[Photo of the Author]

TranslationInfo:

original in de Mulyadi Santosa

en to pl Daniel Maciejewski

AboutTheAuthor:[A small biography about the author]

Nazywam się Mulyadi Santosa. Mieszkam w Indonezji i pracuję jako niezależny autor i konsultant. Moje zainteresowania to klastry, systemy bezpieczeństwa i sieci. Uruchomiłem też małą prywatną firmę zajmującą się dziedziną klastrów. Moją uwagę przyciągają szczególnie OpenMosix and openSSI. W wolnym czasie ulubionymi rozrywkami są czytanie i uprawianie sportu. Możesz wysłać mi e-mail na a_mulyadi@softhome i podyskutować o tym.

Abstract:[Here you write a little summary]

Czasami chcemy przyjrzeć się naszemu Linuxowi bardziej z bliska. Jest w nim wiele programów służących do tworzenia logów, narzędzi do wykrywania intruzów czy programów testujących integralność. Chciałbym teraz przedstawić mechanizm służący do obserwowania Linuxa na poziomie kernela, który oferuje większą niezawodnoœć oraz szerszy wachlarz zastosowań.

ArticleIllustration:[One image that will end up at the top of the article]

[Illustration]

ArticleBody:[The main part of the article]

Wprowadzenie

Pewnego dnia czekalem na listę mailową omawiającą zagadnienie "clustering middleware tool". Przypadkowo znalazłem tam watek dotyczący anomalii spowodowanych przez łatkę kernela. I wtedy osoba odpowiadajšca na tego maila stwierdziła, że spróbuje zrekonstruować powstały problem na podstawie kroków opisanych przez osobę pierwszą. Ta osoba używała narzędzia zwanego Syscalltracker, w celu rozgryzienia zaistniałego problemu. Zastanowiło mnie "Co to za narzędzie ten Syscalltracker? Jakie są jego możliwości?". Dla zwykłego użytkownika jak ja nazwa "Syscalltracker" wyglądała tajemniczo i podejrzanie :)

Syscalltracker

(http://syscalltrack.sourceforge.net) jest kolekcją modułów jądra pozwalających na śledzenie wywołań systemowych wykonanych wewnętrznie przez jądro Linuxa. Po co to ??? Generalnie możemy użyć tego do śledzenia przyczyn pewnych niewłaściwych zachowań systemu, trudnych do ustalenia przez zwykłe mechanizmy śledzenia czy debuggowania. Oto prosty przykład: załóżmy, że masz w katalogu /etc plik konfiguracyjny zwany inetd.conf (podstawowa konfiguracja dla demona INETd). Skonfigurowałeś go w ten sposób, żeby uruchamiał niektóre demony systemowe i wyłączał inne. I wtedy uruchamiasz inetd i wszystko z początku idzie dobrze. Ale nagle /etc/inetd.conf zniknął. Szczęśliwie posiadasz zapasową kopię pliku i szybko przywracasz właściwy stan. Restartujesz inetd bazując na nowej konfiguracji. Tym razem znowu coś dodało nowe linie do inetd.conf i wstawiło komendy uruchamiające jakieś dziwne demony. Zaczynasz się irytować... "kto to zrobił?" "Czy jest to spowodowane przez demona, którego wystarował sam inetd ???". Szybko wrzucasz "cat"na logi systemowe, odpalasz "top" oraz "ps", żeby przyłapać podejrzany proces lub użytkownika, ale niestety nic takiego nie widać.

Istnieje rozwiązanie œledzące tego rodzaju problemy. Nie jest może 100%'owo doskonałe, ale wystarczająco dobre w wiekszości przypadków. Bazuje ono na fakcie, że każda akcja lub komenda wykonana z shella, program użytkownika czy demon (innymi słowy - KAŻDY proces) musi być uruchomiony jednym bądź wiecej wywołaniami procedur systemowych, powszechnie nazywanych wywołaniami systemowymi. Próbujesz skasować plik? To znaczy, że wywołujesz unlink. Uruchomiłeś skrypt shella? Wtedy musi być wywołane exec() lub execve(). Tak więc praktycznie każda akcja odniesiona do systemu jest bezpośrednio interpretowana jako wywołanie systemowe. Właśnie dzięki temu śledzenie bazujące na wywołaniach systemowych może być mocną bronią.

Zainteresowany ???

Więc możesz spróbować. W tym artykule używam dystrybucji Redhat Linux 7.3 jako systemu bazowego. Wejdź na stronę http://syscalltrack.sourceforge.net i ściągnij pakiet z sekcji "download". Ja używam tutaj syscalltrack-0.82.tar.gz o rozmiarze około 500kB. Rozpakuj ten pakiet załóżmy do katalogu /usr/src:

# tar xzvf syscalltrack-0.82.tar.gz



Teraz sprawdź czy masz kod żródłowy jądra w /usr/src:

# rpm -qa | grep -i kernel-source



LUB

# ls -al /usr/src/linux-2.4



Jeżeli powyższe komendy pokażą, że nie masz zainstalowanych Ÿródeł, musisz je doinstalować. źródła znajdziesz na płytce Redhat CD (#2):

# rpm -replacepkgs -Uvh /path/to/your/RPM/kernel-source-2.4.18-3.i386.rpm



Zwróć uwagę, że MUSISZ skompilować Syscaltracker'a bazującego na tej samej wersji jądra (i innych dodatkowych łatkach), która jest aktualnie uruchomiona na twoim Linux'ie. Przykład: jeśli używasz standardowego jądra z Redhat 7.3, wtedy musisz skompilować źródła kernela z płytki Redhat CD. Albo jeśli chcesz używać innego, wybranego przez ciebie jądra, musisz samodzielnie skompilować Syscalltracker'a bazującego na źródłach tego jądra.

Oprócz kodu źródłowego, dla ułatwienia kompilacji Syscalltrackera, potrzebujesz także Redhat'owego pliku konfiguracyjnego jądra. Spróbuj sprawdzić zawartość katalogu /boot:

# ls -al config*



Jeżeli uzyskałeś wynik w stylu 'config-2.4.18-3', skopiuj ten plik to katalogu /usr/src/linux-2.4. Zmień jego nazwę na '.config'

# cp /boot/config-2.4.18-3 /usr/src /linux-2.4/.config



Jeśli tego pliku z jakiegoś powodu nie ma, możesz skopiować go z katalogu ze źródłami jądra. Znajduje się on w podkatalogu configs. Musisz wybrać ten, który odpowiada twojej aktualnie uruchomionej wersji kernela. A więc najpierw sprawdź jaka to wersja:

# uname -a



Wykonanie powyższej komendy powinno w rezultacie wyświetlić wersję kernela. Możesz to zgadnąć. Załóżmy, że uzyskany wynik to "kernel-2.4.18-3-i386" - wtedy potrzebujesz skopiować plik kernel-2.4.18-3-i386.config

# cd /usr/src/linux-2.4
# cp configs/kernel-2.4.18-3-i386.config ./.config



Teraz musisz wykonać:

#cd /usr/src/linux-2.4.18-3
# make mrproper
# make menuconfig



Zaaplikuj potrzebne ci ustawienia a następnie zapisz/wyjdź. Jeśli używasz samodzielnie skompilowanego jšdra, a zgubiłeś stary plik konfiguracyjny jądra, musisz starannie go odtworzyć, aby uniknąć później problemów (Mam nadzieję, że nie :-))

Kompilacja Syscalltracker'a

Do teraz przygotowaliśmy wszystkie potrzebne rzeczy. Możemy rozpocząć kompilację Syscalltracker'a:

# cd /usr/src/syscalltrack-0.82
# ./configure (or ./configure -with-linux=/path/to/your/linux/kernel/source)
# make && make install



Jeżeli kompilacja przebiegła pomyślnie, odnajdziesz dwa nowe moduły:

  1. /lib/modules/2.4.18-3/syscalltrack-0.82/sct_rules.o

  2. /lib/modules/2.4.18-3/syscalltrack-0.82/sct_hijack.o



Są to moduły odpowiedzialne za śledzenie twojego systemu. Sam autor używa terminu "system call hijacking", oznaczającego przechwytywanie wywołań systemowych i wykonywanie pewnych zdefiniowanych wcześniej akcji przed wykonaniem właściwej procedury. Moduły te trzeba załadować. Do ładowania modułów używamy specjalnego wbudowanego skryptu:

# sct_load (jako root)



Upewnij się, że kernel załadował moduły, używając lsmod. Powinieneś zobaczyć coś w stylu:

Module     Size  Used by    Not tainted
sct_rules  257788   2
sct_hijack 110176   1      [sct_rules]
<…..cut………..>


Reguły

Gratulacje !!! Załadowałeś moduły i masz uruchomionego Syscalltracker'a. Ale to nie koniec. Potrzebujesz jeszcze napisać reguły wymagane przez Syscalltracker'a do właściwej pracy. Zacznijmy od prostej reguły:

rule 
 { 
   syscall_name=unlink 
   rule_name=unlink_rule1 
   action 
   { 
     type=LOG 
     log_format {%comm : %params delete by %euid --> %suid} 
   } 
   when=before 
}

Każdą regułę zdefiniowaną dla Syscalltrackera rozpoczyna zastrzeżone słowo "rule" poprzedzone przez "{". Następnie musisz zadeklarować, które wywołanie systemowe chcesz obserwować, używając parametru "syscall_name". Jest wiele wywołań systemowych, z których możesz wybrać. Aby zobaczyć ich kompletną listę spróbuj zajrzeć do pliku '/usr/local/lib/syscalltrack-0.82/syscalls.dat-2.4.18-3'. Znaczenie niektórych wywołań systemowych jest ciężko odgadnąć, ale są też wywołania bardzo łatwe. Teraz wybiorę unlink(). To wywołanie systemowe jest uruchamiane za każdym razem, gdy ktoś lub coś próbuje skasować plik. Myślę, że to dobry wybór na początek, więc naszą ideą jest obserwować kasowania występujące w naszym systemie.

W parametrze "rule_name", musisz dostarczyć nazwę reguły. Jest ona dowolnym wpisem, po prostu napisz dowolną łatwą do zrozumienia nazwę. Ja wybrałem "unlink_rule1". W sekcji "action" musisz wpisać co Syscalltracker ma zrobić każdorazowo gdy wystąpi odpowiednie wywołanie systemowe. Syscalltracker wspiera różne akcje, ale tutaj użyjemy akcji typu LOG. Ta akcja wypisze logi na urządzenie /dev/log. Zgodnie z tym co pisze na stronie internetowej na liście TODO (do zrobienia), są plany aby zrobić przepisywanie wywołań systemowych. Znaczy to, że mógłbyś manipulować parametrami wywołania systemowego i wstawiać swóje własne parametry :-)

Dla akcji LOG musisz zdefiniować format wyjściowy. Dostępne są pewne makra, pozwalające uzyskać szczegółowe informacje:

%ruleid -> nazwa reguły, do której pasuje przechwycone wywołanie systemowe
%sid    -> numer identyfikacyjny wywołania systemowego
%sname  -> nazwa wywołania systemowego
%params -> parametry wywołania systemowego
%pid    -> ID procesu, który wykonuje wywołanie systemowe
%uid    -> ID użytkownika, który używa wywołania systemowego
%euid   -> efektywny ID użytkownika, który używa wywołania systemowego
%suid   -> numer SUID użytkownika który użył wywołania systemowego
%gid    -> ID grupy użytkownika, który używa wywołania systemowego
%egid   -> efektywny ID grupy użytkownika, który używa wywołania systemowego
%sgid   -> numer SGID grupy użytkownika, który użył wywołania systemowego
%comm   -> nazwa komendy, która wykonuje wywołanie systemowe
%retval -> wartoœć zwrócona przez wywołanie systemowe. Działa tylko
           dla akcji LOG z typem "after"

Załóżmy, że wpisałem

.log_format {%comm : %params delete by %euid --> %suid}

Oznacza to "Chcę uzyskać log na temat każdej komendy, która uruchomiła wywołanie systemowe unlink, z efektywnym identyfikatorem użytkownika EID oraz identyfikatorem SUID."

W parametrze when, możemy wybrać pomiędzy "before" i "after". Różnica jest oczywista - jeśli używamy "before" rejestrowanie zdarzenia nastąpi zanim zostanie wykonane wywołanie systemowe. Jeśli użyjemy "after" rejestrowanie zdarzenia będzie miało miejsce po wykonaniu wywołania systemowego.

Na końcu reguły dajemy "}". Cała reguła może być zapisana wewnątrz zwykłego pliku tekstowego, na przykład nazwijmy go "try.conf" i zapiszmy w "/tmp". Teraz musisz załadować tą regułę do Syscalltrackera

# sct_config upload /tmp/try.conf



Jeżeli reguła napisana jest poprawnie, otrzymasz komunikat "Successfully uploaded rules from file '/tmp/try.conf' ".

OK, wszystko poszło dobrze. Teraz przyszła faza testu. Uruchom konsolę, powiedzmy xterm z Xwindow. Na jednej konsoli oglądaj komunikaty Syscalltrackera używając

# sctlog





Za chwilę zobaczysz wynik działania mechanizmu przechwytywania wywołania systemowego odpowiadającego twojej regule. Na innej konsoli zrób coś takiego:

# cd /tmp
# touch ./dummy
# rm ./dummy


Używając powyższej reguły, otrzymasz prawdopodobnie następujący wynik na konsoli z sctlog:

"rm" : "./dummy" delete by 0 --> 0

Na podstawie powyższego komunikatu za chwilę dowiesz się co się dzieje.

Komenda "rm" z parametrem "./dummy" wykonała wywołanie systemowe unlink(). Lub innymi słowy rm zostało użyte do skasowania pliku. Ta komenda używa efektywnego id użytkownika o numerze 0 (lub root)"

Oto przykład innej reguły:

rule
{
   syscall_name = unlink
   rule_name = prevent_delete
   filter_expression {PARAMS[1]=="/etc/passwd" && UID == 0}
   action {
     type = FAIL
     error_code = -1
   }
   when = before
}

Jest ona podobna do te z pierwszego przykładu, z tym, że teraz używamy akcji FAIL. Wyłącznie dla FAIL musimy zdefiniować zwracaną wartość dla przechwyconego wywołania systemowego. Ja używam tu wartości "-1" czyli "operacja nie dozowlona". Kompletną listę wartości znajdziesz w pliku /usr/include/asm/errno.h.

W linii zawierającej "filter_expression" definuję warunek dla którego wykonywane jest sprawdzanie - jeśli pierwszy parametr (wywołania systemowego) jest równy "/etc/passwd". Dlatego potrzebujemy zmiennej PARAMS. Uwaga: każda sekcja ma swoje własne wymagane parametry. To sprawdzanie nie jest idealne ponieważ ktoś może użyć czegoś w stylu "cd /etc && rm -f ./passwd". A to zadziała już prawidłowo !!! Używamy także sprawdzenia czy UID jest równy 0 (root).

Dodaj tą regułę do pliku z poprzednią regułą i przeładuj:

# sct_config delete
# sct_config upload /tmp/try.conf

Zwróć uwagę, że kolejność reguł jest ważna. Jeśli zadeklarujesz regułę "prevent_delete" przed "unlink_rule1" wtedy gdy zrobisz :

# rm -f /etc/passwd



najpierw zostanie dopasowana reguła "prevent_delete" i cała akcja zakończy się niepowodzeniem. Reguła "unlink_rule1" zostanie zignorowana. Ale jeśli zamienisz kolejność reguł ("unlink_rule1" przed "prevent_delete"), wtedy dostaniesz komunikat (wynik pierwszej reguły) bez zatrzymywania akcji !

Ineteresujące jest też inne wywołanie systemowe zwane ptrace. Z "man ptrace", możesz dowiedzieć się, że to wywołanie systemowe jest używane w celu obserwacji i kontroli uruchomienia innego programu. W odpowiednich rękach ptrace może być podręcznym narzędziem do debuggowania, ale w nieodpowiednich może być ono użyte do analizowania dziur systemowych i używania ich. Dodajmy regułę do obserwowania ptrace'a.

Aby to zrobić użyj reguły jak poniżej:

rule
{
    syscall_name=ptrace
    rule_name=ptrace_rule1
    action {
        type=LOG
      log_format {%comm : %params issued ptrace by %euid --> %suid}
  }
    when=before
}

Zauważ, że deklarujemy ptrace jako wywołanie systemowe, które ma być śledzone. Dla testu, użyj programu strace, żeby wypróbować tą regułę. Najpierw załaduj powyższą regułę do syscalltracker'a i uruchom sctlog. Wtedy uruchom strace w stosunku np. do komendy ls:

# strace /bin/ls

W sctlog, możesz zobaczyć kilka linijek w stylu:

"strace" : 3, 2019, 24, -1073748200 issued ptrace   by 0 --> 0
"strace" : 24, 2019, 1, 0 issued ptrace   by 0 --> 0
"strace" : 3, 2019, 44, -1073748200 issued ptrace   by 0 --> 0
"strace" : 3, 2019, 24, -1073748200 issued ptrace   by 0 --> 0
"strace" : 3, 2019, 0, -1073748216 issued ptrace   by 0 --> 0
"strace" : 7, 2019, 1, 0 issued ptrace   by 0 --> 0

Dla tych, którzy nie znali strace, jest to narzędzie (bardzo silne) do śledzenia wywołań systemowych wydawanych wewnątrz wykonywalnego pliku. Strace wewnętrznie używa ptrace aby podczepić się do docelowego programu, dzięki czemu może go śledzić. Obecnie strace oraz Syscalltracker stanowią idealne combo do audytu systemu oraz plików, dlatego myślę, że warto poświęcić im chwilę. Redhat 7.3/8/9 już go ma. Zainstaluj po prostu RPM (tutaj dla Redhat 7.3):

# rpm -Uvh /path/to/your/Redhat/RPM/strace-4.4-4.i386.rpm



Teraz jesteś jeden krok dalej w diagnozowaniu swojego systemu. Syscalltracker daje użytkownikowi elastyczność i możliwość nauczenia się czegoś o wywołaniach systemowych. Dodatkowo jeśli chcesz zobaczyć wszystkie reguły, które zostały załadowane, wpisz:

# sct_config download



Aby usunąć wszystkie załadowane reguły wpisz:

# sct_config delete






I w końcu, jeśli nie potrzebujesz już Syscalltracker'a możesz usunąć go z pamięci:

# sct_unload






Istnieje możliwość, że tej komendzie nie uda się usunąć Syscalltrackera i uzyskamy ostrzeżenie "Device or resource busy". Jeśli tak się stanie, prawdopodobnie Syscalltracker coś jeszcze wykonuje. Daj mu chwilę i spróbuj jeszcze raz. Syscalltracker jest bezpieczny dla systemu i nie obciąża go nadmiernie (o ile oczywiście nie wrzucimy ton reguł :-)). Wniosek: Po prostu pozwól Syscalltracker'owi wykonywać jego robotę. Dostarczajšc mu odpowiednich reguł, bedziesz mogł zacząć obserwować swój system bardziej dokładnie.