PYTHON Pro hackery a reverzní inženýrství
Justin Seitz
Gray Hat Python Justin Seitz
Copyright © 2009 by Justin Seitz. Title of English-language original: Gray Hat Python, ISBN 978-1-59327-192-3 published by No Starch Press. Czech-language edition copyright © 2009 by ZONER software, a.s. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from No Starch Press. Copyright © 2009 Justin Seitz. Překlad originálního anglického vydání knihy Gray Hat Python, ISBN 978-1-59327-192-3, vydal No Starch Press. České vydání vydal ZONER software, a.s., copyright © 2009. Všechna práva vyhrazena. Žádná část této publikace nesmí být reprodukována nebo předávána žádnou formou nebo způsobem, elektronicky ani mechanicky, včetně fotokopií, natáčení ani žádnými jinými systémy pro ukládání bez výslovného svolení No Starch Press.
Python – pro hackery a reverzní inženýrství Autor: Justin Seitz Copyright © ZONER software, a.s. Vydání první v roce 2009. Všechna práva vyhrazena. Zoner Press Katalogové číslo: ZR913 ZONER software, a.s. Nové sady 18, 602 00 Brno Překlad: RNDr. Jan Pokorný Odborná korektura: Miroslav Kučera Šéfredaktor: Ing. Pavel Kristián DTP: Pavel Kristián, ml. © Ilustrace na obálce: Pavel Kristián, ml. Informace, které jsou v této knize zveřejněny, mohou byt chráněny jako patent. Jména produktů byla uvedena bez záruky jejich volného použití. Při tvorbě textů a vyobrazení bylo sice postupováno s maximální péčí, ale přesto nelze zcela vyloučit možnost výskytu chyb. Vydavatelé a autoři nepřebírají právní odpovědnost ani žádnou jinou záruku za použití chybných údajů a z toho vyplývajících důsledků. Všechna práva vyhrazena. Žádná část této publikace nesmí být reprodukována ani distribuována žádným způsobem ani prostředkem, ani reprodukována v databázi či na jiném záznamovém prostředku či v jiném systému bez výslovného svolení vydavatele, s výjimkou zveřejnění krátkých částí textu pro potřeby recenzí. Veškeré dotazy týkající se distribuce směřujte na: Zoner Press ZONER software, a.s Nové sady 18, 602 00 Brno tel.: 532 190 883, fax: 543 257 245 e-mail: knihy@zoner.cz www.zonerpress.cz
ISBN 978-80-7413-048-9
Mami, přál bych si, aby sis dokázala zapamatovat alespoň to, že Tě mám moc rád.
Společnost pro Alzheimerovu nemoc v Kanadě – www.alzheimers.ca
4
Stručný obsah Kapitola 01
Nastavení vývojového prostředí
15
Kapitola 02
Debuggery a jejich design
27
Kapitola 03
Tvorba vlastního debuggeru na Windows
39
Kapitola 04
PYDBG: debugger v Pythonu na Windows
73
Kapitola 05
Immunity Debugger: to nejlepší z obou světů
87
Kapitola 06
Hákování
103
Kapitola 07
Injektáž DLL a kódu
115
Kapitola 08
Fuzzing
131
Kapitola 09
Framework Sulley
145
Kapitola 10
Fuzzing ovladačů Windows
159
Kapitola 11
IDAPython: skriptování IDA Pro
175
Kapitola 12
PyEmu: skriptovatelný emulátor
185
Rejstřík
205
5
Podrobný obsah Předmluva
10
Poděkování
12
Úvod
13
Kapitola 01
Nastavení vývojového prostředí
15
1.1
Požadavky na operační systém
15
1.2
Získání a instalace Pythonu 2.5
16
1.2.1
Instalace Pythonu na Windows
16
1.2.2
Instalace Pythonu pro Linux
16
1.3
Příprava Eclipse a PyDev
17
1.3.1
Nejlepší přítel hackera: ctypes
19
1.3.2
Práce s dynamickými knihovnami
19
1.3.3
Sestrojování datových typů C
21
1.3.4
Předávání parametrů odkazem
23
1.3.5
Definice struktur a unionů
23
Kapitola 02
Debuggery a jejich design
27
2.1
Registry CPU s všeobecným zaměřením
28
2.2
Zásobník
30
2.3
Ladění událostí
32
2.4
Body přerušení
33
2.4.1
Instrukční body přerušení
33
2.4.2
Hardwarové body přerušení
35
2.4.3
Paměťové body přerušení
37
6 Kapitola 03
Tvorba vlastního debuggeru na Windows
39
3.1
Začínáme
39
3.2
Získání stavu registru CPU
47
3.2.1
Procházení vlákny
48
3.2.2
Dejme všechno dohromady
49
3.3
Implementace zpracovatelů ladicích událostí
53
3.4
Všemohoucí bod přerušení
58
3.4.1
Instrukční body přerušení
58
3.4.2
Hardwarové body přerušení
63
3.4.3
Paměťové body přerušení
68
3.5
Závěr
Kapitola 04
72
PYDBG: debugger v Pythonu na Windows
73
4.1
Rozšiřování zpracovatelů bodů přerušení
73
4.2
Zpracovatelé porušení přístupu
76
4.3
Momentky procesu
79
4.3.1
Jak se pořídí momentka procesu
79
4.3.2
Dejme všechno dohromady
82
5.1
Instalace Immunity Debuggeru
Kapitola 05 5.2
5.3
5.4
Immunity Debugger: to nejlepší z obou světů
87
87
Immunity Debugger 101
88
5.2.1
Co jsou PyCommandy
88
5.2.2
Co jsou PyHooks
89
Vývoj exploitu
91
5.3.1
Jak najít instrukce přívětivé k exploitu
91
5.3.2
Filtrování špatných znaků
92
5.3.3
Obcházení DEP na Windows
95
Jak zmařit antiladicí rutiny v malwaru
100
5.4.1
IsDebuggerPresent
100
5.4.2
Jak zmařit iteraci přes procesy
101
7 Kapitola 06
Hákování
103
6.1
Měkké hákování s PyDbg
104
6.2
Tvrdé hákování s Immunity Debuggerem
108
Kapitola 07 7.1
7.2
Injektáž DLL a kódu
Vytvoření vzdáleného vlákna
115
7.1.1
Injektáž DLL
116
7.1.2
Injektáž kódu
119
Jdeme páchat zlo
122
7.2.1
Skrytí souboru
122
7.2.2
Kód zadních vrátek
123
7.2.3
Kompilace s py2exe
127
Kapitola 08 8.1
115
Fuzzing
131
Třídy chyb
132
8.1.1
Přetékání bufferů
132
8.1.2
Celočíselná přetečení
133
8.1.3
Útoky na formátovací řetězec
134
8.2
Souborový fuzzer
135
8.3
Možný rozvoj v budoucnu
142
8.3.1
Pokrytí kódu testy
143
8.3.2
Automatizovaná statická analýza
143
Kapitola 09
Framework Sulley
145
9.1
Instalace Sulley
146
9.2
Základní prvky Sulley
146
9.2.1
Řetězce
147
9.2.2
Oddělovače
147
9.2.3
Statické a znáhodněné prvky
147
9.2.4
Binární data
148
9.2.5
Celá čísla
148
9.2.6
Bloky a skupiny
149
8 9.3
Zavraždění WarFTPD se Sulley
150
9.3.1
FTP 101
151
9.3.2
Vytvoření kostry protokolu FTP
151
9.3.3
Relace Sulley
152
9.3.4
Monitorování sítě a procesu
154
9.3.5
Fuzzing a webové rozhraní Sulley
155
Kapitola 10
Fuzzing ovladačů Windows
159
10.1 Komunikace s ovladačem
160
10.2 Fuzzing ovladače s Immunity Debuggerem
161
10.3 Driverlib – nástroj statické analýzy pro ovladače
165
10.3.1
Odhalování názvů zařízení
165
10.3.2
Jak najdeme rutinu IOCTL dispatch
166
10.3.3
Určení podporovaných IOCTL kódů
168
10.4 Budování fuzzeru ovladače
Kapitola 11
IDAPython: skriptování IDA Pro
170
175
11.1 Instalace IDAPythonu
176
11.2 Funkce IDAPythonu
177
11.2.1
Pomocné funkce
177
11.2.2
Segmenty
177
11.2.3
Funkce
178
11.2.4
Křížové odkazy
178
11.2.5
Háky debuggeru
178
11.3 Ukázky skriptů
179
11.3.1
Nalezení nebezpečných křížových odkazů
180
11.3.2
Pokrytí funkčního kódu testy
181
11.3.3
Výpočet velikosti zásobníku
183
Kapitola 12
PyEmu: skriptovatelný emulátor
185
12.1 Instalace PyEmu
185
12.2 Přehled PyEmu
186
9 12.2.1
PyCPU
186
12.2.2
PyMemory
186
12.2.3
PyEmu
187
12.2.4
Spuštění
187
12.2.5
Modifikátory paměti a registrů
187
12.2.6
Zpracovatelé
188
12.3 IDAPyEmu
192
12.3.1
Emulace funkce
194
12.3.2
PEPyEmu
197
12.3.3
Packery spustitelných souborů
198
12.3.4
Packer UPX
198
12.3.5
Rozpakování UPX s PEPyEmu
199
Rejstřík
205
10
Předmluva V souvislosti s Immunity Debuggerem nejčastěji zaslechnete otázku: "Už je hotový?" V běžném hovoru mezi zasvěcenci se k ní dojde obvykle tak, že po sdělení "Začínám makat na novém ELF importéru pro Immunity Debugger", následuje krátká pauza a pak otázka: "Už je hotový?" nebo "Právě jsem našel chybu v Internet Exploreru!" A pak slyšíte: "Už je ten exploit hotový?" Právě překotný vývoj, modifikace a tvorba činí z Pythonu tu nejlepší volbu, začínáte-li nový projekt související s bezpečností, ať už budujete speciální dekompilátor, nebo kompletní debugger. Někdy mě až jímá závrať, když zde, v South Beach, vejdu do Ace Hardware, kde si mám vybrat svého nového životního partnera. Je jich tam nejméně padesát a jsou pěkně naskládaní v úhledných regálech, jeden vedle druhého. Každý z nich vykazuje drobné, leč podstatné odlišnosti od všech ostatních. Nejsem takový všeuměl, abych dokázal říct, pro který účel je ideální to či ono zařízení, nicméně, když se vytvářejí bezpečnostní nástroje, platí stejné všeobecné principy. Zejména pracujete-li na webových aplikacích, nebo když si aplikace sami sestavujete, tak podobně jako na každý druh šroubku potřebujete speciální šroubovák, i zde potřebujete na každé ohodnocení speciálně uzpůsobený nástroj. Umět dát dohromady něco, co se umí zavěsit (neboli zaháknout) na API SQL, se týmu Immunity vyplatilo už několikrát. Neplatí to samozřejmě jen pro hodnocení. Jakmile se umíte zaháknout na API SQL, snadno už napíšete nějaký nástroj, jímž dokážete detekovat anomálie v dotazech SQL, čímž poskytnete své společnosti, která vás zaměstnává, rychlý prostředek proti vytrvalému útočníkovi. Všichni víme, že je značně obtížné docílit, aby se výzkumy související s bezpečností zařadily jako součást pracovní náplně týmu. Většina výzkumníků zabývajících se bezpečností, jakmile čelí nějakému problému, by nejraději prvně přebudovali knihovnu, se kterou se chystají vypořádat se vzniklým problémem. Řekněme, že se jedná o nějaký druh zranitelnosti v nějakém démonu SSL. Je velmi pravděpodobné, že náš výzkumník by nejraději začal tím, že by klienta SSL vybudoval úplně od začátku, protože "ta knihovna SSL je opravdu hrozná". Toho byste se měli za každou cenu vyvarovat. Ve skutečnosti ta knihovna SSL není až tak hrozná – pouze není napsaná v konkrétním stylu, který preferuje náš konkrétní výzkumník. Umět se ponořit do obrovského bloku kódu, přijít na to, v čem je problém, a umět ho opravit, to je klíč k tomu, abyste měli stále k dispozici aktuální a fungující knihovnu SSL a mohli napsat příhodný exploit, dokud to má ještě nějaký smysl. A schopnost umět docílit, aby experti na bezpečnost pracovali ruku v ruce jako jeden tým, je klíčem k tomu, aby práce pokračovaly dopředu tak rychle, jak požadujete. Je skvělé mít jednoho bezpečnostního experta, který ovládá Python, zrovna tak, jako když máte
11 nějakého experta na Ruby. Rozdíl je v tom, že Pythonisté umějí spolupracovat, dokážou pracovat se starým zdrojovým kódem, aniž by ho museli přepisovat, a i jinak operují jako jeden fungující superorganismus. Mravenčí kolonie, která se usadí v kuchyni, má přibližně stejnou hmotnost jako jedna malá chobotnice, kterou zabijete snadno. Zkuste ale vyhubit mravence! A nyní vstupuje na scénu kniha, která by měla pomoci právě s takovými úlohami. Patrně už máte nějaké nástroje, takže můžete dělat něco z toho, co chcete. Je ovšem možné, že použijete následující argument: "Obstaral jsem si Visual Studio, které má debugger, takže nepotřebuju vytvářet nějaký nový specializovaný debugger." Nebo tohle: "Nemá snad WinDbg pluginové rozhraní?" Odpověď je samozřejmě ano. WinDbg má pluginové rozhraní, a s tímto API můžete dát pomalinku dohromady něco užitečného. Jednoho dne si však řeknete: "Sakra, bylo by mnohem lepší, kdybych se mohl napojit na dalších 5 000 lidí používajících WinDbg, abychom si mohli porovnat své výsledky." A pracujete-li v Pythonu, bude na to stačit asi tak 100 řádků kódu pro XML-RPC klienta i pro server, aby všichni byli synchronizováni a spolupracovali na téže stránce. Hacking není reverzní inženýrství – cílem není dostat se k původnímu zdrojovému kódu aplikace. Cílem je pochopit program nebo systém lépe než ti, kdo ho vybudovali. Jakmile budete zkoumanému předmětu rozumět na této úrovni, budete schopni do takového programu proniknout a všelijak zajímavě z něho těžit. To znamená, že se chystáte stát expertem na vizualizaci, vzdálenou synchronizaci, teorii grafů, řešení lineárních rovnic, postupů statistické analýzy a na celou hromadu dalších věcí. Společnost Immunity se v tomto ohledu rozhodla vše standardizovat na Pythonu, takže pokaždé, když vytvoří nový algoritmus, může se používat ve všech jejich nástrojích. V kapitole 6 Justin ukazuje, jak napsat rychlý hák (hook), jímž se zavěsíte na Firefox, abyste z něj shrábli uživatelská jména a hesla. To je ovšem něco, co by teoreticky měli dělat jen hoši se zlovolnými úmysly, a zkušenosti ukazují, že zlí hoši používají vysokoúrovňové jazyky přesně pro tento druh věcí (viz http://philosecurity.org/2009/01/12/interview-with-an-adware-author). Na druhé straně to ovšem je přesně ten druh akcí, u nichž na jejich přípravu stačí 15 minut, ale právě z toho důvodu jimi dokážete vyburcovat z letargie spokojené vývojáře, protože jim předvedete, že to, co si myslí o svém softwaru, totiž jak je bezpečný, vůbec není pravda. Softwarové firmy sice investují spousty peněz, aby ochránily své produkty a veřejně prohlašují, že je to z bezpečnostních důvodů, ve skutečnosti ovšem jde o ochranu proti nelegálnímu kopírování a o správu digitálních práv (DRM). Takže čeho dosáhnete s touto knihou? Budete schopni svižně vytvářet softwarové nástroje, které manipulují s jinými aplikacemi. A budete to umět dělat tak, že na tvorbě nástrojů budete moci pracovat buď sami, nebo s celým týmem. V tom je budoucnost bezpečnostních nástrojů: rychle je implementovat, rychle modifikovat, rychle se připojovat. Myslím, že už zbývá odpovědět na jedinou otázku: "Už jsou hotové?" Dave Aitel Miami Beach, Florida Únor 2009
12
Poděkování Děkuji své rodině, že mě tolerovala po celou dobu, co jsem psal tuto knihu. Mé čtyři roztomilé dětičky (což jsou Emily, Carter, Cohen a Brady) byly pro tatínka dobrý důvod, aby knihu dopsal. Mám Vás rád, moji andílci. Děkuji i Vám, mí bratři a sestry, že jste mě při psaní této knihy podporovali. Děkuji všem lidem, co napsali tlusté fascikly, protože mě vždy potěší, když vím, že existují lidé, kteří dobře chápou, jak přísný na sebe člověk musí být, chce-li napsat něco odborného – i Vás mám rád. Táto, Tvůj smysl pro humor mi pomohl překonat mnohé dny, kdy se mi ale vůbec nechtělo psát, mám Tě rád, Harolde, ať je pořád všude okolo Tebe hodně veselo. A zde jsou všichni, kdo pomáhali tomuto ještě neopeřenému bezpečnostnímu výzkumníkovi, aby se naučil létat – Jared DeMott, Pedram Amini, Cody Pierce, Thomas Heller (nadčlověk v Pythonu), Charlie Miller. Všem Vám dlužím za mnoho. Neuvěřitelně vstřícně mě také při psaní knihy podporoval tým Immunity: nesmírně jste mi pomohli vyspět nejenom co do Pythonu, ale i jako vývojáři a výzkumníkovi obecně. Velké díky také zasluhují Nico a Dami, vypomáháním mé maličkosti jste strávili mnoho času. Dave Aitel, můj odborný redaktor, mi pomáhal dotáhnout věci do konce a zajišťoval, že budou smysluplné i srozumitelné. Vřelé díky, Dave! Také děkuji panu Fallonovi za revize knihy – mohl jsem se tak ze srdce zasmát nad svými vlastními omyly. A konečně, závěrečné díky (vím, že se mají vždy uvádět naposledy) patří týmu No Starch Press. Tylerovi, že to se mnou vydržel po celou dobu (věř mi, Tylere, byl jsi tím nejtrpělivějším spolupracovníkem, jakého jsem kdy měl). Bille, děkuji za tu porci Perlu a za povzbuzující slova. Megan, díky za to, že se dokončující práce na knize provedly tak bezbolestně, jak to jen šlo. Také samozřejmě děkuji tomu zbytku posádky, který je obvykle skryt v podpalubí, ale bez nich by žádný ze skvělých titulů nespatřil světlo světa. Převeliké díky. Nesmírně si cením všeho, co jste pro mne udělali. A když už jsem docílil toho, že mé poděkování je tak dlouhé, jako když se předávají ceny Grammy, mohu je zakončit tím, že děkuji všem ostatním lidem, kteří mi nějak vypomohli a které jsem patrně zapomněl zařadit do tohoto seznamu – ale Vy svou cenu znáte.
13
Úvod Python jsem se učil konkrétně kvůli hackingu – a troufnu si říci, že tohle je pravdivý výrok i pro mnoho dalších lidí. Strávil jsem poměrně hodně času hledáním jazyka, který by se dobře hodil pro hacking a pro reverzní inženýrství. Před několika lety už bylo očividné, že se přirozenou vůdčí autoritou v divizi programovacích jazyků pro hacking stal právě Python. Ošidné bylo jen to, že vlastně neexistoval žádný manuál, kde by se člověk dočetl, jak používat Python v nejrůznějších hackerských zadáních. Museli jste se prokousávat mračny příspěvků na fórech a stránkami elektronických manuálů a obvykle jste trávili spoustu času tím, že jste procházeli kódem a snažili se, aby fungoval správně. Cílem této knihy je vyplnit tuto mezeru na trhu tím, že podniknete svižnou projížďku po různých způsobech, jak využívat Python v hackerských úlohách a v reverzním inženýrství. Kniha je koncipována tak, abyste zvládli i trochu teoretických vědomostí, na jejichž základě byla vybudovaná většina hackerských nástrojů a technik, jako jsou debuggery, zadní vrátka, fuzzery, emulátory a injektáže kódu. Dozvíte se také leccos o tom, jak využívat předem vybudované nástroje Pythonu, když není nutné vyrábět vlastní řešení. Naučíte se používat nejenom nástroje založené na Pythonu, ale také jak budovat vlastní nástroje v Pythonu. Předem vás však varuji. Tohle není vyčerpávající referenční příručka! Existuje velmi mnoho infosec (information security) nástrojů napsaných v Pythonu, jimiž se nezabývám. Znalosti získané z této knihy vám ovšem umožní, abyste většinu dovedností dokázali přenášet mezi aplikacemi, takže budete schopni používat, ladit, rozšiřovat i přizpůsobovat jakýkoliv nástroj Pythonu, který si zvolíte. S knihou můžete pracovat dvěma způsoby. Jestliže teprve začínáte s Pythonem nebo s budováním hackerských nástrojů, měli byste číst knihu popořádku, od začátku do konce. Osvojíte si minimum nezbytné teorie, projdete kvantum kódu Pythonu a získáte solidní základy, abyste se – až s knihou skončíte – dokázali dobře vypořádat s myriádami úloh z oblasti hackingu a reverzního inženýrství. Jestliže se už vyznáte v Pythonu a máte solidní vědomosti o knihovně ctypes Pythonu, skočte rovnou do kapitoly 2. A vám, kdo už se v dané problematice pohybujete jako ryba ve vodě, bude stačit nalistovat příhodné místo knihy a zkopírovat si odtud potřebný fragment kódu, nebo přečíst pouze ty sekce textu, které potřebujete při své každodenní práci. Poměrně hodně času věnuji v knize debuggerům. V kapitole začínám s jejich teorií, přičemž se postupně dostaneme až k nástroji Immunity Debugger v kapitole 5. Debuggery jsou klíčovými nástroji každého hackera, takže nedělám žádné cavyky ohledně toho, že je probírám ve značné šíři. V kapitolách 6 a 7 se naučíte některé techniky, jimiž se lze na něco zahákovat (hooking), nebo něco
14 někam injektovat. Tyto znalosti můžete přidat k pojmům souvisejícím s laděním, které se využívají při řízení programů a manipulacích s pamětí. Další sekce knihy se věnuje prověřování aplikací pomocí fuzzerů. V kapitole 8 se dozvíte, k čemu vlastně slouží fuzzing a sestrojíme si vlastní základní fuzzer na soubory. V kapitole 9 využijeme mocný pracovní rámec Sulley, abychom rozbili démona FTP ze skutečného světa, a v kapitole 10 se dozvíte, jak vybudovat fuzzer, který ničí ovladače Windows. V kapitole 11 uvidíte, jak se dají automatizovat úlohy statické analýzy v IDA Pro, což je oblíbený binární statický analytický nástroj. Knihu pak zakončíme kapitolou 12, kde probereme PyEmu, což je emulátor založený na Pythonu. Snažil jsem se udržet výpisy kódu v knize pokud možno krátké, ale na místa, kde jsem to považoval za potřebné, jsem vložil podrobná vysvětlení toho, jak kód funguje. Když se učíte nový jazyk nebo zvládáte nové knihovny, je potřeba jistou dobu věnovat tomu, že skutečně sami píšete kód a ladíte své chyby. Vřele to doporučuji, pište kód! Veškerý zdrojový kód této knihy je k dispozici na adrese zonerpress.cz/download/python-src.zip, odkud si jej můžete stáhnout. Začněme kódovat!
Python – pro hackery a reverzní inženýrství
39
03 Tvorba vlastního debuggeru na Windows Když máme za sebou základy, je na čase zrealizovat prakticky, co jsme se naučili, a vytvořit opravdový fungující debugger. Když společnost Microsoft vyvinula Windows, přidala působivou sadu ladicích funkcí, které asistují vývojářům i odborníkům při hodnocení kvality (QA, Quality Assurance), kteří posuzují, do jaké míry cílový produkt naplní očekávání zákazníků. Tyto funkce budeme velmi intenzívně využívat, abychom vytvořili vlastní debugger. Na tomto místě bych chtěl připomenout důležitou věc – v podstatě provádíme podrobný rozbor debuggeru PyDbg od Pedrama Aminiho, protože to je nejčistší dnes dostupná implementace debuggeru Windows napsaná v Pythonu. S požehnáním od Pedrama udržuji – jak to jenom jde – zdrojový kód v této knize co nejblíže PyDbg (například názvy funkcí, proměnné atd.), abyste mohli kód co nejsnáze přenášet ze svého vlastního debuggeru do PyDbg.
3.1 Začínáme Abyste byli schopni aplikovat na nějaký proces nějakou ladicí úlohu, musíte být prvně schopni sdružit s tímto procesem svůj debugger. Debugger musí být buď schopen otevřít vykonatelný soubor a spustit ho, nebo se připojit k nějakému běžícímu procesu. Ladicí API Windows naštěstí poskytuje snadný způsob pro obojí. Mezi otevřením procesu a připojením se k procesu je několik drobných rozdílů. Předností postupu, kdy se otevírá nějaký proces, je hlavně to, že nad ním máte kontrolu ještě předtím, než má šanci spustit nějaký kód. To se může hodit, když analyzujete malware nebo jiný typ kódu se zlotřilými úmysly. Připojení k procesu jednoduše znamená se drze vetřít do již běžícího procesu a analyzovat konkrétní oblasti kódu, o které se zajímáte. To, který z obou postupů použijete, závisí na tom, na co se při ladění zaměřujete a jakou analýzu chcete podniknout.
40
Kapitola 03 – Tvorba vlastního debuggeru na Windows
První metodou, jak docílit, aby nějaký proces běžel pod kontrolou debuggeru, je spustit vykonatelný soubor ze samotného debuggeru. Chcete-li pod Windows vytvořit proces, zavolejte funkci CreateProcessA()1. Když se nastaví konkrétní indikátory předávané do této funkce, proces se automaticky zpřístupní k ladění. Volání funkce CreateProcessA() vypadá takto: BOOL WINAPI CreateProcessA( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
Ačkoliv na první pohled to vypadá jako dost komplikované volání, není tomu tak. V reverzním inženýrství musíte vždy předmět zkoumání rozkrájet na malá sousta, abyste si dokázali udělat celkový obrázek. Budeme pracovat pouze s těmi parametry, které jsou důležité pro vytvoření procesu pod debuggerem. Jsou to parametry lpApplicationName, lpCommandLine, dwCreationFlags, lpStartupInfo a lpProcessInformation. Zbývající parametry se mohou nastavit na NULL. Zajímáte-li se o úplné vysvětlení celého volání, obraťte se na MSDN (Microsoft Developer Network). Prvními dvěma parametry se nastavuje cesta k spustitelnému souboru, který chcete rozběhnout, a případné argumenty příkazového řádku, které přijímá. Parametr dwCreationFlags přebírá speciální hodnotu, která indikuje, zdali se má proces odstartovat jako laděný proces. Poslední dva parametry jsou pointery na struktury (STARTUPINFO2, resp. PROCESS_INFORMATION3), které diktují, jakým způsobem se má proces nastartovat, a poskytují také důležité informace týkající se procesu poté, co úspěšně nastartoval. Vytvořte nyní dva nové soubory Pythonu a pojmenujte je my_debugger.py a my_debugger_defines.py. V tom prvním souboru hodláme vytvořit rodičovskou třídu debugger(), kam budeme kousek po kousku přidávat ladicí funkcionalitu. Veškeré ostatní struktury, uniony a konstanty nacpeme do souboru my_debugger_defines.py, aby se celý příklad lépe udržoval.
my_debugger_defines.py from ctypes import * # Pro lepší přehlednost mapujeme typy Microsoftu na ctypes
1 2 3
Viz funkce CreateProcess v MSDN (http://msdn2.microsoft.com/en-us/library/ms682425.aspx) Viz struktura STARTUPINFO v MSDN (http://msdn2.microsoft.com/en-us/library/ms686331.aspx) Viz struktura PROCESS_INFORMATION v MSDN (http://msdn2.microsoft.com/en-us/library/ms684873.aspx)
Python – pro hackery a reverzní inženýrství WORD
= c_ushort
DWORD
= c_ulong
LPBYTE
= POINTER(c_ubyte)
LPTSTR
= POINTER(c_char)
HANDLE
= c_void_p
# Konstanty DEBUG_PROCESS
= 0x00000001
CREATE_NEW_CONSOLE
= 0x00000010
# Struktury pro funkci CreateProcessA() class STARTUPINFO(Structure): _fields_ = [ ("cb",
DWORD),
("lpReserved",
LPTSTR),
("lpDesktop",
LPTSTR),
("lpTitle",
LPTSTR),
("dwX",
DWORD),
("dwY",
DWORD),
("dwXSize",
DWORD),
("dwYSize",
DWORD),
("dwXCountChars", DWORD), ("dwYCountChars", DWORD), ("dwFillAttribute",DWORD), ("dwFlags",
DWORD),
("wShowWindow",
WORD),
("cbReserved2",
WORD),
("lpReserved2",
LPBYTE),
("hStdInput",
HANDLE),
("hStdOutput",
HANDLE),
("hStdError",
HANDLE),
] class PROCESS_INFORMATION(Structure): _fields_ = [ ("hProcess",
HANDLE),
("hThread",
HANDLE),
("dwProcessId", DWORD), ("dwThreadId", ]
DWORD),
41
Kapitola 03 – Tvorba vlastního debuggeru na Windows
42
my_debugger.py from ctypes import * from my_debugger_defines import * kernel32 = windll.kernel32 class debugger(): def __init__(self): pass def load(self,path_to_exe): # indikátor dwCreation určuje, jak vytvoříme proces # nastavte creation_flags = CREATE_NEW_CONSOLE, chcete-li # abyste viděli grafické uživatelské rozhraní kalkulačky creation_flags = DEBUG_PROCESS # vytvoření instancí struktur startupinfo
= STARTUPINFO()
process_information
= PROCESS_INFORMATION()
# Dvě následující volby umožňují startovanému procesu # zobrazit se v separátním okně. Také to ilustruje, # jak mohou rozličná nastavení ve struktuře STARTUPINFO # ovlivnit laděného startupinfo.dwFlags = 0x1 startupinfo.wShowWindow = 0x0 # Pak inicializujeme proměnnou cb ve struktuře STARTUPINFO, # což je prostě velikost samotné struktury startupinfo.cb = sizeof(startupinfo) if kernel32.CreateProcessA(path_to_exe, None, None, None, None, creation_flags, None, None, byref(startupinfo), byref(process_information)):
Python – pro hackery a reverzní inženýrství
43
print "[*] We have successfully launched the process!" print "[*] PID: %d" % process_information.dwProcessId else: print "[*] Error with error code %d." % kernel32.GetLastError()
Nyní vytvoříme kratičký testovací skript, abychom si ověřili, zda všechno funguje, jak jsme si naplánovali. Soubor pojmenujte jako my_test.py a přesvědčte se, že jste ho uložili do téhož adresáře, jako předchozí dva soubory.
my_test.py import my_debugger debugger=my_debugger.debugger() debugger.load("C:\\WINDOWS\\system32\\calc.exe")
Spustíte-li tento soubor Pythonu, buď z příkazového řádku, nebo ze svého IDE, zplodí proces, který jste zadali, oznámí identifikační číslo procesu (PID) a skončí. Pokud jste jako já použili soubor calc.exe, neuvidíte grafické uživatelské rozhraní (GUI) kalkulačky. Příčinou, proč toto GUI nevidíte, je to, že proces se nevykreslil na obrazovku, protože čeká na debugger, který ho má za úkol zase rozběhnout. Ačkoliv tuto logiku ještě nemáme vybudovanou, budeme ji mít cobydup! V tomto okamžiku už ale víte, jak zplodit proces, který je připraven k ladění. Nyní je čas napsat zase něco kódu, kterým připojíme debugger k běžícímu procesu. Když připravujeme připojení k nějakému procesu, je prospěšné získat handle na samotný proces. Většina funkcí, které budeme používat, požadují platné handle procesu, takže je dobré vědět, zdali se budete schopni připojit k procesu ještě předtím, než ho začneme ladit. To se dělá pomocí funkce OpenProcess()4, která se exportuje z kernel32.dll a má tento prototyp: HANDLE WINAPI OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
Parametr dwDesiredAccess indikuje, jaký typ přístupových oprávnění požadujeme pro objekt procesu, k němuž chceme získat handle. Abychom mohli ladit, musíme ho nastavit na PROCESS_ ALL_ACCESS. Parametr bInheritHandle bude vždy pro naše potřeby nastaven na False a parametr dwProcessId je prostě PID procesu, k němuž chceme získat handle. Pokud funkce uspěje, vrátí handle na objekt procesu.
4 5
Viz funkce OpenProcess v MSDN (http://msdn2.microsoft.com/en-us/library/ms684320.aspx). Viz funkce DebugActiveProcess v MSDN (http://msdn2.microsoft.com/en-us/library/ms6792995.aspx).
Kapitola 07 – Injektáž DLL a kódu
44
K procesu se připojíme prostřednictvím funkce DebugActiveProcess()5, která vypadá takto: BOOL WINAPI DebugActiveProcess( DWORD dwProcessId );
Jednoduše jí předáme PID procesu, k němuž se chceme připojit. Jakmile systém určí, že máme patřičná oprávnění pro připojení k procesu, cílový proces bude předpokládat, že připojovaný proces (debugger) je připraven zpracovávat ladicí události, a vzdá se řízení ve prospěch debuggeru. Debugger zachycuje tyto ladicí události tím, že ve smyčce volá WaitForDebugEvent()6. Funkce vypadá takto: BOOL WINAPI WaitForDebugEvent( __out
LPDEBUG_EVENT lpDebugEvent,
__in
DWORD dwMilliseconds
);
První parametr je pointer na strukturu DEBUG_EVENT7 – tato struktura popisuje ladicí událost. Druhý parametr nastavíme na INFINITE, aby volání WaitForDebugEvent() nevrátilo řízení dřív, než dojde k nějaké události. S každou událostí, kterou bude debugger zachycovat, jsou asociováni zpracovatelé událostí, kteří provádějí jistý druh akce, než povolí procesu pokračovat. Jakmile vykonávání zpracovatelů skončí, chceme, aby proces pokračoval v běhu. Toho docílíme funkcí ContinueDebugEvent()8, která vypadá takto: BOOL WINAPI ContinueDebugEvent( __in
DWORD dwProcessId,
__in
DWORD dwThreadId,
__in
DWORD dwContinueStatus
);
Parametry dwProcessId a dwThreadId jsou pole ve struktuře DEBUG_EVENT, která se inicializuje, když debugger zachytí ladicí událost. Parametr dwContinueStatus signalizuje procesu, aby pokračoval ve vykonávání (DBG_CONTINUE), nebo aby pokračoval výjimkou (DBG_EXCEPTION_NOT_HANDLED). A další věc, která ještě zbývá, je odpojit se od procesu. Zavoláme proto DebugActiveProcessStop()9, která jako svůj jediný parametr přebírá PID procesu, od kterého se chceme odpojit. Dejme nyní všechno dohromady a rozšiřme třídu my_debugger o výbavu umožňující se připojit k nějakému procesu a odpojit se od něho. Přidáme také schopnost otevřít a získat handle procesu.
6 7 8 9
Viz funkce WaitForDebugEvent v MSDN (http://msdn2.microsoft.com/en-us/library/ms681423.aspx). Viz struktura DEBUG_EVENT v MSDN (http://msdn2.microsoft.com/en-us/library/ms679308.aspx). Viz funkce ContinueDebugEvent v MSDN (http://msdn2.microsoft.com/en-us/library/ms679285.aspx). Viz funkce DebugActiveProcessStop v MSDN (http://msdn2.microsoft.com/en-us/library/ms679296.aspx).
Python – pro hackery a reverzní inženýrství
115
07 Injektáž DLL a kódu Vždy, když útočíte na nějaký cíl nebo ho podrobujete reverznímu inženýrství, určitě oceníte, budete-li umět vpašovat kód do vzdáleného procesu a vykonat tento kód v kontextu daného procesu. Ať už kradete hashe hesel, nebo chcete získat vzdálenou desktopovou kontrolu nad cílovým systémem, zde všude existují četná využití pro injektáže DLL a kódu. V této kapitole vytvoříme několik jednoduchých utilit v Pythonu, s nimiž budete moci ovládnout obě techniky a využívat je tak, abyste je v případě potřeby dokázali snadno implementovat. Tyto techniky by měly být součástí arzenálu vývojářů, pisatelů exploitů či shellkódů i těch, kdo testují bezpečnost systémů proti vniknutím. Pomocí injektáže DLL otevřeme uvnitř jiného procesu pop-up okno a pomocí injektáže kódu otestujeme úsek shellkódu, jehož účelem je zlikvidovat proces na základě jeho PID. Jako závěrečné cvičení vytvoříme a zkompilujeme trojského koně (trojan backdoor), který bude celý napsaný v Pythonu. Naše "zadní vrátka" se budou do značné míry spoléhat na injektáž kódu a budou se v nich používat záludné taktiky, kterých se každý dobrý trojský kůň musí držet. Začneme tím, že probereme, jak se vytvoří vzdálené vlákno, protože představuje základ obou technik injektáže.
7.1 Vytvoření vzdáleného vlákna Ačkoliv mezi injektáží DLL a kódu existují sice jisté zásadní rozdíly, obojího se dociluje stejným způsobem: přes vytvoření vzdáleného vlákna. API Win32 je už vybaveno funkcí, která dělá přesně tohle, jmenuje se CreateRemoteThread()1 a exportuje se z kernel32.dll. Má tento prototyp: HANDLE WINAPI CreateRemoteThread(
1
Viz funkce CreateRemoteThread v MSDN (http://msdn.microsoft.com/en-us/library/ms682437.aspx).
116
Kapitola 07 – Injektáž DLL a kódu
HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
Neděste se, ačkoliv má hodně parametrů, jsou dost intuitivní. První parametr, hProcess, by vám měl připadat povědomý – je to handle k procesu, ve kterém startujeme vlákno. Parametr lpThreadAttributes prostě nastavuje bezpečnostní deskriptor pro nově vytvořené vlákno, diktuje, zdali mohou handle vlákna dědit dceřiné procesy. My ho nastavíme na NULL, což znamená, že vlákno bude mít nezděditelné handle a výchozí bezpečnostní deskriptor. Parametr dwStackSize prostě nastavuje velikost zásobníku nově vytvořeného vlákna. Nastavíme ho na nulu, takže dostane výchozí velikost, kterou už proces používá. Další parametr je nejdůležitější: lpStartAddress; indikuje, kde v paměti se vlákno začne vykonávat. Je zcela nezbytné nastavit tuto adresu řádně, aby se mohl vykonat kód, který usnadňuje injektáž. Další parametr, lpParameter, je téměř stejně důležitý jako startovací adresa. Umožňuje totiž poskytnout pointer na takové umístění v paměti, které máte pod svou kontrolou, a předá se jako funkční parametr do funkce, která sídlí na adrese lpStartAddress. Možná v tom teď máte trochu zmatek, ale velmi brzy uvidíte, jak klíčový je tento parametr pro injektáž DLL. Parametr dwCreationFlags diktuje, jakým způsobem se vlákno nastartuje. Vždy ho budeme nastavovat na nulu, což znamená, že se vlákno spustí okamžitě poté, co se vytvoří. Klidně si ale prostudujte dokumentaci MSDN, pokud chcete vědět, jaké další hodnoty dwCreationFlags podporuje. Posledním parametrem je lpThreadId – naplní se identifikátorem (TID) nově vytvořeného vlákna. Poté, co jste porozuměli nejdůležitějšímu funkčnímu volání, bez něhož by injektáž šla jen těžko provést, prozkoumáme, jak s jeho pomocí vsunout do nějakého vzdáleného procesu nějakou DLL a za ní ještě injektovat špetku syrového shellkódu. Postup, jímž získáme vzdálené vytvořené vlákno a nakonec spustíme náš kód, se v obou technikách liší, takže probereme oba postupy, abychom mohli názorně ilustrovat, jaké mohou být mezi nimi rozdíly.
7.1.1 Injektáž DLL Injektáž DLL se využívá i zneužívá už poměrně dlouho. Všude, kam se podíváte, dochází k injektážím DLL. Od líbivých rozšíření Windows, která poskytují třpytivé blikající kurzory myši, až ke kousíčku malwaru, který ukradne informace o vašem účtu, jsou injektáže DLL všudypřítomné. Dokonce i produkty z oblasti bezpečnosti injektují DLL za účelem monitoringu případného zlovolného chování. Milé na injektáži DLL je to, že můžeme napsat a zkompilovat binární soubor, načíst ho do procesu a vykonat jako součást tohoto procesu. To je nesmírně užitečné například tehdy, potřebujete-li se vyhnout softwarovým firewallům, které povolují pouze konkrétním aplikacím dělat
Python – pro hackery a reverzní inženýrství
117
odchozí připojení. Prozkoumáme to poněkud více zblízka tím, že vytvoříme injektovač Pythonu, který umožní vsunout DLL do libovolného procesu, který si zvolíme. Aby proces Windows mohl načítat do paměti nějaké DLL, musí tyto DLL používat funkci LoadLibrary(), která se exportuje z kernel32.dll. Mrkněme se na její prototyp: HMODULE LoadLibrary( LPCTSTR lpFileName );
Parametr lpFileName je prostě cesta k DLL, kterou chcete načíst. Potřebujeme přimět nějaký vzdálený proces, aby zavolal LoadLibraryA s pointerem na řetězcovou hodnotu, což bude právě cesta k té DLL, kterou chceme načíst. Prvním krokem je vyřešit adresu, kde sídlí LoadLibraryA, a pak napsat název DLL, kterou chceme načíst. Až budeme volat CreateRemoteThread(), ukážeme adresou lpStartAddress na adresu, kde je LoadLibraryA, a nastavíme lpParameter tak, aby ukazoval na cestu DLL, kterou jsme si uložili. Jakmile se CreateRemoteThread() odpálí, zavolá LoadLibraryA, jako kdyby ten vzdálený proces sám učinil požadavek na načtení oné DLL. Poznámka. DLL knihovnu, kterou můžete použít k otestování injektáže, naleznete ve zdrojových kódech k této knize, které můžete stáhnout z adresy zonerpress.cz/download/python-src.zip.
Dejme se do práce. Otevřete nový soubor Pythonu, pojmenujte ho dll_injector.py a vložte tam následující kód:
dll_injector.py import sys from ctypes import * PAGE_READWRITE
= 0x04
PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM
= ( 0x1000 | 0x2000 )
kernel32 = windll.kernel32 pid
= sys.argv[1]
dll_path = sys.argv[2] dll_len
= len(dll_path)
# Získá handle k procesu, kam budeme injektovat. h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[*] Couldn't acquire a handle to PID: %s" % pid
118
Kapitola 07 – Injektáž DLL a kódu
sys.exit(0) # Alokuje místo pro cestu DLL 1 arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_MEM, PAGE_READWRITE) # Zapíše cestu DLL do alokovaného umístění 2 written = c_int(0) kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len, byref(written)) # Potřebujeme vyřešit adresu LoadLibraryA 3 h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll") h_loadlib = kernel32.GetProcAddress(h_kernel32,"LoadLibraryA") # Nyní se pokusíme vytvořit vzdálené vlákno, se vstupním bodem nastaveným # na LoadLibraryA a jako jediný její parametr bude pointer na cestu DLL. 4 thread_id = c_ulong(0) if not kernel32.CreateRemoteThread(h_process, None, 0, h_loadlib, arg_address, 0, byref(thread_id)): print "[*] Failed to inject the DLL. Exiting." sys.exit(0) print "[*] Remote thread with ID 0x%08x created." % thread_id.value
Prvním krokem ( 1 ) je alokovat dostatek paměti pro uložení cesty k DLL, do které injektujeme. Poté zapíšeme cestu do nově alokovaného paměťového umístění ( 2 ). Pak musíme vyřešit paměťovou adresu, kde sídlí LoadLibraryA ( 3 ), abychom mohli ukázat následným voláním CreateRemoteThread() ( 4 ) na její paměťové umístění. Jakmile se toto vlákno odpálí, měla by se DLL načíst do procesu a vy byste měli uvidět dialog indikující, že DLL vstoupila do procesu. Spusťte skript takto: python dll_injector.py <PID> <cesta k DLL>
Nyní máte solidní fungující ukázku ohledně užitečnosti injektáže DLL. I když tento dialog asi trochu zaostává za tím, co jste možná očekávali, je velmi důležité rozumět této technice. Nyní proberme injektáž kódu!
Python – pro hackery a reverzní inženýrství
159
10 Fuzzing ovladačů Windows Útoky na ovladače Windows se staly oblíbenou zábavou lovců chyb, vývojářů exploitů a jim podobných. I když v posledních letech docházelo i ke vzdáleným útokům na ovladače, daleko běžnější je lokální útok na ovladač za účelem získání vyšších oprávnění k napadanému stroji. V předchozí kapitole jsme se Sulley našli ve WarFTPD přetečení zásobníku. Nevěděli jsme ovšem, že démon WarFTPD běžel jako limitovaný uživatel, v podstatě jako uživatel, který nastartoval nějaký spustitelný soubor. Kdybychom na něj zaútočili opravdu vzdáleně, skončili bychom na onom stroji pouze s limitovanými oprávněními, což v některých případech značně omezuje, jaký druh informací můžeme ukrást z hostitele i k jakým službám tam můžeme přistupovat. Kdybychom předem věděli, že na lokálním stroji je nainstalovaný ovladač, který je zranitelný k útokům typu přetečení1 nebo zcizení identity2, mohli bychom použít tento ovladač jako prostředek, jehož prostřednictvím bychom získali oprávnění na úrovni System. A nespoutáni žádnými okovy bychom pak byli schopni získávat z tohoto stroje skutečně výživné informace všeho druhu. Abychom mohli komunikovat s ovladačem, potřebujeme přechod mezi uživatelským módem a kernelovým módem (kernel mode). Uděláme to tak, že budeme předávat informace od ovladače pomocí vstupních a výstupních ovládacích prvků (input/output controls, IOCTLs), což jsou speciální brány umožňující službám nebo aplikacím uživatelského módu přistupovat ke kernelovým zařízením nebo komponentám. Podobně jako u jakýchkoliv jiných prostředků pro předávání informací mezi aplikacemi, i zde můžeme exploitovat ty zpracovatele IOCTL, kteří nejsou implementováni bezpečně, a získávat tak vyšší oprávnění nebo způsobit havárii cílového systému.
1 2
Viz Kostya Kortchinsky, "Exploiting Kernel Pool Overflows" (2008), http://immunityinc.com/downloads/KernelPool.odp. Viz Justin Seitz, "I2OMGMT Driver Impersonation Attack" (2008), http://immunityinc.com/downloads/DriverImpersonationAttack_i2omgmt.pdf.
160
Kapitola 10 – Fuzzing ovladačů Windows
Nejprve probereme, jak se připojit k lokálnímu zařízení, které implementuje prvky IOCTL, a jak předávat prvky IOCTL danému zařízení. Následně budeme zkoumat, jak se pomocí Immunity Debuggeru zmutují prvky IOCTL předtím, než se odešlou ovladači. Pak uvidíme, jak pomocí zabudované knihovny debuggeru pro statickou analýzu, driverlib, získáme podrobné informace o ovladači, na který jsme se zaměřili. Podíváme se také pod kapotu driverlib a naučíme se dekódovat důležité řídící toky, názvy zařízení a kódy IOCTL ze zkompilovaného souboru ovladače. Nakonec vezmeme své výsledky z driverlib a s jejich pomocí vybudujeme testovací případy pro samostatný fuzzer ovladače, jenž bude do jisté míry založen na fuzzeru, který jsem vydal pod názvem ioctlizer. Dejme se do práce.
10.1 Komunikace s ovladačem Téměř každý ovladač na systému Windows se v operačním systému registruje konkrétním názvem zařízení a symbolickým odkazem, takže uživatelský mód může získat handle k tomuto ovladači, aby s ním mohl komunikovat. Abychom handle získali, zavolejme CreateFileW3 exportovanou z kernel32.dll. Prototyp funkce vypadá následovně: HANDLE WINAPI CreateFileW( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );
První parametr je název souboru nebo zařízení, k němuž si přejeme získat handle; bude to hodnota symbolického odkazu, kterou exportuje ovladač, na který jsme se zaměřili. Indikátor dwDesiredAccess určuje, zdali chceme z tohoto zařízení číst nebo na něj zapisovat (nebo obojí, nebo ani jedno); pro naše účely bychom měli rádi přístupy GENERIC_READ (0x80000000) a GENERIC_WRITE (0x40000000). Parametr dwShareMode nastavíme na nulu, což znamená, že se k tomuto zařízení nebude moci přistupovat, dokud neuzavřeme handle vrácený z CreateFileW. Parametr lpSecurityAttributes nastavíme na NULL, což znamená, že se na handle bude aplikovat výchozí bezpečnostní deskriptor a že handle nebude moci zdědit žádný z dceřiných procesů, které možná vytvoříme, což se nám právě hodí. Parametr dwCreationDisposition nastavíme na OPEN_EXISTING (0x3), což znamená, že zařízení otevřeme jen tehdy, pokud už existuje; jinak volání CreateFileW nebude úspěšné. Poslední dva parametry nastavíme na nulu, resp. na NULL.
3
Viz funkce CreateFile v MSDN (http://msdn.microsoft.com/en-us/library/aa363858.aspx).
Python – pro hackery a reverzní inženýrství
161
Jakmile obdržíme platné handle z volání CreateFileW, můžeme s jeho pomocí předat do zařízení nějaký IOCTL. IOCTL odešleme přes volání API DeviceIoControl4, také se exportuje z kernel32.dll. Funkční prototyp vypadá takto: BOOL WINAPI DeviceIoControl( HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped );
První parametr je handle vrácené z volání CreateFileW. Parametr dwIoControlCode je kód IOCTL, který budeme předávat ovladači zařízení. Tento kód určuje, jaký typ akce ovladač podnikne, jakmile zpracuje náš IOCTL požadavek. Další parametr, lpInBuffer, je pointer na buffer obsahující informace, které předáváme ovladači zařízení. Ten buffer je jedním z předmětů našeho zájmu, protože budeme fuzzovat to, co obsahuje, než ho předáme ovladači. Parametr nInBufferSize je prostě celé číslo, které říká ovladači, jak velký je buffer, který mu předáváme. Parametry lpOutBuffer a lpOutBufferSize jsou totéž jako dva předchozí parametry, ale používají se pro informace předávané zpět z ovladače, ne pro informace předávané do něj. Parametr lpBytesReturned je volitelná hodnota, která říká, kolik dat se vrátilo z volání. Poslední parametr, lpOverlapped, nastavíme jednoduše na NULL. Jakmile máme připravené základní stavební bloky pro komunikaci s ovladačem, sáhněme po Immunity Debuggeru, abychom se zahákovali na volání DeviceIoControl a zmutovali vstupní buffer předtím, než ho předáme ovladači, na který jsme se zaměřili.
10.2 Fuzzing ovladače s Immunity Debuggerem Zapřáhněme tedy do svých služeb hákovací dovednosti Immunity Debuggeru, abychom zachytili platná volání DeviceIoControl předtím, než dorazí na cílový ovladač. Sestrojíme tak provizorní fuzzer založený na mutacích. Napíšeme jednoduchý PyCommand, který bude zachycovat všechna volání DeviceIoControl, zmutuje buffer v nich obsažený a zaznamená do logu na disk všechny relevantní informace. Nakonec pak přenechá řízení cílové aplikaci. Hodnoty zapisujeme na disk proto, že úspěšný fuzzing při práci s ovladači téměř vždy znamená havárii systému, a my chceme mít zachovanou historii posledního fuzzovacího testu před havárií systému, abychom mohli později tyto testy reprodukovat.
4
Viz funkce DeviceIoControl v MSDN (http://msdn.microsoft.com/en-us/library/aa363216(VS.85).aspx).
Kapitola 10 – Fuzzing ovladačů Windows
162
Varování. Přesvědčte se, že fuzzing neprovozujete na ostrém stroji! Úspěšný fuzzing na nějaký ovladač vede k neblaze proslulé obrazovce "modrá smrt", která znamená, že stroj zhavaroval a bude se muset rebootovat. Myju si ruce, protože víte, na čem jste. Nejlepší je provozovat tyto operace na nějakém virtuálním stroji Windows.
Pusťme se rovnou do kódu! Otevřete nový soubor Pythonu, pojmenujte ho ioctl_fuzzer.py a vložte do něho následující kód:
ioctl_fuzzer.py import struct import random from immlib import * class ioctl_hook( LogBpHook ): def __init__( self ): self.imm = Debugger() self.logfile = "C:\ioctl_log.txt" LogBpHook.__init__( self ) def run( self, regs ): """ # Následujícími ofsety z registru ESP # chytáme argumenty předávané do DeviceIoControl: ESP+4 -> hDevice ESP+8 -> IoControlCode ESP+C -> InBuffer ESP+10 -> InBufferSize ESP+14 -> OutBuffer ESP+18 -> OutBufferSize ESP+1C -> pBytesReturned ESP+20 -> pOverlapped """ in_buf = "" # čteme kód IOCTL ioctl_code = self.imm.readLong( regs['ESP'] + 8 ) # přečteme InBufferSize
1