Nebojte se reverzního inženýrství II.

V minulém díle jsme se vrhli střemhlav do reverzního inženýrství, probrali používané nástroje, popsali velmi obecné základy a na závěr se vrhli na první crackme. V dnešním díle se opět prokoušeme nezbytnou teorií a pustíme se do složitějšího crackme, které již můžeme považovat za reálnou (dost naivní 🙂 ) ochranu aplikace.

Data a kód
crackme_2
Zdrojový kód crackme_2
Každá aplikace se skládá z dat a kódu. Pomiňme nyní situaci, kdy mohou být data kódem a kód daty. Data jsou od kódu oddělena. Stejně tak jsou odděleny různé druhy dat. Na prvním screenshotu je obsah paměti dnešního crackme. Okno aktivujeme v OllyDbg stisknutím klávesové zkratky Alt + M. V okně je vidět rozsah paměti od adresy 00010000h do adresy 7FFE1000h. Ve skutečnosti se jedná o rozsah 00000000h až 7FFFFFFFh. Tento rozsah se nazývá user space (někdy také Ring3) a je volně přístupný běžným aplikacím (rozhodně se nejedná o nějakou sdílenou paměť, přes kterou by si libovolná aplikace mohla manipulovat s kódem a daty v userspacu – každý proces totiž běží ve svém vlastním paměťovém prostoru, který je pouze pro jeho účely). Rozsah od adresy 80000000h až do FFFFFFFFh se nazývá kernel space (někdy také Ring0) a je přístupný pouze aplikacím spojených s jádrem (například drivery). My se dnes podíváme blíže pouze na rozsah paměti user spacu. Detaily zatím pomineme a spokojíme se s obecným popisem.

crackme_2_1

Rozložení paměti dnešního crackme.

Pro nás je aktuálně důležitý rozsah 00400000h až 00404000h. To je rozsah adres náležící naší aplikaci. V prvním screenshotu si tuto informaci můžeme ověřit ve třetím sloupečku, kde je vidět pojmenování binárky, v našem případě crackme_ (délka názvu je oříznuta na délku osmi znaků). Čtvrtý sloupeček má označení Section a udává název sekce binárního souboru. V našem případě se jedná o sekce .text, .rdata a .data. Hned na začátek podotýkám, že pojmenování jednotlivých sekcí je obecně přijímaný úzus a rozhodně se nejedná o pevně definované názvy (historicky jsou standardní názvy definovány ve specifikaci k PE/COFF společnosti Microsoft). Příkladem může být společnost Borland se svým kompilerem Borland C++ Builder, kde je například sekce .text pojmenována CODE a sekce .data jako DATA. Protože není pevně stanoveno, jak se mají jednotlivé sekce jmenovat, můžeme se setkat se situací, kdy žádná ze sekcí nebude pojmenována. Podrobněji se k této tématice vrátíme v některém z dalších dílů, kdy nahlédneme pod pokličku PE formátu. Nyní se věnujme dále námi nalezeným sekcím.
Sekce .text je sekce, která standardně obsahuje kód aplikace. Pokud zapátráme v paměti na crackme z předchozího dílu, všechen kód, který jsme se snažili přečíst/zanalyzovat se nacházel právě v sekci .text (za úkol si čtenář otevře crackme_1 v OllyDbg, případně jiném debuggeru nebo PE editoru, zobrazí si paměť dané aplikace (vzpomínáte na zkratkovou klávesu Alt + M v OllyDbg?) a překontroluje si sekce, jenž jsou v crackme_1 přítomny a pokusí se zodpovědět, proč tomu tak je). OllyDbg se nám snaží napovídat, co se v dané sekci nachází, v pátém sloupečku, kde u sekce .text vidíme jako obsah code. Pro úplnost pouze dodávám, že sekce kódu nemusí být pouze jedna. Může jich být několik a z nich pouze jedna jediná bude brána jako hlavní sekce kódu (nejspíš ta, do níž ukazuje tak zvaný Entry Point, což je adresa, kde aplikace začíná vykonávat svůj kód).
Dále zde máme sekci .rdata a OllyDbg nám napovídá, že obsahuje importy. Pod pojmem import zde rozumíme importované funkce využívané aplikací pro svoji činnost. Tyto funkce se nachází v jiných modulech (standardně v DLL knihovnách, ale může se rovněž jednat o další EXE soubory). Při zavádění aplikace do paměti (čti po spuštění aplikace), si loader (systémová aplikace zaručující správné načtení binárky do paměti) z této sekce zjistí, které moduly/DLL knihovny aplikace používá a které funkce z těchto modulů volá. Nahraje požadované knihovny do paměti spouštěné binárky a do speciální struktury (IAT – Import Address Table – blíže v některém z dalších dílů) zapíše, kde se nachází požadované funkce. Může se to zdát mírně nešikovné a matoucí, ale důvod je velice jednoduchý. DLL knihovny se neustále rozvíjí, optimalizují a mění. Není tedy možné do spouštěné aplikace natvrdo uložit adresy funkcí z DLL knihoven, protože by bylo nutné po každé aktualizaci DLL knihovny aktualizovat rovněž adresy v aplikaci.
Konečně poslední sekce naší aplikace nese označení .data a OllyDbg nám hlásí, že obsahuje data (a už jsme u toho oddělení kódu a dat 🙂 ). Sekce .data obsahuje globální nebo statické proměnné, jenž mohou být volně měněny a jenž znají svou hodnotu již během překladu aplikace. Jinými slovy: Jsou tyto proměnné tak zvaně inicializovány. Klasicky se jedná o číselné proměnné nebo pole znaků, které obsahují inicializační hodnoty. Nyní zajdeme ještě trochu dál a popíšeme si další obecně známé a používané sekce.
Sekce .bss je opakem sekce .data. V této sekci se nachází proměnné, které v době překladu svou hodnotu ještě neznají a nejsou tedy tak zvaně inicializovány nebo jsou inicializovány na hodnotu 0. Další sekcí, která však přímo nesouvisí s aplikací je tak zvaný stack. Se stackem jsem se již seznámili v předchozím díle. Pro doplnění uvedu, že každá jednovláknová aplikace má stack společný pro celý běh aplikace bez ohledu, zda se právě vykonává kód v hlavní aplikaci nebo v některém z modulů importujících funkce. Odlišná situace je u aplikací běžících ve více vláknech. V takovém případě má každé vlákno svůj stack. Při pohledu do prvního screenshotu nám OllyDbg velmi pohodlně ukáže začátek našeho stacku. V pátém sloupečku nám stačí dohledat řetezec ‚stack of main thread‘. Další z obecně používaných sekcí je tak zvaný heap. S heapem jsme se také již seznámili v minulém díle. Heap standardně začíná za .bss a .data sekcemi. Zde se nachází poměrně dost volné paměti. Na rozdíl od stacku, heap má pevně dánu počáteční velikost (jak zjistit počáteční velikost stacku si blíže řekneme někdy příště).
Předposlední mnou zmíněná sekce má označení .reloc a obsahuje tabulku tak zvaných relokací. Relokace umožňuje bezproblémový posun jednotlivých částí (sekcí) aplikace vůči určité adrese. Důvodem je fakt, že adresa, kam se standardně nahrává aplikace, může být v daný okamžik obsazena a je tedy třeba použít náhradní adresu. Tyto adresy jsou právě uloženy v relokační tabulce.
Poslední z obecně používaných sekcí je sekce .rsrc. V této sekci se nachází resource PE souboru (formát standardní okenní aplikace ve Windows). Mezi resources patří například kurzory, ikony, tlačítka, bitmapy, menu nebo fonty.
Každá z těchto sekcí má určitá přístupová práva. Běžně se setkáváme se třemi typy práv, konkrétně práva pro čtení, zápis a vykonávání. Sekce kódu (.text) většinou obsahuje práva pro čtení a vykonávání. Fakt, že neobsahuje práva pro zápis má bezpečnostní charakter. Nebylo by vhodné si nevědomky přepisovat kód aplikace přímo pod rukami, pokud k tomu nemáme vyloženě důvod. Zapisovatelná sekce kódu většinou ukazuje na nestandardní aplikaci (infikovaná, zakryptovaná, využívající dynamickou obfuskaci a podobně). Naopak sekce dat musí být přístupná zápisu, protože z proměnnými pracujeme a měníme jejich hodnoty.
Ještě jednou se podíváme do prvního screenshotu a uvidíme, že ve čtvrtém sloupečku je sekcí .text povícero. Jedná se právě o moduly/DLL knihovny, jejichž funkce aplikace používá. V našem případě se jedná o (systémové) DLL knihovny ntdll.dll a kernel32.dll. Tyto dvě knihovny by měly být přítomny v paměťovém prostoru každého procesu bez rozdílu. Je tedy na první pohled zřejmé, že rozdíl mezi DLL knihovnou a EXE souborem je (v tomto případě) zanedbatelný. Funkce, které jsou tak zvaně exportovány z modulů/DLL knihoven mají svojí volací konvenci (calling convetion). Calling convention je definice určující, jakým způsobem jsou dané funkci předávány parametry, jakým způsobem je navrácena návratová hodnota a zároveň určuje, kdo ‚uklidí‘ stack tak, aby nedošlo k jeho poškození a tím pádem i k porušení běhu aplikace. Existuje celá řada calling conventions. V základu je můžeme rozdělit na dvě skupiny:
– stack uklízí volající
– stack uklízí volaný
Nejznámější verzí calling convention je CDECL. Jedná se o způsob využívaný v programovacím jazyce C (a v operačním systému Linux se jedná o standard). Argumenty do funkce jsou předávány přes stack v obráceném pořadí (zprava doleva), návratová hodnota je předávána v registru EAX a stack ‚uklízí‘ volající. Příklad:

push 1
push 2
push 3
call secti      ; volání nějaké funkce secti, která přebírá tři argumenty
add esp, 12     ; redukce stacku (na stacku zůstaly argumenty, které jsou již k ničemu)

Více zatím nepotřebujeme vědět. Kromě CDECL konvence existují i další. Například STDCALL konvence. Argumenty do funkce jsou předávány přes stack v obráceném pořadí (zprava doleva), návratová hodnota je předávána v registru EAX. Za ‚uklízení‘ stacku je zodpovědný volaný. Tento typ calling convention je využívaná například u Microsoft Win32 API. Příkladem je například API funkce MessageBoxA (vzpomínáte na crackme z předchozího dílu? 😉 ). Posledním typem konvence, jíž zde zmíním, je fastcall (velmi často se můžeme setkat s pojmenováním Microsoft fastcall). První dva argumenty jsou předávány v registrech ECX a EDX, zbytek argumentů je předán přes stack v pořadí zprava doleva. Volaný je zodpovědný za ‚úklid‘ stacku. Pro bližší informace o calling conventions doporučuji pročíst například Wikipedii[1].

Crackme
Pokud jste zvládli minulé crackme, nebudete mít jistě problém s vyřešením toho dnešního 🙂 Pojďme se na něj podívat. Natáhneme si crackme do OllyDbg a provedeme rychlou analýzu kódu pohledem:

00401000 >/$ 6A F5          PUSH -0B                                 ; /DevType = STD_OUTPUT_HANDLE
00401002  |. E8 A3000000    CALL <JMP.&kernel32.GetStdHandle>        ; \GetStdHandle
00401007  |. A3 95304000    MOV DWORD PTR DS:[403095],EAX
0040100C  |. 6A F6          PUSH -0A                                 ; /DevType = STD_INPUT_HANDLE
0040100E  |. E8 97000000    CALL <JMP.&kernel32.GetStdHandle>        ; \GetStdHandle
00401013  |. A3 99304000    MOV DWORD PTR DS:[403099],EAX
00401018  |. 6A 00          PUSH 0                                   ; /pReserved = NULL
0040101A  |. 68 8D304000    PUSH crackme_.0040308D                   ; |pWritten = crackme_.0040308D
0040101F  |. 68 2B000000    PUSH 2B                                  ; |CharsToWrite = 2B (43.)
00401024  |. 68 00304000    PUSH crackme_.00403000                   ; |Buffer = crackme_.00403000
00401029  |. FF35 95304000  PUSH DWORD PTR DS:[403095]               ; |hConsole = NULL
0040102F  |. E8 82000000    CALL <JMP.&kernel32.WriteConsoleA>       ; \WriteConsoleA
00401034  |. 6A 00          PUSH 0                                   ; /pReserved = NULL
00401036  |. 68 91304000    PUSH crackme_.00403091                   ; |pRead = crackme_.00403091
0040103B  |. 68 14000000    PUSH 14                                  ; |ToRead = 14 (20.)
00401040  |. 68 77304000    PUSH crackme_.00403077                   ; |Buffer = crackme_.00403077
00401045  |. FF35 99304000  PUSH DWORD PTR DS:[403099]               ; |hConsole = NULL
0040104B  |. E8 60000000    CALL <JMP.&kernel32.ReadConsoleA>        ; \ReadConsoleA
00401050  |. 68 62304000    PUSH crackme_.00403062                   ; /String2 = "Bad password bad day"
00401055  |. 68 77304000    PUSH crackme_.00403077                   ; |String1 = ""
0040105A  |. E8 5D000000    CALL <JMP.&kernel32.lstrcmpA>            ; \lstrcmpA
0040105F  |. 85C0           TEST EAX,EAX
00401061  |. 74 1E          JE SHORT crackme_.00401081
00401063  |. 6A 00          PUSH 0                                   ; /pReserved = NULL
00401065  |. 68 8D304000    PUSH crackme_.0040308D                   ; |pWritten = crackme_.0040308D
0040106A  |. 68 22000000    PUSH 22                                  ; |CharsToWrite = 22 (34.)
0040106F  |. 68 40304000    PUSH crackme_.00403040                   ; |Buffer = crackme_.00403040
00401074  |. FF35 95304000  PUSH DWORD PTR DS:[403095]               ; |hConsole = NULL
0040107A  |. E8 37000000    CALL <JMP.&kernel32.WriteConsoleA>       ; \WriteConsoleA
0040107F  |. EB 1C          JMP SHORT crackme_.0040109D
00401081  |> 6A 00          PUSH 0                                   ; /pReserved = NULL
00401083  |. 68 8D304000    PUSH crackme_.0040308D                   ; |pWritten = crackme_.0040308D
00401088  |. 68 15000000    PUSH 15                                  ; |CharsToWrite = 15 (21.)
0040108D  |. 68 2B304000    PUSH crackme_.0040302B                   ; |Buffer = crackme_.0040302B
00401092  |. FF35 95304000  PUSH DWORD PTR DS:[403095]               ; |hConsole = NULL
00401098  |. E8 19000000    CALL <JMP.&kernel32.WriteConsoleA>       ; \WriteConsoleA
0040109D  |> 6A 00          PUSH 0                                   ; /ExitCode = 0
0040109F  \. E8 00000000    CALL <JMP.&kernel32.ExitProcess>         ; \ExitProcess

Aniž bysme crackme spustili, můžeme vidět několik Windows API funkcí. Konkrétně: GetStdHandle[2], WriteConsoleA[3], ReadConsoleA[4], lstrcmpA[5], ExitProcess[6]. Jako první věc si zjistíme, co jednotlivé API funkce dělají. Postupem času a se zvyšujícími se zkušenostmi již budeme vědět zcela automaticky, co která API funkce skutečně dělá, jaké argumenty přebírá, jakou hodnotu/hodnoty vrací a nebudeme muset sahat k MSDN až tak moc často.
API funkce GetStdHandle přebírá jako argument jednu ze tří konstant (STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE), což jsou identifikátory tří systémových bufferů (standardní vstup, standardní výstup a standardní chybový výstup). Návratovou hodnotou je pak handle požadovaného standardního ‚zařízení’/bufferu. API funkce WriteConsoleA vypisuje obsah bufferu na standardní ‚zařízení‘ daného handlem (identifikátorem). Návratovou hodnotou je pravdivostní hodnota udávající úspěch či neúspěch funkce. API funkce ReadConsloeA má podobný význam jako předchozí funkce jen s tím rozdílem, že obsah bufferu nezapisuje na standardní ‚zařízení‘, nýbrž se snaží data ze standardního ‚zařízení‘ získat a uložit v definovaném bufferu. Návratovou hodnotou je opět pravdivostní hodnota udávající úspěch či neúspěch funkce. API funkce lstrcmpA je microsoftím ekvivalentem známější standardní funkce strcmp. Porovnává tedy dvě pole znaků a vrací hodnotu určující, zda se pole znaků sobě rovnají nebo ne. Konečně API funkce ExitProcess … už známe z minulého crackme 😉
Podíváme se na první tři řádky: Snažíme se získat handle standardního výstupu a tento handle uložit do proměnné na adrese 00403095h. Další tři řádky provádí to stejné, avšak pro standardní vstup a výsledný handle je uložen na adresu 00403099h (old school reverzeři by v tomto okamžiku bez zaváhání sáhli po tužce a kusu papíru a obě adresy si poznačili a pojmenovali pro pozdější snadnější orientaci). Následuje volání API funkce WriteConsoleA. Zde nás zajímá hlavně na jaké standardní ‚zařízení‘ se obsah bufferu zapisuje a co je jeho obsahem. Proto si před vykonáním volání API funkce zkontrolujeme obsah stacku a konkrétně pak první a druhý argument. U druhého argumentu můžeme rovnou kliknout pravým tlačítkem myši na řádek, kde se nachází a zvolit v kontextovém menu nabídku Follow in Dump -> Immediate constant, což znamená, že OllyDbg bude následovat hodnotu druhého argumentu jako adresu a zobrazí v Dump window (levé dolní okno v hlavním okně) data, která se na této adrese (00403000h) nachází. V našem případě se zde nachází pole znaků:

Crackme #2 by RubberDuck\r\n
Insert password:
crackme_2_2

Koukneme se, co se ukrývá na této adrese za hodnotu.

crackme_2_3

Tak teď už je to jasné 🙂

Jak je na první pohled jasné, aplikace na této adrese požaduje heslo. Při postupném krokování aplikace (vykonáváním jednotlivých instrukcí jednu po druhé) zjistíme, že je tento text vypsán na standardní výstup. Protože předchozí text jasně vyzývá k zadání hesla, je pravděpodobné, že bude následovat volání funkce načítající obsah standardního vstupu. A to se nám také pohledem do kódu potvrdí. Následuje volání API funkce ReadConsoleA (zatím nemůžeme potvrdit, o jaké standardní ‚zařízení‘ se jedná, ale můžeme to odhadnout z předchozího). Adresu bufferu však známe a můžeme, stejně jako v případě funkce WriteConsoleA, zobrazit jeho obsah v Dump window. Před voláním funkce ReadConsoleA na této adrese nevidíme žádnou hodnotu. Po zavolání funkce se ale OllyDbg ‚zasekne‘ a nechce se mu dál. Čeká totiž na vstup od uživatele. Přepneme se do okna konzole, napíšeme nějaký text a stiskneme klávesu ENTER. OllyDbg opět převezme kontrolu nad vykonáváním kódu. Nyní již v bufferu vidíme námi zadaný text (tedy heslo). Na adrese 0040105Ah je volána funkce lstrcmpA. Ta porovnává dvě pole znaků. Prvním pole obsahuje naše heslo (viz adresa 00403077h), druhé je text na adrese 0403062h a má hodnotu ‚Bad password bad day‘. Je zřejmé, že zde dochází k porovnání skutečného hesla s tím zadaným. Na adrese 0040105Fh je instrukce assembleru, se kterou jsme se zatím nesetkali.
Instrukce TEST instrukce provede operaci logického součinu (stejně jako instrukce AND), ale výsledek nikam neuloží. Pouze nastaví příslušný příznak v registru EFLAGS. V tomto případě nastaví Zero flag, pokud je hodnota registru EAX rovna nule. Na dalším řádku se kontroluje, zda došlo k nastavení Zero flagu v EFLAGS registru. Pokud je výsledek funkce lstrcmpA roven nule, instrukce TEST nastaví Zero flag v registru EFLAGS na hodnotu 1. Na dalším řádku je instrukce podmíněného skoku JE, která zkontroluje, zda je Zero flag nastaven na hodnotu 0. Pokud ano, skočí na příslušnou adresu. Pokud ne, pokračuje bezprostředně následující instrukcí. V našem případě Zero flag nastaven nebude a proto nedojde ke skoku a vykonávání programu bude pokračovat bezprostředně za instrukcí JE. Opět vidíme volání API funkce WriteConsoleA. Zkontrolujeme obsah bufferu uvedeného jako argument (na adrese 00403040h). Zde najdeme znakové pole s hodnotou ‚No. This isn’t good password.. :(‚. Na následujícím řádku je nepodmíněný skok na adresu 0040109Dh, kde následuje volání API funkce ExitProcess. Vraťme se nyní na řádek s podmíněným skokem (00401061h) a podívejme se, jaký kód se na této adrese ukrývá. Jedná se znovu o volání API funkce WriteConsoleA a obsah bufferu je tentokrát na adrese 0040302Bh.
Text ‚Good job cracker! :)‘ už vypapdá podstatně lépe. Musíme tedy zadat správné heslo, které je použito jako druhý argument API funkce lstrcmpA. Spustíme crackme normálním způsobem z konzole a na vyžádání zadáme heslo ‚Bad password bad day‘ a stiskneme ENTER.
Právě jsme vyřešili crackme se statickým heslem.

crackme_2_4

Ha! Zvládli jsme to 🙂

Ještě před tím, než tento díl ukončíme, vysvětlíme si způsob, jak můžeme ovlivňovat nastavení EFLAGS a tím pádem dynamicky měnit tok kódu aplikace. Registr EFLAGS je v hlavním okně v pravém okně pod obecnými registry a je označen písmeny C P A Z S T D O. V předchozím díle jsem uvedl, že EFLAGS jsou registry. Jednalo se o výrazné zjednodušení pojmu, abych zbytečně někoho nemátl. Ve skutečnosti se jedná o jediný registr obsahující osm hodnot, jehož jednotlivé bity (hodnoty) mohou být nastaveny buď na hodnotu 0 nebo 1. Dvojklikem na hodnotu v pravém okně hlavního okna můžeme měnit nastavení jednotlivých příznaků (zatím jsme se seznámili se Zero flagem, který má označení Z) registru EFLAGS. Ještě jednou natáhneme do OllyDbg crackme, zadáme libovolný řetězec a odkrokujeme kód až na adresu podmíněného skoku JE. Zde překontrolujeme nastavení Zero flagu a dvojklikem změníme jeho hodnotu z nuly na jedničku. Jak je vidět, kód i při nesprávně zadaném heslu skončí hláškou oznamující úspěch 🙂

crackme_2_5

Změnou nastavení Zero flagu jsme docílili stejného efektu jako při zadání správného hesla 🙂

Odkazy
Calling conventions
GetStdHandle
WriteConsoleA
ReadConsoleA
lstrcmpA
ExitProcess

Poděkování
MazeGen – za konzultaci a korekci

9 komentářů u „Nebojte se reverzního inženýrství II.

  1. F03k

    Zdar, opět velmi názorné, díky.
    Jen to crackme#2 tu nikde nevidím ke stažení… Trpím slepotou nebo se odkaz někam zatoulal?

    1. RubberDuck Autor příspěvku

      Já jsem si říkal, že jsem na něco zapomněl 🙂 Napraveno. Omlouvám se.

  2. Martin

    V clanku se pise:
    – Sekce .bss je opakem sekce .data.
    – Heap standardně začíná za .bss a .data sekcemi.
    Ja jsem z toho nepochopil KDE se sekce .bss nachazi? Na screenshotu neni a ani v mem OllyDbg s otevrenym Crackme2 ji nevidim. Kde ji mam hledat?

    1. RubberDuck Autor příspěvku

      Sekce .bss není povinná. V mnoha případech (a je to standard Microsoftu) se v binárkách vůbec nevyskytuje. Proč? Protože jsou všechny tyto (datové) sekce pokryty jednou jedinou. V případě Microsoftu se jedná o .data sekci, v dalších případech o .rdata sekci. Ve specifických případech může být dokonce přítomna v sekci kódu. A to je případ i našeho crackme. Důvod, proč jsem se zatím tomuto hlouběji nevěnoval, je fakt, že může čtenáře spíše poplést než mu být přínosem. Pro tento okamžik je důležité mít pouze představu, že existují různé sekce a co přibližně obsahují. V pozdějších dílech (s rostoucí náročností crackmes) se tyto rozdíly budou postupně smývat, až zůstane děsivá představa jediné sekce obsahující vše.

      Abych tedy zodpověděl otázku: Otevři si OllyDbg, naloaduj crackme, stiskni ALT + M pro vyvolání okna paměti. Následně dvojklik na řádek crackme_ .data. Objeví se Dump okno. Od adresy 00403077 vidíš pouze samé nuly (na adrese 00403076 je NULL bajt ukončující textový řetězec). Koukni do kódu a vyhledej si adresy z rozsahu 00403000 – 00403FFF a konkrétně pak od 00403077 – 00403FFF.
      0040308D
      00403091
      00403077
      ….
      Všechny tyto adresy jsou buď inicializované na hodnotu 0/NULL nebo nejsou inicializované vůbec. A to jsou právě potenciální adepti pro sekci .bss.
      Tímto zjištěním jsem právě setřel rozdíl mezi jednotlivými datovými sekcemi, kdy všechny mohou být přítomny v jedné jediné sekci.
      Doufám, že jsem aspoň částečně pokryl svou odpovědí tvou otázku.

      1. Martin

        Dekuju, ano to uz mi dava smysl, nenapadlo me o tom takhle premyslet. Obzvlaste ty adresy (0040308D, 00403091, 00403077), to jsou regulerne variables, ale maji hodnotu 00, tak mi to nedoslo. Jsem zvyklej o promnnych premyslet ve forme int x = … a nejak doufam, ze maji v pameti vyhrazeny svoje mistecko, na ktery jim prece nikdo nesmi sahnout 🙂

        1. RubberDuck Autor příspěvku

          Oni mají to své místečko přesně přidělené a nikdo jim na něj nesahá 🙂 Není to tak, že jednou je adresou pro uložení 1234 a jindy 5678. Ta adresa je pevně daná, ale v případě .bss sekce, která má nulovou velikost u binárky na disku (té, která není nahraná do paměti) není třeba tuto informaci ukládat. Pokud vím, že je něco neinicializované nebo nulové, pak je to vždy nula 🙂
          Důvod, proč se jednotlivé sekce spojují do jedné je třeba následující: Pokud máme pět samostatných datových sekcí, musíme pro ně mít i pět hlaviček, aby systémový loader věděl, kam je v pamět umístit, jak velkou paměť jim přiřadit a jaká práva dané paměti nastavit. Je to úspora několika stovek bajtů, což je sice úsměvné, ale zásadně zjednodušuje práci s celou binárkou.
          Co se týká těch adres: Tohle jsou pevně dané adresy. Protože bylo crackme programováno v assembleru, není problém ho upravit tak, aby místo samostatných datových sekcí používalo například jen stack a situace se nezmění. Stejně tak bych mohl celou datovou sekci nadefinovat v sekci kódu. Sekce kódu má defaultně příznak ‚read-execute‘ (a AV produktům se moc nelíbí, pokud má binárka na disku sekci kódu ‚writable‘), ale já můžu za běhu tohle nastavení pozměnit a budu tak mít pouze jednu sekci pro všechno.

  3. Martin

    Drobne chybky:
    Calling convention je definici určující
    -> definice

    Následuje volání API funkce WriteProcessA
    ->WriteConsoleA

    Dump window (levé dolní okno v hlavním okně) data, která se na této adrese (0043000h) nachází.
    ->00403000h

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *