Základy zpětného inženýrství

Tato série článků může posloužit jako lehký úvod do zpětného inženýrství (reverse-engineeringu) cizího souborového formátu. Soubor BNL je dostatečně jednoduchý a málo, či naivně chráněný. Je k dispozici řada vzorků, na kterých se dá zkoumat chování a lze vytvořit vlastní soubor, a tak snadno ověřit teorie, které si reverzní inženýr v průběhu analýzy vytvoří.

Nutné znalosti a vybavení

Na hacking a cracking cizích formátů a šifrování si připravím tyto nástroje a znalosti:

  • Hexaeditor. Protože jsem líný, použil jsem ten, který je zabudován ve FAR manageru. Ale každý si může zvolit takový, který mu bude „sedět do ruky“.
  • Umění interpretovat různé číselné soustavy (desítková, dvojková, šestnáctková).
  • Znalost rozdílu mezi little a big endian, signed a unsigned (znaménkové a neznaménkové typy).
  • Umět sčítat, odčítat, ANDovat, ORovat a XORovat, SHIFTovat a ROLovat.
  • Programátorskou kalkulačku – každý si může zvolit jakoukoli. Mně ta, která je součástí Windows 10, na toto stačila. Dá se přepnout do programátorského módu a hned jsou k dispozici konverze soustav, i všechny důležité číselné operace.
  • Z předchozích článků vím, že Albi tužka obsahuje čipy SoC Sonix SNP70032 nebo SN95300. Našel jsem si tedy datasheety. Z nich plyne, že jsou velmi podobné a obsahují 16 bitové DSP jádro, které má všechno zarovnáno na 16bitová slova.
  • Dále jsem si prosvištěl německý reverzní projekt TipToi-reveng, který rozebírá podobné pero prodávané pod značkou Ravensburger.
  • Léty nasbírané vědomosti o ukládání dat do binárních souborů. Ty sice teď tak snadno nezískáte, ale pokud si při čtení budete vše zkoušet, možná spolu položíme úplné základy.
  • Papír / otevřený Notepad na poznámky.
  • A hlavně – nezdolnej lidskej duch, tvrdohlavost a umanutost.

Základy hraní s bajty a bity

Protože si většina programátorů dnes už vůbec „nesáhne na železo“, některé pojmy, které používám, nemusí řadě čtenářů vůbec nic říkat. Tady si ve velmi zkrácené a zjednodušené formě povíme, jak se různá data ukládají do paměti (a tím i do souborů) a jak se s nimi manipuluje. Předem upozorňuji, že se bude jednat o „lži-dětem“, termín, který použili Ian Stewart a Terry Pratchett ve Vědě na Zeměploše. Tedy informace budou dostatečně přesné k pochopení něčeho nového, ale nebudou pokrývat celou šířku problému. Pokud výše uvedené základy máte, raději kapitolu rovnou přeskočte (protože budete mít neustále tendenci upřesňovat).

Číselné soustavy

Při práci s počítači nízko k hardware, se používají dvojková a šestnáctková číselná soustava, méně pak osmičková, tu tedy vynechám. Dvojková se používá z jednoduchého důvodu – nejnižší jednotka informace je 1bit, který může obsahovat pouze hodnoty 0 nebo 1. Všechny číselné soustavy „fungují“ stejně – každá má N symbolů (dvojková dva – 0/1, desítková 10 – 0-9 a šestnáctková 16 – 0-9 a A-F), zapisují se zprava tak, že symbol na nejnižším místě je násoben N0, další N1 atd. Např. pro binární číslo 11010110 je to takto:

Binární číslo11010110
Pozice76543210
Mocniny1286432168421
Výsledek128640160420

Desítkovou soustavu umíme „z hlavy“, ale platí pro ni ta stejná pravidla, i když si je neuvědomujeme.

To jste nečekali, že dostanete úkol, že jo? Ale pojďte na to, ať se něco naučíte.

Spusťte programátorskou kalkulačku a převeďte výše uvedené binární číslo (číslo ve dvojkové soustavě) na desítkové číslo.

Výsledek je 214

Protože (1*128) + (1*64) + (0*32) + (1*16) + (0*8) + (1*4) + (1*2) + (0*1) = 214

Základní adresovatelnou jednotkou paměti je BYTE (bajt), který obsahuje 8bitů. Sice bychom mohli hodnotu bajtu zapisovat binárně, ale to je dosti náročné a dlouhé, proto si rozdělíme bajt na dva čtyřbitové kousky (nibble). Pomocí 4 bitů umíme vyjádřit 24 = 16 hodnot, na jejichž zápis se nám hodí právě šestnáctková soustava. Když si tedy číslo výše rozdělíme na 1101 a 0110, vyjde nám 8+4+1=13 a 4+2=6, můžeme tedy zapsat bajt jako 0xD6 (protože šestnáctková soustava využívá 16 různých symbolů pro číslice, symboly A až F odpovídají desítkovým 10-15).

Titulní obálka porevolučního časupisu Bajt, české verze amerického časopisu BYTE
Bajt byl i první český porevoluční časopis o počítačích, česká verze amerického časopisu BYTE

Endianita

Další vyšší jednotkou je WORD, 16 bitové slovo. Tady se dostáváme k problému s tzv. endianitou. Můžeme totiž WORD rozdělit na dva bajty a ty za sebou uložit do paměti, ale je otázkou, v jakém pořadí. Buď ho uložíme v pořadí od „menšího“ konce, little-endian, tj. nejdříve uložíme bajt s bity 7-0 a až pak bajt 15-8, nebo ve formátu big-endian, tj. uložíme nejdřív vyšší bajt 15-8 a až pak nižší. Způsob, jakým se ukládá do paměti, je dán napevno architekturou procesoru.

pořadí bajtů little-endian
Ukázka pořadí bajtů little-endian

A ještě vyšší z našeho hlediska je DWORD (double word) 32bitové slovo. Opět existuje více zápisů, little-endian od nižších slov a bajtů, big-endian od vyšších.

Na kterém místě se vyskytuje little endian doubleword 0x443B0ACA

Little endian doubleword 0x443B0ACA je v souboru zapsaný v pořadí od „menšího“ konce. Začíná tedy bajtem o hodnotě „CA“, následuje „0A“, atd. Najdete ho na offsetu 0x1d058

Ještě nám chybí informace o zarovnání (padding) – některé procesory nesnáší, když se celočíselné typy v paměti nenacházejí na správných místech, tj. na adresách beze zbytku dělitelných 2 nebo 4 (to je dáno typem procesoru). Když se pak ukládají do paměti čísla o různých velikostech, musejí se zarovnat tak, aby následující číslo bylo na správné adrese. Některé procesory umožňují i ukládání na „škaredé“ adresy (unaligned access), ale za cenu zpomalení operace – proto je zarovnání často žádoucí. Např. pokud ukládám za sebou samé 32bitové hodnoty (tj. 4 bajtové) a chci mezi ně uložit jednu 16bitovou, musím přidat jeden „absolutně prázdný a falešný“ WORD, abych měl následující DWORD na adrese dělitelné 4.

Všechny tyto informace platí pro neznaménková čísla, tj. čísla v intervalu 0 až 2N-1. Pokud se chceme k binárním číslům chovat jako ke znaménkovým, děláme to nejčastěji tak, že nejvyšší bit je znaménkový (0 – kladné číslo, 1 – záporné číslo) a zbytek je tzv. dvojkový doplněk.

Jako perličku na konec si nechávám informaci, že dané názvosloví „endianity“ vychází z Gulliverových cest Jonathana Swifta, kde vedla dvě království válku o to, ze které strany se mají rozbíjet vejce – zda z „big end“ (tlustokoncoví) nebo „little end“. Podobný spor nyní vedou někteří lidé na Twitteru, zda se ona věc na konci banánu používá jako držák nebo otvírák. 😉

  • bit je hodnota 0 nebo 1
  • bajt má 8 bitů, pro lepší čitelnost se převádí na HEX formát např. 0x4B
  • nibble je půl bajtu – 4 bitový kousek
  • word má 16 bitů, tedy 2 bajty, např. 0x9AF4
  • dword má 32 bitů, tedy 4 bajty, např. 0x2DFF741C
grafické znázornění velikostí bitu, nibble, bajtu, word a dword
Bity, bajty a slova

Ukazatele, offsety a nebo také pointery

Všechny tyto pojmy jsou z našeho hlediska prakticky to stejné – jedná se o číslo, které říká, jak daleko je nějaký objekt od referenčního. Typicky v paměti je to adresa objektu v paměti, v souboru to bývá buď absolutní offset od začátku souboru nebo relativní offset od nějakého počátku vnitřního členění souboru. I index v poli se chová podobně.

Na běžných 8bitových procesorech jsou ukazatele dlouhé 16bitů, na vyšších pak různě veliké, většinou podle počtu “bitů procesoru”. Většinou se ukazatele zvětšují o jedna pro každý bajt, ale jsou architektury, které mají jako nejnižší adresovatelnou jednotku například WORD, a pak zvětšení ukazatele o 1 znamená skok o 2 bajty (1 WORD).

Řetězce a magická čísla

Texty, vesměs označované jako textové řetězce nebo jen řetězce, se do paměti ukládají celkem jednoduše – písmenko za písmenkem, kde je každé písmenko reprezentováno jedním bajtem. Nejčastěji je takový řetězec ukončen bajtem 0x00 (tzv. C zápis), někdy mu předchází délka (PASCALský zápis), ať už bajtová nebo delší. Dokud se pohybujeme v angličtině, je to jednoduché, stačí nám tzv. ASCII tabulka, dokonce jen sedmibitová (tj. 128 znaků). V jazycích, kde jsou pak různá nabodeníčka, umlauty, cedilly czy ogonki, si musíme zvolit, jakým způsobem budeme interpretovat vyšší hodnoty bajtů a jak je budeme mapovat na jednotlivé znaky jazyka. Druhým způsobem je pak použití osmibitového kódování s proměnnou délkou UTF-8 nebo fixní šestnáctibitové UTF-16.

Původně to měl být jen ilustrační obrázek, ale proč z něj neudělat úkol…

Nyní máte všechny potřebné znalosti pro pochopení / přečtení tohoto trička. (zdroj: wish.com)

Tohle nebude zadarmo… BIN > HEX > ASCII

Magická čísla nám v souborech slouží k identifikaci buď vlastního souboru, nebo jeho důležité části. Např. každý Microsoftí exe soubor začíná písmeny MZ (hex hodnotami 0x4D 0x5A), tím operační soubor rozpozná, že se jedná o spustitelný soubor a pokračuje v jeho načítání. Takovýchto konstant se může v souboru vyskytovat více.

Z hlediska rozpoznávání cizích souborových formátů se jedná o cennou nápovědu. Samozřejmě, řada souborů žádná magická čísla neobsahuje, především na jednodušších systémech, kde není nutnost rozlišování různých formátů souborů – například se místo toho použije předpoklad, že soubor se správnou příponou má i správný formát odpovídající té příponě.

Operace nad čísly

Standardní operace jako sčítání, odčítání a celočíselné násobení a dělení můžeme vynechat, jen s tou poznámkou, že velikost výsledku může být omezena šířkou typu. Tj. pokud mám neznaménkový bajt (připomínám, že má 8 bitů, tj. maximálně 28 = 256 hodnot 0-255) s hodnotou 234 a přičtu k němu 50, dojde k takzvanému přetečení a výsledek bude 28. [(234+50) modulo 256]

Dále následují operace bitové. Většina procesorů má operaci NOT, která jen „přepne“ všechny bity v čísle na opačnou hodnotu.

Další operace jsou OR (logický součet), AND (logický součin) a XOR (exkluzivní logický součet). Tyto operace mají dva vstupy a jeden výstup, a dají se zapsat takto pomocí pravdivostní tabulky:

Vstup
A
Vstup
B
Výstup
OR
Výstup
AND
Výstup
XOR
00000
01101
10101
11110
Pravdivostní tabulka operací OR, AND a XOR

Krátký popis jednotlivých operací:

  • Operace OR se používá, když chceme nastavit nějaký bit na jedničku.
  • Operace AND se používá, když chceme jednotlivý bit nastavit na 0 nebo když chceme omezit / odmaskovat nějaký počet bitů (např. 0x1234 AND 0xFF nám efektivně zachová dolních 8 bitů, výsledkem je 0x0034). Toto například můžete znát, když zapisujete masku sítě IPv4.
  • Operace XOR se v našem použití používá nejčastěji k jednoduchému zašifrování. Z hlediska šifrování má tyto „zajímavosti“
    • A XOR 0 = A (identita)
    • A XOR 0xFF = NOT A (negace)
    • Dále pak platí, že (A XOR K) XOR A = K, tj. pokud mám zašifrovaný známý text A a neznámý klíč K, dokážu pomocí xorování zašifrovaného bloku známým nezašifrovaným obsahem získat neznámý klíč.
    • Platí i (A XOR K) XOR K = A, tj. první xorování klíčem blok zašifruje, druhé odšifruje.

Posledními nezmíněnými operacemi jsou posuny a rotace, ty zcela vynechám, jen zmíním, že jednoduché posuny se používají k násobení a dělení mocninami 2 (podobně jako v desítkové soustavě přidávání a ubírání nul zprava, tj. násobení a dělení mocninami 10).

Během dalšího zkoumání souboru budu používat operaci XOR, tak si vyzkoušejte, jestli všemu dobře rozumíte.

  • 0x1D XOR 0x08 = ?
  • 0x1D2C XOR 0x3B4A = ?
  • Dešifrujte text 0x2A000B0A pomocí textu „klic“
  • 0x1D XOR 0x08 = 0x15
  • 0x1D2C XOR 0x3B4A = 0x2666
  • 0x2A000B0A XOR 0x6B6C6963 = 0x416C6269 po převedení do ASCI je výsledkem text „Albi“

Diskuze

Vaše e-mailová adresa nebude zveřejněna.

Scroll to Top