Disassembling a cracking na príklade v C++, časť 1
Mnoho ľudí si myslí že “rozobratie” nejakého exe-čka alebo iného binárneho (kompilovaného) kódu je nemožné alebo skoro nemožné. Stačí pritom len pár dní venovaných assembleru (pre intel/PC architekturu je assembler x86) a pár nástrojom na disassemblerovanie binárneho kódu, prípadne na odstránenie ochranných techník.
Každý binárny program nie je nič iné ako postupnosť strojových inštrukcií. A každej strojovej inštrukcii odpovedá príkaz v jazyku assembler. To znamená, kto ovláda dokonale assembler a dokáže program zapísaný v assembleri aj čítat, tomu nerobí problém pochopiť ako akýkoľvek binárny program funguje. Samozrejme existuje mnoho ochranných techník ktoré sa snažia čítanie programu čo najviac zkomplikovať ale samozrejme na každú ochrannú techniku existuje technika ktorá ju prelomí. Okrem samotných techník a nástrojov, celú situáciu může komplikovať aj samotný kompiler ktorý vytvoril tento binárny kód. Na internete však existuje mnoho manuálov, a návodov s ukážkami ako sa preloží (zkompiluje) trieda v C++ do assembleru, prípadne cyklus, switch a ďalšie konštrukcie programovacieho jazyka. Uvediem príklad v C++. Tento program bol zkompilovaný a následne disassemblerovaný.
Príklad 1: Funkcie z C++ v assembleri
V jazyku C++
func(); main() { int a; func(); a=0x666; func(); } func() { int a; a++; }
V jazyku assembler x86
Poznámka pre neznalcov assembleru:
Znak ; je znakom komentára, ten sa samozrejme nikde v disassemblerovanom kóde nevyskytuje. Uvádzam ho kvôli prehľadnosti.
Príkaz call <adresa> je jump na adresu <adresa>
Lokálne premenné sa ukladajú na adresy [ebp-X]. Teda ak premenná zaberá 2 byte (integer), tak prvá definovaná lokálna premenná sa uloží na adresu [ebp-2], ďalšia na adresu [ebp-4] a pod.
Registre eax, ebx, ecx… su 32-bitové registre, tkzv. procesorove premenné. Nie sú uložené v pamäti ale priamo v procesore. Je to najrýchlejší druh pamäte v počítaci. Väčšinou sa používajú ako medzivýsledky a tiež ako iterátory.
.text:00401000 push ebp ; Začiatok funkcie main() .text:00401001 mov ebp, esp .text:00401003 push ecx .text:00401004 call 401019; V jazyku C++ je to riadok (prvé volanie): func() .text:00401004 ; Tu voláme nejakú funkcie (voláme func()) .text:00401004 ; Jediným tkzv skrytým operandom je adresa začiatku funkcie, .text:00401004 ; presnejšie povedané, je to offset v code segmente. .text:00401004 ; Tento parameter je minimálne vždy pred každým volaním funkcie. .text:00401004 ; Bez neho by sme sa nevedeli vrátiť naspäť. .text:00401009 mov dword ptr [ebp-4], 666h ; V jazyku C++ je to riadok: a=0x666 .text:00401010 call 401019 ;V jazyku C++ je to riadok (druhé volanie): func() .text:00401015 mov esp, ebp .text:00401017 pop ebp .text:00401018 retn ; Koniec funkcie main() .text:00401019 push ebp ; Začiatok funkcie func(). .text:0040101A mov ebp, esp .text:0040101C push ecx .text:0040101D mov eax, [ebp-4] ; premenná "a" je uložená na adrese [ebp-4], šupneme ju do eax .text:00401020 add eax, 1 ; V jazyku C++ je to riadok: a++ .text:00401023 mov [ebp-4], eax ; a tu výsledok operácie uložíme naspäť do premennej "a" .text:00401026 mov esp, ebp .text:00401028 pop ebp .text:00401029 retn ; Koniec funkcie func()
Ako je to vidno na predchádzajúcom príklade, program z C++ zapísaný do assembleru nie je taký hrôzostrašný. Museli sme si však sami vyznačiť kde začína funkcia a kde končí, aké má parametre, čo je premenná a pod. Samozrejme ak by sme to všetko museli robiť manuálne, neexistovalo by na internete toľko rôznych crackov a upraveného software. Existuje totiž veľké množstvo programov ktore disassemblerovanie robia omnoho jednoduchším. Najlepším z nich je program IDA (http://www.hex-rays.com/idapro/). Ten dokáže vytvoriť grafy volania funkcíí, flow chart a podobne. Automaticky rozpozná lokálne a globálne premenné a win32 API volania!. Viď nasledujúci obrázok:

Samozrejme softwarové firmy nie sú nadšené z toho ako sa im niekto hrabe v ich kóde alebo upravuje, preto vznikla rada ochranných technik, ktoré sa predovšetkým snažia čo najviac zkomplikovať čítanie disassemblerovaného kódu (Anti-disassembling). Robia to väčšinou tak, že tam pridajú mnoho zbytočného kódu, ktorý sa v skutočnosti preskakuje veľmi komplikovanými kombináciami jumpov. Medzi týmto zbytočným kódom (ktorý vlastne procesor nevykonáva) sa nachádza aj skutočný kód, ktorý reálne beží. Lenže aj na takéto komplikované miešanice sa našli talenty medzi crackarmi, ktorí venovali svoj čas a vytvorili nástroje likvidujúce túto nepríjemnosť.
Všetkým ktorých zaujíma cracking a chceli by sa tomu hlbšie venovať, odporúčam naštudovať si assembler, a sami si to skúšať. Naprogramujte si nejaký jednoduchý program v C++. Pridajte tam riadky napríklad a=0×666666;, pomocou ktorých sa ľahšie zorientujete v disassemblerovanom kóde, pretože assemblerovský kód pre riadok a=0×666666 nájdete veľmi jednoducho – hľadaním hodnoty 666666. Vyskúšajte ako sa preloží niektorá konštrukcia v C++ do assembleru. Môžete si nastaviť rôzne úrovne optimalizácie a sledovať ako sa výsledný kód mení. Ak sa vám moc skúšať nechce, na internete nájdete veľa referenčných príručok a manuálov.