SPIS TRE¥CI
Transkrypt
SPIS TRE¥CI
SPIS TRECI PRZE DMOWA 11 P ODZI KO W ANI A 12 0X100 WPROWAD ZENIE 13 0X200 PR OGR AMOWANIE 19 0x210 Istota programowania .....................................................................................20 0x220 Pseudokod ......................................................................................................22 0x230 Struktury sterujce ...........................................................................................22 0x231 If-Then-Else ........................................................................................22 0x232 Ptle While/Until ................................................................................24 0x233 Ptle for .............................................................................................25 0x240 Podstawowe pojcia programistyczne .............................................................26 0x241 Zmienne .............................................................................................26 0x242 Operatory arytmetyczne .....................................................................27 0x243 Operatory porównania .......................................................................28 0x244 Funkcje ...............................................................................................30 0x250 Zaczynamy brudzi sobie rce ..........................................................................34 0x251 Wikszy obraz ....................................................................................35 0x252 Procesor x86 ......................................................................................38 0x253 Jzyk asembler ...................................................................................40 0x260 Wracamy do podstaw .....................................................................................53 0x261 acuchy ............................................................................................53 0x262 Ze znakiem, bez znaku, duga i krótka ................................................57 0x263 Wskaniki ...........................................................................................59 0x264 acuchy formatujce .........................................................................63 0x265 Rzutowanie typów .............................................................................67 0x266 Argumenty wiersza polece ...............................................................74 0x267 Zasig zmiennych ...............................................................................78 0x270 Segmentacja pamici .......................................................................................85 0x271 Segmenty pamici w jzyku C ............................................................92 0x272 Korzystanie ze sterty ...........................................................................94 0x273 Funkcja malloc() ze sprawdzaniem bdów .........................................96 0x280 Budujemy na podstawach ...............................................................................98 0x281 Dostp do plików ...............................................................................98 0x282 Prawa dostpu do plików .................................................................104 0x283 Identyfikatory uytkowników ............................................................105 0x284 Struktury ..........................................................................................114 0x285 Wskaniki do funkcji .........................................................................117 0x286 Liczby pseudolosowe ........................................................................118 0x287 Gry hazardowe .................................................................................120 0X300 NADUYCI A 133 0x310 Uogólnione techniki naduy .........................................................................136 0x320 Przepenienia bufora ......................................................................................137 0x321 Luki zwizane z przepenieniem stosowym .......................................140 0x330 Eksperymenty z BASH ....................................................................................152 0x331 Wykorzystanie rodowiska ...............................................................161 0x340 Przepenienia w innych segmentach ..............................................................170 0x341 Podstawowe przepenienie na stercie ...............................................170 0x342 Przepenienia wskaników funkcyjnych .............................................176 0x350 acuchy formatujce ....................................................................................187 0x351 Parametry formatujce .....................................................................188 0x352 Podatno cigów formatujcych na ataki ........................................190 0x353 Odczyt spod dowolnych adresów pamici ........................................192 0x354 Zapis pod dowolnymi adresami pamici ...........................................193 0x355 Bezpo redni dostp do parametrów .................................................201 0x356 Uycie zapisu krótkich liczb cakowitych ...........................................203 0x357 Obej cia z uyciem sekcji dtors .........................................................205 0x358 Kolejny saby punkt programu notesearch ........................................210 0x359 Nadpisywanie globalnej tabeli przesuni ........................................212 0X400 SIEC I 217 0x410 Model OSI .....................................................................................................217 0x420 Gniazda .........................................................................................................220 0x421 Funkcje obsugujce gniazda ............................................................221 0x422 Adresy gniazd ..................................................................................223 0x423 Sieciowa kolejno bajtów ................................................................224 0x424 Konwersja adresu internetowego .....................................................225 6 Hacking. Sztuka penetracji 0x430 0x440 0x450 0x460 0x470 0x480 0X500 0x425 Prosty przykad serwera ....................................................................226 0x426 Przykad klienta WWW .....................................................................230 0x427 Maleki serwer WWW ......................................................................235 Zagldamy do niszych warstw .....................................................................240 0x431 Warstwa cza danych ......................................................................240 0x432 Warstwa sieci ...................................................................................242 0x433 Warstwa transportowa .....................................................................244 Podsuchiwanie w sieci ..................................................................................247 0X441 Sniffer surowych pakietów ...............................................................249 0x442 Sniffer libpcap ..................................................................................251 0x443 Dekodowanie warstw ......................................................................253 0x444 Aktywne podsuchiwanie .................................................................263 Odmowa usugi .............................................................................................276 0x451 Zalew pakietów SYN (SYN Flooding) .................................................276 0x452 Atak Ping of Death ...........................................................................281 0x453 Atak Teardrop ..................................................................................281 0x454 Zalew pakietów ping (Ping Flooding) ................................................281 0x455 Atak ze wzmocnieniem (Amplification) .............................................282 0x456 Rozproszony atak DoS ......................................................................283 Przejcie TCP/IP .............................................................................................283 0x461 Rozczanie za pomoc RST ..............................................................283 0x462 Przejcie z kontynuacj .....................................................................289 Skanowanie portów ......................................................................................289 0x471 Ukryte skanowanie SYN ....................................................................289 0x472 Skanowanie FIN, X-mas i Null ...........................................................290 0x473 Skanowanie z ukrycia .......................................................................290 0x474 Skanowanie z uyciem bezczynnego komputera ...............................291 0x475 Zabezpieczenie wyprzedzajce (Shroud) ...........................................293 Id i wam si gdzie .....................................................................................298 0x481 Analiza z uyciem GDB .....................................................................299 0x482 Prawie ma znaczenie ........................................................................301 0x483 Shellcode wicy port .....................................................................304 SHE LLCODE 309 0x510 Asembler a C .................................................................................................309 0x511 Linuksowe wywoania systemowe w asemblerze .............................312 0x520 cieka do shellcode ......................................................................................315 0x521 Instrukcje asemblera wykorzystujce stos .........................................315 0x522 Badanie za pomoc GDB ..................................................................317 0x523 Usuwanie bajtów zerowych ..............................................................319 0x530 Kod wywoujcy powok ..............................................................................324 0x531 Kwestia uprawnie ...........................................................................328 0x532 Skracamy jeszcze bardziej .................................................................331 Spis treci 7 0x540 Kod powoki wicy port .............................................................................332 0x541 Duplikowanie standardowych deskryptorów plików .........................336 0x542 Rozgaziajce struktury sterujce ....................................................338 0x550 Shellcode nawizujcy poczenie powrotne .................................................343 0X600 R OD KI ZAPOBI EGAWC ZE 349 0x610 rodki zapobiegawcze, które wykrywaj .......................................................350 0x620 Demony systemowe ......................................................................................351 0x621 Byskawiczne wprowadzenie do sygnaów .......................................352 0x622 Demon tinyweb ................................................................................354 0x630 Narzdzia pracy .............................................................................................358 0x631 Narzdzie naduywajce tinywebd ...................................................359 0x640 Pliki dziennika ...............................................................................................364 0x641 Wmieszaj si w tum ........................................................................365 0x650 Przeoczywszy oczywiste ................................................................................366 0x651 Krok po kroku ...................................................................................367 0x652 Ponowne skadanie wszystkiego ......................................................371 0x653 Robocze procesy potomne ................................................................377 0x660 Zaawansowana sztuka kamuflau .................................................................378 0x661 Faszowanie rejestrowanego adresu IP .............................................379 0x662 Nierejestrowane naduycie ...............................................................383 0x670 Caa infrastruktura .........................................................................................386 0x671 Ponowne wykorzystanie gniazda ......................................................386 0x680 Przemyt adunku ............................................................................................390 0x681 Kodowanie acuchów .....................................................................391 0x682 Jak ukry puapk? ...........................................................................394 0x690 Ograniczenia bufora ......................................................................................395 0x691 Polimorficzny kod powoki z drukowalnymi znakami ASCII ...............397 0x6a0 Zabezpieczenia wzmacniajce .......................................................................408 0x6b0 Niewykonywalny stos ....................................................................................408 0x6b1 Powracanie do funkcji biblioteki libc .................................................409 0x6b2 Powrót do funkcji system() ...............................................................409 0x6c0 Randomizacja przestrzeni stosu .....................................................................411 0x6c1 Badanie za pomoc BASH i GDB .......................................................413 0x6c2 Odbijanie od linux-gate ....................................................................417 0x6c3 Wiedza stosowana ...........................................................................420 0x6c4 Pierwsza próba .................................................................................421 0x6c5 Gramy w ko ci ..................................................................................422 0X700 KRYP TOLOGIA 425 0x710 Teoria informacji ............................................................................................426 0x711 Bezwarunkowe bezpieczestwo .......................................................426 0x712 Szyfr z kluczem jednorazowym .........................................................426 8 Hacking. Sztuka penetracji 0x720 0x730 0x740 0x750 0x760 0x770 0x780 0X800 0x713 Kwantowa dystrybucja kluczy ...........................................................427 0x714 Bezpieczestwo obliczeniowe ...........................................................428 Rozlego algorytmów .................................................................................429 0x721 Notacja asymptotyczna .....................................................................430 Szyfrowanie symetryczne ...............................................................................430 0x731 Kwantowy algorytm przeszukiwania autorstwa Lova Grovera ...........432 Szyfrowanie asymetryczne .............................................................................432 0x741 RSA ..................................................................................................433 0x742 Kwantowy algorytm rozkadu na czynniki autorstwa Petera Shora ....437 Szyfry hybrydowe ..........................................................................................438 0x751 Ataki z ukrytym po rednikiem ...........................................................439 0x752 „Odciski palców” komputerów w protokole SSH ...............................443 0x753 Rozmyte „odciski palców” ................................................................447 amanie hase ...............................................................................................451 0x761 Ataki sownikowe .............................................................................453 0x762 Ataki na zasadzie penego przegldu ................................................455 0x763 Tablica wyszukiwania skrótów .........................................................457 0x764 Macierz prawdopodobiestwa hase ................................................457 Szyfrowanie w sieci bezprzewodowej 802.11b ..............................................467 0x771 Protokó Wired Equivalent Privacy (WEP) ..........................................468 0x772 Szyfr strumieniowy RC4 ....................................................................469 Ataki na WEP ................................................................................................470 0x781 Ataki na zasadzie penego przegldu w trybie offline .......................470 0x782 Ponowne uycie strumienia klucza ....................................................471 0x783 Tablice sownikowe z wektorami IV ..................................................472 0x784 Przekierowanie IP .............................................................................473 0x785 Atak Fluhrera, Mantina i Shamira (FMS) ............................................474 P ODSUMOWANIE 485 0x810 Bibliografia i dodatkowe informacje ..............................................................486 0x820 Kody ródowe ..............................................................................................487 O P YC IE C D 489 SKOR OWID Z 491 Spis treci 9 0x200 PROGRAMOWANIE Haker to termin uywany do okrelenia zarówno piszcych kod, jak i tych, którzy wykorzystuj znajdujce si w nim bdy. Cho obie grupy hakerów maj róne cele, to i jedni, i drudzy stosuj podobne techniki rozwizywania problemów. A ze wzgldu na fakt, e umiejtno programowania pomaga tym, którzy wykorzystuj bdy w kodzie, za zrozumienie metod zastosowania pomaga tym, którzy programuj, wielu hakerów robi obie rzeczy jednoczenie. Interesujce rozwizania istniej i w zakresie technik pisania eleganckiego kodu, i technik sucych do wykorzystywania saboci programów. Hakerstwo to w rzeczywistoci akt znajdowania pomysowych i nieintuicyjnych rozwiza problemów. Metody stosowane w narzdziach wykorzystujcych saboci programów zwykle s zwizane z zastosowaniem regu funkcjonowania komputera w sposób, którego nigdy nie brano pod uwag — pozornie magiczne rezultaty osiga si zwykle po skupieniu si na omijaniu zaimplementowanych zabezpiecze. Metody uywane podczas pisania programów s podobne, bo równie wykorzystuj reguy funkcjonowania komputera w nowy i pomysowy sposób, jednak w tym przypadku kocowym celem jest osignicie najbardziej wydajnego lub najkrótszego kodu ródowego. Istnieje nieskoczenie wiele programów, które mona napisa w celu wykonania dowolnego zadania, jednak wikszo z istniejcych rozwiza jest niepotrzebnie obszerna, zoona i niedopracowana. Pozostaa niewielka ich liczba to programy niedue, wydajne i estetyczne. Ta waciwo programów nosi nazw elegancji, za przemylane i pomysowe rozwizania prowadzce do takiej wydajnoci nazywane s sztuczkami (ang. hacks). Hakerzy stojcy po obu stronach metodyki programowania doceniaj zarówno pikno eleganckiego kodu, jak i geniusz pomysowych sztuczek. W wiecie biznesu mniejsz wag przykada si do przemylanych metod programowania i eleganckiego kodu, a wiksz do tworzenia funkcjonalnego kodu w jak najkrótszym czasie i jak najtaniej. Ze wzgldu na gwatowny wzrost mocy obliczeniowej oraz dostpnoci pamici powicenie dodatkowych piciu godzin na opracowanie nieco szybszego i mniej wymagajcego pod wzgldem pamici fragmentu kodu po prostu nie opaca si, gdy mamy do czynienia z nowoczesnymi komputerami dysponujcymi gigahercowymi cyklami procesora i gigabajtami pamici. Podczas gdy optymalizacje czasu i wykorzystania pamici pozostaj niezauwaone przez wikszo (z wyjtkiem wyrafinowanych) uytkowników, nowa funkcjonalno ma potencja marketingowy. Kiedy najwaniejszym kryterium s pienidze, powicanie czasu na opracowywanie przemylanych sztuczek optymalizacyjnych nie ma po prostu sensu. Elegancj programowania pozostawiono hakerom, hobbistom komputerowym, których celem nie jest zarabianie, lecz wycinicie wszystkiego, co si da, z ich starych maszyn Commodore 64, autorom programów wykorzystujcych bdy, piszcym niewielkie i niesamowite fragmenty kodu, dziki którym potrafi przedostawa si przez wskie szczeliny systemów zabezpiecze, oraz wszystkim innym doceniajcym warto szukania i znajdowania najlepszych moliwych rozwiza. S to osoby zachwycajce si moliwociami oferowanymi przez programowanie i dostrzegajce pikno eleganckich fragmentów kodu oraz geniusz przemylanych sztuczek. Poznanie istoty programowania jest niezbdne do zrozumienia, w jaki sposób mona wykorzystywa saboci programów, dlatego te programowanie to naturalny punkt wyjcia dla naszych rozwaa. 0x210 Istota programowania Programowanie to pojcie niezmiernie naturalne i intuicyjne. Program jest tylko seri instrukcji zapisanych w okrelonym jzyku. Programy wystpuj wszdzie i nawet technofoby codziennie korzystaj z programów. Opisywanie drogi dojazdu w jakie miejsce, przepisy kulinarne, rozgrywki pikarskie i spirale DNA to s rónego typu programy. Typowy „program” opisu dojazdu samochodem w konkretne miejsce moe wyglda tak: Najpierw zjed w dó Alej Kociuszki, prowadzc na wschód. Jed ni do momentu, kiedy zobaczysz po prawej stronie koció. Jeeli ulica bdzie zablokowana ze wzgldu na prowadzone roboty, skr w prawo w ul. Moniuszki, potem skr w lewo w ul. Prusa i w kocu skr w prawo w ul. Orzeszkowej. W przeciwnym razie moesz po prostu kontynuowa jazd Alej Kociuszki i skrci w ul. Orzeszkowej. Jed ni i skr w ul. Docelow. Jed ni jakie 8 kilometrów — nasz dom bdzie si znajdowa po prawej stronie. Dokadny adres to ul. Docelowa 743. Kady, kto zna jzyk polski, pojmie podane instrukcje i bdzie móg kierowa si nimi podczas jazdy. Bez wtpienia nie zapisano ich zbyt byskotliwie, ale s jednoznaczne i zrozumiae. 20 0x200 Jednak komputer nie zna jzyka polskiego — rozumie jedynie jzyk maszynowy (ang. machine language). W celu poinstruowania go, aby wykona pewn czynno, instrukcj t naley zapisa w zrozumiaym dla niego jzyku. Jednake jzyk maszynowy jest bardzo ezoteryczny i trudny do opanowania. Skada si z serii bitów oraz bajtów i róni si, w zalenoci od danej architektury komputerowej. Tak wic w celu napisania programu w jzyku maszynowym dla procesora z rodziny Intel x86 naley okreli warto zwizan z kad instrukcj, sposób interakcji poszczególnych instrukcji oraz mnóstwo innych niskopoziomowych szczegóów. Tego rodzaju programowanie jest bardzo niewdzicznym oraz kopotliwym zadaniem i z pewnoci nie jest intuicyjne. Do pokonania komplikacji zwizanych z pisaniem programów w jzyku maszynowym potrzebny jest translator. Jedn z form translatora jzyka maszynowego jest asembler. To program, który tumaczy jzyk asemblera na kod zapisany w jzyku maszynowym. Jzyk asemblera jest bardziej przystpny od kodu maszynowego, poniewa dla rónych instrukcji i zmiennych wykorzystuje nazwy (mnemoniki), a nie same wartoci liczbowe. Jednak jzyk asemblera wci jest daleki od intuicyjnego. Nazwy instrukcji s ezoteryczne, a sam jzyk wci jest zaleny od architektury. Oznacza to, e tak samo, jak jzyk maszynowy dla procesorów Intel x86 róni si od jzyka maszynowego dla procesorów Sparc, jzyk asemblera x86 jest róny od jzyka asemblera Sparc. aden program napisany przy uyciu jzyka asemblera dla architektury jednego procesora nie bdzie dziaa w architekturze innego procesora. Jeeli program zosta napisany w jzyku asemblera x86, musi zosta przepisany, aby mona byo uruchomi go w architekturze Sparc. Ponadto w celu napisania wydajnego programu w jzyku asemblera naley zna wiele niskopoziomowych szczegóów, dotyczcych architektury danego procesora. Problemów tych mona unikn przy uyciu kolejnej formy translatora, noszcego nazw kompilatora (ang. compiler). Kompilator konwertuje kod zapisany w jzyku wysokopoziomowym do postaci kodu maszynowego. Jzyki wysokopoziomowe s o wiele bardziej intuicyjne od jzyka asemblera i mog by konwertowane do wielu rónych typów jzyka maszynowego dla odmiennych architektur procesorów. Oznacza to, e jeli zapisze si program w jzyku wysokiego poziomu, ten sam kod moe zosta skompilowany przez kompilator do postaci kodu maszynowego dla rónych architektur. C, C++ lub FORTRAN to przykady jzyków wysokopoziomowych. Program napisany w takim jzyku jest o wiele bardziej czytelny1 od jzyka asemblera lub maszynowego, cho wci musi by zgodny z bardzo surowymi reguami dotyczcymi sposobu wyraania instrukcji, poniewa w przeciwnym razie kompilator nie bdzie w stanie ich zrozumie. 1 Oczywicie pod warunkiem, e przez czytelny rozumiemy taki, który przypomina jzyk angielski — przyp. tum. Programowanie 21 0x220 Pseudokod Programici dysponuj jeszcze jednym rodzajem jzyka programowania, noszcym nazw pseudokodu. Pseudokod (ang. pseudo-code) to po prostu wyraenia zapisane w jzyku naturalnym, ustawione w struktur ogólnie przypominajc jzyk wysokopoziomowy. Nie jest on rozumiany przez kompilatory, asemblery ani komputery, ale stanowi przydatny sposób skadania instrukcji. Pseudokod nie jest dobrze zdefiniowany. W rzeczywistoci wiele osób zapisuje pseudokod odmiennie. Jest to rodzaj nieokrelonego, brakujcego ogniwa midzy jzykami naturalnymi, takimi jak angielski lub polski, a wysokopoziomowymi jzykami programowania, takimi jak C lub Pascal. Pseudokod stanowi wietne wprowadzenie do uniwersalnych zaoe programistycznych. 0x230 Struktury sterujce Bez struktur sterujcych program byby jedynie seri kolejno wykonywanych instrukcji. Jest to wystarczajce w przypadku bardzo prostych programów, ale wikszo programów, takich jak np. opis dojazdu, taka nie jest. Prezentowany opis dojazdu zawiera instrukcje: Jed ni do momentu, kiedy zobaczysz po prawej stronie koció. Jeeli ulica bdzie zablokowana ze wzgldu na prowadzone roboty… Instrukcje te nazywamy strukturami sterujcymi, poniewa zmieniaj przebieg wykonywania programu z prostego, sekwencyjnego, na bardziej zoony i uyteczny. 0x231 If-Then-Else W naszym opisie dojazdu Aleja Kociuszki moe by w remoncie. Do rozwizania problemu wynikajcego z tej sytuacji potrzebujemy specjalnego zestawu instrukcji. Jeeli zdefiniowane okolicznoci nie wystpi (remont), bd wykonywane standardowe instrukcje. Tego rodzaju specjalne przypadki mona wzi pod uwag w programie, korzystajc z jednej z najbardziej naturalnych struktur sterujcych: if-then-else (jeeli-wówczas-w przeciwnym przypadku). Ogólnie wyglda to mniej wicej tak: If (warunek) then { Zestaw instrukcji do wykonania, gdy warunek jest speniony; } Else { Zestaw instrukcji do wykonania, gdy warunek nie jest speniony; } W tej ksice bdziemy posugiwali si pseudokodem przypominajcym jzyk C, wic kada instrukcja bdzie zakoczona rednikiem, a ich zbiory bd grupowane za pomoc nawiasów klamrowych i wyróniane wciciem. Pseudokod struktury if-then-else dla wskazówek dojazdu mógby przedstawia si nastpujco: 22 0x200 Jed w dó Alej Kociuszki; Jeeli (ulica zablokowana) { Skr w prawo w ul. Moniuszki; Skr w lewo w ul. Prusa; Skr w prawo w ul. Orzeszkowej; } else { Skr w prawo w ul. Orzeszkowej; } Kada instrukcja znajduje si w osobnym wierszu, a róne zestawy instrukcji warunkowych s zgrupowane wewntrz nawiasów klamrowych i dla zwikszenia czytelnoci zastosowano dla nich wcicie. W C i wielu innych jzykach programowania sowo kluczowe then jest domniemane i tym samym pomijane, wic pominlimy je równie w pseudokodzie. Oczywicie, istniej jzyki, których skadnia wymaga zastosowania sowa kluczowego then — przykadami mog tu by BASIC, Fortran, a nawet Pascal. Tego typu rónice syntaktyczne midzy jzykami programowania s niemal nieistotne, jako e podstawowa struktura jest taka sama. Gdy programista zrozumie zaoenia tych jzyków, nauczenie si rónych odmian syntaktycznych jest stosunkowo atwe. Poniewa w dalszych rozdziaach bdziemy programowali w jzyku C, prezentowany pseudokod bdzie przypomina ten jzyk. Naley jednak pamita, e moe on przyjmowa róne postaci. Inn powszechn regu skadni podobnej do C jest sytuacja, w której zestaw instrukcji umieszczonych w nawiasach klamrowych zawiera tylko jedn instrukcj. Dla zwikszenia czytelnoci wci warto dla takiej instrukcji zastosowa wcicie, ale nie jest ono wymagane. Zgodnie z t regu mona wic napisa wczeniej przedstawiony opis dojazdu tak, aby uzyska równowany fragment pseudokodu: Jed w dó Alej Kociuszki; Jeeli (ulica zablokowana) { Skr w prawo w ul. Moniuszki; Skr w lewo w ul. Prusa; Skr w prawo w ul. Orzeszkowej; } else Skr w prawo w ul. Orzeszkowej; Regua mówica o zestawach instrukcji dotyczy wszystkich struktur sterujcych opisywanych w niniejszej ksice, a sam regu równie mona zapisa w pseudokodzie. Programowanie 23 If (w zestawie instrukcji znajduje si tylko jedna instrukcja) Uycie nawiasów klamrowych do zgrupowania instrukcji jest opcjonalne; Else { Uycie nawiasów klamrowych jest niezbdne; Poniewa musi istnie logiczny sposób zgrupowania tych instrukcji; } Nawet opis skadni mona potraktowa jak program. Istnieje wiele odmian struktury if-then-else, np. instrukcje select i case, ale logika pozostaje taka sama: jeeli stanie si tak, zrób to i to, w przeciwnym przypadku wykonaj, co poniej (a poniej mog si znajdowa kolejne instrukcje if-then). 0x232 Ptle While/Until Innym podstawowym pojciem programistycznym jest struktura sterujca while, bdca rodzajem ptli. Programista czsto chce wykona zestaw instrukcji kilka razy. Program moe wykona to zadanie, stosujc ptl, ale wymaga to okrelenia warunków jej zakoczenia, aby nie trwaa w nieskoczono. Ptla while wykonuje podany zestaw instrukcji, dopóki warunek jest prawdziwy. Prosty program dla godnej myszy mógby wyglda nastpujco: While (jeste godna) { Znajd jakie jedzenie; Zjedz jedzenie; } Te dwa zestawy instrukcji za instrukcj while bd powtarzane, dopóki mysz bdzie godna. Ilo poywienia odnalezionego przez mysz przy kadej próbie moe waha si od maego okruszka po cay bochen chleba. Podobnie liczba wykona zestawu instrukcji w ptli while zaley od iloci poywienia znalezionego przez mysz. Odmian ptli while jest ptla until, wykorzystywana w Perlu (w C skadnia ta nie jest uywana). Ptla until jest po prostu ptl while z odwróconym warunkiem. Ten sam program dla myszy zapisany z uyciem ptli until wygldaby nastpujco: Until (nie jeste godna) { Znajd jakie jedzenie; Zjedz jedzenie; } Logicznie kada instrukcja podobna do until moe by przeksztacona w ptl while. Opis dojazdu zawiera zdanie: „Jed ni do momentu, kiedy zobaczysz po prawej stronie koció”. Moemy to w prosty sposób przeksztaci w standardow ptl while — wystarczy, e odwrócimy warunek: 24 0x200 While (nie ma kocioa po prawej stronie) Jed Alej Kociuszki; 0x233 Ptle for Kolejn struktur sterujc jest ptla for, wykorzystywana wtedy, gdy programista chce, aby ptla wykonaa okrelon liczb iteracji. Instrukcja dojazdu: „Jed ni jakie 8 kilometrów” przeksztacona do ptli for mogaby wyglda nastpujco: For (8 iteracji) Jed prosto 1 kilometr; W rzeczywistoci ptla for jest po prostu ptl while z licznikiem. Powysz instrukcj mona równie dobrze zapisa tak: Ustaw licznik na 0 While (licznik jest mniejszy od 8) { Jed prosto 1 kilometr; Dodaj 1 do licznika; } Skadnia pseudokodu dla ptli for sprawia, e jest to jeszcze bardziej widoczne: For (i=0; i<8; i++) Jed prosto 1 kilometr; W tym przypadku licznik nosi nazw i, a instrukcja for jest podzielona na trzy sekcje oddzielone rednikami. Pierwsza sekcja deklaruje licznik i ustawia jego pocztkow warto — w tym przypadku 0. Druga sekcja przypomina instrukcj while z licznikiem — dopóki licznik spenia podany warunek, kontynuuj ptl. Trzecia i ostatnia sekcja opisuje, co ma si dzia z licznikiem przy kadej iteracji. W tym przypadku i++ jest skróconym sposobem na powiedzenie: „Dodaj 1 do licznika o nazwie i”. Zastosowanie wszystkich omówionych struktur sterujcych umoliwia przeksztacenie opisu dojazdu ze strony 6 w poniszy pseudokod przypominajcy jzyk C: Rozpocznij jazd na wschód Alej Kociuszki; While (po prawej stronie nie ma kocioa) Jed Alej Kociuszki; If (ulica zablokowana) { Skr w prawo w ul. Moniuszki); Skr w lewo w ul. Prusa); Skr w prawo w ul. Orzeszkowej); } Programowanie 25 Else Skr(prawo, ul. Orzeszkowej); Skr(lewo, ul. Docelowa); For (i=0; i<8; i++) { Jed prosto 1 kilometr; } Zatrzymaj si przy ul. Docelowej 743; 0x240 Podstawowe pojcia programistyczne W kolejnych punktach zostan przedstawione podstawowe pojcia programistyczne. S one wykorzystywane w wielu jzykach programowania, róni si jedynie pod wzgldem syntaktycznym. Podczas prezentacji bd integrowane z przykadami w pseudokodzie przypominajcym skadni jzyka C. Na kocu pseudokod bdzie bardzo przypomina kod napisany w jzyku C. 0x241 Zmienne Licznik wykorzystywany w ptli for jest rodzajem zmiennej. O zmiennej moemy myle jak o obiekcie przechowujcym dane, które mog by zmieniane — std nazwa. Istniej równie zmienne, których wartoci nie s zmieniane, i trafnie nazywa si je staymi. W przykadzie motoryzacyjnym jako zmienn moemy potraktowa prdko samochodu, natomiast kolor lakieru samochodu byby sta. W pseudokodzie zmienne s po prostu pojciami abstrakcyjnymi, natomiast w C (i wielu innych jzykach) zmienne przed wykorzystaniem musz by zadeklarowane i naley okreli ich typ. Jest tak dlatego, e program napisany w C bdzie kompilowany do postaci programu wykonywalnego. Podobnie jak przepis kucharski, w którym przed faktycznymi instrukcjami wymieniane s wszystkie potrzebne skadniki, deklaracje zmiennych pozwalaj dokona przygotowa przed przejciem do faktycznej treci programu. Ostatecznie wszystkie zmienne s przechowywane gdzie w pamici, a ich deklaracje pozwalaj kompilatorowi wydajniej uporzdkowa pami. Jednak na koniec, mimo wszystkich deklaracji typów zmiennych, wszystko odbywa si w pamici. W jzyku C dla kadej zmiennej okrelany jest typ, opisujcy informacje, które bd przechowywane w tej zmiennej. Do najczciej stosowanych typów zaliczamy int (liczby cakowite), float (liczby zmiennoprzecinkowe) i char (pojedyncze znaki). Zmienne s deklarowane poprzez uycie tych sów kluczowych przed nazw zmiennej, co zobrazowano na poniszym przykadzie. int a, b; float k; char z; Zmienne a i b s zadeklarowane jako liczby cakowite, k moe przyjmowa wartoci zmiennoprzecinkowe (np. 3,14), a z powinna przechowywa warto znakow, 26 0x200 tak jak A lub w. Wartoci mona przypisywa zmiennym podczas deklaracji lub w dowolnym póniejszym momencie. Suy do tego operator =. int a = 13, b; float k; char z = 'A'; k = 3.14; z = 'w'; b = a + 5; Po wykonaniu powyszych instrukcji zmienna a bdzie posiadaa warto 13, zmienna k bdzie zawieraa liczb 3,14, a zmienna b bdzie miaa warto 18, poniewa 13 plus 5 równa si 18. Zmienne s po prostu sposobem pamitania wartoci, jednak w jzyku C naley najpierw zadeklarowa typ kadej zmiennej. 0x242 Operatory arytmetyczne Instrukcja b = a + 7 jest przykadem bardzo prostego operatora arytmetycznego. W jzyku C dla rónych operacji matematycznych wykorzystywane s ponisze symbole. Pierwsze cztery operatory powinny wyglda znajomo. Redukcja modulo moe stanowi nowe pojcie, ale jest to po prostu reszta z dzielenia. Jeeli a ma warto 13, to 13 podzielone przez 5 równa si 2, a reszta wynosi 3, co oznacza, e a % 5 = 3. Ponadto skoro zmienne a i b s liczbami cakowitymi, po wykonaniu instrukcji b = a / 5, zmienna b bdzie miaa warto 2, poniewa jest to cz wyniku bdca liczb cakowit. Do przechowania bardziej poprawnego wyniku, czyli 2,6, musi zosta uyta zmienna typu float (zmiennoprzecinkowa). Operacja Symbol Przykad Dodawanie + b = a + 5 Odejmowanie - b = a - 5 Mnoenie * b = a * 5 Dzielenie / b = a / 5 Redukcja modulo % b = a % 5 Aby program korzysta z tych zaoe, musimy mówi w jego jzyku. Jzyk C dostarcza kilku skróconych form zapisu tych operacji arytmetycznych. Jedna z nich zostaa przedstawiona wczeniej i jest czsto wykorzystywana w ptlach for. Pene wyraenie Skrót Wyjanienie i = i + 1 i++ lub ++i Dodaje 1 do wartoci zmiennej. i = i - 1 i-- lub i-- Odejmuje 1 od wartoci zmiennej. Programowanie 27 Te skrócone wyraenia mog by czone z innymi operacjami arytmetycznymi w celu utworzenia bardziej zoonych wyrae. Wówczas staje si widoczna rónica midzy i++ i ++i. Pierwsze wyraenie oznacza: „Zwiksz warto i o 1 po przeprowadzeniu operacji arytmetycznej”, natomiast drugie: „Zwiksz warto i o 1 przed przeprowadzeniem operacji arytmetycznej”. Poniszy przykad pozwoli to lepiej zrozumie. int a, b; a = 5 b = a++ * 6; Po wykonaniu tego zestawu instrukcji zmienna b bdzie miaa warto 30, natomiast zmienna a bdzie miaa warto 6, poniewa skrócony zapis b = a++ * 6 jest równowany poniszym instrukcjom. b = a * 6; a = a + 1; Jeeli jednak zostanie uyta instrukcja b = ++a * 6, kolejno dodawania zostanie zmieniona i otrzymamy odpowiednik poniszych instrukcji. a = a + 1; b = a * 6; Poniewa kolejno wykonywania dziaa zmienia si, w powyszym przypadku zmienna b bdzie miaa warto 36, natomiast warto zmiennej a wci bdzie wynosia 6. Czsto w programach musimy zmodyfikowa zmienn w danym miejscu. Przykadowo moemy doda dowoln warto, tak jak 12, i przechowa wynik w tej samej zmiennej (np. i = i + 12) Zdarza si to tak czsto, e równie utworzono skrócon form tej instrukcji. Pene wyraenie Skrót Wyjanienie i = i + 12 i+=12 Dodaje okrelon warto do wartoci zmiennej. i = i - 12 i-=12 Odejmuje okrelon warto od wartoci zmiennej. i = i * 12 i*=12 Mnoy okrelon warto przez warto zmiennej. i = i / 12 i/=12 Dzieli warto zmiennej przez okrelon warto. 0x243 Operatory porównania Zmienne s czsto wykorzystywane w instrukcjach warunkowych wczeniej opisanych struktur sterujcych, a te instrukcje warunkowe opieraj si na jakiego rodzaju porównaniu. W jzyku C operatory porównania wykorzystuj skrócon skadni, która jest stosunkowo powszechna w innych jzykach programowania. 28 0x200 Warunek Symbol Przykad Mniejsze ni < (a < b) Wiksze ni > (a < b) Mniejsze lub równe <= (a <= b) Wiksze lub równe >= (a >= b) Równe == (a == b) Nie równa si != (a != b) Wikszo z tych operatorów jest zrozumiaa, jednake naley zauway, e w skrócie porównania równoci wykorzystywane s dwa znaki równoci. Jest to wane rozrónienie, poniewa podwójny znak równoci jest stosowany do sprawdzenia równoci, natomiast jeden znak równoci jest uywany do przypisania wartoci zmiennej. Instrukcja a = 7 oznacza: „Umie w zmiennej a warto 7”, natomiast a == 7 oznacza: „Sprawd, czy warto zmiennej a wynosi 7”. (Niektóre jzyki programowania, takie jak Pascal, wykorzystuj do przypisywania wartoci zmiennym nastpujcy znak — := — aby wyeliminowa ryzyko bdnego zrozumienia kodu). Naley te zwróci uwag, e wykrzyknik zwykle oznacza nie. Ten symbol moe by wykorzystany do odwrócenia dowolnego wyraenia. !(a < b) jest równowane (a >= b) Operatory porównania mog by czone ze sob za pomoc skrótów dla operatorów logicznych OR i AND. Operator logiczny Symbol Przykad OR || ((a < b) || (a < c)) AND && ((a < b) && !(a < c)) Przykadowa instrukcja zawierajca dwa mniejsze warunki poczone operatorem OR zostanie oszacowana jako prawdziwa, jeeli warto zmiennej a bdzie mniejsza od wartoci zmiennej b LUB warto zmiennej a bdzie mniejsza od wartoci zmiennej c. Podobnie przykadowa instrukcja zawierajca dwa mniejsze warunki poczone operatorem AND zostanie oszacowana jako prawdziwa, jeeli warto zmiennej a bdzie mniejsza od wartoci zmiennej b I warto zmiennej a nie bdzie mniejsza od wartoci zmiennej c. Takie instrukcje powinny by grupowane za pomoc nawiasów i mog zawiera wiele rónych odmian. Wiele rzeczy moemy sprowadzi do zmiennych, operatorów porównania i struktur sterujcych. W przykadzie myszy poszukujcej poywienia gód moemy przedstawi jako zmienn boolowsk o wartoci true lub false (prawda lub fasz). Oczywicie, 1 oznacza true, a 0 — false. Programowanie 29 While (gód == 1) { Szukaj poywienia; Zjedz poywienie; } Oto kolejny skrót czsto wykorzystywany przez programistów i hakerów. Jzyk C nie zawiera adnych operatorów boolowskich, wic kada warto niezerowa jest uznawana za true, a instrukcja jest szacowana jako false, gdy zawiera 0. W rzeczywistoci operatory porównania faktycznie zwracaj 1, jeeli porównanie jest spenione, a w przeciwnym przypadku zwracaj 0. Sprawdzenie, czy zmienna gód ma warto 1, zwróci 1, jeeli gód ma warto 1, lub zwróci 0, jeeli gód ma warto 0. Poniewa program wykorzystuje tylko te dwa przypadki, mona w ogóle pomin operator porównania. While (gód) { Szukaj poywienia; Zjedz poywienie; } Sprytniejszy program sterujcy zachowaniem myszy z uyciem wikszej liczby danych wejciowych to przykad, jak mona czy operatory porównania ze zmiennymi. While ((gód) && !(kot_obecny)) { Szukaj poywienia; If (!(poywienie_znajduje_si_w_puapce_na_myszy; Zjedz poywienie; } W tym przykadzie zaoono, e dostpne s równie zmienne opisujce obecno kota oraz pooenie poywienia z wartociami 1 dla spenienia warunku i 0 dla przypadku przeciwnego. Wystarczy pamita, e warto niezerowa oznacza prawd, a warto 0 jest szacowana jako fasz. 0x244 Funkcje Czasami zdarzaj si zestawy instrukcji, których programista bdzie potrzebowa kilka razy. Takie instrukcje mona zgrupowa w mniejszym podprogramie, zwanym funkcj. W innych jzykach funkcje nazywane s take procedurami i podprocedurami. Przykadowo wykonanie skrtu samochodem skada si z kilku mniejszych instrukcji: wcz odpowiedni kierunkowskaz, zwolnij, sprawd, czy nie nadjedaj inne samochody, skr kierownic w odpowiednim kierunku itd. Opis trasy z pocztku tego rozdziau zawiera cakiem sporo skrtów, jednake wymienianie wszystkich 30 0x200 drobnych instrukcji przy kadym skrcie byoby mudne (i zmniejszaoby czytelno kodu). W celu zmodyfikowania sposobu dziaania funkcji moemy przesya do niej zmienne jako argumenty. W tym przypadku do funkcji jest przesyany kierunek skrtu. Function Skrt(zmienna_kierunku) { Wcz zmienna_kierunku kierunkowskaz; Zwolnij; Sprawd, czy nie nadjedaj inne samochody; while (nadjedaj inne samochody) { Zatrzymaj si; Obserwuj nadjedajce samochody; } Obró kierownic w zmienna_kierunku; while (skrt nie jest ukoczony) { if (prdko < 8 km/h) Przyspiesz; } Obró kierownic do pierwotnego pooenia; Wycz zmienna_kierunku kierunkowskaz; } Funkcja ta zawiera wszystkie instrukcje konieczne do wykonania skrtu. Gdy program, w którym funkcja taka si znajduje, musi wykona skrt, moe po prostu wywoa t funkcj. Gdy zostaje wywoana, znajdujce si w niej instrukcje s wykonywane z przesanymi argumentami. Nastpnie wykonywanie programu powraca do momentu za wywoaniem funkcji. Do tej funkcji mona przesa warto lewo albo prawo, co spowoduje wykonanie skrtu w tym kierunku. Domylnie w jzyku C funkcje mog zwraca wartoci do elementu wywoujcego. Dla osób znajcych matematyk jest to zupenie zrozumiae. Wyobramy sobie funkcj obliczajc silni — jest oczywiste, e zwraca wynik. W jzyku C funkcje nie s oznaczane sowem kluczowym function. Zamiast tego s deklarowane przez typ danych zwracanej zmiennej. Taki format bardzo przypomina deklaracj zmiennej. Jeeli funkcja w zamierzeniu ma zwraca liczb cakowit (moe to by np. funkcja obliczajca silni jakiej liczby x), jej kod mógby wyglda nastpujco: int factorial(int x) { int i; for(i=1; i < x; i++) x *= i; return x; } Programowanie 31 Funkcja ta jest deklarowana liczb cakowit, poniewa mnoy kad warto od 1 do x i zwraca wynik, który jest liczb cakowit. Instrukcja return na kocu funkcji przesya zawarto zmiennej x i koczy wykonywanie funkcji. Funkcj obliczajc silni mona wówczas wykorzysta jak kad zmienn typu integer w gównej czci kadego programu, który „wie” o tej funkcji: int a=5, b; b = factorial(a); Po ukoczeniu wykonywania tego krótkiego programu zmienna b bdzie miaa warto 120, poniewa funkcja factorial() zostanie wywoana z argumentem 5 i zwróci 120. Ponadto w jzyku C kompilator musi „wiedzie” o funkcji, zanim jej uyje. W tym celu mona po prostu napisa ca funkcj przed póniejszym wykorzystaniem jej w programie lub skorzysta z prototypu funkcji. Prototyp funkcji jest prostym sposobem poinformowania kompilatora, i powinien spodziewa si funkcji o podanej nazwie, zwracajcej okrelony typ danych i przyjmujcej argumenty o okrelonym typie danych. Faktyczny kod funkcji mona umieci pod koniec programu, ale mona j wykorzysta w dowolnym miejscu, poniewa kompilator bdzie ju wiedzia o jej istnieniu. Przykadowy prototyp funkcji factorial() mógby wyglda nastpujco: int factorial(int); Zwykle prototypy funkcji s umieszczane blisko pocztku programu. W prototypie nie ma potrzeby definiowania nazw zmiennych, poniewa dzieje si to w samej funkcji. Kompilator musi jedynie zna nazw funkcji, typ zwracanych danych i typy danych argumentów funkcjonalnych. Jeeli funkcja nie posiada wartoci, któr moe zwróci (np. wczeniej opisana funkcja skr()), powinna by zadeklarowana jako typ void. Jednake funkcja skr() nie posiada jeszcze caej funkcjonalnoci wymaganej przez nasz opis dojazdu. Kady skrt w opisie dojazdu zawiera zarówno kierunek, jak i nazw ulicy. To oznacza, e funkcja skrcajca powinna przyjmowa dwie zmienne: kierunek skrtu oraz nazw ulicy, w któr naley skrci. To komplikuje funkcj skrcajc, poniewa przed wykonaniem skrtu naley zlokalizowa ulic. Bardziej kompletna funkcja skrcajca, w której wykorzystano skadni podobn do jzyka C, jest zamieszczona w pseudokodzie poniszego listingu. void skr(zmienna_kierunku, nazwa_docelowej_ulicy) { Szukaj tabliczki z nazw ulicy; nazwa_biecego_skrzyowania = odczytaj tabliczk z nazw ulicy; while (nazwa_biecego_skrzyowania != nazwa_docelowej_ulicy) { Szukaj innej tabliczki z nazw ulicy; nazwa_biecego_skrzyowania = odczytaj tabliczk z nazw ulicy; } 32 0x200 Wcz zmienna_kierunku kierunkowskaz; Zwolnij; Sprawd, czy nie nadjedaj inne samochody; while (nadjedaj inne samochody) { Zatrzymaj si; Obserwuj nadjedajce samochody; } Obró kierownic w zmienna_kierunku; while (skrt nie jest ukoczony) { if (prdko < 8 km/h) Przyspiesz; } Obró kierownic do pierwotnego pooenia; Wycz zmienna_kierunku kierunkowskaz; } Funkcja ta zawiera sekcj, która wyszukuje odpowiednie skrzyowanie poprzez odnajdywanie tabliczki z nazw ulicy, odczytywanie z niej nazwy i przechowywanie jej w zmiennej o nazwie nazwa_biecego_skrzyowania. Wyszukiwanie i odczytywanie tabliczek z nazwami odbywa si do momentu odnalezienia docelowej ulicy. Wówczas wykonywane s pozostae instrukcje skrtu. Moemy teraz zmieni pseudokod z instrukcjami dojazdu, aby wykorzystywa funkcj skrtu. Rozpocznij jazd na wschód Alej Kociuszki; while (po prawej stronie nie ma kocioa) { Jed Alej Kociuszki; } If (ulica zablokowana) { Skr(prawo, ul. Moniuszki); Skr(lewo, ul. Prusa); Skr(prawo, ul. Orzeszkowej); } else { Skr(prawo, ul. Orzeszkowej); } Skr(lewo, ul. Docelowa); For (i=0; i<5; i++) { Jed prosto 1 kilometr; } Zatrzymaj si przy ul. Docelowej 743; Funkcje nie s czsto wykorzystywane w pseudokodzie, bo zwykle jest on stosowany przez programistów do zarysowania zaoe programu przed napisaniem kompilowalnego kodu. Poniewa pseudokod nie musi dziaa, nie trzeba wypisywa penych funkcji — wystarczy jedynie krótki zapisek: Tu zrób co zoonego. Jednak Programowanie 33 w jzyku programowania, takim jak C, funkcje s czsto wykorzystywane. Wikszo prawdziwej uytecznoci C pochodzi ze zbiorów istniejcych funkcji, zwanych bibliotekami. 0x250 Zaczynamy brudzi sobie rce Gdy zaznajomilimy si ju troch ze skadni jzyka C i wyjanilimy kilka podstawowych poj programistycznych, przejcie do faktycznego programowania w C nie jest niczym nadzwyczajnym. Kompilatory jzyka C istniej dla niemal kadego systemu operacyjnego i architektury procesora, jednak w niniejszej ksice bdziemy wykorzystywali wycznie system Linux i procesor z rodziny x86. Linux jest darmowym systemem operacyjnym, do którego kady ma dostp, a procesory x86 s najpopularniejszymi procesorami konsumenckimi na wiecie. Poniewa hacking wie si gównie z eksperymentowaniem, najlepiej bdzie, jeeli w trakcie lektury bdziesz dysponowa kompilatorem jzyka C. Dziki dyskowi LiveCD doczonemu do niniejszej ksiki moesz praktycznie stosowa przekazywane informacje, jeeli tylko dysponujesz procesorem z rodziny x86. Wystarczy woy dysk CD do napdu i ponownie uruchomi komputer. Uruchomione zostanie rodowisko linuksowe, które nie narusza istniejcego systemu operacyjnego. W tym rodowisku moesz wypróbowywa przykady z ksiki, a take przeprowadza wasne eksperymenty. Przejdmy do rzeczy. Program firstprog.c jest prostym fragmentem kodu w C, wypisujcym 10 razy „Hello world!”. Listing 2.1. firstprog.c #include <stdio.h> int main() { int i; for(i=0; i < 10; i++) // Wykonaj ptl 10 razy. { puts("Hello, world!\n"); // Przeka acuch do wyjcia. } return 0; // Poinformuj OS, e program zakoczy si bez bdów. } Gówne wykonywanie programu napisanego w C rozpoczyna si w funkcji main(). Tekst znajdujcy si za dwoma ukonikami jest komentarzem ignorowanym przez kompilator. Pierwszy wiersz moe wprawia w zakopotanie, ale jest to jedynie skadnia informujca kompilator, aby doczy nagówki dla standardowej biblioteki wejcia-wyjcia o nazwie stdio. Ten plik nagówkowy jest dodawany do programu podczas kompilacji. Znajduje si on w /usr/include/stdio.h i definiuje kilka staych i prototypów funkcji dla odpowiednich funkcji w standardowej bibliotece we-wy. Poniewa funkcja main() wykorzystuje funkcj printf() ze standardowej biblioteki 34 0x200 we-wy, wymagany jest prototyp funkcji printf() przed jej zastosowaniem. Ten prototyp funkcji (a take wiele innych) znajduje si w pliku nagówkowym stdio.h. Sporo moliwoci jzyka C to pochodne jego zdolnoci do rozbudowy oraz wykorzystania bibliotek. Reszta kodu powinna by zrozumiaa i przypomina wczeniej prezentowany pseudokod. S nawet nawiasy klamrowe, które mona byo pomin. To, co bdzie si dziao po uruchomieniu tego programu, powinno by oczywiste, ale skompilujmy go za pomoc GCC, aby si upewni. GNU Compiler Collection jest darmowym kompilatorem jzyka C, tumaczcym jzyk C na jzyk maszynowy, zrozumiay dla komputera. W wyniku tumaczenia powstaje wykonywalny plik binarny, któremu domylnie nadawana jest nazwa a.out. Czy skompilowany program wykonuje to, co chcielimy? reader@hacking:~/booksrc $ reader@hacking:~/booksrc $ -rwxr-xr-x 1 reader reader reader@hacking:~/booksrc $ Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! reader@hacking:~/booksrc $ 0x251 gcc firstprog.c ls -l a.out 6621 2007-09-06 22:16 a.out ./a.out Wikszy obraz Dobrze, tego wszystkiego mona si nauczy na podstawowym kursie programowania — s to podstawy, ale niezbdne. Wikszo pocztkowych kursów programowania uczy jedynie, jak czyta i pisa w jzyku C. Prosz mnie le nie zrozumie — pynna znajomo C jest bardzo przydatna i wystarczy do stania si dobrym programist, ale jest to tylko cz wikszej caoci. Wikszo programistów uczy si jzyka z góry do dou i nigdy nie ma szerszego obrazu. Hakerzy s tak dobrzy, poniewa wiedz, jak wszystkie czci tego wikszego obrazu wchodz ze sob w interakcje. Aby w programowaniu patrze szerzej, wystarczy sobie zda spraw, e kod w jzyku C bdzie kompilowany. Kod nie moe nic zrobi, dopóki nie zostanie skompilowany do wykonywalnego pliku binarnego. Mylenie o kodzie ródowym w C jak o programie jest czstym bdem wykorzystywanym codziennie przez hakerów. Instrukcje w pliku a.out s zapisane w jzyku maszynowym — podstawowym jzyku zrozumiaym dla procesora. Kompilatory tumacz jzyk kodu C na jzyk maszynowy rónych architektur procesorów. W tym przypadku procesor pochodzi z rodziny wykorzystujcej architektur x86. Istniej równie architektury procesorów Sparc (uywane w stacjach roboczych Sun) oraz architektura PowerPC Programowanie 35 (wykorzystywana w macintoshach przed zastosowaniem procesorów Intela). Kada architektura wymaga innego jzyka maszynowego, wic kompilator jest porednikiem — tumaczy kod w jzyku C na jzyk maszynowy docelowej architektury. Dopóki skompilowany program dziaa, przecitny programista skupia si wycznie na kodzie ródowym. Jednak haker zdaje sobie spraw, e w rzeczywistoci wykonywany jest program skompilowany. Majc dogbn wiedz na temat dziaania procesora, haker moe manipulowa dziaajcymi programami. Widzielimy kod ródowy naszego pierwszego programu i skompilowalimy go do pliku wykonywalnego dla architektury x86. Ale jak wyglda ten wykonywalny plik binarny? Narzdzia programistyczne GNU zawieraj program o nazwie objdump, który moe by uyty do badania skompilowanych plików binarnych. Rozpocznijmy od poznania kodu maszynowego, na który zostaa przetumaczona funkcja main(). reader@hacking:~/booksrc $ objdump -D a.out | grep -A20 main.: 08048374 <main>: 8048374: 55 push %ebp 8048375: 89 e5 mov %esp,%ebp 8048377: 83 ec 08 sub $0x8,%esp 804837a: 83 e4 f0 and $0xfffffff0,%esp 804837d: b8 00 00 00 00 mov $0x0,%eax 8048382: 29 c4 sub %eax,%esp 8048384: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp) 804838b: 83 7d fc 09 cmpl $0x9,0xfffffffc(%ebp) 804838f: 7e 02 jle 8048393 <main+0x1f> 8048391: eb 13 jmp 80483a6 <main+0x32> 8048393: c7 04 24 84 84 04 08 movl $0x8048484,(%esp) 804839a: e8 01 ff ff ff call 80482a0 <printf@plt> 804839f: 8d 45 fc lea 0xfffffffc(%ebp),%eax 80483a2: ff 00 incl (%eax) 80483a4: eb e5 jmp 804838b <main+0x17> 80483a6: c9 leave 80483a7: c3 ret 80483a8: 90 nop 80483a9: 90 nop 80483aa: 90 nop reader@hacking:~/booksrc $ Program objdump zwróci zbyt wiele wierszy wynikowych, aby rozsdnie si im przyjrze, wic przekazalimy wyjcie do grep z opcj powodujc wywietlenie tylko 20 wierszy za wyraeniem regularnym main.:. Kady bajt jest reprezentowany w notacji szesnastkowej, systemie liczbowym o podstawie 16. System liczbowy, do którego jestemy najbardziej przyzwyczajeni, wykorzystuje podstaw 10, a przy liczbie 10 naley umieci dodatkowy symbol. W systemie szesnastkowym stosowane s cyfry od 0 do 9 reprezentujce 0 do 9, ale take litery A do F, które reprezentuj w nim liczby od 10 do 15. Jest to wygodny zapis, poniewa bajt zawiera 8 bitów, z których kady moe mie warto prawda lub fasz. To oznacza, e bajt ma 256 (28) moliwych wartoci, wic kady bajt moe by opisany za pomoc dwóch cyfr w systemie szesnastkowym. 36 0x200 Liczby szesnastkowe — rozpoczynajc od 0x8048374 po lewej stronie — s adresami pamici. Bity jzyka maszynowego musz by gdzie przechowywane, a tym „gdzie” jest pami. Pami jest po prostu zbiorem bajtów z tymczasowej przestrzeni magazynowej, którym przypisano adresy liczbowe. Podobnie jak rzd domów przy lokalnej ulicy, z których kady posiada wasny adres, pami moemy traktowa jako rzd bajtów, z których kady ma wasny adres. Do kadego bajta pamici mona uzyska dostp za pomoc jego adresu, a w tym przypadku procesor siga do tej czci pamici, aby pobra instrukcje jzyka maszynowego skadajce si na skompilowany program. Starsze procesory Intela z rodziny x86 wykorzystuj 32-bitowy schemat adresacji, natomiast nowsze — 64-bitowy. Procesory 32-bitowe posiadaj 232 (lub te 4 294 967 296) moliwych adresów, natomiast procesory 64-bitowe dysponuj iloci adresów równ 264 (1,84467441 × 1019). Procesory 64-bitowe mog pracowa w trybie kompatybilnoci 32-bitowej, co umoliwia im szybkie wykonywanie kodu 32-bitowego. Bajty szesnastkowe znajdujce si porodku powyszego listingu s instrukcjami w jzyku maszynowym procesora x86. Oczywicie, te wartoci szesnastkowe s jedynie reprezentacjami binarnych jedynek i zer, które rozumie procesor. Ale poniewa 0101010110001001111001011000001111101100111100001… przydaje si tylko procesorowi, kod maszynowy jest wywietlany jako bajty szesnastkowe, a kada instrukcja jest umieszczana w osobnym wierszu, co przypomina podzia akapitu na zdania. Gdy si zastanowimy, okae si, e bajty szesnastkowe same w sobie te nie s przydatne — tutaj dochodzi do gosu jzyk asembler. Instrukcje po prawej stronie s zapisane w asemblerze. Asembler jest po prostu zbiorem mnemoników dla odpowiednich instrukcji jzyka maszynowego. Instrukcja ret jest znacznie atwiejsza do zapamitania i zrozumienia ni 0xc3 czy te 11000011. W przeciwiestwie do C i innych jzyków kompilowanych, instrukcje asemblera wystpuj w relacji jedendo-jednego z instrukcjami odpowiednimi jzyka maszynowego. Oznacza to, e skoro kada architektura procesora posiada wasny zestaw instrukcji jzyka maszynowego, kada z nich ma równie wasn odmian asemblera. Jzyk asemblera jest dla programistów tylko sposobem reprezentacji instrukcji jzyka maszynowego podawanych procesorowi. Dokadny sposób reprezentacji tych instrukcji maszynowych jest tylko kwesti konwencji i preferencji. Cho teoretycznie mona stworzy wasn skadni asemblera dla architektury x86, wikszo osób wykorzystuje jeden z gównych typów — skadni AT&T lub Intel. Kod asemblera przedstawiony na stronie 21 jest zgodny ze skadni AT&T, poniewa niemal wszystkie dezasemblery w Linuksie domylnie wykorzystuj t skadni. Skadni AT&T mona atwo rozpozna dziki kakofonii symboli % i $ umieszczanych przed wszystkimi elementami (prosz ponownie spojrze na stron 21). Ten sam kod mona wywietli w skadni Intela, dodajc do polecenia objdump dodatkow opcj -M: reader@hacking:~/booksrc $ objdump -M intel -D a.out | grep -A20 main.: 08048374 <main>: 8048374: 55 push ebp 8048375: 89 e5 mov ebp,esp Programowanie 37 8048377: 83 ec 08 sub esp,0x8 804837a: 83 e4 f0 and esp,0xfffffff0 804837d: b8 00 00 00 00 mov eax,0x0 8048382: 29 c4 sub esp,eax 8048384: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-4],0x0 804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4],0x9 804838f: 7e 02 jle 8048393 <main+0x1f> 8048391: eb 13 jmp 80483a6 <main+0x32> 8048393: c7 04 24 84 84 04 08 mov DWORD PTR [esp],0x8048484 804839a: e8 01 ff ff ff call 80482a0 <printf@plt> 804839f: 8d 45 fc lea eax,[ebp-4] 80483a2: ff 00 inc DWORD PTR [eax] 80483a4: eb e5 jmp 804838b <main+0x17> 80483a6: c9 leave 80483a7: c3 ret 80483a8: 90 nop 80483a9: 90 nop 80483aa: 90 nop reader@hacking:~/booksrc $ Uwaam, e skadnia Intela jest znacznie bardziej czytelna i atwiejsza do zrozumienia, wic bdzie ona stosowana w niniejszej ksice. Niezalenie od reprezentacji jzyka asemblera, polecenia rozumiane przez procesor s cakiem proste. Instrukcje te skadaj si z operacji i czasem dodatkowych operacji opisujcych cel i (lub) ródo operacji. Operacje te przenosz dane w pamici, aby wykona jakiego rodzaju podstawowe dziaania matematyczne, lub przerywaj dziaanie procesora, by wykona co innego. To jest wszystko, co procesor potrafi wykona. Jednak, podobnie jak za pomoc stosunkowo niewielkiego alfabetu napisano miliony ksiek, tak samo, korzystajc z niewielkiego zbioru instrukcji maszynowych, mona utworzy nieskoczenie wiele programów. Procesory posiadaj równie wasny zestaw specjalnych zmiennych zwanych rejestrami. Wikszo instrukcji wykorzystuje rejestry do odczytywania lub zapisywania danych, wic ich poznanie jest niezbdne do zrozumienia instrukcji. Duy obraz wci si powiksza… 0x252 Procesor x86 Procesor 8086 by pierwszym procesorem w architekturze x86. Zosta opracowany i wyprodukowany przez firm Intel, która póniej wprowadzaa na rynek bardziej zaawansowane procesory z tej rodziny: 80186, 80286m 80386 i 80486. Jeeli pamitasz, e w latach 80. i 90. ubiegego wieku mówiono o procesorach 386 i 486, chodzio wówczas o te wanie ukady. Procesor x86 posiada kilka rejestrów, które s dla niego czym w rodzaju wewntrznych zmiennych. Mógbym teraz przeprowadzi abstrakcyjny wykad na temat rejestrów, ale uwaam, e lepiej zobaczy co na wasne oczy. Narzdzia programistyczne GNU zawieraj równie debuger o nazwie GDB. Debugery s uywane przez programistów do wykonywania skompilowanych programów krok po kroku, badania pamici programu i przegldania rejestrów procesora. Programista, który 38 0x200 nigdy nie korzysta z debugera do przyjrzenia si wewntrznym mechanizmom pracy programu, jest jak siedemnastowieczny badacz, który nigdy nie uywa mikroskopu. Debuger, tak jak mikroskop, pozwala hakerowi na precyzyjne przyjrzenie si kodowi maszynowemu, ale moliwoci debugera wykraczaj daleko poza t metafor. W przeciwiestwie do mikroskopu, debuger umoliwia obejrzenie wykonywania programu ze wszystkich stron, wstrzymanie go oraz zmian wszelkich parametrów. Poniej GDB zosta uyty do wywietlenia stanu rejestrów procesora tu przed rozpoczciem programu: reader@hacking:~/booksrc $ gdb -q ./a.out Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1". (gdb) break main Breakpoint 1 at 0x804837a (gdb) run Starting program: /home/reader/booksrc/a.out Breakpoint 1, 0x0804837a in main () (gdb) info registers eax 0xbffff894 -1073743724 ecx 0x48e0fe81 1222704769 edx 0x1 1 ebx 0xb7fd6ff4 -1208127500 esp 0xbffff800 0xbffff800 ebp 0xbffff808 0xbffff808 esi 0xb8000ce0 -1207956256 edi 0x0 0 eip 0x804837a 0x804837a <main+6> eflags 0x286 [ PF SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) quit The program is running. Exit anyway? (y or n) y reader@hacking:~/booksrc $ Punkt wstrzymania zosta ustawiony przy funkcji main(), wic wykonywanie programu zostanie zatrzymane tu przed uruchomieniem naszego kodu. Nastpnie GDB uruchamia program, zatrzymuje si w punkcie wstrzymania i wywietla wszystkie rejestry procesora i ich biecy stan. Pierwsze cztery rejestry (EAX, ECX, EDX i EBX) s rejestrami ogólnego przeznaczenia. S to odpowiednio rejestry: akumulatorowy, licznika, danych i bazowy. S one wykorzystywane do rónych celów, ale su gównie jako zmienne tymczasowe dla procesora, gdy wykonuje instrukcje kodu maszynowego. Programowanie 39 Kolejne cztery rejestry (ESP, EBP, ESI i EDI) s równie rejestrami ogólnego przeznaczenia, ale czasem nazywa si je wskanikami i indeksami. S to odpowiednio: wskanik, wskanik bazowy, ródo i przeznaczenie. Pierwsze dwa rejestry s nazywane wskanikami, poniewa przechowuj 32-bitowe adresy, wskazujce do miejsc w pamici. Te rejestry s istotne dla wykonywania programu i zarzdzania pamici. Póniej omówi je dokadniej. Kolejne dwa rejestry, z technicznego punktu widzenia równie wskanikowe, czsto wykorzystywane s do wskazywania róda i celu, gdy dane musz by odczytane lub zapisane. Istniej instrukcje odczytujce i zapisujce, które uywaj tych rejestrów, ale przewanie mona je traktowa jak zwyke rejestry ogólnego przeznaczenia. Rejestr EIP jest rejestrem wskanika instrukcji, wskazujcym aktualnie wykonywan instrukcj. Tak jak dziecko podczas czytania pokazuje sobie palcem poszczególne sowa, tak procesor odczytuje konkretne instrukcje, korzystajc z EIP jak z palca. Oczywicie, rejestr ten jest bardzo istotny i bdzie czsto wykorzystywany podczas debugowania. Aktualnie wskazuje adres pamici 0x804838a. Pozostay rejestr, EFLAGS, zawiera kilka flag bitowych wykorzystywanych do porówna oraz segmentacji pamici. Pami jest dzielona na kilka rónych segmentów, co opisz póniej, a rejestr ten pozwala je ledzi. Przewanie moemy je zignorowa, poniewa rzadko konieczny jest bezporedni dostp do nich. 0x253 Jzyk asembler Poniewa w niniejszej ksice wykorzystujemy skadni Intela w jzyku asembler, musimy odpowiednio skonfigurowa narzdzia. W GDB skadni dezasemblera mona ustawi na intelowsk, wpisujc po prostu set disassembly intel lub w skrócie set dis intel. Moemy skonfigurowa GDB tak, aby to ustawienie byo aktywne przy kadym uruchomieniu, umieszczajc to polecenie w pliku .gdbinit w katalogu domowym. reader@hacking:~/booksrc (gdb) set dis intel (gdb) quit reader@hacking:~/booksrc reader@hacking:~/booksrc set dis intel reader@hacking:~/booksrc $ gdb -q $ echo "set dis intel" > ~/.gdbinit $ cat ~/.gdbinit $ Po skonfigurowaniu GDB tak, eby wykorzystywa skadni Intela, zapoznajmy si z ni. W skadni Intela instrukcje asemblera maj zwykle ponisz posta: operacja <cel>, <ródo> Wartociami docelowymi i ródowymi s rejestr, adres pamici lub warto. Operacje s zwykle intuicyjnie rozumianymi mnemonikami. Operacja mov przenosi warto ze róda do celu, sub odejmuje, inc inkrementuje itd. Przykadowo ponisze instrukcje przenosz warto z ESP do EBP, a nastpnie odejmuj 8 od ESP (zapisujc wynik w ESP). 40 0x200 8048375: 89 e5 mov ebp,esp 8048377: 83 ec 08 sub esp,0x8 Dostpne s równie operacje wykorzystywane do sterowanie przebiegiem wykonywania. Operacja cmp suy do porównywania wartoci, a niemal wszystkie operacje, których nazwa rozpoczyna si liter j, su do przeskakiwania do innej czci kodu (w zalenoci od wyniku porównania). W poniszym przykadzie najpierw 4-bajtowa warto znajdujca w ERB minus 4 jest porównywana z liczb 9. Nastpna instrukcja jest skrótem od przeskocz, jeeli mniejsze lub równe ni (ang. jump if less than or equal to), odnoszcym si do wyniku wczeniejszego porównania. Jeeli warto ta jest mniejsza lub równa 9, wykonywanie przeskoczy do instrukcji znajdujcej si pod adresem 0x8048393. W przeciwnym przypadku wykonywana bdzie kolejna instrukcja z bezwarunkowym przeskokiem. Jeeli warto nie bdzie mniejsza ani równa 9, procesor przeskoczy do 0x80483a6. 804838b: 83 7d fc 09 cmp DWORD PTR [ebp-4],0x9 804838f: 7e 02 jle 8048393 <main+0x1f> 8048391: eb 13 jmp 80483a6 <main+0x32> Przykady te pochodz z naszej poprzedniej dezasemblacji. Skonfigurowalimy debuger do wykorzystywania skadni Intela, wic wykorzystajmy go do kroczenia przez nasz pierwszy program na poziomie instrukcji asemblera. W kompilatorze GCC mona poda opcj –g, dziki czemu zostan doczone dodatkowe informacje debugowania, co da GDB dostp do kodu ródowego. reader@hacking:~/booksrc $ gcc -g firstprog.c reader@hacking:~/booksrc $ ls -l a.out -rwxr-xr-x 1 matrix users 11977 Jul 4 17:29 a.out reader@hacking:~/booksrc $ gdb -q ./a.out Using host libthread_db library "/lib/libthread_db.so.1". (gdb) list 1 #include <stdio.h> 2 3 int main() 4 { 5 int i; 6 for(i=0; i < 10; i++) 7 { 8 printf("Hello, world!\n"); 9 } 10 } (gdb) disassemble main Dump of assembler code for function main(): 0x08048384 <main+0>: push ebp 0x08048385 <main+1>: mov ebp,esp 0x08048387 <main+3>: sub esp,0x8 0x0804838a <main+6>: and esp,0xfffffff0 0x0804838d <main+9>: mov eax,0x0 0x08048392 <main+14>: sub esp,eax Programowanie 41 0x08048394 <main+16>: mov DWORD PTR [ebp-4],0x0 0x0804839b <main+23>: cmp DWORD PTR [ebp-4],0x9 0x0804839f <main+27>: jle 0x80483a3 <main+31> 0x080483a1 <main+29>: jmp 0x80483b6 <main+50> 0x080483a3 <main+31>: mov DWORD PTR [esp],0x80484d4 0x080483aa <main+38>: call 0x80482a8 <_init+56> 0x080483af <main+43>: lea eax,[ebp-4] 0x080483b2 <main+46>: inc DWORD PTR [eax] 0x080483b4 <main+48>: jmp 0x804839b <main+23> 0x080483b6 <main+50>: leave 0x080483b7 <main+51>: ret End of assembler dump. (gdb) break main Breakpoint 1 at 0x8048394: file firstprog.c, line 6. (gdb) run Starting program: /hacking/a.out Breakpoint 1, main() at firstprog.c:6 6 for(i=0; i < 10; i++) (gdb) info register eip eip 0x8048394 0x8048394 (gdb) Najpierw wypisywany jest kod ródowy oraz dezasemblacja funkcji main(). Nastpnie na pocztku main() ustawiany jest punkt wstrzymania i program jest uruchamiany. Ten punkt wstrzymania po prostu informuje debuger, aby wstrzyma wykonywanie programu, gdy dojdzie do tego punktu. Poniewa punkt wstrzymania zosta ustawiony na pocztku funkcji main(), program dociera do punktu wstrzymania i zatrzymuje si przed wykonaniem jakichkolwiek instrukcji z funkcji main(). Nastpnie wywietlana jest warto EIP (wskanika instrukcji). Naley zwróci uwag, e EIP zawiera adres pamici, wskazujcy instrukcj w dezasemblacji funkcji main() (pogrubionej na listingu). Wczeniejsze instrukcje (zapisane kursyw), razem nazywane prologiem funkcji, s generowane przez kompilator w celu ustawienia pamici dla reszty zmiennych lokalnych funkcji main(). Jednym z powodów koniecznoci deklaracji zmiennych w jzyku C jest pomoc w konstrukcji tej czci kodu. Debuger „wie”, e ta cz kodu jest generowana automatycznie i potrafi j pomin. Prologiem funkcji zajmiemy si póniej, a na razie, wzorujc si na GDB, pominiemy go. Debuger GDB zawiera bezporedni metod badania pamici za pomoc polecenia x, które jest skrótem od examine. Badanie pamici jest bardzo wan umiejtnoci kadego hakera. Wikszo technik hakerskich przypomina sztuczki magiczne — wydaj si niesamowite i magiczne, dopóki nie poznasz oszustwa. Zarówno w magii, jak i hakerstwie, jeeli popatrzymy w odpowiednie miejsce, sztuczka stanie si oczywista. Jest to jeden z powodów, dla których dobry magik nigdy nie wykonuje dwa razy tego samego triku w czasie jednego wystpu. Jednak za pomoc debugera, takiego jak GDB, moemy dokadnie zbada kady aspekt wykonywania programu, wstrzyma go, przej krok dalej i powtórzy tyle razy, ile potrzeba. Poniewa dziaajcy program to w wikszoci procesor i segmenty pamici, zbadanie pamici jest pierwsz z metod przekonania si, co naprawd si dzieje. 42 0x200 Polecenie examine w GDB moe zosta uyte do przyjrzenia si okrelonym adresom pamici na róne sposoby. To polecenie oczekuje dwóch argumentów: miejsca w pamici, które ma by zbadane, oraz okrelenia sposobu wywietlania zawartoci. Do okrelania formatu wywietlania równie uywany jest jednoliterowy skrót, który mona poprzedzi liczb elementów do zbadania. Najczciej wykorzystywane s ponisze skróty formatujce: o — wywietla w formacie ósemkowym, x — wywietla w formacie szesnastkowym, u — wywietla w standardowym systemie dziesitnym, bez znaków, t — wywietla w formacie binarnym. Liter tych mona uy z poleceniem examine do zbadania okrelonego adresu pamici. W poniszym przykadzie wykorzystano biecy adres z rejestru EIP. Skrócone polecenia s czsto wykorzystywane w GDB i nawet info register eip mona skróci do i r eip. (gdb) i r eip (gdb) x/o 0x8048384 (gdb) x/x 0x8048384 (gdb) x/u 0x8048384 (gdb) x/t 0x8048384 (gdb) eip 0x8048384 0x8048384 <main+16>: $eip <main+16>: $eip <main+16>: $eip <main+16>: 0x8048384 <main+16> 077042707 0x00fc45c7 16532935 00000000111111000100010111000111 Pami, któr wskazuje rejestr EIP, moe by zbadana za pomoc adresu przechowywanego w EIP. Debuger umoliwia bezporednie odwoywanie si do rejestrów, wic $eip jest równowane wartoci przechowywanej aktualnie w EIP. Warto 077042707 w systemie ósemkowym jest tym samym, co 0x00fc45c7 w systemie szesnastkowym, co jest tym samym, co 16532935 w systemie dziesitnym, co z kolei jest tym samym, co 00000000111111000100010111000111 w systemie dwójkowym. Format polecenia examine mona równie poprzedzi cyfr, okrelajc liczb badanych jednostek w docelowym adresie. (gdb) x/2x $eip 0x8048384 <main+16>: (gdb) x/12x $eip 0x8048384 <main+16>: 0x8048394 <main+32>: 0x80483a4 <main+48>: (gdb) 0x00fc45c7 0x83000000 0x00fc45c7 0x83000000 0x7e09fc7d 0xc713eb02 0x84842404 0x01e80804 0x8dffffff 0x00fffc45 0xc3c9e5eb 0x90909090 0x90909090 0x5de58955 Programowanie 43 Domylnym rozmiarem jednej jednostki jest 4-bajtowa jednostka zwana sowem. Rozmiar jednostek wywietlania dla polecenia examine moe by zmieniany poprzez dodanie odpowiedniej litery za liter formatujc. Poprawnymi literami rozmiaru s: b — pojedynczy bajt, h — pósowo o rozmiarze dwóch bajtów, w — sowo o rozmiarze czterech bajtów, g — sowo podwójne o rozmiarze omiu bajtów. Wprowadza to pewien zamt, poniewa czasami termin sowo okrela równie wartoci 2-bajtowe. W takim przypadku podwójne sowo lub DWORD oznacza warto 4-bajtow. W ksice tej terminy „sowo” i DWORD bd okrelay wartoci 4-bajtowe. Do okrelania wartoci 2-bajtowych bdzie uywany termin „pósowo”. Ponisze wyjcie z GDB obrazuje pami wywietlan w rónych rozmiarach. (gdb) x/8xb $eip 0x8048384 <main+16>: (gdb) x/8xh $eip 0x8048384 <main+16>: (gdb) x/8xw $eip 0x8048384 <main+16>: 0x8048394 <main+32>: (gdb) 0xc7 0x45 0xfc 0x00 0x00 0x00 0x00 0x83 0x45c7 0x00fc 0x0000 0x8300 0xfc7d 0x7e09 0xeb02 0xc713 0x00fc45c7 0x84842404 0x83000000 0x01e80804 0x7e09fc7d 0x8dffffff 0xc713eb02 0x00fffc45 Jeeli dobrze si przyjrzysz, zauwaysz w powyszych danych co dziwnego. Pierwsze polecenie examine pokazuje pierwsze osiem bajtów i, oczywicie, polecenia examine wykorzystujce wiksze jednostki wywietlaj wicej danych. Jednake wynik pierwszego polecenia pokazuje, e pierwszymi dwoma bajtami s 0xc7 i 0x45, ale gdy badane jest pósowo w dokadnie tym samym adresie pamici, pokazywana jest warto 0x45c7, czyli bajty s odwrócone. Ten sam efekt moemy zauway w przypadku penego 4-bajtowego sowa wywietlanego jako 0x00fc45c7. Jeeli jednak pierwsze cztery bajty s wywietlane bajt po bajcie maj one kolejno 0xc7, 0x45m 0xfc i 0x00. Jest tak, poniewa w procesorze x86 wartoci s przechowywane w kolejnoci little endian, co oznacza, e najmniej istotny bajt jest przechowywany jako pierwszy. Jeeli np. cztery bajty maj by interpretowane jako jedna warto, bajty musz by uyte w odwrotnej kolejnoci. Debuger GDB jest na tyle mdry, e wie, jak przechowywane s wartoci, wic gdy badane jest sowo lub pósowo, bajty musz by odwrócone w celu wywietlenia poprawnych wartoci w ukadzie szesnastkowym. Przejrzenie tych wywietlanych wartoci w systemie szesnastkowym i jako liczb dziesitnych bez znaku moe uatwi zrozumienie tej kwestii. (gdb) x/4xb $eip 0x8048384 <main+16>: (gdb) x/4ub $eip 0x8048384 <main+16>: (gdb) x/1xw $eip 44 0x200 0xc7 0x45 0xfc 0x00 199 69 252 0 0x8048384 <main+16>: 0x00fc45c7 (gdb) x/1uw $eip 0x8048384 <main+16>: 16532935 (gdb) quit The program is running. Exit anyway? (y or n) y reader@hacking:~/booksrc $ bc -ql 199*(256^3) + 69*(256^2) + 252*(256^1) + 0*(256^0) 3343252480 0*(256^3) + 252*(256^2) + 69*(256^1) + 199*(256^0) 16532935 quit reader@hacking:~/booksrc $ Pierwsze cztery bajty s pokazane zarówno w notacji szesnastkowej, jak i standardowej, dziesitnej. Kalkulator dziaajcy w wierszu polece bc posuy do wykazania, e jeeli bajty zostan zinterpretowane w nieprawidowej kolejnoci, otrzymamy w wyniku zupenie nieprawidow warto 3343252480. Kolejno bajtów w danej architekturze jest istotnym czynnikiem, którego naley by wiadomym. Cho wikszo narzdzi debugujcych i kompilatorów zajmuje si automatycznie szczegóami zwizanymi z kolejnoci bajtów, czasem bdziesz musia samodzielnie manipulowa pamici. Oprócz konwersji kolejnoci bajtów, GDB moe za pomoc polecenia examine wykonywa inne konwersje. Widzielimy ju, e GDB moe dezasemblowa instrukcje jzyka maszynowego na zrozumiae dla czowieka instrukcje asemblera. Polecenie examine przyjmuje równie liter formatujc i (skrót od instruction), co powoduje wywietlenie pamici jako dezasemblowanych instrukcji jzyka asembler. reader@hacking:~/booksrc $ gdb -q ./a.out Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1". (gdb) break main Breakpoint 1 at 0x8048384: file firstprog.c, line 6. (gdb) run Starting program: /home/reader/booksrc/a.out Breakpoint 1, main () at firstprog.c:6 6 for(i=0; i < 10; i++) (gdb) i r $eip eip 0x8048384 0x8048384 <main+16> (gdb) x/i $eip 0x8048384 <main+16>: mov DWORD PTR [ebp-4],0x0 (gdb) x/3i $eip 0x8048384 <main+16>: mov DWORD PTR [ebp-4],0x0 0x804838b <main+23>: cmp DWORD PTR [ebp-4],0x9 0x804838f <main+27>: jle 0x8048393 <main+31> (gdb) x/7xb $eip 0x8048384 <main+16>: 0xc7 0x45 0xfc 0x00 0x00 (gdb) x/i $eip 0x8048384 <main+16>: mov DWORD PTR [ebp-4],0x0 (gdb) 0x00 0x00 Programowanie 45 W powyszym przykadzie program a.out zosta uruchomiony w GDB z punktem wstrzymania ustawionym w main(). Poniewa rejestr EIP wskazuje na pami, która faktycznie zawiera instrukcje w jzyku maszynowym, adnie poddaj si one procesowi dezasemblacji. Wczeniejsza dezasemblacja za pomoc objectdump potwierdza, e siedem bajtów, które wskazuje EIP, to faktycznie jzyk maszynowy dla odpowiedniej instrukcji asemblera. 8048384: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-4],0x0 Ta instrukcja asemblera przenosi warto 0 do pamici znajdujcej si pod adresem przechowywanym w rejestrze EBP minus 4. To wanie w tym miejscu pamici przechowywana jest zmienna i z jzyka C. Zmienna ta zostaa zadeklarowana jako liczba cakowita (int), wykorzystujca 4 bajty pamici w przypadku procesora x86. To polecenie po prostu zeruje zmienn i dla ptli for. Gdybymy teraz zbadali t pami, zawieraaby ona tylko przypadkowe mieci. Pami w tej lokacji mona zbada na róne sposoby: (gdb) i r ebp ebp 0xbffff808 (gdb) x/4xb $ebp - 4 0xbffff804: 0xc0 0x83 (gdb) x/4xb 0xbffff804 0xbffff804: 0xc0 0x83 (gdb) print $ebp - 4 $1 = (void *) 0xbffff804 (gdb) x/4xb $1 0xbffff804: 0xc0 0x83 (gdb) x/xw $1 0xbffff804: 0x080483c0 (gdb) 0xbffff808 0x04 0x08 0x04 0x08 0x04 0x08 Rejestr EBP zawiera adres 0xbffff808, a instrukcja asemblera bdzie zapisywaa do adresu o tej wartoci pomniejszonej o 4 — 0xbfffff804. Polecenie examine moe zbada bezporednio ten adres pamici lub wykona odpowiednie obliczenia w locie. Za pomoc polecenia print równie mona wykona proste dziaania arytmetyczne, a wynik zostanie zapisany w tymczasowej zmiennej w debugerze. Zmienna ta nosi nazw $1 i moe póniej zosta uyta do szybkiego uzyskania dostpu do okrelonego miejsca w pamici. Kada z przedstawionych wyej metod pozwala uzyska ten sam rezultat: wywietlenie znalezionych w pamici 4 bajtów mieci, które zostan wyzerowane po wykonaniu biecej instrukcji. Uruchommy biec instrukcj, korzystajc z polecenia nexti, bdcego skrótem dla next instruction (nastpna instrukcja). Procesor odczyta instrukcj z EIP, wykona j i przestawi EIP na kolejn instrukcj. 46 0x200 (gdb) nexti 0x0804838b 6 for(i=0; i < 10; i++) (gdb) x/4xb $1 0xbffff804: 0x00 0x00 0x00 0x00 (gdb) x/dw $1 0xbffff804: 0 (gdb) i r eip eip 0x804838b 0x804838b <main+23> (gdb) x/i $eip 0x804838b <main+23>: cmp DWORD PTR [ebp-4],0x9 (gdb) Zgodnie z oczekiwaniami, poprzednia instrukcja zeruje 4 bajty odnalezione w adresie EBP minus 4, czyli pamici przydzielonej zmiennej i z kodu C. EIP przechodzi do kolejnej instrukcji. Omówienie kolejnych instrukcji warto przeprowadzi cznie. (gdb) x/10i $eip 0x804838b <main+23>: 0x804838f <main+27>: 0x8048391 <main+29>: 0x8048393 <main+31>: 0x804839a <main+38>: 0x804839f <main+43>: 0x80483a2 <main+46>: 0x80483a4 <main+48>: 0x80483a6 <main+50>: 0x80483a7 <main+51>: (gdb) cmp jle jmp mov call lea inc jmp leave ret DWORD PTR [ebp-4],0x9 0x8048393 <main+31> 0x80483a6 <main+50> DWORD PTR [esp],0x8048484 0x80482a0 <printf@plt> eax,[ebp-4] DWORD PTR [eax] 0x804838b <main+23> Pierwsza instrukcja, cmp, jest instrukcj porównania, która porównuje zawarto pamici wykorzystywanej przez zmienn i z kodu C z wartoci 9. Kolejna instrukcja, jle, oznacza przeskocz, jeeli mniejsze lub równe (ang. jump if less than equal to). Wykorzystuje ona wynik poprzedniego porównania (który jest przechowywany w rejestrze EFLAGS) do przestawienia EIP tak, aby wskazywa inn cz kodu, jeeli cel poprzedniej operacji porównania jest mniejszy lub równy ródu. W tym przypadku instrukcja powoduje przeskok do adresu 0x8048393, jeeli warto przechowywana w pamici dla zmiennej i jest mniejsza lub równa wartoci 9. W przeciwnym przypadku EIP przejdzie do kolejnej instrukcji, która jest instrukcj przeskoku bezwarunkowego. Powoduje ona przeskok EIP do adresu 0x80483a6. Te trzy instrukcje cznie tworz struktur sterujc if-then-else: jeeli i jest mniejsze lub równe 9, przejd do instrukcji pod adresem 0x8048393; w przeciwnym przypadku przejd do instrukcji pod adresem 0x80483a6. Pierwszy adres, 0x804893 (pogrubiony), jest po prostu sta instrukcj przeskoku, a drugi adres — 0x80483a6 (zapisany kursyw) — znajduje si na kocu funkcji. Poniewa wiemy, e w lokacji pamici porównywanej z 9 przechowywane jest 0 i wiemy, e 0 jest mniejsze lub równe 9, po wykonaniu kolejnych dwóch instrukcji EIP powinien wskazywa na 0x8048393. Programowanie 47 (gdb) nexti 0x0804838f 6 (gdb) x/i $eip 0x804838f <main+27>: (gdb) nexti 8 printf("Hello (gdb) i r eip eip 0x8048393 (gdb) x/2i $eip 0x8048393 <main+31>: 0x804839a <main+38>: (gdb) for(i=0; i < 10; i++) jle 0x8048393 <main+31> world!\n"); 0x8048393 <main+31> mov call DWORD PTR [esp],0x8048484 0x80482a0 <printf@plt> Zgodnie z oczekiwaniami, wczeniejsze dwie instrukcje przeniosy wykonywanie programu do 0x8048393, co doprowadza do kolejnych dwóch instrukcji. Pierwsza z nich jest kolejn instrukcj mov, która zapisze adres 0x8048484 do adresu pamici znajdujcego si w rejestrze ESP. Ale co wskazuje ESP? (gdb) i r esp esp 0xbffff800 0xbffff800 (gdb) W tej chwili ESP wskazuje adres pamici 0xbffff800, wic po wykonaniu instrukcji mov zostanie tutaj zapisany adres 0x8048484. Ale dlaczego? Co takiego specjalnego jest w adresie pamici 0x8048484? Jest tylko jeden sposób, aby si o tym przekona. (gdb) x/2xw 0x8048484 0x8048484: 0x6c6c6548 0x6f57206f (gdb) x/6xb 0x8048484 0x8048484: 0x48 0x65 0x6c 0x6c 0x6f 0x20 (gdb) x/6ub 0x8048484 0x8048484: 72 101 108 108 111 32 (gdb) Dowiadczony czytelnik moe zauway w tej pamici co szczególnego, zwaszcza w zakresie bajtów. Po nabraniu dowiadczenia w badaniu pamici tego typu wzorce stan si bardziej oczywiste. Bajty te zawieraj si w zakresie znaków ASCII. ASCII jest uzgodnionym standardem, który odwzorowuje wszystkie znaki z klawiatury (a take kilka innych) na stae liczby. Bajty 0x48, 0x65 i 0x6f odpowiadaj literom alfabetu przedstawionym w tabeli znaków ASCII (tabela 2.1). Tabel mona znale na stronach podrcznika ASCII dostpnego w wikszoci systemów Unix po wpisaniu man ascii. Na szczcie, polecenie examine w GDB posiada równie moliwo przyjrzenia si tego typu pamici. Litera formatujca c umoliwia automatyczne sprawdzenie bajta w tablicy ASCII, a litera formatujca s powoduje wywietlenie caego acucha danych znakowych. 48 0x200 Tabela 2.1. Tablica znaków ASCII Oct Dec Hex Char Oct Dec Hex Char 000 0 00 NUL '\0' 100 64 40 @ 001 1 01 SOH 101 65 41 A 002 2 02 STX 102 66 42 B 003 3 03 ETX 103 67 43 C 004 4 04 EOT 104 68 44 D 005 5 05 ENQ 105 69 45 E 006 6 06 ACK 106 70 46 F 007 7 07 BEL '\a' 107 71 47 G 010 8 08 BS '\b' 110 72 48 H 011 9 09 HT '\t' 111 73 49 I 012 10 0A LF '\n' 112 74 4A J 013 11 0B VT '\v' 113 75 4B K 014 12 0C FF '\f' 114 76 4C L 015 13 0D CR '\r' 115 77 4D M 016 14 0E SO 116 78 4E N 017 15 0F SI 117 79 4F O 020 16 10 DLE 120 80 50 P 021 17 11 DC1 121 81 51 Q 022 18 12 DC2 122 82 52 R 023 19 13 DC3 123 83 53 S 024 20 14 DC4 124 84 54 T 025 21 15 NAK 125 85 55 U 026 22 16 SYN 126 86 56 V 027 23 17 ETB 127 87 57 W 030 24 18 CAN 130 88 58 X 031 25 19 EM 131 89 59 Y 032 26 1A SUB 132 90 5A Z 033 27 1B ESC 133 91 5B [ 034 28 1C FS 134 92 5C \ '\\' 035 29 1D GS 135 93 5D ] 036 30 1E RS 136 94 5E ^ 037 31 1F US 137 95 5F _ 040 32 20 SPACE 140 96 60 041 33 21 ! 141 97 61 a 042 34 22 " 142 98 62 b 043 35 23 # 143 99 63 c Programowanie 49 Tabela 2.1. Tablica znaków ASCII — cig dalszy Oct Dec Hex Char Oct Dec Hex Char 044 36 24 $ 144 100 64 d 045 37 25 % 145 101 65 e 046 38 26 & 146 102 66 f 047 39 27 ' 147 103 67 g 050 40 28 ( 150 104 68 h 051 41 29 ) 151 105 69 i 052 42 2A * 152 106 6A j 053 43 2B + 153 107 6B k 054 44 2C , 154 108 6C l 055 45 2D - 155 109 6D m 056 46 2E . 156 110 6E n 057 47 2F / 157 111 6F o 060 48 30 0 160 112 70 p 061 49 31 1 161 113 71 q 062 50 32 2 162 114 72 r 063 51 33 3 163 115 73 s 064 52 34 4 164 116 74 t 065 53 35 5 165 117 75 u 066 54 36 6 166 118 76 v 067 55 37 7 167 119 77 w 070 56 38 8 170 120 78 x 071 57 39 9 171 121 79 y 072 58 3A : 172 122 7A z 073 59 3B ; 173 123 7B { 074 60 3C < 174 124 7C | 075 61 3D = 175 125 7D } 076 62 3E > 176 126 7E ~ 077 63 3F ? 177 127 7F DEL (gdb) x/6cb 0x8048484 0x8048484: 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 32 ' ' (gdb) x/s 0x8048484 0x8048484: "Hello, world!\n" (gdb) Polecenia te pokazuj, e pod adresem 0x8048484 przechowywany jest acuch danych "Hello, world!\n". Ten acuch jest argumentem funkcji printf(), co wskazuje, e przeniesienie adresu tego acucha do adresu przechowywanego w ESP 50 0x200 (0x8048484) ma co wspólnego z t funkcj. Poniszy wynik z gdb pokazuje adres acucha danych przenoszony do adresu, który wskazuje ESP. (gdb) x/2i $eip 0x8048393 <main+31>: mov DWORD PTR [esp],0x8048484 0x804839a <main+38>: call 0x80482a0 <printf@plt> (gdb) x/xw $esp 0xbffff800: 0xb8000ce0 (gdb) nexti 0x0804839a 8 printf("Hello, world!\n"); (gdb) x/xw $esp 0xbffff800: 0x08048484 (gdb) Kolejna instrukcja wywouje funkcj printf(). Wypisuje ona acuch danych. Wczeniej instrukcje stanowiy przygotowania do wywoania tej funkcji, a wynik tego wywoania zosta pogrubiony poniej. (gdb) x/i $eip 0x804839a <main+38>: call 0x80482a0 <printf@plt> (gdb) nexti Hello, world! 6 for(i=0; i < 10; i++) (gdb) Korzystajc dalej z debugera, przyjrzyjmy si kolejnym dwóm instrukcjom. Ponownie wikszy sens ma opisanie ich razem. (gdb) x/2i $eip 0x804839f <main+43>: lea eax,[ebp-4] 0x80483a2 <main+46>: inc DWORD PTR [eax] (gdb) Te dwie instrukcje po prostu zwikszaj warto zmiennej i o 1. Instrukcja lea, akronim od Load Effective Address (wczytaj efektywny adres), wczytuje znany ju adres EBP minus 4 do rejestru EAX. Wykonanie tej instrukcji przedstawiono poniej. (gdb) x/ i $eip 0x804839f <main+43>: lea eax,[ebp-4] (gdb) print $ebp - 4 $2 = (void *) 0xbffff804 (gdb) x/x $2 0xbffff804: 0x00000000 (gdb) i r eax eax 0xd 13 (gdb) nexti 0x080483a2 6 for(i=0; i < 10; i++) (gdb) i r eax eax 0xbffff804 -1073743868 (gdb) x/xw $eax Programowanie 51 0xbffff804: (gdb) x/dw $eax 0xbffff804: (gdb) 0x00000000 0 Kolejna instrukcja, inc, zwikszy warto znalezion pod tym adresem (przechowywan teraz w rejestrze EAX) o 1. Wykonanie tej instrukcji równie przedstawiono poniej. (gdb) x/i $eip 0x80483a2 <main+46>: inc DWORD PTR [eax] (gdb) x/dw $eax 0xbffff804: 0 (gdb) nexti 0x080483a4 6 for(i=0; i < 10; i++) (gdb) x/dw $eax 0xbffff804: 1 (gdb) W wyniku warto przechowywana pod adresem pamici EBP minus 4 (0xbfffff804) jest powikszana o 1. To zachowanie odpowiada fragmentowi kodu C, w którym warto zmiennej i jest powikszana w ptli for. Kolejn instrukcj jest bezwarunkowy przeskok. (gdb) x/i $eip 0x80483a4 <main+48>: jmp 0x804838b <main+23> (gdb) Instrukcja po wykonaniu przekieruje program z powrotem do instrukcji spod adresu 0x804838b. Dokonuje tego poprzez proste ustawienie EIP na t warto. Spogldajc ponownie na pen dezasemblacj, powiniene stwierdzi, które czci kodu C zostay skompilowane do których instrukcji jzyka maszynowego. (gdb) disass main Dump of assembler code for function main: 0x08048374 <main+0>: push ebp 0x08048375 <main+1>: mov ebp,esp 0x08048377 <main+3>: sub esp,0x8 0x0804837a <main+6>: and esp,0xfffffff0 0x0804837d <main+9>: mov eax,0x0 0x08048382 <main+14>: sub esp,eax 0x08048384 <main+16>: mov DWORD PTR [ebp-4],0x0 0x0804838b <main+23>: cmp DWORD PTR [ebp-4],0x9 0x0804838f <main+27>: jle 0x8048393 <main+31> 0x08048391 <main+29>: jmp 0x80483a6 <main+50> 0x08048393 <main+31>: mov DWORD PTR [esp],0x8048484 0x0804839a <main+38>: call 0x80482a0 <printf@plt> 0x0804839f <main+43>: lea eax,[ebp-4] 0x080483a2 <main+46>: inc DWORD PTR [eax] 0x080483a4 <main+48>: jmp 0x804838b <main+23> 52 0x200