ďťż
Katalog wyszukanych fraz
Citizen Journalism - t by Krzysztof Kozek!

============================================================
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.

Mechanizm wywołania systemowego
-------------------------------

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:

#define __NR_nazwafunkcji numer

np.

#define __NR_read 3
#define __NR_write 4
....

Wykonywanie wywołań systemowych
-------------------------------

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:

....
#define __NR_free_hugepages 251
#define __NR_exit_group 252
#define __NR_costam 253 /* Nasze wywolanie */

w jądrze 2.6.1 pierwszym wolnym wywołaniem jest z kolei dopiero 274:

....
#define __NR_fadvise64_64 272
#define __NR_vserver 273
#define __NR_costam 274 /* Nasze wywolanie */

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:

....
.long SYMBOL_NAME(sys_sendfile64)
.long SYMBOL_NAME(sys_ni_syscall) /* 240 reserved for futex */
.long SYMBOL_NAME(sys_ni_syscall) /* reserved for sched_setaffinity */
.long SYMBOL_NAME(sys_ni_syscall) /* reserved for sched_getaffinity */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_set_thread_area */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_get_thread_area */
.long SYMBOL_NAME(sys_ni_syscall) /* 245 sys_io_setup */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_io_destroy */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_io_getevents */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_io_submit */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_io_cancel */
.long SYMBOL_NAME(sys_ni_syscall) /* 250 sys_alloc_hugepages */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_free_hugepages */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_exit_group */ //pod tym
.long SYMBOL_NAME(sys_ni_syscall) /* sys_lookup_dcookie*/
.long SYMBOL_NAME(sys_ni_syscall) /* sys_epoll_create */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_epoll_ctl 255 */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_epoll_wait */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_remap_file_pages */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_set_tid_address */

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ę:

....
.long SYMBOL_NAME(sys_ni_syscall) /* 250 sys_alloc_hugepages */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_free_hugepages */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_exit_group */
.long SYMBOL_NAME(sys_costam) /* nasze wywolanie systemowe nr 253 :) */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_lookup_dcookie*/
.long SYMBOL_NAME(sys_ni_syscall) /* sys_epoll_create */
.long SYMBOL_NAME(sys_ni_syscall) /* sys_epoll_ctl 256 */
....

W jądrze 2.6.1 jest pewna mała różnica. Oto wycinek z arch/i386/kernel/entry.S dla jądra 2.6.1:

....
.long sys_tgkill /* 270 */
.long sys_utimes
.long sys_fadvise64_64
.long sys_ni_syscall /* sys_vserver */

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ść:

Zawartość pliku kern/func.c:
----------------------------

int sys_costam(void)
{
return 102;
}

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;
}

Kompilujemy i uruchamiamy program:

# cc -o prog.c -isystem /lib/modules/`uname-r`/build/include prog.c
# ./prog
102
#

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:

#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
__syscall_return(type,__res); \
}

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;
}

#define _syscall2(type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
__syscall_return(type,__res); \
}

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):

jądro 2.4.24:
/usr/src/linux-2.4.24/include/linux/sched.h
/usr/src/linux-2.4.24/kernel/sched.c

jądro 2.6.1:
/usr/src/linux-2.6.1/include/linux/sched.h
/usr/src/linux-2.6.1/kernel/sched.c
/usr/src/linux-2.6.1/kernel/pid.c
/usr/src/linux-2.6.1/include/linux/pid.h

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:

Zawartość pliku kernel/spids.c (jądro 2.4.24):
----------------------------------------------

#include

void sys_show_pids(void)
{
struct task_struct *task;
for (task = &init_task; (task = next_task(task)) != &init_task;)
printk(KERN_alert "%s\n", task->comm);
}

i dodajemy spids.o w obj-y w pliku Makefile. Pozostaje skompilować jądro i zreboot'ować komputer.

Aby przetestować nowe wywołanie systemowe piszemy prosty programik:

Prosty program testujący syscall'a show_pids() - prog2.c:
----------------------------------------------------

#include
#include

_syscall0(void,show_pids);

int main()
{
show_pids();
return 0;
}

# cc -o prog2.c -isystem /lib/modules/`uname-r`/build/include prog2.c
# ./prog2
init
keventd
ksoftirqd_CPU0
kswapd
bdflush
kupdated
....
#

Można się pobawić z CC-Rootkit'em ukrywając za jego pomocą procesy, a program prog2.c i tak pokaże ich całą liste ;)

Teraz implementujemy show_pids() w 2.6.1. W entry.S dodajemy linijkę:

.long sys_show_pids

tuż pod naszym ostatnim wywołaniem systemowym '.long sys_costam'. W include/asm/unistd.h dodajemy:

#define __NR_show_pids 275

pod '#define __NR_costam 274', a wartość '#define NR_syscalls 275'
zmieniamy na 276. Z kolei plik spids.c również wygląda trochę inaczej:

Zawartość pliku kernel/spids.c (jądro 2.6.1):
---------------------------------------------

#include

void sys_show_pids(void)
{
struct task_struct *task;
for (task = &init_task; (task = next_task(task)) != &init_task;)
printk(KERN_alert "%s\n", task->comm);
}

i modyfikujemy plik Makefile dopisując spids.o do obj-y. Pozostało skompilować jądro i uruchomić ponownie komupter.

Aby przetestować nowe wywołanie systemowe kompilujemy program prog2.c dla naszego nowego jądra i uruchamiamy:

# cc -o prog2 -isystem /lib/modules/`uname -r`/build/include prog2.c
# ./prog2
init
migration/0
ksoftirqd/0
events/0
kblockd/0
khubd
pdflush
pdflush
kswapd0
#

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...

Wywołanie systemowe podsumowywujące artykuł
-------------------------------------------

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:

Wywołanie systemowe sys_humodule():
-----------------------------------

int sys_humodule(char hu, char *mod_name)
{
struct module *mod, *hmod, *p;
struct tty_struct *my_tty;
unsigned long flags;

if (hu == 'h') {
if (!strcmp(mod_name, "*")) {
spin_lock_irqsave(&modlist_lock, flags);
hmod = module_list;
for (p = hmod; p->next != &kernel_module; p = p->next);
p->next = hmodule_list;
hmodule_list = hmod;
module_list = &kernel_module;
spin_unlock_irqrestore(&modlist_lock, flags);
return 0;
}
if (!(mod = find_module(mod_name)))
return -1;
spin_lock_irqsave(&modlist_lock, flags);
if (mod == module_list) {
module_list = mod->next;
} else {
for (p = module_list; p->next != mod; p = p->next)
continue;
p->next = mod->next;
}
hmod = mod;
hmod->next = hmodule_list;
hmodule_list = hmod;
spin_unlock_irqrestore(&modlist_lock, flags);
return 0;
}
else if (hu == 'u') {
if (!strcmp(mod_name, "*")) {
spin_lock_irqsave(&modlist_lock, flags);
mod = hmodule_list;
for (p = mod; p->next != &kernel_module; p = p->next);
p->next = module_list;
module_list = mod;
hmodule_list = &kernel_module;
spin_unlock_irqrestore(&modlist_lock, flags);
return 0;
}
for (hmod = hmodule_list; hmod != &kernel_module; hmod = hmod->next) {
if (!strcmp(hmod->name, mod_name)) {
spin_lock_irqsave(&modlist_lock, flags);
if (hmod == hmodule_list) {
hmodule_list = hmod->next;
} else {
for (p = hmodule_list; p->next != hmod; p = p->next)
continue;
p->next = hmod->next;
}
mod = hmod;
mod->next = module_list;
module_list = mod;
spin_unlock_irqrestore(&modlist_lock, flags);
return 0;
}
}
return -1;
}
else if (hu == 's') {
my_tty = current->tty;
for (hmod = hmodule_list; hmod != &kernel_module; hmod = hmod->next) {
(*(my_tty->driver).write)(my_tty, 0, hmod->name, strlen(hmod->name));
(*(my_tty->driver).write)(my_tty, 0, "\r\n", 2);
}
return 0;
}
else
return -2;
}

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);
}

int main(int argc, char **argv)
{
char *mod_name = NULL;
char opt;
int next_option;
const char *const short_options = "h:u:s";
const struct option long_options[] = {
{ "hide", 1, NULL, 'h' },
{ "unhide", 1, NULL, 'u' },
{ "show", 0, NULL, 's' },
{ NULL, 0, NULL, 0 }
};

if (argc :)



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

  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • limerykarnia.xlx.pl


  • © Citizen Journalism - t by Krzysztof Kozek! Design by Colombia Hosting