Format BMP okiem hakera
Atak
Michał Gynvael Coldwind Składnikiewicz
stopień trudności
Pliki graficzne są dziś szeroko rozpowszechnionym nośnikiem
informacji, spotyka się je praktycznie na każdym komputerze.
Dobry programista powinien wiedzieć jak wyglądają nagłówki
poszczególnych formatów plików graficznych, i jak są
przechowywany jest sam obraz. A jak to zwykle bywa, diabeł tkwi
w szczegółach.
iniejszy artykuł ma na celu zapoznać niezależnie od wersji) która zawiera m.in. iden-
czytelnika z formatem przechowywa- tyfikator pliku tzw. liczbę magiczną (ang. ma-
Nnia obrazu BMP, wskazać w nich miej- gic number), oraz offset na którym znajdują się
sca które można wykorzystać do przemycenia dane bitmapy. Bezpośrednio po BITMAPFILE-
ukrytych danych, miejsca w których programi- HEADER, na offsecie 0Eh, znajduje się struktu-
sta może popełnić błąd podczas implementacji ra BITMAPINFOHEADER (dodam że deklara-
oraz zapoznać ze samym formatem. Przykła- cje omawianych struktur można znalezć w pliku
dy będą w miarę możliwości zilustrowane pew- wingdi.h w Platform SDK), która zawiera infor-
nymi bugami w istniejącym oprogramowaniu, macje o obrazie, jego rozdzielczości, głębi kolo-
znalezionymi przez autora oraz inne osoby. rów czy użytej metody kodowania/kompresji. W
przypadku bitmap o głębi 4 lub 8 bitów, zaraz za
Wstęp do BMP strukturą BITMAPINFOHEADER znajduje się
Niesławny format BMP znany jest przede
wszystkim z plików o ogromnych wielkościach
(w porównaniu do JPEG czy PNG). Format ten
Z artykułu dowiesz się
stworzony został przez firmy IBM oraz Microsoft
" jak zbudowany jest plik BMP,
na potrzeby systemów OS/2 oraz Windows, obie
" na co uważać podczas implementowania ob-
firmy rozwijały go jednak oddzielnie, co spowo-
sługi formatu BMP,
dowało powstanie kilku wariantów tego formatu.
" gdzie szukać błędów w aplikacjach korzystają-
Niniejszy tekst skupia się na BMP w wersji Win-
cych z BMP.
dows V3, pozostałe wersje (OS/2 V1 i V2 oraz
Windows V4 i V5) pozostawiam czytelnikowi do
własnej analizy jako zadanie domowe :).
Co powinieneś wiedzieć
Niezależnie od wersji, ogólna budowa pli-
ku, przedstawiona na Rysunku 1, pozostaje ta- " mieć ogólne pojęcie na temat plików binarnych,
" mieć ogólne pojęcie na temat bitmap.
ka sama. Na samym początku pliku znajduje się
struktura BITMAPFILEHEADER (jest ona stała,
hakin9 Nr 3/2008
2 www.hakin9.org
Format BMP okiem hakera
paleta barw, którą jest odpowiedniej ile wg. bfSize jest potrzebne, po czym
wielkości tablica struktur RGBQUAD. wczytał cały plik (aż do końca) do za-
W przypadku bitmap o głębi kolorów alokowanego bufora. Funkcja działa
16 bitów zamiast palety barw w tym wyśmienicie, pod warunkiem że war-
miejscu znajduję się prosta struktura tość bfSize jest równa lub większa od
składająca się z trzech DWORD'ów faktycznej wielkości pliku. Jeśli war-
które są maskami bitowymi określa- tość bfSize będzie mniejsza, dojdzie
jącymi które bity w danych obrazu od- do klasycznego błędu przepełnienia
powiadają za barwę, kolejno, czerwo- bufora który wprawny włamywacz
ną, zieloną oraz niebieską, natomiast mógł by wykorzystać do wykonania
w bitmapach, o głębi 24 bity lub więk- własnego kodu. Dobrym pomysłem
szejm paleta barw nie występuje. Da- jest zignorowanie wartości tego pola,
ne obrazu zaczynają się na offsecie i korzystanie jedynie z wielkości pliku
podanym w BITMAPFILEHEADER, otrzymanej od systemu plików. Sta-
zazwyczaj od razu po ostatnim na- nowcza większość aplikacji faktycz-
główku. Budowa danych zależy za nie ignoruje to pole, co z kolei pozwa-
równo od użytego kodowania jak i la wykorzystać je w celu ukrycia 32
głębi kolorów. bitów danych.
Tak przedstawia się ogólna budo- Dwa kolejne pola bfReserved1
wa formatu BMP. Szczegółowa budo- oraz bfReserved2 według specyfi-
wa formatu BMP przedstawiona jest kacji powinny być wyzerowane, po-
Rysunek 1. Budowa pliku BMP
w dalszej części artykułu. nieważ są zarezerwowane na przy-
szłość. Póki co są jednak niewyko- Na początek najbardziej trywialny
Nagłówek rzystywane, więc mogą posłużyć do programista z góry zakłada że da-
BITMAPFILEHEADER ukrycia kolejnych 32 bitów danych ne obrazu znajdują się za nagłówka-
Nagłówek BITMAPFILEHEADER (oba pola są WORDami, czyli mają mi i ignoruje pole bfOffBits tak dzia-
(patrz Tabela 1) rozpoczyna się na po 16 bitów każde). Warto zaznaczyć ło się w przypadku starszych wersji
początku pliku (offset 0) i ma wielkość iż żadna z testowanych przez auto- Total Commander (na przykład 6.51,
14 bajtów (0Eh). Najmniej interesują- ra aplikacji nie sprawdzała czy w w/w wersje nowsze, na przykład 7.01 nie
cym polem struktury jest pierwsze po- polach faktycznie znajdują się zera. ignorują już tego pola). Pomijając pro-
le bfType, które zawsze ma wartość Ostatnie pole stanowi kolejną pu- blemy z wyświetlaniem prawidłowych
odpowiadającą ciągowi ASCII BM. łapkę. Pole bfOffBits, bo o nim mowa, bitmap które mają dane obrazu odsu-
Kolejnym polem jest DWORD bfSi- jest 32 bitową wartością bez znaku nięte od nagłówków, pozwala to na
ze w którym wg. specyfikacji powinna (DWORD, czyli w terminologii C jest przykład stworzyć plik BMP który wy-
znalezć się całkowita wielkość pliku w to unsigned int) która mówi o tym w świetlany w Total Commanderze bę-
bajtach. Wielkość pliku prawidłowego którym miejscu pliku (a dokładniej, dzie prezentował inną grafikę niż gdy-
pliku łatwo obliczyć dodając wielko- od którego bajtu pliku) zaczynają się by ten tam plik BMP podać innej, pra-
ści poszczególnych nagłówków, pale- faktyczne dane obrazu. Zdarzają się widłowo obsługującej pole bfOffBits,
ty barw oraz danych obrazu. To pole przypadki w których to pole jest wy- aplikacji. Taki właśnie efekt zapre-
stanowi pierwszą pułapkę, ale w nią zerowane część aplikacji w tym wy- zentowany jest na Rysunku 2 (użyte
wpadają jedynie nieuważni programi- padku uznaje że dane obrazu znajdu- grafiki pochodzą z http://icanhasche-
ści. Rozważmy kod z Listingu 1 pro- ją się bezpośrednio za nagłówkami. ezburger.com), dla ukazania efektu
gramista wczytał nagłówek, zaufał Programista implementujący obsłu- ten sam plik BMP podano Listerowi
polu bfSize i zaalokował tyle pamięci gę BMP może popełnić kilka błędów. (część Total Commandera odpowie-
dzialna za podgląd plików) oraz Irfa-
Tabela 1. Struktura BITMAPFILEHEADER
nView 4.10. Należy zaznaczyć iż plik
Typ i nazwa pola Opis jest oczywiście dwa razy większy niż
byłby normalnie (ponieważ zawiera
WORD bfType Identyfikator BMP, zazwyczaj lite-
dwa obrazki).
ry BM
Drugim błędem który programi-
DWORD bfSize Całkowita wielkość pliku
sta może popełnić jest założenie że
WORD bfReserved1 Zarezerwowane, zaleca się nadanie
polu bfOffBits można zaufać i będzie
wartości 0
ono na pewno mniejsze od wielkości
WORD bfReserved2 Zarezerwowane, zaleca się nadanie
pliku, a tym bardziej dodatnie (jak pi-
wartości 0
sałem wcześniej jest to DWORD, czy-
li liczbą bez znaku, ale należy pamię-
DWORD bfOffBits Pozycja (offset) danych w pliku
tać że suma dwóch liczb 32 bitowych
www.hakin9.org hakin9 Nr 2/2008 3
Atak
jest nadal liczbą 32 bitową, czyli nie Warto zauważyć iż odsunięcie rokości bitmapy (biWidth), jej wyso-
ma tak na prawdę różnicy czy jest to danych od nagłówków stwarza do- kości (biHeight) oraz głębi kolorów,
DWORD czy SDWORD jeśli nastą- wolną ilość miejsca na ukrycie ewen- czyli ilości bitów które opisują każ-
pi integer overflow). Tego typu błąd, tualnych dodatkowych danych. dy kolejny piksel (biBitCount). War-
niegrozny ale jednak, występuje w Podsumowując strukturę BIT- tości z tych pól bardzo często służą
Microsoft Paint do wersji 5.1 włącznie MAPFILEHEADER, są tu dwa miej- do wyliczenia całkowitej ilości bajtów
(czyli tej dołączonej do Microsoft Win- sca w których programista może po- potrzebnej do przechowania bitmapy
dows XP SP2, wersja 6.0, dołączo- pełnić błąd, a także 64 bity (8 bajtów) w pamięci. W tym celu implementuje
na do Microsoft Windows Vista, zo- w samym nagłówku, w których moż- się następujące równanie:
stała poprawiona). Przykładowe wy- na zapisać (ukryć) dodatkowe dane.
korzystanie widać na Rysunku 3, ze- PotrzebnaIlośćBajtów =
stawiono na nim aplikację Microsoft Nagłówek Szerokość * Wysokość * (Głębia / 8)
Paint oraz IrfanView, które wyświetla- BITMAPINFOHEADER
ją ten sam plik BMP. Jak można do- Drugim z kolei nagłówkiem plików W przypadku BMP szerokość, czyli
myślić się z rysunku IrfanView posta- BMP w wersji Windows V3 jest BIT- biWidth, w tym równaniu zaokrągla-
nowił zignorować błędnie wypełnione MAPINFOHEADER, struktura skła- na jest w górę do najbliższego iloczy-
pole bfOffBits i uznał że dane obrazu dająca się z 11 pól o łącznej długości nu liczby 4 (więcej o tym będzie w pa-
znajdują się bezpośrednio za nagłów- 40 bajtów (28h), rozpoczynająca się ragrafie Dane obrazu BI _ RGB), czy-
kami, natomiast mspaint.exe wykonał od offsetu 0Eh. li jeśli bitmapa na przykład ma szero-
operacje WyświetlBitmapę(Początek- Pierwsze pole biSize określa kość 109 pikseli, to w tym równaniu
Danych + bfOffBits), co poskutkowa- wielkość niniejszego nagłówka, po zostanie użyta wartość 112. Tak więc
ło wyświetleniem fragmentu pamięci tej wielkości aplikacje rozpoznają czy to równanie w przypadku BMP ma
należącej do aplikacji. Należy dodać nagłówkiem jest faktycznie BITMA- następującą postać:
że w wypadku gdy PoczątekDanych PINFOHEADER, i czy plik BMP jest
+ bfOffBits wskazuje na nieistnieją- na pewno wersją Windows V3 forma- PotrzebnaIlośćBajtów =
cy fragment pamięci, zostaje rzuco- tu BMP. Prawidłową wartością jest ZaokrąglonaSzerokość *
ny wyjątek (Naruszenie Ochrony Pod- oczywiście 40 (28h). Niektóre apli- Wysokość * (Głębia / 8)
czas Odczytu, ang. Read Access Vio- kacje, takie jak IrfanView czy Mozilla,
lation), w wypadku mspaint.exe jest przyjmują że plik BMP jest plikiem w Tak wyliczona wartość używana jest
on jednak obsługiwany. Należy za- wersji Windows V3 nawet w wypad- zazwyczaj do alokacji pamięci na po-
uważyć iż jeżeli tego typu błąd wystą- ku gdy biSize posiada jakąś inną, nie- trzeby docelowej bitmapy. Jest to jed-
pił by w aplikacji posiadającej w pa- znaną, wartość daje to możliwość nocześnie miejsce, w którym istnie-
mięci wrażliwe dane, to sprawny so- ukrycia kolejnych 32 bitów danych, z je prawdopodobieństwo błędnej im-
cjotechnik mógł by z powodzeniem tym że nie wszystkie programy będą plementacji. Załóżmy na chwilę że
wydobyć od nieświadomego użyt- potrafiły poradzić sobtie z wyświetle- programista założył że PotrzebnaIlo-
kownika zrzut ekranu na którym wi- niem bitmapy w takim wypadku. śćBajtów jest wartością typu LONG
dać zle wyświetlaną bitmapę która tak Drugim, trzecim oraz piątym z lub DWORD (32 bity), a tak się czę-
na prawdę przedstawiała by fragment kolei polem są kolejno biWidth, bi- sto zdarza. Jeżeli wynik równania bę-
pamięci na przykład z hasłem i logi- Height oraz biBitCount. Są to, jak dzie większy od FFFFFFFFh, czy-
nem danego użytkownika. nazwa wskazuje, informacje o sze- li maksymalnej liczby którą można
zapisać w 32 bitowej zmiennej typu
Tabela 2. Struktura BITMAPINFOHEADER całkowitego/naturalnego, to nastą-
pi przepełnienie zmiennej całkowi-
Typ i nazwa pola Opis
tej (ang. Integer Overflow), co z ko-
DWORD biSize Wielkość nagłówka, w tym wypadku 28h
lei może doprowadzić do błędu ty-
LONG biWidth Szerokość bitmapy
pu przepełnienia bufora. Wezmy pod
LONG biHeight Wysokość bitmapy
uwagę kod z Listingu 2. Programista
zaokrągla szerokość po czym wyli-
WORD biPlanes Ilość płaszczyzn, przyjęto wartość 1
cza potrzebną ilość bajtów, a następ-
WORD biBitCount Ilość bitów na piksel
nie alokuje pamięć i wczytuje wiersz
DWORD biCompression Rodzaj zastosowanego kodowania/kompresji
po wierszu całą bitmapę do zaaloko-
DWORD biSizeImage Wielkość nieskompresowanej bitmapy w pamięci
wanej pamięci. Wszystko wydaje się
LONG biXPelsPerMeter DPI poziome być w porządku, ale załóżmy na chwi-
lę że otrzymaliśmy bitmapę o wielko-
LONG biYPelsPerMeter DPI pionowe
ści 65536x65536x8, czyli szerokość
DWORD biClrUsed Użyta ilość kolorów
i wysokość mają wartość 10000h.
DWORD biClrImportant Ilość ważnych kolorów
Po podstawieniu wartości w równa-
www.hakin9.org
4 hakin9 Nr 2/2008
Format BMP okiem hakera
niu na potrzebną ilość bajtów otrzy-
Listing 1. Niebezpieczny kod wczytujący bitmapę
mamy 10000h * 10000h * (8/8), czy-
li 100000000h. DWORD pomieścić
void *ReadBMPtoMemory(const char *name, unsigned int *size)
może jedynie najmłodsze 32 bity tej
{
liczby, w związku z czym w zmiennej char *data = NULL;
BITMAPFILEHEADER bmfh;
size zapisane zostanie 00000000h,
FILE *f = NULL;
czyli 0. Następnie dojdzie do aloka-
size_t ret = 0;
cji pamięci, która zakończy się suk-
/* Otwórz plik */
cesem (przykładowo system Win-
f = fopen(name, rb );
dows zaalokuje 16 bajtów, mimo ze if(!f) return NULL;
/* Wczytaj naglowek i zaalokuj pamięć */
malloc dostał 0 w parametrze), a po-
fread(&bmfh, 1, sizeof(bmfh));
tem zostanie w to miejsce wczytane
*size = bmfh.bfSize;
65536 wierszy po 65536 pikseli każ-
data = malloc(bmfh.bfSize);
dy, czyli 4 GB danych, co spowodu-
if(!data) goto err;
je przepełnienie bufora oraz wyrzuce- memset(data, 0, bmfh.bfSize);
/* Wczytaj plik */
nie wyjątku (ang. Write Access Viola-
fseek(f, 0, SEEK_SET);
tion). W przypadku gdy wyjątek zosta-
do {
nie obsłużony prawdopodobnie bę-
ret += fread(data + ret, 1, 0x1000, f);
dzie również możliwość wykonania
} while(!feof(f));
kodu, a w wypadku gdy nie zostanie /* Powrót */
fclose(f);
obsłużony, aplikacja po prostu zakoń-
return data;
czy działanie z odpowiednim komuni-
/* Obsluga bledow */
kacje o błędzie.
err:
Czwartym z kolei polem, pomi-
if(f) fclose(f);
niętym wcześniej, jest biPlanes, które if(data) free(data);
return NULL;
mówi o ilości płaszczyzn. Przyjęte jest
}
że w tym polu powinna być wartość 1.
Niektóre programy ignorują wartość
tego pola, przez co możliwe jest ukry-
cie kolejnych 16 bitów danych. W przypadku tego pola pułapka wy- jąć bardzo małą wartość (na przykład
Kolejnym, szóstym polem jest gląda bardzo podobnie jak w przy- 1), i wtedy nawet mała bitmapa może
biCompression field. To pole przyj- padku pola bfSize z BITMAPFILEHE- zająć kilkanaście kartek A4, lub bar-
muje pewne z góry ustalone war- ADER, zaleca się więc zignorowanie dzo dużą wartość, przez co cała bit-
tości które mówią o sposobie ko- wartości tego pola. Nieostrożne uży- mapa będzie wielkości milimetr na mi-
dowania i kompresji użytej w przy- cie wartości biSizeImage przy aloka- limetr. To pole może zostać wykorzy-
padku danego pliku BMP. Dostęp- cji pamięci, a następnie brak kontro- stane również do przechowania pew-
ne wartości w BMP Windows V3 to li pozycji wskaznika zapisu przy de- nej informacji (64 bity łącznie), szcze-
BI _ RGB (0), BI _ RLE8 (1), BI _ RLE4 kompresji może prowadzić do prze- gólnie jeśli nie zależy nam na popraw-
(2) oraz BI _ BITFIELDS (3). Kolejne pełnienia bufora. ności rozdzielczości drukowanej.
wersje formatu BMP zakładają rów- Dwa kolejne pola biXPelsPerMe- Przedostatnim polem jest biClrU-
nież dwie inne wartości: BI _ JPEG (4) ter oraz biYPelsPerMeter mówią o sed które mówi o ilości kolorów w pa-
oraz BI _ PNG (5). Różne rodzaje ko- poziomej i pionowej ilości pikseli przy- lecie barw. Jeżeli to pole jest wyzero-
dowanie BMP są omówione w kolej- padających na metr (informacja ana- wane, przyjmuje się że ilość kolorów
nych podpunktach. logiczna do DPI, ang. Dots Per Inch). w palecie jest równa liczbie 2 podnie-
Następnym polem jest biSizeIma- Informacje te są potrzebne głównie w sionej do potęgi biBitCount (do 8 bi-
ge określające całkowitą wielkość bit- przypadku drukowania danej bitma- tów włącznie), czyli na przykład w
mapy po ewentualnej dekompresji py. Pojawia się tutaj pewna grozba w przypadku 8-bitowej bitmapy przyj-
(jeżeli bitmapa nie jest kompresowa- przypadku implementacji drukowania muje się że paleta ma 256 kolorów.
na, to pole może być ustawione na 0). bitmap rozdzielczość ta może przy- Co ciekawe, programiści mają ten-
dencje ufać temu polu i zakładać że
Tabela 3. Struktura RGBQUAD
jeżeli biClrUser wynosi na przykład 1,
Typ i nazwa pola Opis
to w bitmapie pojawi się jedynie pierw-
BYTE rgbBlue Wartość barwy niebieskiej szy z kolei kolor, czyli 00. Jeżeli w bit-
mapie pojawi się więcej kolorów pra-
BYTE rgbGreen Wartość barwy zielonej
widłowym działaniem powinno być al-
BYTE rgbRed Wartość barwy czerwonej
bo wyświetlanie pozostałych kolorów
BYTE rgbReserved Zarezerwowane
jako czarny (czyli wyzerowanie pozo-
www.hakin9.org hakin9 Nr 2/2008 5
Atak
stałej części palety), albo wykonanie
operacji modulo (wyświetlony _ kolor
= kolor % biClrUsed). Tak zachowu-
ją się MSPaint, Internet Explorer, czy
Paint Shop Pro. Bardzo dużo progra-
mów jednak rysuje bitmapę na swój
własny sposób, czego przykład jest
przedstawiony na Rysunku 4. W tym
miejscu należało by się zaintereso-
wać czemu tak się dzieje, oraz skąd
się biorą pozostałe kolory ponie-
waż w palecie w pliku BMP zadekla- Rysunek 2. Wykorzystanie ignorowania pola bfOffBits
rowany został tylko jeden. Odpowiedz
na to drugie pytanie jest dość prosta te przeglądarki obsługują wprowadzo- nika na wyciek informacji a nawet wy-
najwyrazniej programy alokują pa- ny w HTML 5 tag