original in de Mulyadi Santosa
en to pl Daniel Maciejewski
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.
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ń.
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 :)
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ą.
# 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 :-))
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:
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………..>
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
# sct_config delete
# sct_unload