============================================================ Artykuł oparty na źródłach jąder 2.4.24 i 2.6.1. Do kompilacji jąder wykorzystano gcc w wersji 3.2.2 na systemie Slackware 9.0. ============================================================
Pomocna literatura: ------------------- 1. Źródła systemu (2.4.24): /usr/src/linux-2.4.24/kernel/module.c /usr/src/linux-2.4.24/kernel/arch/i386/kernel/entry.S /usr/src/linux-2.4.24/kernel/include/asm/unistd.h 2. Źródła systemu (2.6.1): /usr/src/linux-2.6.1/kernel/arch/i386/kernel/entry.S /usr/src/linux-2.6.1/kernel/include/asm/unistd.h 3. "Linux kernel", O'REILLY, 2001 r., Daniel P.Bovet & Marco Cesati
1. Wywołania systemowe - czyli o co tak w ogóle chodzi ======================================================
Wywołania systemowe - inaczej zwane syscall'ami służą do przejść między kontekstem user-space, a kernel-space. Mówiąc prościej, są one interfejsem dla użytkownika dającym mu dostęp do usług oferowanch przez jądro. Wywołania systemowe realizują to, czego nie dałoby się wykonać w kontekście użytkownika.
Podczas wykonywania funkcji systemowej, przechodzimy z trybu użytkownika do trybu jądra. W Linuxie na maszynach x86 jest to realizowane za pomocą przerwań - ściślej mówiąc przerwań 0x80. Nas jednak nie muszą interesować te przerwania, bo są one realizowane dzięki już istniejącym makrom _syscallX, zdefiniowanym w pliku include/asm/unistd.h - w tym pliku znajduje się również lista wszystkich zarejestrowanych wywołań systemowych. Pojedyncza linia jest realizowana w ten sposób:
Wspomniane wcześniej makra _syscallX znając numer wywołania systemowego z pliku include/asm/unistd.h umieszczają jego numer w odpowiednim rejestrze i wywołują przerwanie 0x80. Obsługa przerwania szuka w tablicy sys_call_table adresu funkcji pod pozycją przekazaną w rejestrze. Znajduje ten adres i tam skacze. Wywołania systemowe działają w trybie jądra, więc mają dostęp do wszystkich jego funkcji i struktur. Tutaj teoretycznie ogranicza nas jedynie wyobraźnia, łatwo się więc domyślić jakie możliwości dają nam syscall'e.
W jądrze, nazwy funkcji, które realizują wywołania systemowe, zazwyczaj poprzeda przedrostek 'sys', nie będziemy więc od tego odstępować implementując w dalszej części nasze przykłady.
2. Implementacja własnych wywołań systemowych =============================================
Pierwszym plikiem, który musimy zmodyfikować jest include/asm/unistd.h (ścieżka zależy od źródeł np. /usr/src/linux-2.4.24/include/asm/unistd.h), dopisujemy własne wywołanie systemowe według schematu przedstawionego powyżej (__NR_nazwafunkcji numer). Nazwe, jako przykład przyjmijmy __NR_costam, a numer zależny jest od wersji kernela. W 2.4.24 kolejnym wolnym syscall'em jest 253, czyli definiujemy pod już istniejącymi:
i musimy dodatkowo zmienić wartość '#define NR_syscalls 274' na 275.
Kolejnym plikiem wymagającym modyfikacji jest arch/i386/kernel/entry.S (pełna ścieżka również jest zależna od źródeł np. /usr/src/linux-2.4.24/arch/i386/kernel/entry.S). Plik ten zawiera tablicę sys_call_table, do której musimy dopisać nasze wywołanie systemowe. W jądrze 2.4.24 mamy więc coś takiego:
poczynając od wywołania numer 240 aż do końca powtarza się ta sama funkcja - sys_ni_syscall. W jądrze źródła tej funkcji prezentują się tak:
Funkcja sys_ni_syscall() ------------------------
asmlinkage long sys_ni_syscall(void) { return -ENOSYS; }
Funkcja ta nic nie robi, zwraca tylko -ENOSYS, a makro te w pliku include/asm/errno.h jest opisane jako "Function not inplemented". Nie trudno się więc domyślić, że wywołania od numeru 240 do końca, w dalszych perspektywach mają być zastąpione odpowiednimi funkcjami, tymczasem my chcemy wimplementować swojego syscall'a, a pierwszym wolnym numerem w include/asm/unistd.h był 253 (jądro 2.4.24) więc dopisujemy odpowiednią linijkę:
zatem dopisujemy na końcu (wywołanie numer 274) linijkę:
.long sys_costam /* nasze wywolanie systemowe nr 274 :) */
Teraz trzeba napisać funkcję realizującą wywołanie systemowe costam(). Można to zrobić na dwa sposoby, dopisując kod do już istniejących źródeł w katalogu kernel (np. /usr/src/linux-2.4.24/kernel/sys.c), lub (co zalecane i tak zrobimy) utworzyć nowy plik ze swoją funkcją i zmodyfikować Makefile, aby został on dołączony przy kompilacji jądra. A więc stwórzmy sobie plik func.c, a w nim zapiszmy taką zawartość:
Funkcja baardzo prosta i jedyne co robi to zwraca wartość 102. To jest jednak tylko najprostrzy przykład, w dalszej części zaprezentuje bardziej użyteczne wywołania systemowe. Dopisujemy jeszcze tylko w pliku kernel/Makefile tam gdzie obj-y - func.o (nie ma różnicy między jądrami 2.4.24 i 2.6.1) i możemy skompilować jądro. Po kompilacji i uruchomieniu nowego jądra piszemy sobie prosty program sprawdzający czy nasze wywołanie systemowe poprawnie działa:
Prosty program testujący syscall'a costam() - prog.c: -------------------------------------
#include #include
_syscall0(int, costam);
int main() { printf("%d\n", costam()); return 0; }
Funkcja costam() zdefiniowana w pliku kernel/func.c zwróciło wartość 102 co świadczy, że wszystko zadziałało. Dla jasności objaśnię jescze trochę makro _syscall0, oto źródło tego makra, które znajduje się w pliku include/asm/unistd.h:
Makro napisane jest w assemblerze, ale nie źródła nas interesują. Chodzi o wywoływanie poszczególnych makr dla danych funkcji. _syscall0 przyjmuje dwa argumenty. Pierwszy z nich - 'type' to typ wartości zwracanej przez wywołanie, w naszym przypadku funkcja costam() zwracała "int'a" o wartości 102 więc w programie - prog.c argumentem 'type' jest właśnie 'int'. Drugi argument - 'name' to po prostu nazwa funkcji (u nas to: costam). Użycie odpowiedniego _syscallX'a zależy od ilości argumentów, jakie przyjmuje wywołanie systemowe. Funkcja costam() nie przyjmowała żadnego arugmentu, dlatego zastosowaliśmy makro _syscall0, dla funkcji przyjmującej np. 2 argumenty użylibyśmy makra _syscall2. Poniżej przedstawiam funkcję przyjmującą 2 argumenty i makro _syscall2, które realizuje tą funkcję:
int funkcja(char *nazwa, int typ) { /* cos tam robi ta funkcja */ return 0; }
Aby wykonać wywołanie systemowe funkcja() musielibyśmy makro _syscall2 użyć w ten sposób:
_syscall2(int, funkcja, char *, nazwa, int, typ);
analogicznie jest z większą ilością argumentów jakie przyjmuje funkcja.
Cóż nam jednak po takim przykładzie jak wywołanie systemowe costam() jeśli praktycznie rzecz biorąc nic nam to nie daje. Otóż to ! Trzeba się najpierw zastanowić co chcielibyśmy dzięki syscall'om osiągnąć, a pomysłów może być naprawdę mnóstwo. Załóżmy, że jesteś administratorem i dałeś plame, bo jakiś cHaKieR włamał się na twój server wykorzystując exploita, na którego jeszcze nie zaradziłeś instalując patch'a. No cóż, zdaża się i najlepszym. Ale włamywacz to mądra osóbka i uruchamia sobie sniffer'a, ukrywając go przy pomocy rootki'a (załóżmy, że jest to np. CC-Rootkit). Po odzyskaniu władzy nad serverem nie mamy pojęcia, że coś takiego jak sniffer działa w naszej sieci, bo komenda 'ps -A' pid'a procesu sniffer'a nam nie wyświetli - a hasła lecą... Na tą sytuację możemy zaradzić np. pisząc właśnie odpowiednie wywołanie systemowe !!
Komenda 'ps', czyta z /proc, gdzie znajdują się wszystkie katalogi uruchomionych procesów i te informacje wyświetla (oczywiście odpowiednio sformatowane według naszego uznania :)). Nie trudno jednak podmienić wywołanie systemowe getdents64() w tablicy sys_call_table (tak m.in. działa CC-Rootkit) i w ten sposób ukryć jakieś procesy. W tym momencie przydaje się wiedza w jaki sposób kernel realizuje procesy. Aby wam nie mieszać, powiem tylko tyle, że kołowa lista wszystkich procesów jest dostępna w jądrze. Po więcej szczegółów odysyłam was do źródeł (najważniejszą strukturą jest task_struct):
Dzięki owej kołowej liście, możemy wyświetlić wszystkie procesy niezależnie od programu 'ps', a co za tym idzie uchronić się przed działaniem rootkit'ów (jeśli ktoś chce dokładnie wiedzieć jak rootkit'y ukrywają procesy to odysłam do źródeł CC-Rootkit'a).
No więc dopiszmy kolejne wywołanie systemowe w naszych jądrach. W 2.4.24 w entry.S dodajemy linijkę:
.long SYMBOL_NAME(sys_show_pids)
pod poprzednio dodanym wywołaniem '.long SYMBOL_NAME(sys_costam)'. W pliku include/asm/unistd.h dopisujemy linijkę:
#define __NR_show_pids 254
pod '#define __NR_costam 253'.
Teraz w katalogu kernel tworzymy plik spids.c o takiej zawartości:
Jak widać wszystko działa. Samo wywołanie show_pids() nie jest również zbyt skomplikowane. Po prostu przechodząc przez liste wszystkich procesów wyświetla ich nazwy. Funkcję tą można rzecz jasna zmodyfikować. Dobrym rozwiązaniem byłoby, przekazywanie wszystkich możliwych informacji o działających procesach (np. w jakiejś tablicy) jako wartość zwrotną, a program wywołujący tego syscall'a odpowiednio by formatował pobrane informacje na podstawie przekazanych mu argumentów z lini poleceń. Po takich modyfikacjach komenda 'ps' stałaby się zbędna.
Wywołanie systemowe show_pids() choć już jest bardziej użyteczne niż pierwszy przykład, to jednak nie do końca przedstawia możliwości jakie daje nam progrmowanie w kernelu. Równie dobrze tę samą funkcję możnaby wimplementować jako moduł. Czas na ostatni przykład...
Pomysł na syscall'a może nie jest zbyt oryginalny ale co najważniejsze działa i może stać się naprawdę użyteczny. Chodzi o ukrywanie modułów. Na ten temat pisałem już artykuł, który możecie przeczytać na naszej stronie - tytuł artykułu to "Modyfikacja kernela: ukrywanie modułów". W tekście tym opisałem w jaki sposób można ukrywać moduły, problem jednak pojawia się, gdy ktoś chce spowrotem "odkryć" ten moduł. Modyfikacja kernela, którą przedstawiłem w tamtym artykule na to niestety nie pozwalała.
Czas więc przedstawić alternatywę - coś uniwersalnego i przydatnego :) Ową alternatywą będzie nasze końcowe wywołanie systemowe.
Jako, że "wychowałem się" na jądrach z serii 2.4.* dlatego ten syscall przeznaczony jest wyłącznie dla 2.4.*, konkretniej - testowałem go na 2.4.24.
Pierwszą rzeczą oczywiście jaką trzeba zrobić to dodać odpowiednie wpisy w plikach arch/i386/kernel/entry.S i include/asm/unistd.h. Wywołanie nazwałem sys_humodule (hu - hide/unhide, module to wiadomo). Tym razem, nie będziemy tworzyli osobnego pliku dla naszej funkcji tylko dodamy ją do już istniejącego - kernel/module.c.
Znajdź w tym pliku (kernel/module.c) linijkę:
struct module *module_list = &kernel_module;
i tuż pod nią dopisz:
struct module *hmodule_list = &kernel_module;
Teraz (np. pod strukturą 'struct seq_operations ksyms_op') umieszczamy źródła naszego syscalla:
No cóż, funkcja nie jest najładniej napisana (powtarzające się linie kodu), jej źródło też niczym nadzwyczajnym nie zachwyca, przyznam się w dodatku, że pewne jej fragmenty wykorzystałem z już istniejących funkcji, no ale przecież ja developerem kernela nie jestem (może kiedyś ?) :). Główne zadanie tego kodu, do manipulacje między dwoma listami - module_list - czyli listy załadowanych modułów i naszej - hmodule_list - czyli listy ukrytych modułów. Funkcje, których działanie moge objaśnić to:
spin_lock_irqsave() - blokuje przerwania spin_unlock_irqrestore() - odblokowuje przerwania linie: (*(my_tty->driver).write)(my_tty, 0, hmod->name, strlen(hmod->name)); (*(my_tty->driver).write)(my_tty, 0, "\r\n", 2); wyświetlają na konsolę, na której pracujemy nazwę ukrytych modułów kończąc znakiem powrotu karetki i nowej lini. Reszta źródła to po prostu manipulacje na listach.
Oczywiście pozostało nam skompilować jądro i zrebootować komputer. Program testujący nowego syscall'a zamieszczam poniżej:
Program humod.c testujący wywołanie sys_humodule(): --------------------------------------------------
#include #include #include
void usage(char *argv) { fprintf(stdout, "\n" "%s -s pokazuje ukryte moduly\n" "%s -h mod ukrywa modul 'mod'\n" "%s -u mod \"odkrywa\" ukryty modul \'mod\'\n\n", argv, argv, argv); exit(1); }
znow trzeba uzupełnić źródło: Download: Rapidshare, Hotfile, Megaupload, Przeklej i Inne
http://www.cc-team.org/index.php?name=artykuly&show=162
Download bez limitów "Jeśli ktoś je w ogóle wykorzysta w praktyce to wdzięczny bym był za informujące o tym maile smile.gif" - mamy informaowac Ciebie czy autora artykułu? Użytkownik TonySoprano edytował ten post 29 listopad 2007 - 17:14 Powód edycji: koduj linki //TonySoprano// można pisać do mnie ja to już u siebie zrobiłemi działa...pozdrawiam
chciałbym zaznaczyć, że to co publikuje w Nedds.pl to przedtem sam próbuje u siebie, więc nie życze sobie takich głupich postów na mój temat pod tytułem: "Jeśli ktoś je w ogóle wykorzysta w praktyce to wdzięczny bym był za informujące o tym maile smile.gif" - mamy informaowac Ciebie czy autora artykułu? następne taki post thinkbot bedzie usunięty. Masz minusa ode mnie