Zpět na seznam článků     Číst komentáře (72)     Verze pro tisk

Guessing attacks

Autor: Bystroushaak   
10.10.2012

Nedávno uveřejněné články na téma vytváření FTP dictionary crackeru v Pythonu mě donutily sepsat tuto reakci. Nenajdete zde konkrétní implementace pro každý protokol, který na světě existuje, protože proč taky, Python má knihovny na všechno možné a úprava jednotlivých scriptů je záležitostí změny pár řádek. Povíme si radši něco o tom, o co se vlastně tyto útoky snaží a ukážeme si několik triků jak zvýšit jejich účinnost. Pro plné pochopení budete potřebovat základní znalosti Pythonu a Unixu. Odměnou aktivním čtenářům budiž menší challenge :)


Článek je věnován nitrexxovi, třeba mu to pomůže překousnout strach z kritiky a dojde mu, že tím lidé neútočí na jeho osobu, ale naopak, jde o snahu vylepšení článku.

Situační nákres

Je rok 2015, nacházíte se před vaším počítačem, je noc a jste na počátku týdenních prázdnin/dovolené. Tamtamy na IRC k vám donesou informaci o neproniknutelném stroji, do kterého tvůrci uschovali neznámé tajemství.

Na stroji neběží žádná jiná služba kromě telnetu na portu 2222, který zobrazí sporadické:

  1. czCube password:

Krátké googlení vám prozradí, že k počítači nikdy nezískáte fyzický přístup, protože se nachází v amatérském satelitu na orbitě a vy se k němu připojujete pomocí IP2PacketRadio nodu.

Co takhle sociotechnika, nebo vzdálený útok na samotné uživatele ala password sniffer?

Bohužel, další googlení prozrazuje, že team stojící za czCube byl popraven za vlastnictví 100W žárovky zabijáky z evropské unie. Jejich těla a všechno technické vybavení bylo zabaveno a spáleno v ekoelektrárně na biomasu, takže tudy cesta nevede.

Zkusíte systému poslat delší string, ale krátká odpověď vám napoví, že buffer overflow se nekoná, protože autoři vstup hned ze začátku ořízli na 10 znaků:

  1. czCube password: a
  2. Wrong password *!
  3. czCube password: aaa
  4. Wrong password ***!
  5. czCube password: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  6. Wrong password **********!
  7. czCube password:

Na kariéru kosmonauta jste se vykašlali už kdysi dávno, v první třídě s první pětkou, takže spíš než o letu do vesmíru zauvažujete o možnosti postavit satelit-zabiják, který by czCube vyhledal a snesl na zem, nebo se napojil na její interní sběrnice přímo ve vesmíru. Krátký průzkum vám prozradí, že by se to krapánek prodražilo a s cenou $40000 za vynesení krychle o hraně 10cm na to radši rychle zapomenete a rozhodujete se, že se do systému dostanete vzdáleně, ať to stojí co to stojí.

Jak se tedy dostat do tohoto satelitu na obloze (soubor czcube_server.py v příloze)? Je jasné, že vám nezbude nic jiného, než heslo prostě zkoušet tak dlouho, dokud se netrefíte. Nažhavíte tedy konzoli a jdete na to..

Guessing attacks

Hádací útoky si dovolím rozdělit na tři typy:

  • bruteforce
  • dictionary
  • side-channel

Je mi jasné, že většina z vás o těchto typech již slyšela, některé možná trochu překvapí třetí typ útoku, kterému se budu v krátkosti věnovat na konec, protože nemá smysl ho tu rozpitvávat - zasloužil by si samostatnou knihu a jsem přesvědčený, že se jí dřív nebo později dočká.

Bruteforce

Bruteforce je ze všech třech útoků nejprimitivnější, trvá nejvíce času a většina moderních systémů jejichž správci ví co dělají vás s ním pošle do háje.

Útok spočívá jednoduše v tom, že budete postupně zkoušet všechny možné kombinace. Prvně zkusíte heslo 'a', pak 'b', pak 'c', až dorazíte k 'z'. Tehdy prostě přidáte jedno písmenko a jedete odznovu: 'aa', 'ab' .. 'az', 'ba', 'bb' .. 'zz'. Pak přidáte další písmeno a pokračujete dál, dokud neuhodnete heslo.

Zkoušet něco podobného ručně by u hesel s více jak pár znaky mohlo trvat opravdu dlouho, proto je vhodné činnost zautomatizovat, například scriptem v Pythonu 2.7, který je, buďme realisté, v roce 2015 stále ještě široce využíván :)

czcube_cracker.py

Jako každý problém, i tento je možné dekomponovat na jednodušší úlohy:

  1. komunikace s czCube
  2. ověřování hesla - potřebujeme funkci, která nám řekne jestli už tam jsme
  3. generování řetězce

Komunikace

Komunikaci s czCube obstará telnetlib, která se připojí na zadanou adresu a umožní nám s ní komunikovat.

  1. import telnetlib
  2.  
  3. HOST    = "localhost"
  4. PORT    = 2222
  5. TIMEOUT = 5
  6.  
  7. tn = telnetlib.Telnet(HOST, PORT, TIMEOUT)

V tomto případě se připojíme na localhost kvůli interaktivní ukázce, viz podkapitola Ukázka.

Ověřování

Jelikož víme co za prompt můžeme očekávat (výzvu k zadání hesla, nebo oznámení o špatném hesle), použijeme metodu expect() objektu Telnet, která očekává dva argumenty: prompts a nepovinný timeout. Funguje tak, že zasekne běh programu do té doby, dokud server neodpoví jedním z řetězců předaných v poli prompts, nebo dokud nevyprší timeout. Odesílání dat probíhá pomocí metody write().

Když nyní víme vše potřebné pro komunikaci, můžeme napsat funkci, která zkusí zadat systému heslo a vrátí nám odpověď.

  1. PROMPT  = "czCube password: "
  2. ERROR   = "Wrong password"
  3.  
  4. def tryPassword(tn, password):
  5.   tn.write(password + "\n")
  6.    
  7.   # cekej na prompt
  8.   e = tn.expect([PROMPT], TIMEOUT)
  9.    
  10.   # expect() vraci pole o trech prvcich:
  11.   # [status kod, match object, precteny string]
  12.   # -> testujeme zda li je vraceny string neco jineho, nez
  13.   # PROMPT/ERROR
  14.   return not (e[2].startswith(PROMPT) or e[2].startswith(ERROR))

Důležité je nezapomenout vybrat z bufferu před prvním použitím funkce tryPassword() prompt, který na nás čeká hned po otevření spojení:

  1. tn.expect([PROMPT], TIMEOUT)

Nyní můžeme používat naší funkci pro ověření hesla, která nám vrací hodnoty True/False podle toho, jestli jsme použili správné, nebo špatné heslo.

Generátor

Komunikační stránku celé věci máme vyřešenou, nyní se tedy konečně můžeme věnovat implementaci podprogramu, který při každém jeho zavolání vrátí o trochu více rozvinutý string využitelný k lámání hesla.

Někoho naivnějšího by možná napadlo si řetězce před-generovat do paměti a pak je zkoušet. To by nebyl moc dobrý nápad, protože všechny kombinace zabírají skutečně velmi velké množství paměti. My si na to napíšeme speciální objekt, který nám při každém zavolání přepočítá string.

Tento typ objektu se nazývá iterátor a umožňuje nám procházet sekvence pomocí jednotného rozhraní. Podpora pro iterátory je přímo zabudovaná do Pythonu, proto s ním poté můžeme pracovat pomocí jednoduché for smyčky.

Implementace iterátoru v Pythonu je dost jednoduchá - stačí ve vlastní třídě nadefinovat dvě metody: __iter__() a next(). __iter__() musí vracet objekt iterátoru, v tomto případě tedy self, neboli sám sebe. next() vrací vždy další prvek ze sekvence, nebo vyjímku StopIteration, pokud byl dosažen konec sekvence.

Zde je objekt, který při každém svém použití updatuje string přesně tak jak potřebujeme pro naše účely:

  1. class Brute():
  2.   def __init__(self, length, seed):
  3.     # seed si ulozime jako pole znaku
  4.     self.seed = list(seed) if isinstance(seed, str) else seed
  5.  
  6.     # pospojujeme objekty v pameti
  7.     self.next_brute = Brute(length - 1, self.seed) if length > 1 else None
  8.  
  9.     # ukazatel na aktualni pismeno, na zacatku -1, aby se objekt
  10.     # neukazoval ve vypisu, kdyz ho jeste nepouzivame
  11.     self.act = -1
  12.  
  13.   def update(self):
  14.     # zvys hodnotu act o jedna
  15.     # pokud act prekroci velikost pole self.seed, aktualizuj dalsi objekt
  16.     # v rade
  17.     if self.act < len(self.seed) - 1:
  18.       self.act += 1
  19.     else:
  20.       self.act = 0
  21.  
  22.     if self.next_brute:
  23.       self.next_brute.update()
  24.     else:
  25.       raise StopIteration()
  26.  
  27.   def __iter__(self):
  28.     return self
  29.  
  30.   def next(self):
  31.     self.update() # proved jeden krok
  32.     return self.__str__()
  33.  
  34.   # vrat textovou reprezentaci vsech objektu jdoucich v rade za sebou
  35.   def __str__(self):
  36.     # preskakuj zatim nepouzite objekty - jinak by misto B vypisoval
  37.     # AAAAAAB
  38.     if self.act < 0:
  39.       return ""
  40.  
  41.     # zjisti znak dalsiho objektu v rade
  42.     out = ""
  43.     if self.next_brute:
  44.       out += self.next_brute.__str__()
  45.  
  46.     return out + self.seed[self.act]

Někoho možná zarazí použití inline if. Neděste se, funguje to stejně jako klasická if podmínka, jen je to kompaktnější zápis co se týče přiřazování proměnných.

  1. if podmínka:
  2.   prom = něco
  3. else:
  4.   prom = něco_jiného

lze přepsat na

  1. prom = něco if podmínka else něco_jiného

Objekt při vytvoření očekává dva parametry - length, který udává kolik písmen chceme nechat vygenerovat a seed, který pro změnu určuje z čeho se budou kombinace skládat. Jako seed je možné použít jak string (v tom případě se použijí jednotlivé znaky), tak pole stringů, což se nám bude hodit později.

Použití objektu je díky využití iterátoru silně triviální:

  1. for i in Brute(2, "abc"):
  2.   print i

vypíše:

  1. a
  2. b
  3. c
  4. aa
  5. ab
  6. ac
  7. ba
  8. bb
  9. bc
  10. ca
  11. cb
  12. cc

Doufám že vám nemusím složitě vysvětlovat, že nyní stačí místo vypisování stringu použít již dříve nadefinovanou metodu tryPassword(), které předáme jako druhý argument vygenerovaný string a bruteforcer je na světě.

Ukázka

V příloze najdete dva soubory; czcube_bruteforcer.py a czcube_server.py.

Jejich funkce by měla být všem jasná z jejich názvu, ale pro jistotu - server je jednoduchý server, který očekává vaše heslo na portu 2222, tak jak jsem psal v první kapitole.

Server heslo neví, je v něm uložené jako osolený hash. Jakmile mu dodáte správné heslo, použije ho pro rozbalení zašifrovaných dat, které vám vypíše na výstup, takže se máte proč snažit ;)

Bruteforcer je pak implementace scriptu tak jak jsem o ní psal, s tím že se automaticky snaží připojit k serveru, který je zapotřebí nejdříve spustit, jinak vše skončí chybou.

Prosím ty čtenáře, kteří se do serveru dostanou, aby si obsah nechali pro sebe a nekazili challenge ostatním.

Problémy

Pokud jste zkoušeli předchozí příklady, nejspíše jste si všimli jednoho zřejmého problému; rychlosti, nebo spíš pomalosti.

Důvodů proč script běží tak pomalu je hned několik:

1) Implementace generátoru stringů pomocí iterátorů není zrovna nejefektivnější co se týče volání funkcí, ale pořád zvládá generovat řádově tisíce až deseti-tisíce požadavků za vteřinu, což je přípustné u síťových služeb, ale pro lámání hesel zašifrovaných souborů by to chtělo něco efektivnějšího, například vnořené for smyčky, či implementaci v kompilovaném jazyce.

2) Čekání na odezvu od serveru po zadání každého hesla (expect() v tryPassword()) je neefektivní.

3) Paralelizace. U tohoto serveru to sice fungovat nebude, protože obsluhuje vždy jen jednoho klienta, ale u FTP, HTTP, telnetu či SSH byste mohli spustit script vícekrát, třeba stokrát.

Návrhy na vylepšení

Abych to tedy shrnul na jedno místo:

Hromadné posílání dat

Zkuste data posílat hromadně, najednou třeba sto, tisíc nebo deset tisíc pokusů bez čtení odpovědí. Odpovědi pak přečtěte a parsujte naráz, pokud natrefíte na heslo, zjistěte ho dělením intervalů.

Ukládání stavu

Málokdy si můžete dovolit luxus nepřerušeného běhu, mít možnost script spustit vícekrát je také velké plus. Upravte iterátor tak, aby se dalo zadat od jaké hodnoty má začínat. K tomu se také bude hodit dodělat do scriptu průběžný výpis, abyste věděli, kde script předtím skončil.

Paralelizace

Jakmile budete mít hotové ukládání stavů, zkuste paralelizaci pomocí vícero vláken. Vyzkoušejte si jejich synchronizaci a ukončení všech ostatních vláken ve chvíli, kdy jedno z nich objeví heslo.

Hint: Data od serveru můžete rozdělit na jednotlivé řádky, z nichž můžete převodem na set() hromadně vyfiltrovat duplicitní záznamy a detekovat, jestli vám vrátil něco nečekaného.

Zkoušení hesel na přeskáčku, pozpátku

Nezačínejte od začátku, ale třeba od půlky, nebo od konce (vyžaduje reverzaci iterátoru). Málokdo používá hesla jako aaaa, takže se dá čekat, že první čtvrtina všech pokusů bude zbytečná.

Randomizace

Považujete se za šťastlivce? Zkuste minimálně v jednom vlákně náhodný generátor, existuje jistá nenulová šance, že se trefí dřív než vaše spořádané pokusy :)

Poučení

Bruteforce je nejméně efektivní ze všech možných způsobů hádání hesel, protože musíte otrocky vyzkoušet všechny možné kombinace, což u hesel s délkou nad deset znaků i v případě velmi výkonných počítačů s téměř nulovou latencí trvá desítky až stovky let, když uživatel použije pouze malá/velká písmena bez diakritiky a čísla.

Pokud uživatel použije dostatečně silné (složité a dlouhé) heslo, může být fyzikálně nemožné tento typ útoku provést i pokud vám v tom nebrání faktory jako omezený počet pokusů, rychlost linky či nutnost komunikace pouze v jednom threadu.

V době UTF se navíc zkoušení všech možných hesel podstatně protahuje a pokud by uživatelé serveru kam se snažíte vlámat použili vcelku krátké heslo v čínštině, asi byste byli bez šance, i kdybyste měli sebelepší cracker.

Pokud by vás zajímalo kolik pokusů je zapotřebí k vyzkoušení všech kombinací hesla, na internetu se dají najít různé kalkulačky, nebo si to můžete spočítat sami - jedná se o jednoduchou úlohu na variace.

Pro ilustraci; Za předpokladu, že dokážete každou vteřinu vyzkoušet milion hesel a-zA-Z0-9, tak:

1 znak zabere 1 vteřinu
2 znaky zaberou 1 vteřinu
3 znaky zaberou 1 vteřinu
4 znaky zaberou 15 vteřin
5 znaků zabere 15 minut, 31 vteřin
6 znaků zabere 16 hodin, 2 minuty a 11 vteřin
7 znaků zabere 41 dní, 10 hodin, 15 minut a 46 vteřin
8 znaků zabere 7 let, 11 dní, 18 hodin, 17 minut a 32 vteřin
9 znaků zabere 435 let, 364 dní, 6 minut, 38 vteřin
10 znaků zabere 27031 let, 288 dní, 51 minut, 6 vteřin

Shrnuto: Bruteforce je nejtriviálnější a nejméně efektivní - dá se přirovnat k slepému mlácení sekerou do dveří, což skvěle funguje v případě dřevěných dveří, ale až jednou narazíte na železné, tak spíš umřete stářím, než je prostou silou rozmlátíte.

Dictionary

Slovníkový útok je vylepšením předchozího útoku, kdy místo generování textů zkoušíte předem připravená slova z textovénho souboru, takzvaného wordlistu.

Tento typ útoku staví na předpokladu, že většina lidí na světě nemá poruchu osobnosti, takže jim dělá problém si zapamatovat zcela náhodný string typu "uF8x/q`ab§7dN" a tak místo toho používají různá více/méně lehce uhádnutelná hesla. Jeden můj kolega z práce takhle všude používá heslo supersuper (to není tajemství, ví to o něm celé oddělení).

Pokud se podíváte na tvrdá data získaná z uniklé databáze 37000 uživatelů, zjistíte, že se najdou řádově stovky lidí, kteří používají stejná hesla. Oblíbené je 123456, 123456789, martin, milacek, maminka. Však to znáte..

Na internetu se povalují různě velké slovníky, které se snaží obsáhnout seznamy těch nepoužívanějších hesel a frází o obsahu milionů položek. I když se tento počet může zdát vcelku velký, ve skutečnosti je stále o celé řády menší, než počet kombinací hesla při bruteforce (miliardy, triliardy, sextioliny a já nevím co ještě :)).

Úprava stávajícího scriptu tak, aby používal slovník je tak triviální, že vás s ní ani nebudu zatěžovat. Najdete ho v příloze pod jménem czcube_dict.py.

Kde vzít slovník

Shánění dobrého slovníku je podstatně složitější, než napsání scriptu, který bude hesla zkoušet. Na internetu se dají relativně jednoduše najít docela rozsáhlé a dobré slovníky. Pokud budete googlit, nehledejte "slovník", ale "wordlist", nezapomeňte také na to, že pro Čechy bude vhodnější český.

Pár odkazů:

Pokud budete hledat, najdete jich tisíce - jedná se o jednoduchý a tudíž oblíbený typ útoku, který proti běžným uživatelům dosahuje slušné účinnosti a zároveň nevyžaduje prakticky žádné znalosti. Díky tomu se mu věnuje každý druhý a je obzvlášť oblíbený u lam, které mají pocit že když mají na stránkách wordlist, wwwhack a brutus, jsou z nich hackeři.

Jestliže se někdy budete chtít pobavit, doporučuji zkusit wwwhack a brutus jako keywordy do google a omezit vyhledávání na stránky pouze česky. Dojde vám, že soom je na tom sice špatně, ale pořád je kam upadat :)

Vlastní slovníky

Ve chvíli kdy použijete cizí slovník, postupujete o jeden level výše než bruteforceři - namísto abyste tupě zkoušeli množinu všech možných variací znaků, zkoušíte slova, která uživatel mohl použít k vytváření hesel. Jedná se však jen o jednodušší variantu bruteforce, u které prakticky vzato stále není nutné používat mozek.

Jelikož se snažíte vlámat do satelitu na orbitě, lze jen stěží předpokládat, že autoři zvládnuvší postavit funkční satelit byli takoví idioti, že použili slovníkové heslo. Bude to tedy chtít něco víc - na míru postavený slovník.

K tomu budete potřebovat trochu znalostí z psychologie a pár zkušeností s uživatelskými hesly.

Co byste měli vědět o heslech

V textu Co je vlastně soom? jsem se zmínil, že na lidi na internetu je možné nahlížet podobně jako na model neuronu. Na jednu stranu dodáte vstupy a na druhé se čas od času dočkáte výstupu v podobě nějakého obsahu.

Tento poznatek můžete využít i při vytváření slovníku; co udělá většina lidí, když vytváří své heslo? Použije informace ze vstupů - ze svého okolí a ze své paměti.

Díky tomu jak funguje lidská mysl je pravděpodobné, že nebudou použita běžná slova bez nějakého speciálního významu pro daného jednotlivce. Stěží se setkáte s heslem "až", "protože", "včera", "hlavně" a tak podobně. Ne že by to bylo nemožné, ale heslo je pro člověka často něčím speciálním (nazývejme to JehoTajemstvím), když už si ho musí pamatovat.

Často můžete narazit na podstatné jméno ze života uživatele - jméno holky, rodičů, babičky, psa, kočky, oblíbený sportovec či herec, postava z knihy, datum narození, kus telefonního čísla.

Z vlastní zkušenosti musím říct, že v hesle se občas vyskytne název stránky, něco ve smyslu soomheslo, což mě přivádí k dalšímu poznatku - často je v hesle variace na slovo heslo, anglicky password, pass, pwd.

V heslech běžných uživatelů se skoro nikdy nevyskytují speciální znaky, jednak proto že je neumí zadat, druhak proto že je pro ně opruz a námaha je psát. Maximum na co narazíte jsou znaky ".,-_*!?", ale to je více/méně vyjímka.

Další důležitý poznatek je, že běžní lidé, kteří IT moc neholdují používají jednoduše zadatelná dlouhá hesla. Znal jsem několik lidí, kteří používají jako heslo asdfghjkl, qwertzuiop, 147896325, 741236985 a tak podobně. Tyto hesla jsou při crackování z venčí nehorázně složitá - ve slovníku běžné mluvy je nenajdete, protože nic neznamenají a jsou dlouhá, takže bruteforcem by vám to trvalo tisíc let. Jediná souvislost mezi použitými znaky je, že je lze zadat na klávesnici pomocí geometrického obrazce - čáry, kruhu, spirály.

Vyjímkou nejsou ani uživatelé používající heslo otevrise, nebo hovnokleslo, tedy memy, které se mezi lidmi šíří již od dětství.

Poslední věc kterou pro jistotu znovu zmiňuji; většina uživatelů nepoužívá hesla tvořená jedním slovem. Skoro vždy se jedná o složeninu a velmi často jsou na konci pro uživatele specifická čísla. Pokud má uživatel doma koucoura mourka, skoro určitě jeho heslo nebude Mourek, ale Mourek1545, kde 1545 je část telefonu, datum narození matky či nějaký jednoduše napsatelný řetězec. Vzpomínáte si ještě na nbusr123? :)

Shrnutí: Heslo bude s nějvětší pravděpodobností složeno ze slova, které je pro uživatele nečím speciální (JehoTajemstvím), občas v něm bývá slovo heslo či jeho ekvivalenty. Většinou tam nejsou speciální znaky, kromě "-" či "_" a na konci je s velkou pravděpodobností nějaký suffix - často číslo, které je nějak svázané s životem uživatele, nebo něco jednoduše napsatelného na klávesnici.

Generování slovníku

Teď když už máme teorii hesel za sebou je čas odvést zase jednou trochu práce. Víte z čeho se heslo skládá, ale možná vám není úplně jasné, kde to vzít. Kde asi? Přece na internetu!

Postup se dá rozdělit do těchto kroků:

  1. shromažďování
  2. normalizace
  3. extrakce
  4. filtrace
  5. variace

Shromažďování dat

Facebook

V dnešní době je moderní na sebe nabonzovat na Facebooku první poslední.

Seženete tam vcelku jednoduše informace o datu narození nejen cílového uživatele, ale i jeho rodiny, jeho kolegů a vůbec celého jeho sociálního okruhu. Zjistíte co má a co nemá rád, co četl, co jedl a často i kdy byl naposledy na záchodě.

Prvně se musíte stát přáteli, ale na to je jednoduchá rada - přidejte si jako přátele všechny ze socálního okruhu uživatele. Někteří vás schválí a jakmile cílový uživatel uvidí že máte nějaké společné známé, půjde to jednoduše. Sexy vzhled ve vašem (falešném) profilu bývá výhodou.

Potom už zbývá jen napsat spidera (o tom zase možná někdy jindy), co se rekurzivně prožere veškerým facebookovým obsahem cílového uživatele a vše uloží na disk. Nic vám samozřejmě nebrání udělat to ručně, ale je to opruz, na který osobně nemám náladu. Navíc psát spidery je zábava.

Ostatní zdroje

Pokud není cílový uživatel cool a in, možná facebook nepoužívá. Co potom?

Potom bude na čase zadat jeho jméno do googlu a googlit. Hledáte všechno, kde můžete najít informace o vašem cílovém uživateli, tedy profily na stránkách jako jsou lide.cz, příspěvky v diskuzích, stránky jeho psa, blogy, osobní stránky. Rodinné fotky a jejich popisky, články z novin, místa angažování.

Pokud narazíte na seznam oblíbených knih, najděte si ebooky a uložte je na disk, pokud na seznam oblíbených filmů, tak k nim stáhněte titulky, pokud na oblíbenou hudbu (last.fm a podobné služby), postahujte k ní texty (lyrics).

Googlete podle jména, podle nicku, podle emailu, podle ICQ, podle ip adresy, podle všeho co na daného uživatele seženete, pospojujte zdánlivě oddělené identity pomocí společných informací.

Všechno co dostanete stáhněte na disk, později se to bude hodit, ale je nutné to nepřehnat a nestahovat zase celé stránky, když tam uživatel poslal jen jeden příspěvek. Na konci data budete muset vyfiltrovat a je tedy cílem mít jich co nejméně a zároveň nejvíce zaměřených na cílového uživatele.

Lehce offtopic challenge

Vyzývám vás k napsání API v pythonu ke všem větší stránkám (facebook, google+, lide.cz, různé seznamky, blogovací služby atd..), abyste mohli činnost získávání informací zautomatizovat.

Vytvořte pluginy reagující na nějaké jasně dané API (například na stejný typ parametru předaného z příkazové řádky vrátí na stdin obsah danné stránky, kam se nedostanete jednoduše wgetem) a script, který je umožňuje jednoduše masivně používat.

Praktická ukázka shromažďování a normalizace

Pro jednoduchost nebudu zacházet moc daleko a nebudu se věnovat všem možným webům. Chcete se dostat do satelitu czCube, vzpomínáte? Jako ideální místo k získání wordlistu tak budou oficiální stránky projektu.

K získání surových dat použiji linuxový shell a program wget:

  1. wget -r -nd -erobots=off -l 5 -A.html -A.htm -A.txt -A.pdf http://www.czcube.org/cs/index.html

Tím stáhnu všechny html, htm, txt a pdf soubory co se na stránkách nachází a vede na ně odkaz. Snažím se tu o generování wordlistu, což je mnohem lehčí z .txt než .pdf, takže si pdf převedu hromadně na text pomocí utility pdftotext:

  1. find . -name "*.pdf" -exec pdftotext {} \; # pdftotext neumí víc souborů najednou, proto použiji find

Nyní by se mi hodilo převést na text i htm a html soubory. Použiji k tomu lynx, který má ale tu nepříjemnou vlastnost, že na konec uvádí seznam odkazů, což mohu eliminovat grepem s parametrem -v, který způsobí vypsání pouze řádků bez odkazů ("file://", "http://").

  1. find . -name "*.html" -exec sh -c 'lynx -dump {} | grep -v "file://" | grep -v "http://" > {}.txt; echo {}' \; # sh kvuli '>' do souboru..

Vyčistím bordel, sloučím vše do jednoho souboru a mrknu se, kolik mi toho zbylo:

  1. cat *.txt > words.dat
  2. rm *.pdf *.html *.txt
  3. du -h words.dat
  1. 352K    words.dat

Jak máte možnost vidět, zbylo mi 352KB čistého textu. Z něj nyní budu muset udělat seznam slov.

Extrakce wordlistu

Extrakce slov ze získaných dat není nic složitého, během psaní článku jsem si k tomu napsal jednoduchý script, který najdete v přílohách jako extractor.py. Bylo by možné použít i regulární výrazy a různé unixové utility, ale neměl jsem náladu trávit hodinu debugováním bash scriptu, když jednoduchou ukázku napíšu za půl hodiny v pythonu.

  1. cat words.dat | ./extractor.py --min 2 --max 10 --double > wordlist.txt
  2. wc -l wordlist.txt
  1. 40600 wordlist.txt

Mnou použitý extractor je silně triviální. Jen rozdělí vstup na UTF slova a vyfiltruje speciální znaky přičemž dovoluje mít ve slovech tečky a čárky.

Čím lepší budete mít extractor, tím efektivnější bude výsledné crackování, protože tím větší šance, že správně získáte JehoTajemství a suffix.

Ukázka neschopnosti mého extractoru je např. telefonní číslo oddělené mezerami, které můj kód rozdělí do několika pod-čísel a nevnímá ho jako celek. Zapracovat se dá na mnoha dalších věcech, jako je rozpoznávání emailových adres, ICQ, telefonního čísla, PSČ, datumů + konverze do několika dalších alternativních formátů.

Filtrace

Poté co jsem extractorem získal 40600 slov je na řadě filtrace, kdy se budu snažit většiny z nich zbavit, jinak by totiž další krok - variace mohl získat nepříjemnou složitost.

Pokud chcete, data si můžete schovat a později je vyzkoušet samotná, není zde však příliš velká šance na úspěch, protože i když je v souboru s velkou šancí JehoTajemství a suffix, potřebujete je ještě složit dohromady.

Redukci velkosti je možné provést těmito způsoby;

  • odstraněním duplikátů - v pythonu se dá elegantně vyřešit převedením na set()
  • derivací šumu (cool název pro odstranění slov bez speciálního významu)

Odstranění duplikátů

Jak už jsem psal, v pythonu stačí soubor načíst a převést na set, což je setřízená množina unikátních prvků. Nic vám samozřejmě nebrání použít k tomu programy jako sort | uniq, nebo si napsat vlastní sorter/deduplicator.

Je zbytečné na něco tak nicotného psát script, proto použiji python naschvál poněkud nestandartně (nechci mít soubor seřazený, pak by mi neseděla další ukázka :P):

  1. python -c "for i in set(open('wordlist.txt').read().splitlines()): print i" > wordlist.uniq.txt
  2. wc -l wordlist.uniq.txt
  1. 7576 wordlist.uniq.txt

Tímto získám soubor s pouhými 7576 slovy, což představuje redukci o ~30000 slov. To je docela dobré zvýšení odstupu signálu od šumu, nemyslíte? :)

Odstranění šumu

Wordlist stále obsahuje relativně velké množství šumu, u kterého je nepravděpodobné, že by byl použit jako heslo. Podívejme se na prvních 10 prvků:

  1. head wordlist.uniq.txt
  1. koncovou
  2. *ESTEC*
  3. foul
  4. kvalitních
  5. prefix
  6. *mirage*
  7. *xChaos.*
  8. *xChaos,*
  9. Until
  10. *JN,*

Hvězdami jsem zvýraznil slova, u kterých existuje zvýšená pravděpodobnost použití jako hesla, protože mohou představovat pro uživatele něco speciálního, co by mohl použít na JehoTajemství. Zbytek je šum, kterého se chceme pokud možno zbavit.

Samozřejmě je možné ručně projít všech 7576 slov a vymazat ty, která považujete za běžná a tudíž nezajímavá. U větších projektů by to však mohlo být nereálné a i u menších to bude nehorázně nudná a otrocká práce. Jelikož vás provází síla unixu, použijeme ho na další podstatnou redukci.

Příprava databáze šumu

Filtraci provedeme takto: nejdříve seženeme velké množství textu, řádově megabajty. Text potom zpracujeme a získáme z něj frekvenci nejčastěji používaných slov. Často používaná slova pak využijeme k vytvoření databáze šumu, díky které můžeme zmenšit náš slovník.

Každému je doufám jasné, že k získání šumu je nutné použít nějakou obecnou databázi textů, nikoliv třeba odborné weby, nebo zdrojové kódy. Dají se použít knihy, ale jejich převod na text je často opruz s nepříliš dobrými výsledky.

Na většinu webů budete skoro vždy potřebovat HTML parser, který oseká okolní prvky tak, aby vám zbyl jen text. Do začátku mohu doporučit http://fora.babinet.cz/viewprintable.php?id=47542 a http://www.cepsr.com/tisk.php?ID=01, kde se nachází spousta slovní vaty a budete mít jen minimální práci s parsováním. Obecně potřebujete dlouhé texty bez speciálních výrazů, na což jsou ideální texty o politice, nebo kdákání na blogíscích (zde ovšem pozor na wYpaTl4t0r).

V tomto článku použiji jako zdroj pouze web http://zpovedka.cz, protože se jedná o prakticky ideální příklad se spoustou relativně dlouhých textů, blogy jsou v url identifikovány číslem, což mi usnadní stahování, a jako bonus tam není reklama.

Tentokrát se již nedá použít rekurzivní wget, protože by vás mohl zavést na místa, kam nechcete. Jelikož jsou všechny články seřazeny sekvenčne za sebou od url http://zpovedka.cz/000001.php do url http://zpovedka.cz/001819.php (max. v době psaní článku), použiji k jejich stažení curl, který má na rozdíl od wgetu přímo podporu stahování sekvenčních url.

  1. mkdir zpovedka.cz
  2. cd zpovedka.cz
  3.  
  4. curl http://zpovedka.cz/00[0001-1819].php -O
  5. du -chs
  1. 109M    .
  2. 109M    total

Jak vidíte, ze zpověďky se stáhlo 109MB html stránek. Nyní je na čase vše převést na čistý text. U lynxu to tentokrát chce použít parametr -force_html, protože soubory mají příponu .php a to se lynxu nezdá.

  1. find . -name "*.php" -exec sh -c 'lynx -force_html -dump {} > {}.txt; echo {}' \;
  2. rm *.php
  3. du -chs
  1. 56M .
  2. 56M total

Převod na čistý text srazil velikost skoro o polovinu. Když se podíváte na jednotlivé textové soubory, zjistíte, že to pořád není úplně ono. Na začátku se nachází hlavička zabírající dva řádky, na konci je pak zobrazen formulář pro komentáře a odkazy, které tam nacpal lynx. Neuškodí tedy vše ořezat a poté hromadně uložit do souboru noise.dat:

  1. find . -name "*.txt" -exec sh -c "tail -n +3 {} | head -n \`grep -n 'Pokud máš k věci co říct a dokážeš pomoci, neváhej a piš.' {} | cut -f1 -d:\` >> noise.dat; echo {}" \;
  2.  
  3. # uklizeni bordelu
  4. mv noise.dat ../
  5. cd ..
  6. rm -fr zpovedka.cz
  7.  
  8. du -h noise.dat
  1. 47M noise.dat

Jak máte možnost vidět, ořezáním nepotřebných částí se podařilo stáhnout velikost souboru o 9MB na 47MB.

Když to budete zkoušet, zjistíte že onen zběsilý find (za ten mám peklo jisté :P) v průběhu práce vypisuje chyby - nelekejte se jich, jedná se o smazané stránky, které byly přesto stáhnuty na disk a nyní se přeskakují.

Pokud vám zvědavost nedá, zde je krátký popis jak příkaz funguje: pomocí tailu oříznu 3 řádky ze začátku souboru, kde se nachází hlavička. Potom headem vyberu veškerý text až po řádek, na kterém se vyskytuje řetězec "Pokud máš k věci co říct a dokážeš pomoci, neváhej a piš.", který se v textu vždy nachází před formulářem pro vložení nových příspěvků. Číslo řádku zjistím grepem, který díky parametru -n zobrazuje čísla řádků + výskyt řetězce. Jelikož potřebuji pouze číslo řádku, odříznu ho z výstupu grepu pomocí příkazu cut. Výstup z headu, který dostal jako parametr -n číslo řádku z grepu, přesměruji do souboru noise.dat v režimu rozšiřování (>>), díky kterému se řádky přidají na konec. To celé se díky příkazu find s parametrem -exec vykoná pro všechny textové soubory ve složce. Trochu krkolomné, ale tohle není kurz krasoprogramování, nýbrž získávání dat.

Ze souboru nyní pomocí extractor.py vyextrahuji surový seznam slov, které budu později dále analyzovat:

  1. cat noise.dat | ./extractor.py --min 2 --max 10 --double --lower > noise_raw.txt
  2. du -h noise_raw.txt
  1. 44M noise_raw.txt

Chvilku to trvá, tak si zatím třeba uvařte čaj.

Vytváříme databázi šumu

Nyní si spočítáme, kolikrát se které výrazy v textu objevují. Samozřejmě abychom na to mohli napsat python script (ke své hanbě se přiznám, že jsem ho i napsal), základní unixové nástroje ale práci odvedou stejně tak dobře, ba dokonce rychleji:

  1. cat noise_raw.txt | sort | uniq -c > noise_analysed.txt
  2. du -h noise_analysed.txt
  1. 6,9M    noise_analysed.txt

Pokud nakoukneme do souboru noise_analysed.txt, zjistíme, že se nám tam uložily počty výskytu jednotlivých slov:

  1. tail noise_analysed.txt # soom mi k utf nedovoli geshi :X
      4 то
      1 тобой
      1 фильма
      2 час
      2 час,
      4 что
      1 шестой
      2 это
      2 этот
      1 最後の楽

Analýzu výskytu slov jsme prováděli proto, abychom náhodou neodstranili potenciální JehoTajemství, která se na zpovedka.cz vyskytla náhodou. To uděláme tak, že odfiltrujeme pouze slova, která mají větší úroveň používání, což nám značí právě číslo před nimi.

Otázkou je, jakou hodnotu zvolit. Pokud vybereme moc malou, zbude nám až moc slov. Pokud moc velkou, může se stát, že odfiltrujeme JehoTajemství. Jako první vás pravděpodobně napadne průměr. Nezavrhuji to, ale osobně jsem se rozhodl pro průměr mezi celkovým průměrem a mediánem, který není tolik náchylný na lokální výstřelky. Nazývejme to Hranicí.

Teď přijde oznámení, které spoustu z těch, ehm, méně zkušených asi moc nepotěší. Rozhodl jsem se vynechat část, kde spočítám Hranici a použiji script, který do dalšího textového souboru blacklist_raw.txt uloží všechna slova, která mají výskyt větší než Hranice. Také jsem vynechal kód, který odmaže čísla před každým příkazem a samotná slova uloží do souboru blacklist.txt. Nechci dát nástroje do rukou lidem, kteří netuší jak je použít - destruktivní chování bývá nejčastější u lidí s minimem schopností.

Odečtení šumu

Pokud se vám povedlo vyčistit blacklist, máte nyní k dispozici 409927 nejpoužívanějších slov, což je docela slušný wordlist. Nyní stačí jen vyfiltrovat slova ze souboru wordlist.uniq.txt, která se nacházejí zároveň v souboru blacklist.txt a uložit je do wordlist.txt. To se dá udělat jednoduše, nebo složitě. Osobně rád dělám věci jednoduše, takže tady je příkaz v bashi:

  1. cat wordlist.uniq.txt blacklist.txt blacklist.txt | sort -f | uniq -iu > wordlist_cleaned.txt
  2. wc -l wordlist_cleaned.txt
  1. 1719 wordlist_cleaned.txt

Jak vidíte, celá filtrace spočívá v tom, že uniqu pošlu soubor blacklist.txt dvakrát, díky čemuž jsou výrazy z něj zaručeně odfiltrovány. Parametry -f a -i slouží k porovnávání bez ohledu na velikost písmen.

Pokud se do souboru podíváte, zjistíte, že se v něm z nějakého důvodu některá slova vyskytují jen s čárkou na konci. To není zrovna ideální, proto si je naklonujeme i bez čárky pomocí extractoru a jeho parametru -d:

  1. cat wordlist_cleaned.txt | ./extractor.py -d | sort | uniq  > wordlist.txt
  2. wc -l wordlist.txt
  1. 1934 wordlist.txt

Díky filtraci pomocí blacklistu se nám podstatně snížil prostor, ve kterém se může ukrývat JehoTajemství - z původních 7576 slov na pouhých 1934, jejichž otestování trvalo na mé mašině 1m 17.741s.

Pokud chcete zjistit tajemství, které se ukrývá v czCube (tedy czcube_server.py), budete muset ještě trochu zapracovat - napovím jen tolik, že ve wordlist.txt jsou uloženy obě komponenty hesla, tedy jak JehoTajemství, tak Suffix. K tomu abyste přišli na to co je co se vám bude hodit, že náš bruteforcer přijímá i pole stringů, jestli si ještě vzpomínáte :) Bude to chtít ovšem buďto ještě zredukovat velikost wordlistu, nebo zrychlit crackování, protože vyzkoušet všech 3742290 kombinací by trvalo na mém stroji necelých 42 hodin :)

Problémy a návrhy na vylepšení

Pokud se podíváte do souboru wordlist.txt, zjistíte, že se v něm stále nachází spousta běžných anglických slov, které nebyly odchyceny, protože zpovedka.cz je český web. Vytvořte anglický blacklist a slova odečtěte.

Při detailnějším pohledu si jistě všimnete, že se zde nachází spousta jmen a datumů. Zkuste napsat jejich detektor a permutátor do různých formátů. K dostání se do czCube to sice není nutné, ale v reálném životě se to bude hodit.

Některá slova končí čárkou, jiná tečkou, jiná písmenem. Zařiďte, aby všechna končila písmenem, tečkou i čárkou.

Nemáte jistotu velkých a malých písmen. Heslo může obsahovat string z wordlistu, ale může mít na začátku malé písmeno, nebo být celý malými, i když je ve wordlistu s velkým písmenem.

Poučení

Díky zaměření útoku je možné podstatně zmenšit počet hesel, která je nutno vyzkoušet. To se hodí, pokud útočíte na cíle s pomalou linkou, nebo paranoidním adminem, který omezil počet spojení.

Na rozdíl od bruteforce nemusíte zkoušet nepředstavitelná čísla kombinací, která v dohledné době prostě nevyzkoušíte, ale vcelku reálné tisíce a miliony, které netrvají tak dlouho. Na druhou stranu, na rozdíl od bruteforce, kde máte více/méně záruku úspěchu v konečném čase (i když se může nacházet miliardy let v budoucnosti), u dictionary se může stát, že se prostě netrefíte a nic s tím nenaděláte, pokud umí admin vybrat správně heslo.

Side-channel

Side-channel útoky se od výše uvedených líší docela podstatně. Necílí totiž na samotné heslo, ale na veškeré vedlejší projevy systému. Přesto jsem si je troufl zařadit, protože vás ve většině případů nevpustí do systému, jako kdybyste použili exploit, ale pouze podstatně zredukují počet hesel, které musíte otestovat.

Funguje to tak, že se zaměřujete na cokoliv, co dokážete o systému zjistit, nejčastěji na fyzikální podstatu systému, protože lidé kteří zařízení a algoritmy navrhují pracují s vrstvami abstrakce, které zakrývají postranní úniky.

Může to být doba běhu, jeho akustické, infračervené (teplo), ultrafialové, elektromagnetické, neutronové a třeba i gravitační vyzařování, nebo jeho spotřeba. Aby to nebyla čistě pasivní nuda, tak systém při některých typech útoků vystavujete extrémním podmínkám - připojujete ho na vyšší napětí, než na jaké byl stavěn, ohříváte ho, nebo mrazíte, podrobujete gama či rentgenovému záření, silnému prosvětlení, ozařování v mikrovlnce, abyste způsobili nefunkčnost některých součástí, jako je například generátor náhodných čísel.

Side-channel útoky v naprosté většině případů nejsou jednoduché a vyžadují seriózní znalosti a/nebo vybavení. Přesto vám pár z nich přiblížím, alespoň ve zkratce.

TEMPEST aka Electromagnetic Eavesdropping aka Van Eck phreaking aka EMSEC

I když se nejedná zrovna o guessing, ale spíš password sniffing, neexistuje zářnější příklad postranního kanálu než Van Eck phreaking, nesprávně známější spíš pod kódovým označením TEMPEST. Jedná se o typ útoku, který cílí na nechtěné elektromagnetické vyzařování.

Spousta lidí si myslí, že postavit funkční vysílač je náročná práce, která vyžaduje hodně znalostí z elektrotechniky. Pravda je taková, že ve skutečnosti je mnohem těžší postavit kus elektroniky co nic nevysílá (o šířce spektra, čistotě a výkonu se teď nebavme). To vám jistě potvrdí každý, kdo někdy musel řešit odrušení elektromotorů, například ve vysavači.

Princip vysílače

Jednoduché vysílače nejsou žádná magie. Jak si možná pamatujete ze základní školy, když do kusu drátu pustíte elektřinu, například z 9V baterie, stane se z něj elektromagnet, takže k sobě začne přitahovat magnetické předměty. Čím delší máte drát stočený do cívky, tím silnější eletromagnet bude.

Pokud máte citlivý měřák, dokážete změřit toto statické magnetické pole na vzdálenost desítek centimetrů, možná metrů. Pokud však magnet rozkmitáte (začnete ho velice rychle vypínat a zapínat), elektromagnetické pole kolem něj kmitá s ním a na cívkách v okolí dochází k indukci. Při určitých rychlostech vypínání a zapínání se pole šíří do dálky lépe než při jiných, takže dochází k vysílání tohoto pole na velmi dlouhé vzdálenosti. To se pak velice slabě, ale přeci jen indukuje na kovových předmětech (antény) i desítky kilometrů daleko. Pokud k nim připojíte zesilovač a frekvenční filtr, můžete z toho co se vám naindukuje rekonstruovat zapínání a vypínání vysílače - jeho rychlost, plynulost a do jisté míry i intenzitu.

Samozřejmě, tohle všechno platí jen pro nejprimitivnější vysílače ala jiskřiště, ve skutečném světě je to docela věda, protože se snažíte vysílat jen na malém frekvenčním pásmu, s minimálním zasahováním do ostatních, s co největší efektivitou převodu energie a tak dále.

Počítač jako vysílač

Abych se vrátil zpět k TEMPESTU - váš počítač je v podstatě zařízení, které velice rychle přepíná elektřinu v mnoha různých vodičích, ať už se jedná o kabely připojující periferie, nebo o vnitřní sběrnice zajišťující přenos informací mezi vnitřními komponentami. Díky tomu se jedná o vysílač na všech možných vlnových délkách, které jsou tlumeny kovovou krabicí počítače.

Externí komponenty, jako jsou monitor, klávesnice a myš však ničím tlumené nejsou a díky tomu vysílají s velkou radostí do okolí vše, co se přes ně přenáší. Signály nejsou čisté, jelikož původně samozřejmě nikdo neplánoval, že mají vysílat. Různě se do sebe mísí, přičemž některé vysílají na tak nízkých frekvencích, že o kus dál už jsou utlumeny k nerozeznání.

Přes to všechno je možné průběhy napětí, které na váš monitor doručují signál pro zobrazení odposlouchávat i na desítky metrů. Nezáleží přitom na typu použitého monitoru, funguje to jak pro LCD, tak pro CRT na jednotky, max. desítky metrů.

Klávesnice na tom nejsou o nic lépe - ba naopak, díky dlouhým nestíněným drátům, po kterých si sviští povely jen ve chvíli kdy mačkáte tlačítka je to ještě jednodušší. Útočník tak vůbec nemusí kompromitovat váš systém, prostě si heslo přečte tak jak ho píšete.

Obrana

Existují dvě možné techniky obrany, obě založené na principu faradayovi klece:

1. Všechny součástky a kabely musí být náležitě odstíněny. Touto cestou se vydaly vojenské organizace po celém světe. Je to dražší a komplikovanější, ale jistější, jako bonus to nejspíš přidává jistou odolnost vůči elektrické složce EMP.

2. Místo kde počítač používáte musí být odstíněné. Ideální jsou klece z uzemněného pletiva, alobal na zdech také není marný. Inspiraci je možné čerpat třeba z filmu Nepřítel státu.

Možné využití

TEMPEST pro Vás nemusí být nutně škodlivý - ve chvíli kdy víte, že monitor vlastně funguje jako vysílač, můžete ho použít jako jednoduchou zvukovou kartu, kterou je možné naladit na FM rádio. Pokud vás to zajímá, hledejte výraz "tempest for eliza".

Další možné využití je transport citlivých informací, když nedostanete možnost zapojení externích médií a přístupu na internet: Na vysokých školách jsou uzamčené učebny, ve kterých se nacházejí počítače se zdrojovými kódy Windows. Studenti, kteří se upíší ke vstupu nesmí, ba ani nemohou připojit žádné zařízení, protože počítač na ně nereaguje, porty jsou zaslepeny, bedna zamknutá a internetová konektivita není dostupná. Na druhou stranu, nic jim nebrání studovat zdrojové kódy a spouštět programy - od toho tam zdrojové kódy Windows jsou, aby bylo možné prostudovat systém a provádět různé testy.

Teoreticky je tedy možné za pomocí přítomného IDE napsat program, který pomocí TEMPESTU začne vysílat řádku po řádce zdrojové kódy do světa. Za předpokladu, že někdo naslouchá (i kdyby šlo jen o pasivní záznamové zařízení na záchodové kabince o patro níže), může tak dojít k úniku dat.

Odběrové útoky

Ač se to nezdá, důležité informace o systému je možné zjistit i studiem odběru elektřiny.

Když vynechám, že policie takto zaměřuje nelegální pěstírny marihuany, tak každý počítačový čip spotřebovává v průběhu času nekonstantní množství elektřiny a odběr tedy neustále lehce kolísá podle toho, jaké jsou zrovna vodivě propojeny obvody na čipu a co provádí za operaci. Nejedná se samozřejmě o nijak velké hodnoty, kolísání se pohybuje v mezích, na které potřebujete laboratorní přístroje.

U normálních počítačů by tento typ útoku nejspíš zklamal - jednak je zdroj s vyhlazovacími kondenzátory umístěný uvnitř počítače, druhak jsou na desce desítky a stovky čipů, které systém ovlivňují. Potřebujete něco triviálnějšího, tak primitivního, že to nemá ani vlastní napájecí zdroj.

Takovým systémem jsou smartcard a RFID čipy. První jmenované naleznete v podobě telefonních, nebo bankomatních čipů, druhé všude tam, kde se používají čipy bezkontaktní - vstupní karty, pasy, opencard, čipy v autobusech, psí známky.

Tyto čipy nejsou tvořeny stovkami miliónů až miliardami tranzistorů, naopak, jedná se o vcelku primitivní obvody, které se skoro vždy vejdou pod 10 000 tranzistorů. Jedním důvodem je cena, druhým hlavně spotřeba elektřiny, což je významný faktor hlavně u bezkontaktních (RFID) čipů, které se musí napájet z nosné vlny vysílače.

U takto velkých obvodů se již dají měřit odběrové charakteristiky, protože nad máte stoprocentní kontrolu nad jejich vstupy a výstupy, takže víte přesně co daný obvod dělá. Můžete tak doslova měřit odběr v každém taktu, což vám dá představu o typech právě používaných obvodů, díky čemuž můžete odhalit algortmus, který karta používá.

Detailům se zde nebudu věnovat, protože bez znalostí architektury čipů na úrovni vysoké školy se nejspíš moc chytat nebudete, navíc nejsem odborník a ani jsem v tomhle ohledu nedělal žádné pokusy, protože to chce docela drahé elektronické vybavení.

Shrnutí

Odběrové útoky mohou pomoci podstatně zredukovat počet možných hesel, protože vám prozradí informace o algoritmu. Vyžadují silné znalosti o architektuře počítačových čipů a solidní technické vybavení. Pokud nepochopíte do posledního detailu jednoduché počítače, jako je třeba tohle, nemá smysl se v tom dál pitvat, protože vám to stejně nic neřekne.

Timing attack

Ač se to nezdá, dost informací se dá o systému zjistit i podle rychlosti reakcí.

Osobně jsem tomuto typu útoku nevěnoval pozornost, dokud mi nevyrazila dech přednáška Time is on my side z 28c3, kde Sebastian Schinzel předvedl originální útok na systém po síti, který vám umožňuje zredukovat počet pokusů k uhodnutí hesla na pouhé stovky tisíc, pokud programátor udělá tu chybu a použije standardní funkce pro porovnávání řetězců na plaintextové heslo.

strcmp()

Jak doufám všichni víte, pokud chcete v jazyce C porovnat dva řetězce, použijete funkci strcmp(). Jelikož je většina systémů je postavená na C, platí zde uvedené informace prakticky pro libovolný systém, nebo interpretovaný programovací jazyk.

strcmp() funguje velice jednoduše - přijímá dva textové řetězce, které prochází a znak po znaku porovnává. Pokud se znaky v polích na stejném indexu nerovnají, vrátí číslo větší, nebo menší nule, dle toho jak se liší. Jestliže jsou oba řetězce stejné, vrátí nulu.

Podívejme se nyní na strcmp(), tak jak jí implementuje linuxová glibc:

  1. #include <string.h>
  2. #include <memcopy.h>
  3.  
  4. #undef strcmp
  5.  
  6. /* Compare S1 and S2, returning less than, equal to or
  7.    greater than zero if S1 is lexicographically less than,
  8.    equal to or greater than S2.  */
  9. int
  10. strcmp (p1, p2)
  11.      const char *p1;
  12.      const char *p2;
  13. {
  14.   register const unsigned char *s1 = (const unsigned char *) p1;
  15.   register const unsigned char *s2 = (const unsigned char *) p2;
  16.   unsigned reg_char c1, c2;
  17.  
  18.   do
  19.     {
  20.       c1 = (unsigned char) *s1++;
  21.       c2 = (unsigned char) *s2++;
  22.       if (c1 == '\0')
  23.         return c1 - c2;
  24.     }
  25.   while (c1 == c2);
  26.  
  27.   return c1 - c2;
  28. }
  29. libc_hidden_builtin_def (strcmp)

Jelikož se jedná sice o reálnou, ale přeci jen poněkud zbytečně komplikovanou ukázku, přidám ještě zjednodušenou implementaci, která funguje téměř ekvivalentně:

  1. int strcmp(char *s1, char *s2){
  2.     for(; *s1 == *s2; s1++, s2++)
  3.         if (*s1 == '\0')
  4.             break;
  5.    
  6.     return *s1 - *s2;
  7. }

Funkce prochází přes ukazatele s1 a s2, které dostala jako argumenty, a porovnává, jestli jsou hodnoty na které ukazují stejné. Pokud ano, pokračuje se dál. Jakmile se liší, nebo nastane konec řetězce, funkce se ukončí a vrátí výsledek.

Abych se nemusel uchylovat k C, přepíši danou ukázku 1:1 do pythonu. Působí to trochu křečovitě, ale funguje stejně:

  1. def strcmp(s1, s2):
  2.     cnt = 0
  3.     min_len = min(len(s1), len(s2))
  4.    
  5.     while s1[cnt] == s2[cnt]:
  6.         if not cnt < min_len - 1:
  7.             return len(s1) - len(s2)
  8.    
  9.         cnt += 1
  10.    
  11.     return ord(s1[cnt]) - ord(s2[cnt])

Důkladně si prosím uvedené zdrojové kódy prostudujte, dokud nebudete mít jistotu, že jim rozumíte.

Výkonnostní optimalizace

Za předpokladu, že jste skutečně pochopili uvedené zdrojové kódy, tak by vám mělo být jasné, že funkce trvá pro různé řetězce stejné délky různou dobu. Je to tak z důvodu výkonnostní optimalizace, kdy je zbytečné nadále porovnávat dva řetězce, když už jsme narazili na první odlišnost, tudíž víme že nemůžou být stejné.

Nepraktická ukázka

Aby se neřeklo, že je kapitola o side-channel útocích samá nuda a žádná akce, připravil jsem nepraktickou ukázku, která útočí na strcmp() v pythonu, takže si můžete vyzkoušet, že útok na strcmp() je skutečně možný. Ve skutečném světě by vám ukázka byla k ničemu, protože útočit na lokální počítač nemá většinou smysl a můžete mi věřit, že raw přístup k IP vrstvě, routování packetů dle vytížení a multitaskingovost na straně serveru vše PODSTATNĚ komplikuje.

V příloze najdete složku timing_attack/, ve které se nachází několik souborů, jedním z nich je cmptest.py. Obsahuje výše uvednou implementaci funkce strcmp() a několik měřících funkcí, které nejsou tak podstatné. Důležité je, že když mu předáte jako první parametr číslo, provede tolikrát funkci strcmp("h", "heslo") a strcmp("x", "heslo"), výsledek vám potom vypíše na terminál.

  1. ./cmptest.py 10000000
  1. 10000000:
  2.     strcmp('x', 'heslo') > strcmp('h', 'heslo')  (12.5968060493 > 11.8393321037)

Zde jasně vidíme, že při deseti milionech pokusů trvá provedení testu s "x" 12,59 sekundy, zatímco s "h" jen 11.83. To je docela pěkný a jasně měřitelný rozdíl.

Jelikož jsem měl stále ještě nějaké pochyby, rozhodl jsem se napsat další script, který zkusí poměřit celou abecedu a vypíše, jak dlouho trvalo jednotlivé měření, navíc vybere nejkratší a nejdelší písmeno. Script se jmenuje cmprate.py a zkouší porovnávat jak naší starou známou funkce strcmp(), tak funkci dcmp(), která je jen jednoduchý obal v podobě "return s1 == s2" nad pythonovským == operátorem. Zde je vidět výsledek při milionu porovnání:

  1. ./cmprate.py  1000000
  1. Function; dcmp
  2. Password; heslo
  3.  
  4.     a : 0.293820858002      b : 0.310147047043  
  5.     c : 0.300177097321      d : 0.302853107452  
  6.     e : 0.304003000259      f : 0.298863172531  
  7.     g : 0.301759958267      h : 0.299163818359  
  8.     i : 0.30108499527       j : 0.302097082138  
  9.     k : 0.303493976593      l : 0.298543930054  
  10.     m : 0.294486999512      n : 0.305546998978  
  11.     o : 0.301103830338      p : 0.294248819351  
  12.     q : 0.293974876404      r : 0.294204950333  
  13.     s : 0.296105861664      t : 0.294260978699  
  14.     u : 0.301180839539      v : 0.295403003693  
  15.     w : 0.296595096588      x : 0.295566082001  
  16.     y : 0.296170949936      z : 0.299213171005  
  17.  
  18. Min; a : 0.293820858002
  19. Max; b : 0.310147047043
  20.  
  21. Press return key to continue..
  22.  
  23. Function; strcmp
  24. Password; heslo
  25.  
  26.     a : 1.29385995865       b : 1.29009699821  
  27.     c : 1.28069210052       d : 1.28151607513  
  28.     e : 1.2850959301        f : 1.2793431282    
  29.     g : 1.27082300186       h : 1.19221711159  
  30.     i : 1.32058596611       j : 1.30693387985  
  31.     k : 1.293653965         l : 1.30821704865  
  32.     m : 1.28762984276       n : 1.29457807541  
  33.     o : 1.290391922         p : 1.3181951046    
  34.     q : 1.31833600998       r : 1.29655194283  
  35.     s : 1.31078600883       t : 1.29456710815  
  36.     u : 1.31070494652       v : 1.29148507118  
  37.     w : 1.3075799942        x : 1.31262302399  
  38.     y : 1.30018806458       z : 1.30558419228  
  39.  
  40. Min; h : 1.19221711159
  41. Max; i : 1.32058596611
  42.  
  43. Press return key to continue..

Jak je vidět, funkce strcmp() je skutečně předvidatelná, a skončí pro správné hodnoty rychleji, než pro nesprávné. dcmp() však vypadá kompletně náhodně, tomu se ještě budu trochu věnovat dále.

cmprate.py mi každopádně potvrdil, že se nesnažím implementovat nedosažitelnou ideu, takže jsem rovnou napsal script cmpcrack.py, který postupně testuje abecedu a zkouší heslo.

  1. ./cmpcrack.py 1000000
  1. h [1.1929199695587158, 'h']
  2. he [1.5170519351959229, 'e']
  3. hes [1.8253459930419922, 's']
  4. hesl [2.1278860569000244, 'l']
  5. Got it: heslo

Jak máte možnost vidět, script potřebuje cca milion pokusů na ohodnocení každého písmena. To je nejmenší počet testů, kterého se mi podařilo dosáhnout na mém počítači s linuxem mint 13 a několika běžícími aplikacemi, jako je prohlížeč, IRC klient, SSH klient, jabber a přehrávač hudby. Jsem přesvědčený, že na realtime systému (viz dále) by měl stačit i podstatně menší počet pokusů.

Realtime OS

Ukázka sice fungovala s poněkud absurdní (v pythonu se to dá vyřešit mnohem elegantněji, ale pak by to nebylo ekvivalentní k C kódu) implementací strcmp(), ale naprosto selhala v případě operátoru porovnání.

Napadlo mě, že jeden z možných důvodů, proč tomu tak bylo je jisté rozostření, kdy se snažíme z relativně pomalého pythonu měřit nativní operaci, která běží tak rychle, že měření v podstatě vrací jen čas nutný k zavolání funkce, který je ovlivněn spoustou dalších věcí, jako třeba fragmentace paměti a počtem běžících procesů.

Další co mě napadlo je, že na všech moderních desktopových systémech nikdy neběží jen jedna věc. I kdybyste povypínali všechny možné ostatní procesy, stále se toho děje na pozadí mnoho, co vůbec nemůžete ovlivnit. Je tu stránkování, je tu složitý proces přepínání procesů podle vytížení počítače a priorit, jsou tu různé cache, je tu zřetězování a predikce instrukcí v procesoru.

Díky tomu všemu je těžké něco opravdu přesně měřit ve chvíli kdy se bavíme o nanosekundách, což může implementace == pro řetězec splňovat, jelikož implementace Pythonu který používám je napsaná v C.

Sebastian Shinzel na podobný problém také narazil a rozhodl se to vyřešit silnou úpravou Windows. Osobně jsem se v pokusech vydal jednodušší cestou - moderní operační systém jsem vůbec nepoužil, protože proč používat multitaskingový OS na něco, na co se zcela očividně nehodí. Namísto toho jsem si vybral široce dostupný realtime OS; DOS, konkrétně verzi FreeDOS.

DOS

DOS je z dnešního pohledu silně triviální systém, u kterého mi trvalo velice dlouho, než jsem pochopil v čem spočívá jeho výhoda. Na první pohled se to může zdát poněkud překvapivé, ale největší výhoda je v tom, že nedělá nic. Zatímco dnešní operační systémy se starají o všechno možné, DOS prostě načte váš program do paměti, předá mu velení a to je vše, od této doby má váš program na starost vše, co má počítač nyní dělat. Jakmile skončí, předá velení s trochou štěstí zpět DOSu a ten umožní spouštění dalších programů.

Ve skutečnosti to tak jednoduché není, protože je zde ještě DOS API ovládané pomocí různých přerušení a rezidentní programy, které mohou za určitých podmínek běžet kdesi na pozadí, ale nekažme si pohled na svět složitostmi, které se dají jednoduše povypínat.

PythonD

Jelikož se mi v závěru článku nechtělo měnit jazyk, hledal jsem python pro DOS. Na internetu se naštěstí našel nadšenec, který před pár lety přeložil python 2.4. Jedná se sice o starší, ale stále použitelnou verzi. Pokud máte zájem, najdete ho zde: http://www.progecam.com/download/opensource/python/pyd24210.zip

Úprava live cd

DOS v tomto případě nemůžete spustit ve virtualizačních nástrojích, protože jen bůh ví, co by se vám vracelo jako čas. Proto jsem si upravil starší live CD, abych FreeDOS nemusel instalovat. Použil jsem k tomu program isomaster.

Python jsem si rozbalil do složky LIVE/python, a k tomu jsem si vyrobil soubor python.bat, který najdete v příloze a samozřejmě jsem si do LIVE/src nakopíroval i zdrojáky předešlých ukázek.

Po spuštění live CD jsem vybral v úvodním menu 1 a v druhém menu 3, tedy že chci podporu rozšířené paměti. Po bootu jsem se ocitl na disku A: a chvíli jsem tápal, než mi došlo, že Live CD je připojeno na disku X:. V určitou chvíli mě napadlo scripty trochu poladit a skutečně jsem ocenil, že na disku Z: je mountlý ramdisk, takže tam pomocí programu edit můžu zkopírované scripty upravovat.

Přestože jsem však vyzkoušel lecos, útok na == se mi nepodařilo provést a hodnoty které vrací jsou i při stovkách milionů pokusů neodlišitelné od šumu i přes použití DOSu. Hypotéza s realtime OS tedy padla.

_PyString_Eq

Náhodnost == mi nedala spát, proto jsem se nakonec rozhodl prozkoumat implementaci operátoru porovnání pro stringy, kterou najdete ve zdrojácích pythonu, konkrétně v souboru Python-2.7.3/Objects/stringobject.c na řádce 1249:

  1. int
  2. _PyString_Eq(PyObject *o1, PyObject *o2)
  3. {
  4.     PyStringObject *a = (PyStringObject*) o1;
  5.     PyStringObject *b = (PyStringObject*) o2;
  6.     return Py_SIZE(a) == Py_SIZE(b)
  7.       && *a->ob_sval == *b->ob_sval
  8.       && memcmp(a->ob_sval, b->ob_sval, Py_SIZE(a)) == 0;
  9. }

Jak je vidět, implementace porovnávání je trochu jiná než strcmp() v C, protože prvně porovná délku řetězce. To by se v C nevyplatilo, protože stringy nemají samostatně uloženou délku a její zjišťování pomocí strlen() by bylo zbytečně náročné.

Z výše uvedeného zdrojového kódu tedy plyne, že pythonní operátor porovnání je vůči naivní implementaci timing attacků odolný.

Návrhy

Script cmpcrack.py není úplně nejvhodněji napsaný a jednoduše se může zatoulat kam by neměl. Napište funkci, která v daném kroku ohodnotí všechna písmena v poli tests a když zjistí že mezi nimi není skoro žádný rozdíl, vrátí proměnnou guess o písmeno zpět, protože nejspíš došlo k špatnému pokusu. Díky tomu nebudete muset čekat, až špatný pokus dosáhne 10 znaků a hledání bude resetováno.

Zkuste navrhnout způsob útoku na pythoní operátor porovnávání. Dokážete navrhnout metodiku získání očekávané délky? Je vůbec možné na _PyString_Eq() zaútočit?

Pokud budete útočit přes síť, neměřte prvně milion pokusů pro jedno písmeno, milion pro druhé atd, ale posílejte serveru všechna na střídačku. To vám umožní do jisté míry eliminovat fluktuace v zatížení serveru a rozložit je do více znaků, takže tolik neovlivní výsledky měření. Za upozornění děkuji Jendovi.

Obrana

Pokud někdy budete psát aplikaci, ve které porovnáváte plaintextové heslo, nepoužívejte triviální operace pro porovnávání řetězců, ale API které vám nabízí kryptografické knihovny.

Pokud nemáte dostupné podobné API, nevytvářejte strcmp(), ale strncmp(), která vždy projde celý string. Další možnost je vložení náhodné umělé pauzy.

Úplně nejlepším řešením je však hesla v plaintextu neukládat a používat osolené hashe, na které timing attacky nefungují.

Poučení

Timing attack je často opomíjenou chybou, která je ovšem docela těžká na exploitaci v případě, že se snažíte útočit po internetu - vzpomeňte si na trable s == a vynásobte to počtem neznámých zařízení nacházejícími se mezi vámi a obětí, která se budou snažit co nejoptimálněji dynamicky směřovat traffic, navrch přidejte i nemožnost použít proxy, protože by vnášela do systému další rozostření.

Velký užitek tento typ útoku najde ve chvílích, kdy necílíte na složitý hardware, ale na něco relativně jednoduchého, jako je jednočip, nebo třeba DVD přehrávač, či satelit na orbitě (kde vám to ovšem zkazí odezva radiové sítě a nízká rychlost).

Co se týče side channel útoků, dále již je pitvat nebudu, pevně věřím, že každý kdo chtěl pochopil a kdo nepochopil, tomu stejně další informace nepomohou.

Fail2ban

Jedním z největších nepřátel hádacích útoků je po nízké přenosové rychlosti a dlouhé odezvě také omezení počtu pokusů. To se dá v zásadě rozdělit na tyto typy:

  1. Omezení na uživatele
  2. Omezení na IP
  3. Omezení podle lokace IP

Omezení na uživatele

Pokud se setkáte s prvním typem, jste vyřízení a nenapadá mě nic, co by vám mohlo pomoct. Po vyzkoušení několika pokusů je uživatel zablokován a vy končíte, dál máte prostě smůlu a neexistuje způsob, jak to obejít.

S tímto typem ochrany se naštěstí setkáte jen velmi zřídka u vyjímečně důležitých věcí, protože umožňují jednoduchý DDOS, kdy zabráníte přihlašování i legitimních uživatelů, které tím moc nepotěšíte, tudíž vytvoří tlak na admina, aby systém odstranil.

Tento systém se dá očekávat u banky, nebo zbraňových systémů a vůbec celkově všude tam, kde je kritické, aby se nikdo nedostal dovnitř a pohodlí uživatelů není takovou prioritou.

Omezení na IP

Mnohem častějším, i když stále zdaleka ne samozřejmým, je systém omezení počtu pokusů z jedné IP adresy. Díky tomu s velkou šancí nezablokujete legitimní uživatele a pokud ano, můžou prostě přijít k jinému počítači a zkusit to tam.

Stejným způsobem můžete postupovat i vy - rozprostřít hádání mezi počítače botnetu (prvně to tedy chce nějaký mít ^-^), použít proxy servery, zkoušet hádat jen jeden požadavek za hodinu atp..

Omezení podle lokace IP

Tento typ ochrany implementuje třeba gmail, kdy vám nedovolí se jen tak přihlásit z jiné země a vyžaduje ověření pomocí SMS.

Myšlenka je prostá - spousta útoků na uživatelské účty jde z ciziny, proto blokací cizinců eliminujete většinu pokusů.

Řešení je ještě prostější - proxy v zemi uživatele.

Parametry banu

Je důležité si uvědomit, že každý ban má jasně dané parametry, které potřebujete zjistit, abyste mohli systém banování obejít.

Počet pokusů

Jedním z nejdůležitějších parametrů je samozřejmě počet pokusů, které můžete vyzkoušet než chytnete ban. Typicky to bývá někde mezi 5 až 10.

Časová jednotka

Dalším důležitým parametrem je samozřejmě doba, kterou na pokusy máte. Může se jednat o vteřiny až minuty, jen zřídka jde o hodiny, či dny.

Na jak dlouho

Jakmile ban dostanete, důležitá informace je za jak dlouho přestane platit a zda se to nějak stupňuje, tedy že poprvé dostanete na hodinu, podruhé na dvě a potřetí na celý den.

Závěr

Zatímco bruteforce se dá přirovnat k oslovování všech žen, které jdou po ulici náhodnými větami, dictionary je podstatně účinnější, protože zkoušíte na konkrétní ženu věci, o kterých se obecně ví, že na ženské zabírají, tedy různé lichotky a vtip.

Dictionary s vlastním, na míru dělaným slovníkem je jako když si prvně o danné ženě zjistíte všechny dostupné informace a nastudujete si jednotlivé detaily o oblíbených filmech a knihách, takže ženu můžete bavit věcmi, které má ráda a které se jí líbí, stále však nemáte jistotu, že vás nebude brát jen jako kamaráda.

Side-channel útoky jsou techniky, které dokáže použít jen zkušený svůdník, jenž umí vyhodnotit každý podvědomý pohled ženy, to jak se jí zachvěje sval nad ústy, jak pohne nosem, jak její žíla na krku začne viditelně rychleji pulzovat, jaký zaujme postoj a jak se zasměje. Sám přitom neustále zkouší měnit vlastní postoje tak, aby je zkorigoval s co nejpozitivnější reakcí a po chvilce doslova krmí ženu zpětnou vazbou tak, aby vyvolal co nejlepší dojem. K tomu aby mohl něco takového provést však potřebuje kromě talentu také mnoho zkušeností a detailní povědomí o tom, jak ženy fungují, proto to zvládá jen poměrně málo lidí.

Kontakt

Článek jsem napsal, protože chci a můžu. Pokud se to někomu nelíbí, můžete si stěžovat na lampárně, nebo do zdi.

Pokud máte nějaké konstruktivní připomínky, nebo si chcete o článku pokecat, můžete mi napsat na:

  • bystrousak@kitakitsune.org

Hackovat nikoho učit nebudu, tak to ani nezkoušejte, podobné maily mažu bez odpovědí, obzvlášť blbým žádostem se veřejně vysmívám na IRC.

Licence

Tento článek chce být svobodný, proto ho šiřte jak se vám zlíbí, ovšem v nezměněné podobě. Byl bych však rád, kdybych na to byl šiřitelem upozorněn, čistě proto, že tak narazím na zajímavá místa.

Příloha


Líbil se Vám článek?
Budeme potěšeni, pokud vás zaujme také reklamní nabídka

Social Bookmarking

     





Hodnocení/Hlasovalo: 1.44/34

1  2  3  4  5    
(známkování jako ve škole)