Cover
Zacznij teraz za darmo 8_Debugging.pdf
Summary
# Introductie tot dynamische analyse en debugging
Dit gedeelte introduceert de concepten van basis en geavanceerde dynamische analyse, en legt het belang van debugging uit als een methode om het gedrag van software te begrijpen.
### 1.1 Dynamische analyse
Dynamische analyse omvat het bestuderen van het gedrag van software tijdens de uitvoering ervan. Dit kan op twee niveaus worden onderverdeeld: basis en geavanceerd [3](#page=3).
#### 1.1.1 Basis dynamische analyse
Bij basis dynamische analyse wordt de binaire code wel uitgevoerd, en worden tools gebruikt om het gedrag van het draaiende proces te observeren. Gangbare tools hiervoor zijn onder andere Procmon, Process Explorer, Sandboxie, Wireshark en Regshot [3](#page=3).
#### 1.1.2 Geavanceerde dynamische analyse
Geavanceerde dynamische analyse, vaak samengevat als "debugging", opereert op een dieper niveau dan basis dynamische analyse. Het vereist een grondig begrip van de software-uitvoering om het gedrag te kunnen doorgronden en analyseren [4](#page=4) [6](#page=6).
### 1.2 Debugging
Debugging is een essentiële methode om het gedrag van software te begrijpen, door de code stap voor stap te volgen tijdens de uitvoering. Er bestaan twee hoofdsoorten debugging [4](#page=4):
#### 1.2.1 Source-level debugging
Dit is de meest gebruikelijke vorm van debugging, waarbij men de broncode van de software gebruikt. Dit type debugging vindt meestal plaats tijdens het programmeren en wordt ondersteund door geïntegreerde ontwikkelomgevingen (IDE's), zoals Visual Studio [4](#page=4).
#### 1.2.2 Assembly-level debugging
Ook wel low-level debugging genoemd, opereert dit type debugging direct op het niveau van de CPU. Assembly-level debuggers vereisen geen toegang tot de broncode, wat ze nuttig maakt voor het analyseren van software waarvoor de broncode niet beschikbaar is. Beide vormen van debugging maken gebruik van breakpoints en het stapsgewijs doorlopen van de code, zij het op verschillende abstractieniveaus [4](#page=4).
> **Tip:** Een dieper inzicht in geavanceerde statische analyse (het analyseren van assembly code) zal het gemakkelijker maken om debugging toe te passen [6](#page=6).
##### 1.2.2.1 Kernel mode vs. User mode debugging
Low-level debugging kan worden uitgevoerd in zowel User Mode als Kernel Mode [5](#page=5).
* **Kernel Mode Debugging:** Dit is complexer en vereist doorgaans twee verschillende besturingssystemen: één voor de debugger en één voor de te debuggen software (debuggee). Voor Windows-debugging is Microsoft WinDbg een veelgebruikte en gratis tool hiervoor. Via deze methode kan Windows, inclusief alle draaiende applicaties en threads, volledig worden gedebugd [5](#page=5).
* **User Mode Debugging:** Malware en de meeste andere software worden doorgaans gedebugd in User Mode [5](#page=5).
#### 1.2.3 De relatie tussen geavanceerde statische analyse en debugging
Debugging biedt een visuele weergave van de codeflow tijdens de uitvoering, wat het proces eenvoudiger maakt dan pure assembly-analyse. Door tools zoals IDA Pro te gebruiken om de flows van een sample te begrijpen en vervolgens dynamisch de variabelen te inspecteren wanneer data uit het geheugen wordt gelezen, kan men detecteren wat er precies gebeurt [6](#page=6).
> **Tip:** Hoewel debugging veel inzicht biedt, is het cruciaal om niet te verdwalen in details en de focus te houden op het grotere geheel [6](#page=6).
---
# Debugging tools en geheugenconcepten
Dit onderdeel behandelt diverse debugging tools en herhaalt fundamentele geheugenconcepten die essentieel zijn voor het begrijpen van programmadrivers en geheugemanipulatie [7](#page=7) [8](#page=8).
### 2.1 Debugging tools
Er is een breed scala aan user-mode debuggers beschikbaar, elk met hun eigen specificaties wat betreft ondersteuning voor architecturen (32-bit/64-bit) en besturingssystemen (Linux/Windows). Enkele veelgebruikte tools zijn [7](#page=7):
* OllyDbg [7](#page=7).
* Immunity Debugger [7](#page=7).
* IDA Pro Free [7](#page=7).
* WinDbg [7](#page=7).
* x64dbg [7](#page=7).
* Radare2 [7](#page=7).
* gdb [7](#page=7).
* Binary Ninja [7](#page=7).
Daarnaast bestaat er software zoals Cheat Engine, die functioneert als een geheugenscanner en debugger [7](#page=7).
Voor de doeleinden van deze cursus worden Immunity Debugger of x32dbg (een onderdeel van x64dbg) aanbevolen [7](#page=7).
### 2.2 Geheugenconcepten
Om effectief te kunnen debuggen, is een goed begrip van geheugenconcepten cruciaal.
#### 2.2.1 Fysiek geheugen versus logisch geheugen
* **Logisch geheugen (virtueel geheugen)**: Elk proces dat wordt uitgevoerd, krijgt een eigen toegewezen logische geheugenruimte. Deze logische geheugenruimte begint steevast bij adres $0x00000000$ (of $0x0000000000000000$ voor 64-bit systemen) voor elk proces [8](#page=8).
* **Fysiek geheugen**: Dit is het daadwerkelijke RAM-geheugen van de computer.
* **Vertaling**: Het besturingssysteem, specifiek de Windows Kernel, beheert de vertaling tussen logisch en fysiek geheugen met behulp van een paginatabel, die uniek is voor elk proces [8](#page=8).
#### 2.2.2 Pagina's en paginakaders
* **Pagina's**: De logische geheugenruimte van een proces is opgedeeld in eenheden die 'pagina's' worden genoemd [8](#page=8).
* **Paginakaders**: Het fysieke geheugen is opgedeeld in vergelijkbare eenheden, die 'paginakaders' heten. De paginatabel zorgt ervoor dat de pagina's van een proces worden toegewezen aan beschikbare paginakaders in het fysieke geheugen [8](#page=8).
De 'Memory' weergave in een debugger biedt inzicht in deze geheugenadressen en de inhoud ervan [9](#page=9).
> **Tip**: Het concept van logisch geheugen per proces zorgt voor isolatie; een proces kan niet zomaar het geheugen van een ander proces benaderen. Dit is een fundamenteel beveiligingsmechanisme.
---
# Processtart en geheugenstructuur
Dit gedeelte behandelt het proces van het laden van uitvoerbare bestanden in het geheugen en de daaruit voortvloeiende structuur van een draaiend proces, inclusief logisch geheugen en de stack.
### 3.1 Het laden van een uitvoerbaar bestand (image)
Het starten van een uitvoerbaar bestand, in Windows-terminologie een "image" genoemd, wordt afgehandeld door de Windows Image Loader. Dit is het eerste stuk code dat wordt uitgevoerd in een nieuw gebruikersmodusproces. De Image Loader voert verschillende cruciale taken uit om het proces in het geheugen te initialiseren [11](#page=11):
* Het mapt bestanden en modules naar specifieke secties in het geheugen [11](#page=11).
* Het registreert alle open handles die aan het proces zijn gekoppeld, inclusief handles naar kernelobjecten zoals bestanden en API's [11](#page=11).
* Het wijst een unieke Process ID (PID) toe aan het proces en registreert dit bij de kernel [11](#page=11).
* Het configureert de Process Environment Block (PEB), die essentiële informatie over het proces bevat [11](#page=11).
* Het zet de logische geheugenconfiguratie op, waaronder de heap-structuur [11](#page=11).
* Het creëert minimaal één thread, de daadwerkelijke "container" die code uitvoert [11](#page=11).
* Voor elke thread worden ook contextgegevens aangemaakt, zoals een Thread ID (TID), de Thread Environment Block (TEB), en de benodigde stacks [11](#page=11).
### 3.2 Structuur van een draaiend proces (32-bit)
Een draaiend 32-bits proces heeft de illusie van een privé geheugenbereik, typisch van `0x00010000` tot `0x7FFFFFFF`. Binnen dit bereik heeft het proces lineaire toegang, wat betekent dat de toegangstijd gelijk is, ongeacht of het eerste of laatste adres wordt benaderd. De belangrijkste componenten van de geheugenstructuur van een proces zijn [14](#page=14):
* **Logisch geheugen:** Elk proces heeft een eigen virtuele adresruimte [11](#page=11) [14](#page=14).
* **Stack:** Elke thread beschikt over minimaal één stack, die relatief klein is en gebruikt wordt voor tijdelijke opslag en het uitwisselen van argumenten tussen functieaanroepen [14](#page=14) [18](#page=18).
* **Heap:** Een globale heap wordt voornamelijk gebruikt voor dynamische geheugenallocaties tijdens de uitvoering van het programma [14](#page=14) [18](#page=18).
* **Code-secties (.text):** Deze secties bevatten de instructies die door de CPU worden opgehaald om de programmalogica uit te voeren. De uitvoering start bij het adres gespecificeerd door `AddressOfEntryPoint` [14](#page=14).
### 3.3 Geheugensecties
Het geheugen van een proces wordt vaak visueel voorgesteld met het hoogste adres onderaan. Het kan worden onderverdeeld in verschillende secties [19](#page=19):
1. **Data:** Bevat statische waarden, zoals strings, die bij de start van het proces worden geladen. Dit worden ook wel globale waarden genoemd [19](#page=19).
2. **Code:** Bevat de instructies die door de CPU worden opgehaald en die bepalen wat het programma doet [19](#page=19).
3. **Heap:** Gebruikt voor dynamische geheugenallocaties terwijl het programma draait. Dit wordt ook wel dynamisch geheugen genoemd [19](#page=19).
4. **Stack:** Gebruikt voor het opslaan van lokale variabelen per functie of subroutine, waardoor het zeer vluchtig is [19](#page=19).
### 3.4 Stack versus Heap
De stack en de heap zijn twee fundamentele geheugenstructuren die verschillend worden gebruikt en beheerd [18](#page=18):
* **Heap:**
* Is meer globaal in gebruik [18](#page=18).
* Klassen en objecten worden op de heap gecreëerd [18](#page=18).
* Moet actief worden beheerd door het programma; variabelen moeten expliciet worden verwijderd of vrijgegeven [18](#page=18).
* Het gebruik van de heap is langzamer [18](#page=18).
* Termen als `malloc` of `new` impliceren meestal heap-operaties [18](#page=18).
* **Stack:**
* Wordt per thread gebruikt [18](#page=18).
* Fungeert als tijdelijke opslag en voor het uitwisselen van argumenten tijdens functieaanroepen [18](#page=18).
* Variabelen die binnen functies worden gedeclareerd, worden op de stack opgeslagen [18](#page=18).
* Wordt automatisch vrijgegeven naarmate functieaanroepen worden voltooid en variabelen buiten scope gaan [18](#page=18).
* Termen als `PUSH`, `POP`, `CALL`, `RETURN`, `EBP` of `ESP` zijn typisch gerelateerd aan stack-operaties [18](#page=18).
#### 3.4.1 De stack en registers (ESP & EBP)
De stack werkt volgens het Last In – First Out (LIFO) principe en dient als kortetermijngeheugen, voornamelijk voor data-uitwisseling tussen functies. Het maakt gebruik van het `Push`-`Pull` mechanisme [20](#page=20):
* **`push`:** Plaatst data op de stack [20](#page=20).
* **`pop`:** Leest de laatst toegevoegde waarde van de stack [20](#page=20).
Deze operaties vinden plaats op de locatie die door de stack pointer wordt aangegeven [20](#page=20).
* **`ESP` (Stack Pointer):** Wijst naar het huidige top-adres van de stack [20](#page=20).
* **`EBP` (Base Pointer):** Wordt vaak gebruikt om de basis van de huidige stack-frame te markeren, wat helpt bij het efficiënt benaderen van lokale variabelen en argumenten [22](#page=22).
Andere instructies die direct met de stack werken zijn `call`, `leave`, `enter` en `return` [20](#page=20).
> **Tip:** Het begrijpen van de stack en de werking van `ESP` en `EBP` is cruciaal voor debugging, omdat de stack de "call stack" vormt die aangeeft hoe functies elkaar hebben aangeroepen [20](#page=20).
#### 3.4.2 Push en Pop in actie
Het `PUSH` en `POP` mechanisme demonstreren hoe data op de stack wordt geplaatst en verwijderd, wat leidt tot veranderingen in de `ESP` [21](#page=21).
> **Voorbeeld:**
> Stel dat `ESP` initieel op `0x00140008` staat.
>
> 1. **`PUSH EAX`**: Als `EAX` de waarde `0x50505050` bevat, wordt deze waarde op de stack geplaatst, en `ESP` wordt verlaagd om te wijzen naar de nieuwe top. De stack kan er dan als volgt uitzien, met `ESP` op `0x00140004` [21](#page=21).
> 2. **`POP EBX`**: Als de waarde op de top van de stack (`0x50505050`) wordt gepopd naar `EBX`, wordt deze waarde aan `EBX` toegewezen, en `ESP` wordt verhoogd om te wijzen naar de volgende item op de stack. Na deze operatie zou `ESP` terugkeren naar `0x00140008` [21](#page=21).
#### 3.4.3 Functieaanroepen en de stack
Bij het aanroepen van functies, met name in Assembly-code, speelt de stack een centrale rol [22](#page=22).
* **Argumenten pushen:** Voordat een functie wordt aangeroepen, worden de benodigde argumenten op de stack gepusht. Als `nr1` en `nr2` de argumenten zijn, worden ze in omgekeerde volgorde gepusht. Bijvoorbeeld, als `ESP` `0x12F04C` is:
* `PUSH nr2` → `ESP` wordt verlaagd naar `0x12F048` [22](#page=22).
* `PUSH nr1` → `ESP` wordt verlaagd naar `0x12F044` [22](#page=22).
* **`CALL` instructie:** De `CALL` instructie initieert de functieaanroep en voert de volgende stappen uit [22](#page=22):
1. `PUSH EIP`: Het adres van de volgende instructie na de `CALL` (het retouradres) wordt op de stack gepusht, resulterend in `ESP` op `0x12F040` [22](#page=22).
2. `PUSH EBP`: De huidige `EBP` wordt op de stack gepusht om de vorige stack frame op te slaan.
3. `ESP` wordt verder verlaagd om ruimte te maken voor lokale variabelen binnen de functie, bijvoorbeeld naar `0x12F034` [22](#page=22).
4. `EIP` wordt ingesteld op het geheugenadres van de aan te roepen functie [22](#page=22).
Hierdoor bevat de stack alle informatie die nodig is om de functie correct uit te voeren en na voltooiing terug te keren naar de juiste plaats in de aanroepende code [22](#page=22).
---
# Debugging technieken en geavanceerde concepten
Dit onderwerp duikt dieper in methoden om de uitvoering van code te controleren en te manipuleren tijdens het debuggen, inclusief het omgaan met uitzonderingen en het aanpassen van geheugenrepresentaties zoals endianness.
### 4.1 Endianness
Endianness verwijst naar de manier waarop bytes in het geheugen worden geïnterpreteerd, met name de volgorde van bytes en hun transmissie over digitale links. Het onderscheidt code van data. Code wordt opgeslagen in de volgorde waarin deze moet worden uitgevoerd, terwijl waarden zoals strings en pointers in omgekeerde volgorde worden opgeslagen [23](#page=23) [24](#page=24).
**Voorbeeld van endianness:**
* **Big Endian:** De waarde `0x12345678` wordt opgeslagen als `0x12`, `0x34`, `0x56`, `0x78` [24](#page=24).
* **Little Endian:** Dezelfde waarde `0x12345678` wordt opgeslagen als `0x78`, `0x56`, `0x34`, `0x12` [24](#page=24).
Een string zoals `"FLAG"` (`0x464c4147`) wordt in Little Endian geheugen opgeslagen als `\x47\x41\x4c\x46` [24](#page=24).
### 4.2 Debugging
Debugging stelt u in staat de status van een programma op elk willekeurig moment tijdens de uitvoering te zien. Dit omvat het visualiseren van de inhoud van registers, geheugen, stack en heap. Het is belangrijk om te beseffen dat tijdens het debuggen malware ook wordt uitgevoerd, wat een gevaarlijke methode kan zijn [26](#page=26).
#### 4.2.1 Stepping technieken
Net zoals in programmeeromgevingen zoals Visual Studio, zijn er verschillende stepping technieken beschikbaar [27](#page=27):
* **Single Stepping:** Hiermee beweegt u één regel code per keer door het programma. Dit is alleen nuttig om specifieke secties van de code te begrijpen, aangezien het bijna nooit haalbaar is om een heel programma op deze manier te doorlopen [27](#page=27).
* **Step Into:** Wanneer een functie wordt aangeroepen, stapt de debugger in de functie en doorloopt deze regel voor regel [27](#page=27).
* **Step Over:** Wanneer een functie wordt aangeroepen, wordt deze volledig uitgevoerd en de volgende instructie die u ziet, is die na het retourneren van de functie. Het is belangrijk om te weten dat sommige functies nooit retourneren. Stepping over is nuttig bij API-aanroepen, omdat u meestal weet wat deze functies doen [27](#page=27).
* **Continue running:** Laat het programma lopen totdat het eindigt, er een uitzondering optreedt, of een breakpoint wordt geraakt [27](#page=27).
#### 4.2.2 Breakpoints
Breakpoints kunnen worden ingesteld op geheugenadressen om de uitvoering van het programma op een specifiek punt te pauzeren. Alleen locaties met code kunnen breakpoints bevatten. Een effectieve methode is om de code in een disassembler zoals IDA Pro te bekijken en nuttige adressen voor breakpoints te noteren. Een breakpoint breekt altijd vóór de uitvoering van de code op dat adres [28](#page=28).
**Breakpoint Voorbeeld 1:**
Als een functie wordt aangeroepen op adres `0x0040100D` (`call eax`), zal het plaatsen van een breakpoint daar de programma-uitvoering op die locatie stoppen [28](#page=28).
**Breakpoint Voorbeeld 2:**
Bij de API-functie `CreateFileW`, waar de bestandsnaam in een register (zoals `EDX`) wordt gepusht maar niet direct zichtbaar is in de code, kunt u een breakpoint zetten op het adres van de `CALL CreateFileW` instructie (bijvoorbeeld `0x00401055`). Wanneer het breakpoint wordt geraakt, kunt u de inhoud van het register `EDX` inspecteren om de bestandsnaam te achterhalen [29](#page=29).
#### 4.2.3 Uitzonderingen (Exceptions)
Debuggers worden vaak gebruikt om uitzonderingen te detecteren zonder dat de applicatie crasht. Dit werkt vergelijkbaar met de "source-level" debugging in IDE's waar het programma pauzeert bij een fout. Wanneer een breakpoint wordt gezet, wordt de instructie `0xCC` (INT3) toegevoegd aan het programma, wat een interrupt genereert die de debugger activeert [30](#page=30).
Wanneer een programma crasht, controleert Windows of er een debugger is aangekoppeld. Als dat zo is, krijgt de debugger de eerste kans om de uitzondering af te handelen. Als er geen debugger is of deze de uitzondering doorgeeft, kan het programma de uitzondering zelf afhandelen (bijvoorbeeld met `try-catch`). Als het programma de uitzondering niet kan afhandelen, wordt deze "omhoog gegooid" naar de ouder van het programma, wat meestal resulteert in een crash [30](#page=30).
#### 4.2.4 Modifying execution (Noppen)
Een significant voordeel van live debugging is de mogelijkheid om de uitvoering van code te wijzigen terwijl deze plaatsvindt. De meest voorkomende wijziging is "noppen", waarbij een instructie (zoals een aanroep of sprong) effectief wordt "uitgewist" uit de uitvoering. Dit gebeurt door de instructie te vervangen door één of meer `NOP` (No Operation) codes, waarvan `0x90` een bekende opcode is [31](#page=31).
Het is belangrijk om bij het wijzigen van instructies deze te vervangen door instructies van gelijke lengte om offsets in het PE-image niet te verstoren. Een `NOP` is een enkele opcode, waardoor elke instructie kan worden vervangen door meerdere `NOP`s om een zogenaamde "NOP sled" te creëren [31](#page=31).
Alle aanpassingen tijdens het debuggen gebeuren in het live geheugen, maar de gewijzigde code kan indien nodig worden geëxporteerd als een nieuw uitvoerbaar bestand [31](#page=31).
> **Tip:** Veel malware-samples maken gebruik van deze "nopping" techniek. Wees voorzichtig met het toepassen van deze methode, vooral tijdens examens.
---
# Praktische debugging en lab oefeningen
Dit gedeelte behandelt de praktische aspecten van debugging, inclusief het gebruik van debuggers, hun interface en het instellen van breakpoints, gevolgd door lab oefeningen.
### 5.1 Debuggen: De basisprincipes
Het debuggen van software omvat het proces van het identificeren en corrigeren van fouten. Hoewel er veel verschillende debuggers bestaan, zijn hun kernfunctionaliteiten grotendeels vergelijkbaar [33](#page=33).
#### 5.1.1 Starten van een debugger
Bij het starten van een debugger zijn er twee primaire methoden:
* **Open een nieuw proces**: Deze methode start het programma direct onder controle van de debugger. Dit kan handig zijn om anti-debugging mechanismen te triggeren die mogelijk aanwezig zijn in de malware. Bovendien biedt het de mogelijkheid om argumenten mee te geven aan het uit te voeren bestand [33](#page=33).
* **Attache aan een lopend proces**: Hierbij koppelt de debugger zich aan een proces dat al actief is. Het voordeel hiervan is dat sommige anti-debugging technieken omzeild kunnen worden en de uitvoering sneller kan zijn. Het nadeel is dat men geen inzicht heeft in wat er gebeurde vóór het moment van aanhaken [33](#page=33).
> **Tip:** In de meeste gevallen maakt de keuze tussen 'open' en 'attach' geen significant verschil. Gebruik echter waar mogelijk de 'open' methode. Het is aan te raden om de debugger als administrator uit te voeren. Het leren en effectief gebruiken van de functietoetsen is zeer nuttig [34](#page=34).
Wanneer een programma wordt gestart met een debugger, zal deze standaard pauzeren bij de `WinMain` functie of het Entry Point zoals gedefinieerd in de PE-header [34](#page=34).
#### 5.1.2 De debugger interface
De interface van een debugger bestaat uit verschillende belangrijke vensters die inzicht geven in de staat van het programma tijdens de uitvoering [35](#page=35):
* **CPU Instructions (Disassembler Window)**: Toont de machinecode van het programma, gedesassembeld naar leesbare instructies [35](#page=35).
* **Registers & Flags**: Geeft de huidige inhoud van de CPU-registers en de statusvlaggen weer. Deze worden constant bijgewerkt en tonen de actuele staat van de processor [35](#page=35).
* **Full Memory Dump**: Biedt een weergave van het volledige geheugen dat door het programma wordt gebruikt [35](#page=35).
* **The Stack**: Toont de inhoud van de stack, een geheugenstructuur die wordt gebruikt voor functieaanroepen, lokale variabelen en return-adressen [35](#page=35).
> **Tip:** Veel van deze vensters zijn toegankelijk via sneltoetsen met `Alt+[letter]`. Daarnaast biedt de debugger controle over de programma-uitvoering, zoals stappen door de code [36](#page=36).
##### 5.1.2.1 Belangrijke registers
Specifieke registers zijn cruciaal voor het begrijpen van de programmatoestand:
* **EDX**: Kan wijzen naar een locatie in het geheugen van het actieve proces, bijvoorbeeld `meat.exe` [37](#page=37).
* **ESP (Extended Stack Pointer)** en **EBP (Extended Base Pointer)**: Deze registers geven de positie van de stack pointer en de basis van de huidige stack frame aan [37](#page=37).
* **EIP (Extended Instruction Pointer)**: Dit register is essentieel omdat het de *volgende* instructie aangeeft die door de CPU zal worden uitgevoerd [37](#page=37).
##### 5.1.2.2 De Stack
De stack is een dynamische datastructuur die cruciaal is voor het beheer van functieaanroepen en lokale variabelen. Belangrijke indicatoren op de stack zijn [38](#page=38):
* **Current Stack Pointer (ESP)**: Geeft de huidige top van de stack aan [38](#page=38).
* **Current EBP**: Markering het einde van de vorige stack frame en dient als referentiepunt voor lokale variabelen [38](#page=38).
* **Return Address (oud EIP)**: Dit is het adres waar het programma naartoe moet terugkeren na het voltooien van de huidige functieaanroep [38](#page=38).
#### 5.1.3 Breakpoints instellen
Breakpoints zijn signalen die de debugger vertellen om de uitvoering van het programma te pauzeren op specifieke punten in de code [39](#page=39).
* **Tijdens pauze**: Wanneer het programma pauzeert bij het entry point (bijvoorbeeld `WinMain` of OEP), is dit een goed moment om breakpoints in te stellen [39](#page=39).
* **Methoden voor instellen**: Breakpoints kunnen worden ingesteld door naar het gewenste adres te scrollen in het disassembler venster, of door het adres direct in te typen op de command line van de debugger, bijvoorbeeld `bp 401000` [39](#page=39).
* **Breakpoints Window**: Dit venster biedt een overzicht van alle ingestelde breakpoints, waar ze ingeschakeld of uitgeschakeld kunnen worden [39](#page=39).
* **Belangrijk**: Het is cruciaal om breakpoints te plaatsen in de code van het hoofdprogramma. Breakpoints in systeembibliotheken zoals `ntdll.dll` zijn vaak van beperkt nut voor de analyse van de applicatie zelf [39](#page=39).
#### 5.1.4 Tools en overwegingen
* **Immunity Debugger**: Een oudere debugger die nog steeds werkt, met name voor 32-bit applicaties. Het vereist Python 2.x en de installer wordt meegeleverd met de debugger. De ontwikkeling ervan is gestopt [40](#page=40).
* **x64dbg en x32dbg**: Deze debuggers worden momenteel beschouwd als industriestandaarden. Ze bieden uitgebreide functionaliteiten voor zowel 32-bit als 64-bit applicaties [40](#page=40).
* **Andere tools**: Tools zoals Binary Ninja en IDA Pro (met hun debugging features) kunnen ook nuttig zijn [40](#page=40).
> **Tip:** Vergeet niet om basisanalyse uit te voeren voordat je begint met geavanceerd debuggen. Kijk naar het grotere plaatje: waar worden functies aangeroepen en welke data wordt er aan meegegeven [41](#page=41)?
### 5.2 Demo en Lab Oefeningen
Het analyseren van een demonstratievoorbeeld (demo) van een eerder geanalyseerd sample kan zeer leerzaam zijn [41](#page=41).
#### 5.2.1 Opgave Lab Oefeningen
Voor de lab oefeningen is de opdracht om met behulp van basis- en geavanceerde analyse technieken alle 'flags' te vinden in de onderstaande uitvoerbare bestanden [42](#page=42):
* `L7a-CTF.exe` (MD5: `2352bb086d1896c80d0d818724bf21b1`) [42](#page=42).
* `L7b-ATM.exe` (MD5: `00142388fe661d303ed892a75bda0e1c`) [42](#page=42).
* `L7c-Crypto.exe` (MD5: `a158b630c7cfe877fa45e57feb0e01d9`) [42](#page=42).
* `(SameGame.exe)` (MD5: `6aa7a9e95b196e0ddd111442c8baed50`) [42](#page=42).
> **Tip:** Deze oefeningen worden eenvoudiger zodra de debugging-technieken beheerst worden [42](#page=42).
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Basis statische analyse | Een methode van codeanalyse waarbij het uitvoerbare bestand niet wordt uitgevoerd. Hierbij worden tools gebruikt om informatie te extraheren zoals strings, bestandstype, meta-gegevens en detectie van verpakte executables. |
| Basis dynamische analyse | Een methode van codeanalyse waarbij het uitvoerbare bestand wel wordt uitgevoerd in een gecontroleerde omgeving. Hierbij worden tools gebruikt om het gedrag van het programma te monitoren, zoals procesactiviteit, registerwijzigingen en netwerkverkeer. |
| Geavanceerde statische analyse | Een diepgaande methode van codeanalyse die zich richt op het analyseren van de assembly code en CPU-instructies van een binair bestand zonder het uit te voeren. Decompiler tools kunnen hierbij helpen om de code te interpreteren. |
| Geavanceerde dynamische analyse | Een methode van codeanalyse die het uitvoeren van het programma combineert met geavanceerde monitoringtechnieken om gedetailleerd inzicht te krijgen in het gedrag, met name door het gebruik van debuggers. |
| Debugging | Het proces van het vinden en oplossen van fouten in software. Bij het analyseren van binaire bestanden wordt debugging gebruikt om de uitvoering van code stap voor stap te volgen en de interne staat van het programma te inspecteren. |
| Source-level debugging | Het type debugging dat bekend is van softwareontwikkelomgevingen zoals Visual Studio, waarbij code wordt geanalyseerd op basis van de broncode. |
| Assembly-level debugging | Het type debugging dat op CPU-niveau opereert en directe analyse van machinecode instructies mogelijk maakt. Dit vereist geen toegang tot de broncode. |
| Kernel Mode | Een operationele modus van het besturingssysteem waarin code met maximale privileges draait. Kernel-mode debugging is complexer en vereist doorgaans twee besturingssystemen. |
| User Mode | Een operationele modus van het besturingssysteem waarin applicaties draaien met beperkte privileges. Malware en software worden meestal gedebugd in User Mode. |
| Fysiek geheugen | Het daadwerkelijke RAM-geheugen in een computer. |
| Logisch geheugen (Virtueel geheugen) | Een geheugenruimte die aan elk proces wordt toegewezen door het besturingssysteem. Dit maakt het mogelijk dat elk proces de illusie heeft van een eigen, lineaire geheugenruimte. |
| Paginatabel | Een datastructuur die door het Windows Kernel wordt gebruikt om de mapping tussen logische geheugenadressen (pagina's) en fysieke geheugenadressen (paginaframes) bij te houden. Deze tabel is uniek per proces. |
| Windows Image Loader | Het onderdeel van het Windows besturingssysteem dat verantwoordelijk is voor het laden van uitvoerbare bestanden (images) en hun modules in het geheugen van een nieuw proces. |
| Process Environment Block (PEB) | Een datastructuur die door het besturingssysteem wordt gebruikt om informatie op te slaan over een proces, zoals de command-line argumenten, geladen modules en de locatie van de heap. |
| Thread Environment Block (TEB) | Een datastructuur die vergelijkbaar is met de PEB, maar specifiek informatie bevat over een thread binnen een proces, zoals de stack en registers. |
| Stack | Een geheugenstructuur die per thread wordt gebruikt voor het opslaan van lokale variabelen, functieparameters en returnadressen. Het werkt volgens het Last-In, First-Out (LIFO) principe. |
| Heap | Een geheugenruimte die dynamisch door een programma wordt gebruikt voor het toewijzen en vrijgeven van geheugen tijdens de uitvoering. Dit wordt vaak gebruikt voor objecten en grotere datastructuren. |
| ESP (Extended Stack Pointer) | Een register dat de huidige top van de stack aangeeft. |
| EBP (Extended Base Pointer) | Een register dat vaak wordt gebruikt om het begin van de stack frame van een functie aan te geven, wat helpt bij het beheren van lokale variabelen en parameters. |
| PUSH | Een instructie die een waarde op de stack plaatst, waarbij de stack pointer (ESP) wordt verlaagd. |
| POP | Een instructie die de bovenste waarde van de stack verwijdert en deze opslaat in een register, waarbij de stack pointer (ESP) wordt verhoogd. |
| CALL | Een instructie die de uitvoering naar een andere locatie (een functie) stuurt en het huidige instructieadres (EIP) op de stack plaatst om later terug te kunnen keren. |
| RETURN | Een instructie die de uitvoering terugstuurt naar de locatie na de CALL-instructie, door het opgeslagen EIP van de stack te halen. |
| Endianness | De volgorde waarin bytes van een meerbyte-waarde worden opgeslagen of verzonden in computergeheugen of via netwerken. Little-endian slaat de minst significante byte eerst op, terwijl big-endian de meest significante byte eerst opslaat. |
| Breakpoint | Een punt in de code waar de uitvoering van een programma wordt onderbroken, zodat de programmeur de status van het programma kan inspecteren. |
| INT3 (Interrupt 3) | Een speciale interrupt die door de processor wordt gegenereerd en wordt gebruikt om breakpoints te implementeren. Het instruciecode is `\xCC`. |
| Exception | Een onverwachte gebeurtenis die de normale uitvoering van een programma verstoort, zoals een deling door nul of een geheugenfout. Debuggers kunnen worden gebruikt om exceptions te vangen. |
| NOP (No Operation) | Een instructie die niets doet en de programmateller (EIP) simpelweg naar de volgende instructie verhoogt. Wordt vaak gebruikt om code te verwijderen of om ruimte te creëren. |
| NOP sled | Een reeks NOP-instructies die aan het begin van een code-injectie wordt geplaatst. Als de uitvoering ergens in de NOP-sled terechtkomt, zal het programma sequentieel door de NOPs lopen totdat de daadwerkelijke payload wordt bereikt. |
| WinMain | De standaard entry point functie voor Windows applicaties. |
| OEP (Original Entry Point) | Het oorspronkelijke startadres van een uitvoerbaar bestand, zoals gedefinieerd in de PE-header. |
| MD5 | Message-Digest Algorithm 5, een cryptografische hashfunctie die wordt gebruikt om de integriteit van bestanden te verifiëren aan de hand van een unieke hashwaarde. |