background image

38

 

HAKIN9

ATAK

7-8/2008

pierwszej części artykułu, 
opublikowanej w Hakin9 5/2008, 
opisałem podstawową strukturę pliku 

graficznego GIF oraz omówiłem kompresję 
LZW. Niniejsza – druga – część artykułu 
poświęcona będzie rozszerzeniom formatu 
GIF (takim, jak animacja, komentarze czy 
wreszcie rozszerzenia aplikacji), miejscom, 
w których można ukryć/szukać dodatkowych 
danych oraz możliwym do popełnienia podczas 
implementacji błędom.

Przed przystąpieniem do lektury warto 

przypomnieć sobie ogólną budowę formatu 
GIF, podział na części oraz podział danych 
na fragmenty – data Sub-blocks (w tym celu 
można posłużyć się na przykład poprzednią 
częścią artykułu lub sięgnąć do opisu 
standardu GIF [1]).

Animowane GIFy

Plik GIF nawet bez korzystania z rozszerzeń 
oferuje możliwość zapisania więcej niż jednej 
klatki obrazu – w takim przypadku wszystkie 
klatki są używane do stworzenia jednej wynikowej 
grafiki. Standard 89a wprowadził dodatkową 
strukturę – blok kontroli grafiki (ang. Graphic 
Control Extension
, GCE), który zawiera dodatkowe 
informacje umożliwiające stworzenie z serii 
klatek faktycznej animacji. Blok zawiera między 
innymi takie informacje, jak wielkość opóźnienia 
przed wyświetleniem następnej klatki czy sposób 
przejścia do następnej klatki.

MICHAŁ „GYNVAEL

COLDWIND”

SKŁADNIKIEWICZ

Z ARTYKUŁU 

DOWIESZ SIĘ

jakie rozszerzenia zostały 

wprowadzone do formatu GIF w 

wersji 89a,

na co uważać podczas 

implementowania rozszerzeń 

formatu GIF,

gdzie szukać błędów w 

aplikacjach korzystających z GIF,

gdzie ukryć lub szukać ukrytych 

danych w plikach GIF.

CO POWINIENEŚ 

WIEDZIEĆ

mieć ogólne pojęcie na temat 

plików binarnych,

mieć ogólne pojęcie na temat 

bitmap,

mieć pojęcie o plikach GIF.

Według dokumentacji jeden taki blok może 

(ale nie musi) poprzedzać dane obrazu (a 
konkretniej nagłówek Image Descriptor) lub 
rozszerzenie zwykłego tekstu (ang. Plain Text 
Extension
). Dany blok ma zasięg lokalny, czyli 
wpływa jedynie na następującą po nim klatkę 
animacji.

Znaczna część pól w strukturze GCE 

(patrz Tabela 1) ma stałą, ustaloną z góry, 
wartość. Wyjątkami są pola DisposalMethod
UserInputFlagTransparentColorFlag
DelayTime oraz TransparentColorIndex. Pole 
DisposalMethod (metoda usunięcia) określa, w 
jaki sposób dana klatka ma zostać skasowana 
przed narysowaniem następnej. Dostępne są 4 
opcje:

•   [0] – Nieokreślona metoda usunięcia 

(zazwyczaj równoważna z opcją 1),

•   [1] – Nie usuwaj (następna klatka zostanie 

wyrysowana na obecnej),

•   [2] – Wypełnij obraz kolorem tła,
•   [3] – Powróć do poprzedniego obrazu.

Dokumentacja zaleca, aby opcji trzeciej używać 
jedynie w przypadku niewielkich fragmentów 
obrazu (z uwagi na subiektywnie wysokie zużycie 
pamięci – chociaż dla obecnych komputerów nie 
stanowi to problemu), a w razie, gdyby dekoder 
nie potrafił poradzić sobie z przywróceniem 
poprzedniego obrazu, dopuszczalne jest 
wypełnienie obrazu kolorem tła.

Stopień trudności

Format GIF 

okiem hakera

Format GIF, oprócz podstawowej funkcjonalności oferowanej 

przez wszystkie standardowe formaty graficzne, udostępnia 

również kilka rozszerzeń umożliwiających stworzenie animacji czy 

też dodanie komentarza do grafiki.

background image

39

 

HAKIN9 

FORMAT GIF OKIEM HAKERA

7-8/2008

Pozostałe opcje (4-7) są 

zarezerwowane do przyszłego użytku 
– ale w praktyce różne dekodery 
(przy testach zostały wykorzystane: FF 
– Mozilla Firefox 2.0.0.14, O – Opera 9.24, 
S – Safari 3.1 (525.13), IE – Microsoft 
Internet Explorer 7.0.6000.16643, IV 
– IrfanView 4.10) już je implementują, i to 
w odmienny sposób:

•   [4] – IE, IV i FF traktują jako powrót do 

poprzedniego obrazu, O i S jako nie 
usuwaj
,

•   [5] – IE i IV traktują jako powrót do 

poprzedniego obrazu, a FF, O, S jako 
nie usuwaj,

•   [6] – IE i IV traktują jako powrót do 

poprzedniego obrazu, FF jako nie 
usuwaj
, a O i S jako wypełnij obraz 
kolorem tła
,

•   [7] – IE, IV, S i O traktują jako powrót 

do poprzedniego obrazu, a FF jako nie 
usuwaj
.

Jak widać, nie ma w dekoderach zgody 
co do tego, jak traktować metody 
niezdefiniowane przez dokumentację. 
IE oraz IV są zgodne, iż wszystkie 
niestandardowe wartości powinny być 
traktowane jako powrót do poprzedniego 
obrazu, natomiast pozostałe dekodery 
nie mają jednego określonego zdania. 
Różnice te teoretycznie pozwalają na 
stworzenie GIFa, który wyglądać będzie 
inaczej w każdej z wspomnianych 
przeglądarek graficznych czy 
internetowych. 

Należy jeszcze dodać, iż Safari ma 

poważne problemy wydajnościowe 
w przypadku opcji nie usuwaj – dla 

porównania, testowy GIF (50 klatek 
256x256) na Firefoksie renderował 
się około 5 sekund, natomiast Safari 
poświęciło na niego prawie 3 minuty, 
dodatkowo każda kolejna klatka 
renderowała się dłużej od poprzedniej, 
a przeglądarka odpowiadała na 
polecenia użytkownika tylko pomiędzy 
klatkami. Końcowe klatki renderowały 
się około 20 sekund, więc możliwe 
jest przeprowadzenie skutecznego 
ataku DoS przy pomocy odpowiednio 
spreparowanej strony WWW (test został 
wykonany na komputerze Intel Core 2 
Quad 2.4GHz z 4GB RAM).

Pole TransparentColorFlag 

(flaga przezroczystości) oraz 
TransparentColorIndex (numer 
przezroczystego koloru) odpowiedzialne 
są oczywiście za istnienie i wybór koloru, 
który będzie uznany za przezroczysty 
– czyli oznaczał piksele nie narysowane, 
pod którymi prześwitywać będzie 
poprzednia zawartość bufora. Warto 
zaznaczyć, iż jeżeli flaga przezroczystości 
jest wygaszona (tj. równa 0), to pole 
TransparentColorIndex może zostać 
przeznaczone na przechowanie 
dowolnego bajtu danych.

Pole DelayTime (opóźnienie) jest 

informacją o wielkości opóźnienia (w 
setnych sekundy) przed wyświetleniem 
kolejnej klatki. Ostatnie pole (a w zasadzie 
flaga) – UserInputFlag – określa, czy 
dekoder powinien zaczekać na interakcję 
z użytkownikiem (pojęcie to nie jest 
definiowane przez dokumentację, tak 
więc interakcją z użytkownikiem może 
być dowolne zdarzenie uznane za 
takowe przez dekoder, np. naciśnięcie 

przycisku myszy lub dowolnego 
klawisza). W przypadku, gdy flaga jest 
aktywna oraz gdy ustawiony jest czas 
opóźnienia (tj. jest niezerowy), dekoder 
powinien zaprezentować kolejną klatkę 
po odczekaniu wskazanego czasu lub 
po zaistnieniu określonego zdarzenia. W 
praktyce wszystkie z testowanych przeze 
mnie dekoderów ignorowały tę flagę i nie 
czekały na interakcję.

Dodatkowe dane mogą zostać 

przechowane w polu Reserved, które 
zazwyczaj nie jest kontrolowane przez 
dekodery.

Warto zaznaczyć, iż w praktyce 

możliwe jest wielokrotne umieszczenie 
struktury GCE przed danymi. 
Wszystkie testowane dekodery traktują 
wtedy ostatnią strukturę GCE jako 
obowiązującą, a resztę ignorują – jest to 
więc całkiem niezłe miejsce na ukrycie 
dodatkowych informacji.

Dodatkowo, programista może 

popełnić ewentualny błąd zakładając, iż 
BlockSize będzie na pewno równe 4 i 
jednocześnie korzystając z niego w celu 
załadowania danych do statycznego 
bufora – taka sytuacja prowadzi w 
prostej linii do przepełnienia bufora. W 
praktyce większość dekoderów wymaga, 
aby BlockSize był rzeczywiście równy 4 
(jest to sprawdzane, w przypadku innej 
wartości dekoder przerywa pracę).

Rozszerzenie Netscape

W GCE zabrakło informacji o tym, 
czy animacja jest jednorazowa, 
czy też powinna być zapętlona w 
nieskończoność. W tym celu powstało 
rozszerzenie aplikacji NETSCAPE2.0 
(jego obsługa została wprowadzona 
w Netscape Navigator 2.0 Beta 4), 
którego pole NumberOfIterations (patrz 
Tabela 2) określa, czy aplikacja powinna 
animować GIF w nieskończoność 
(wartość 0), czy zaprzestać po określonej 

Tabela 1. 

Struktura Graphic Control Extension

Typ i nazwa pola

Opis

BYTE ExtensionIntroducer

Znacznik rozszerzenia, zawsze 0x21

BYTE GraphicControlLabel

Rodzaj rozszerzenia, zawsze 0xF9

BYTE BlockSize

Wielkość następującego bloku danych, zawsze 4

BYTE Reserved:3

Zarezerwowane

BYTE DisposalMethod:3

Metoda usunięcia klatki

BYTE UserInputFlag:1

Flaga oczekiwania na interakcje

BYTE TransparentColorFlag:1

Flaga przezroczystości

WORD DelayTime

Czas opóźnienia

BYTE TransparentColorIndex

Numer koloru przezroczystego

BYTE BlockTerminator

Terminator bloku, zawsze 0

Rysunek 1. 

Artystyczna wizja własnej 

interpretacji GIF przez różne przeglądarki

background image

ATAK

40

 

HAKIN9 7-8/2008

FORMAT GIF OKIEM HAKERA

41

 

HAKIN9 

7-8/2008

liczbie przebiegów. Co ciekawe, 
NN 2.0 Beta 4 uznawała wszystkie 
animacje posiadające niniejszą 
strukturę jako mające się wykonywać w 
nieskończoność, bez względu na wartość 
pola NumberOfIterations ; zostało to 
poprawione w wersji Beta 5. Animacja 
nie posiadająca tej struktury (która 
powinna się znajdować zaraz za GCT) 
jest traktowana przez dekodery wedle 
uznania – zazwyczaj jako animacja, 
którą powinno odegrać się raz (IE, FF, 
O, S). Istnieją jednak dekodery (IV), 
które uznają, iż animacja powinna być 
odgrywana w nieskończoność.

Niektóre dekodery (np. IE i FF) 

wyświetlają klatki animacji również 
podczas ładowania GIFa. To wyświetlenie 
nie jest przez nie zaliczane jako 
faktyczne wyświetlenie animacji, przez 
co efektywnie animacja wyświetlana jest 
o jeden raz więcej. IrfanView natomiast 
ignoruje nagłówek całkowicie.

Pole One powinno mieć zawsze 

wartość 1, natomiast nie wszystkie 
dekodery je testują. Ze sprawdzonych 
dekoderów jedynie Firefox zwrócił uwagę 
na jego zmianę – jest to więc miejsce, w 
którym można ukryć bajt danych.

Kolejną możliwą modyfikacją jest 

powiększenie bloku zawierającego 
wartość Magic. Dekodery zawarte 
w IE oraz FF nie zwróciły uwagi na 
zmianę (powstało trochę miejsca 
do umieszczenia danych), natomiast 
dekoder zawarty w Operze uznał, iż 
animację należy odtworzyć jedynie raz, 
a dekoder z Safari – że animację należy 
odtwarzać w nieskończoność. 

W przypadku zwiększenia drugiego 

sub-bloku danych jedynie Opera 
zareagowała identycznie, jak poprzednim 
razem, reszta dekoderów zignorowała 
zmianę i zachowała się tak, jak gdyby 
otrzymała prawidłowo wypełniony 
nagłówek.

Analogicznie, jak w poprzednim 

przypadku, programista powinien zwrócić 
uwagę na możliwość wystąpienia 
przepełnienia bufora.

Komentarze

Struktura komentarzy (ang. Comment 
Extension
, CE) jest bardzo prosta (co 
pokazuje Tabela 3) – składa się ze 
stałego nagłówka komentarzy, po nim 
następuje seria sub-bloków z danymi 
(dla przypomnienia: każdy sub-blok 
składa się z bajtu określającego 
wielkość danych w bajtach oraz 
odpowiednią ilość bajtów danych) 
i wreszcie sub-blok terminujący (z 
wielkością danych równą 0).

Komentarze z plików GIF wyświetlają 

jedynie nieliczne aplikacje, większość 
dekoderów je ignoruje.

Głównym błędem, jaki można 

popełnić podczas obsługi komentarzy 
GIF, jest brak sprawdzania, co komentarz 
faktycznie zawiera – czy nie znajdują 
się w nim znaki specjalne (np. końca 
linii, terminatora tekstu) lub specjalnych 
sekwencji. Tego typu błąd został 
znaleziony w roku 2001 w przeglądarce 
Netscape Navigator przez Floriana 
Wescha i umożliwiał umieszczenie kodu 
JavaScript wewnątrz komentarza. Skrypt 
ten zostałby wykonany w momencie 
wyświetlenia pliku GIF (NN wyświetlał 
również komentarze) przez przeglądarkę, 
np. w wyniku wejścia przez użytkownika 
na daną stronę WWW.

Programista może łatwo przeoczyć 

także inny błąd, zakładając podczas 
implementacji obsługi komentarzy, iż 
komentarz nie będzie większy od jednego 
sub-bloku (czyli nie będzie przekraczał 
255 znaków) – takie założenie może 
doprowadzić do przepełnienia bufora. 

Plik GIF może zawierać dowolną ilość 

struktur CE.

Rozszerzenie Plain Text

Ostatnim rozszerzeniem GIF jest Plain 
Text Extension
 – rozszerzenie teoretycznie 
umożliwiające zapisanie tekstu na 
bitmapie jako faktycznego tekstu, a nie 
serii pikseli. W takim wypadku dekoder 
byłby odpowiedzialny za prawidłowe 
wyrysowanie tekstu na wynikowej bitmapie. 
Niestety, w praktyce okazuje się, iż 
żaden z przetestowanych dekoderów nie 
miał zaimplementowanej obsługi tego 
rozszerzenia. 

Co więcej, w kilku artykułach, na które 

natknąłem się podczas badań, autorzy 
sugerują, iż jest to martwe rozszerzenie 
i powinno zostać zignorowane przez 
programistę.

Niemniej jednak z uwagi na 

możliwość przyszłego pojawienia się 
tego typu dekoderów, lub pojawienia się 
potrzeby zaimplementowania takiego 

Tabela 2. 

Struktura NETSCAPE2.0

Typ i nazwa pola

Opis

BYTE 

ExtensionIntroducer

Znacznik rozszerzenia, zawsze 0x21

BYTE ApplicationExtensi

onLabel

Rodzaj rozszerzenia, zawsze 0xFF

BYTE BlockSize1

Długość sub-bloku danych, zawsze 0x0B

BYTE Magic[11]

Zawsze „NETSCAPE2.0”

BYTE BlockSize2

Długość sub-bloku danych, zawsze 0x03

BYTE One

Zawsze 0x01

WORD 

NumberOfIterations

Ilość powtórzeń animacji

BYTE BlockTerminator

Terminator bloku, zawsze 0

Tabela 3. 

Struktura Comment Extension

Typ i nazwa pola

Opis

BYTE ExtensionIntroducer

Znacznik rozszerzenia, zawsze 0x21

BYTE CommentLabel

Rodzaj rozszerzenia, zawsze 0xFE

SubBlock CommentData[]

Jeden lub więcej sub-bloków danych

BYTE BlockTerminator

Terminator bloku, zawsze 0

W Sieci

•   http://www.w3.org/Graphics/GIF/spec-

gif89a.txt – standard GIF89a.

background image

ATAK

40

 

HAKIN9 7-8/2008

FORMAT GIF OKIEM HAKERA

41

 

HAKIN9 

7-8/2008

dekodera, postanowiłem omówić również 
i to rozszerzenie.

Rozszerzenie PTE opiera się o 

koncept stary jak świat znany z konsoli 
poleceń – stałą wielkość pojedynczego 
znaku oraz podzielenie ekranu na siatkę 
o stałej całkowitej rozdzielczości. W 
strukturze PTE podaje się takie informacje 
jak początek siatki (położenie lewego 
górnego rogu) – pola TextGridLeftPosition 
oraz TextGridTopPosition (siatka 
w cale nie musi się zaczynać na 
0,0!), szerokość i wysokość siatki 
– pola TextGridWidth i TextGridHeight
szerokość i wysokość pojedynczego 
znaku – pola CharacterCellWidth oraz 
CharacterCellHeight, i kolor czcionki 
oraz tła (kolor korzysta z Global Color 
Table
). Sam tekst który ma zostać 
wyrenderowany jest podzielony na 
sub-bloki i jest dołączony po sub-
bloku zawierającym konfigurację PTE. 
Całość zakończona jest podobnie jak 
w przypadku poprzednich rozszerzeń 
– terminującym bajtem o wartości 0.

Standard zaleca aby korzystać 

jedynie ze znaków o wielkości 8x8 lub 
8x16, oraz aby szerokość i wysokość 
siatki była tak dobrana aby ilość znaków 
w wierszu czy kolumnie była całkowita 
– w przypadku gdy nie jest, część 
ułamkowa powinna zostać zignorowana.

Należy zauważyć iż wybór kroju 

czcionki należy całkowicie do dekodera, 
i jest niezależne od intencji osoby 
tworzącej GIF'a.

Pierwszym wektorem ataku, który 

nasuwa się od razu po przeczytaniu 
zaleceń standardu, jest użycie innej 
wielkości znaku niż 8x8 czy 8x16. 
Zauważmy iż dla programisty ważną 
informacją będzie ilość znaków w 
linii, którą wylicza się za pomocą 
prostego dzielenia – TextGridWidth/
CharacterCellWidth
 (można się obyć 
bez tego równania, jednak z dużym 
prawdopodobieństwem pojawi się ono 
w programie; analogicznie liczy się 
maksymalną ilość linii w siatce). Celnym 
atakiem na każde dzielenie jest użycie z 
zera w mianowniku – co, niesprawdzone, 
powoduje wygenerowanie wyjątku Divide 
By Zero
, a z kolei nieobsłużony wyjątek 
prowadzi do skutecznego ataku typu DoS.

Innym podejściem do problemu 

wielkości znaku może być użycie 
większego znaku od siatki, oraz, w 
drugim podejściu, większego znaku 
od całego obrazu. Można w tym celu 
użyć odpowiednio dużego znaku (np. 
255x255), lub odpowiednio małego 
obrazu (np. 4x4) dla standardowej 
wielkości znaku. Jeżeli programista 
zaniedba sprawdzanie takich 
przypadków, taki atak doprowadzi do 
przepełnienia bufora obrazu – jednak 
prawdopodobieństwo uruchomienia 
zewnętrznego kodu w takim wypadku 
wydaje się być niezwykle niskie. 
Niejako rozwinięciem tego pomysłu 
jest użycie siatki większej niż obraz, 
ale z wieloma małymi znakami 

– nieuważny programista mógł nie 
sprawdzić wielkości, co skończyło by 
się przepełnieniem bufora – z nadal 
niewielkim (ale jednak większym niż 
poprzednio) prawdopodobieństwem 
wykonania kodu.

Innym wektorem ataku mogły by 

być indeksy kolorów – tła i znaku. Jeżeli 
programista nie sprawdzi czy index nie 
wykracza po za paletę kolorów, może 
to doprowadzić do wycieku informacji 
(analogicznego jak w przypadku palety, 
plików BMP i przeglądarek Mozilla 
Firefox i Opera – błąd został opisany 
w hakin9 3/2008), lub odmowy usługi 
(DoS). Pojawia się również pytanie – a 
co jeśli plik nie będzie w ogóle posiadał 
globalnej palety kolorów? Kolejny 
możliwy wyciek informacji lub DoS.

Podobnie jak w przypadku 

poprzednich rozszerzeń, można 
przetestować zachowanie dekodera 
w przypadku zmiany wielkości sub-
bloku konfiguracyjnego. Warto również 
sprawdzić czy dekoder jest w jakiś 
sposób wrażliwy na nieobecność 
sub-bloku konfiguracyjnego, oraz 
na ekstremalną ilość sub-bloków 
zawierających tekst.

Z uwagi na fakt iż obecnie mało który 

dekoder obsługuje rozszerzenie PTE, to 
rozszerzenie staje się bardzo dobrym 
miejscem na ukrycie dodatkowych 
informacji.

Podsumowanie

We wszystkich wymienionych 
rozszerzeniach warto zwrócić również 
uwagę na możliwość dołożenia 
kolejnego sub-bloku danych do 
wymaganych sub-bloków – powinno 
to pomóc stworzyć miejsce do ukrycia 
kolejnych danych.

Tym artykułem kończę tematykę 

plików GIF. Po lekturze obu artykułów 
uważny Czytelnik powinien być w stanie 
zaimplementować bezpiecznie obsługę 
formatu GIF, wraz z rozszerzeniami, a 
także skutecznie odnaleźć błędy ukryte w 
testowanych aplikacjach.

Michał Składnikiewicz

Michał Składnikiewicz, inżynier informatyki, ma 

wieloletnie doświadczenie jako programista oraz 

reverse engineer. Obecnie pracuje w Hispasec 

– międzynarodowej firmie specjalizującej się w 

bezpieczeństwie komputerowym. 

Kontakt z autorem: gynvael@coldwind.pl

Tabela 4. 

Struktura Plain Text Extension

Typ i nazwa pola

Opis

BYTE ExtensionIntroducer

Znacznik rozszerzenia, zawsze 0x21

BYTE PlainTextLabel

Rodzaj rozszerzenia, zawsze 0x01

BYTE BlockSize

Wielkość sub-bloku – zawsze 0x0C

WORD TextGridLeftPosition

Początek siatki – współrzędna X

WORD TextGridTopPosition

Początek siatki – współrzędna Y

WORD TextGridWidth

Szerokość siatki

WORD TextGridHeight

Wysokość siatki

BYTE CharacterCellWidth

Szerokość pojedynczego znaku

BYTE CharacterCellHeight

Wysokość pojedynczego znaku

BYTE TextForegroundColorIndex

Kolor znaku

BYTE TextBackgroundColorIndex

Kolor tła znaku

SubBlock PlainTextData[]

Sub-bloki zawierające tekst

BYTE BlockTerminator

Terminator bloku, zawsze 0