2 september 2026 • Jorn Heibrink • Laatst bewerkt op 7 oktober 2026

Modulaire malware

Leestijd: 2 minuten

Voor het Surelock Red Team zijn we continu bezig met het verbeteren en uitbreiden
van de technische capaciteiten. Intern wordt veel onderzoek gedaan en de resultaten
van deze onderzoeken willen wij graag delen. Het vergroten van kennis zorgt
namelijk ook voor het vergroten van awareness.

Het onderwerp wat bij dit eerste artikel centraal staat is de zogenaamde agent die
gebruikt wordt tijdens de simulaties. De agent is het programma wat wordt
geïnstalleerd op een doelsysteem om van afstand acties uit te kunnen voeren.
Hiermee kan gecontroleerd het gedrag van malware gesimuleerd worden.

Om een zo breed mogelijk spectrum aan malware en de bijbehorende acties te
kunnen simuleren, was het nodig om onze agent uit te breiden. Het probleem hierbij is dat het toevoegen van functionaliteiten kan zorgen voor een grotere voetafdruk, wat de kans op detectie verhoogt. Daarom is er gezocht naar een modulaire oplossing, om nog steeds alle geavanceerde functionaliteiten uit te kunnen voeren, zonder dat dit te veel impact heeft op de voetafdruk van de agent.

De optie die hier het beste uit de bus gekomen is, is het uitvoeren van zogenaamde Object Files. Deze methode is gekozen met de volgende overwegingen:

  • Kleine voetafdruk vergeleken met traditionele methodes zoals reflective DLL injection.
  • Relatief eenvoudig te verwerken.
  • Uit te voeren in-memory.
  • Gebruikt door bekende C2 frameworks (Cobalt Strike, Sliver, Havoc, etc.).
  • Er bestaat al veel ondersteuning voor.

Genoeg redenen om dit te onderzoeken en te implementeren in de agents van Surelock.

Wat is een Object File?

Een Object File, ook wel Common Object File Format (COFF), is een bestand wat gegenereerd wordt door een compiler tijdens het compileren van een programma. Meerdere Object Files worden vervolgens aan elkaar verbonden door een linker om het uiteindelijke programma te genereren:

Gegeneerde bestanden die gelinkt aan elkaar zijn door een compiler die het uiteindelijke programma genereren

De compiler zet de code om in individuele object files. Deze object files maken gebruik van bijna dezelfde structuur als een executable (.exe); het Portable Executable (PE) formaat. Het enige verschil is dat een object file een extra symbol tabel heeft.

Deze symbol tabel bevat de namen van alle externe variabelen die gebruikt worden door het object. Bijvoorbeeld, wanneer de code in main.cpp gebruik maakt van een functie uit functies.cpp genaamd functie1, zal deze geregistreerd staan in de symbol tabel van main.o.

De functie staat dus wel geregistreerd binnen main.o, het object weet alleen niet op welke locatie deze functie te vinden is. De linker zorgt er voor dat deze functies het correcte adres krijgen in de uiteindelijke PE app.exe.

Wanneer we een object file willen uitvoeren, moeten we er voor zorgen dat we net als een linker de symbol tabel kunnen verwerken naar de daadwerkelijke locatie van de functies. Hiervoor moeten we eerst de structuur van een COFF begrijpen, dus de structuur van een PE.

Wat is een Portable Executable (PE)

Een Portable Executable is het formaat wat Windows gebruikt voor programma’s. Het bevat verschillende sections met alle data die nodig is om het programma uit te voeren. Deze sections zijn gestructureerd aan de hand van de zogenaamde headers. De headers bepalen bijvoorbeeld hoe groot de specifieke sections zijn, en waar ze te vinden zijn in memory.

Als we een PE onder de loep nemen met PE-bear, zien we dat deze bestaat uit meerdere sections:

Verschillende sections met alle data die nodig zijn om een programma uit te voeren

Een aantal relevante sections voor dit onderzoek zijn:

  • .text wordt gebruikt om de machine code van het programma op te slaan.
  • .data wordt gebruikt om globale waardes van de code op te slaan die al ingesteld zijn.
  • .rdata wordt gebruikt om Read-Only globale waardes van de code op te slaan. Dit zullen bijvoorbeeld const waardes zijn die niet binnen een functie vallen.
  • .pdata wordt gebruikt om functies op te slaan die gebruikt worden voor het afhandelen van foutmeldingen.
  • .xdata wordt gebruikt om de daadwerkelijke foutmeldingen op te slaan.
  • .bss wordt gebruikt om globale waardes van de code op te slaan die nog niet ingesteld zijn. Aangezien deze niet ingesteld zijn, zal dit leeg zijn voordat het programma uitgevoerd wordt.
  • .reloc wordt gebruikt om informatie op te slaan over de locatie van het programma in memory. De compiler gaat er van uit dat het programma op een specifiek basisadres in memory uitgevoerd wordt. Als dit adres anders is dan verwacht moeten de locaties van de variabelen opnieuw bepaald worden. Dit kan berekend worden met behulp van de waardes in de Relocation Table.

Uitvoeren van een PE

Tijdens het uitvoeren van de PE moet er voor gezorgd worden dat alle verschillende functies en variabelen uit de sections te vinden zijn. We nemen het volgende simpele C programma als voorbeeld:


#include <stdio.h>

char cVoorbeeld[12] = "Surelock\n";

int main(void) {
      printf("%s", cVoorbeeld);
}

Wanneer we dit uitvoeren binnen een debugger zoals IDA, zien we dat het omgezet is naar de volgende machine code:


.text:00007FF7DAA11000 main proc near
.text:00007FF7DAA11000 sub     rsp, 28h
.text:00007FF7DAA11004 lea     rdx, cVoorbeeld
.text:00007FF7DAA1100B lea     rcx, Format
.text:00007FF7DAA11012 call    printf
.text:00007FF7DAA11017 nop
.text:00007FF7DAA11018 xor     eax, eax
.text:00007FF7DAA1101A add     rsp, 28h
.text:00007FF7DAA1101E retn
.text:00007FF7DAA1101E main endp

Waar cVoorbeeld verwijst naar het volgende adres in de .data section:

.data:00007FF7DAA31000 cVoorbeeld db 'Surelock',0Ah,0

En Format naar:

.data:00007FF7DAA3100C Format db '%s',0

De code uit de .text sectie verwijst dus naar de globale variabelen in .data aan de hand van de locatie in memory. Hierbij is ook te zien dat de compiler een globale waarde aanmaakt voor de "%s" waarde, welke door IDA correct geïnterpreteerd wordt als het format.

Linkers

Maar wat nu als we het voorbeeldprogramma opsplitsen in twee bestanden? In bestand1.c hebben we de volgende code:

char cVoorbeeld[12] = "Surelock\n";

En in bestand2.c hebben we de volgende code:


#include <stdio.h>
#include "bestand1.c"

extern char cVoorbeeld[12];

int main(void) {
     printf("%s", cVoorbeeld);
}

Als we bestand2.c compilen naar een object, maar niet linken, zien we het volgende wanneer we het disassemblen:

main:
0000000000000000: 48 83 EC 28        sub         rsp,28h
0000000000000004: 48 8D 15 00 00 00  lea         rdx,[cVoorbeeld]
                  00
000000000000000B: 48 8D 0D 00 00 00  lea         rcx,[$SG9917]
                  00
0000000000000012: E8 00 00 00 00     call        printf
0000000000000017: 90                 nop
0000000000000018: 33 C0              xor         eax,eax
000000000000001A: 48 83 C4 28        add         rsp,28h
000000000000001E: C3                 ret

Wanneer we vervolgens naar het adres van cVoorbeeld kijken zien we dat deze wijst naar 0x00000000 en dus leeg is. Als we naar de symbol table kijken bevestigen we dat cVoorbeeld aanwezig is als externe waarde:


> dumpbin bestand2.obj /SYMBOLS

Dump of file bestand2.obj

File Type: COFF OBJECT

COFF SYMBOL TABLE

[...]

009 00000000 SECT3  notype       External     | cVoorbeeld

[...]

Als we dit object uit willen voeren in memory, moeten we weten waar de locatie van deze functie is. Hiervoor implementeren we een Object Loader die we ook in onze agent kunnen gebruiken.

De Object Loader

Het doel van de Object Loader is om een object bestand uit te lezen en deze uit te voeren in memory. Hiervoor moeten al de symbols verwerkt worden zodat ze wijzen naar het correcte adres in memory. Het kan dus eigenlijk gezien worden als een Linker die we verwerken in de agent.

Als we dit zelf willen implementeren moeten globaal de volgende stappen doorlopen worden:

  1. Parsen van het COFF object bestand.
  2. Memory grootte berekenen en toewijzen gebaseerd op het COFF bestand.
  3. Kopieer de onderdelen naar de nieuwe memory.
  4. Parse function symbols en verwijs deze naar het correcte adres.
  5. Vind het beginpunt van het object door over de symbols te loopen.
  6. Voer de geladen code uit.
  7. Lees de resultaten uit.

In de komende secties worden deze stappen gegroepeerd verder toegelicht.

Stap 1: COFF parsen

Om een COFF object te parsen moeten we eerst weten waar alle onderdelen te vinden zijn. Zoals eerder benoemd kunnen we dit bepalen aan de hand van de header. De specificatie van deze header is te vinden in de documentatie van Microsoft. In deze sectie is een poging gedaan om deze documentatie samen te vatten.

Een COFF bestand heeft de volgende structuur:

COFF header

De header begint direct aan het begin van het bestand, dus bij memory offset 0. Vanuit daar volgen de volgende onderdelen:

  • Machine: Het type machine waar het bestand op uitgevoerd kan worden. Voor ons zal dit 0x8664 zijn (x64).
  • NumberOfSections: Hoeveel sections er aanwezig zijn in het bestand.
  • TimeDateStamp: Wanneer het bestand gemaakt is.
  • PointerToSymbolTable: Een pointer naar de locatie van de Symbol Table gebaseerd op de offset, handig als we directe toegang willen tot de Symbol Table.
  • NumberOfSymbols: Hoeveel symbols er aanwezig zijn in de Symbol Table.
  • SizeOfOptionalHeader: De grootte van de optionele header, dit is alleen relevant voor een PE, bij een Object zal dit 0 zijn.
  • Characteristics: Flags die bepalen wat voor type bestand het is.

Dus wanneer we toegang willen hebben tot de Symbol Table, kunnen we het adres op de volgende manier berekenen:

Basisadres in memory + PointerToSymbolTable

 

Sections header

Na de COFF header krijgen we de section headers, deze headers bevatten informatie over de specifieke sections. Elke section header bevat de volgende informatie:

  • Name: De naam van de section (.text, .data, etc.).
  • VirtualSize: De virtuele grootte wanneer het in memory geladen is. Dit is 0 voor object files.
  • VirtualAddress: De eerste byte van de section, relatief tot het startpunt in memory. Dit is ook 0 voor object files.
  • SizeOfRawData: De grootte van de section on disk.
  • PointerToRawData: De offset van de data van de section.
  • PointerToRelocations: De offset van de relocations die horen bij deze section.
  • PointerToLinenumbers: Niet meer ondersteund, dus zal 0 moeten zijn.
  • NumberOfRelocations: De hoeveelheid aan relocations voor deze section.
  • NumberOfLinenumbers: Niet meer ondersteund, dus zal ook 0 moeten zijn.
  • Characteristics: Flags die bepalen wat voor type section het is.

Als we tijdens het parsen toegang willen tot een van de section headers, kunnen we daar de volgende waardes voor gebruiken:

Basisadres in memory + grootte van COFF header + grootte van section header * Nummer van de 
section header die we willen parsen

Of als we direct bij de data in de section willen, kunnen we het volgende gebruiken:

Basisadres in memory + PointerToRawData van de section

 

Relocation tabel

De Relocation Tabel bevat informatie over hoe de data in de sections aangepast moet worden om in memory geladen te kunnen worden. Voor elke section bestaat een array, waar elke waarde de volgende informatie bevat:

  • VirtualAddress: Het adres van het item waar de relocation uitgevoerd moet worden, relatief tot het begin van de section.
  • SymbolTableIndex: De index van de bijbehorende waarde in de Symbol Table.
  • Type: Het soort relocation dat uitgevoerd moet worden.

Dit kan gebruikt worden om te bepalen wat uiteindelijk het absolute adres is van een symbol binnen het hele object:

Absolute adres van een symbol = Adres van de symbol tabel + SymbolTableIndex * De Offset 
gebaseerd op het Type

Vervolgens kan het relatieve adres tot de section bepaald worden met:

Relatieve adres van een symbol = Absolute adres van een symbol - Absolute adres van de section

 

Symbol Tabel

De Symbol Tabel bevat alle data die we nodig hebben over de specifieke symbols. Het bevat een array met voor elke symbol de volgende data:

  • Name: De naam van de symbol. Het datatype is afhankelijk van de lengte van de naam.
  • Value: Deze waarde is afhankelijk van de SectionNumber en StorageClass.
  • SectionNumber: De section waar de symbol bij hoort.
  • Type: Het type wat hoort bij de Symbol, zoals functie, integer, struct, etc.
  • StorageClass: Bepaalt hoe de data opgeslagen is in de symbol. Bijvoorbeeld of het een externe waarde is.
  • NumberOfAuxSymbols: De hoeveelheid aan auxiliary symbols achter de entry in de tabel. Geeft meer informatie over gerelateerde objecten.

Met de waarde van de StorageClass kunnen we bepalen waar de symbol zich moet bevinden. Wanneer deze waarde bijvoorbeeld IMAGE_SYM_CLASS_EXTERNAL is bij een externe symbol, betekent dat het volgende volgens de Microsoft documentatie:

“De symbol is te vinden in een ander COFF object. Wanneer het sectienummer 0 is, zal de Value de grootte zijn van de symbol. Als het niet 0 is, is de Value het relatieve adres van de symbol.”

Dus aan de hand van de StorageClass weten we hoe we de Value kunnen gebruiken om het adres te berekenen.

 

Symbol strings

Het laatste deel bevat de strings voor alle symbols, voor wanneer de naam van de symbol langer is dan 8 bytes en niet direct in de Symbol Tabel past.

Nu hebben we alle informatie die we nodig hebben om een COFF bestand te verwerken en in memory te mappen.

Stap 2: Memory mappen

De sections bevatten alle data die we nodig hebben om het programma uit te voeren. We moeten deze sections parsen en de memory toewijzen.

We berekenen de benodigde grootte in memory door over alle aanwezige sections heen te loopen, en de SizeOfRawData op te vragen uit de header. Deze hoeveelheid memory kunnen we dan toewijzen als PAGE_READWRITE zodat we er naar kunnen schrijven.

Sections die alle data bevatten die nodig zijn voor de uitvoering van het programma

Vervolgens loopen we weer over alle sections en kopiëren we deze naar de toegewezen memory. We weten op welk adres de data zich bevindt aan de hand van de PointerToRawData en tot waar het loopt door hier de SizeOfRawData bij op te tellen.

Symbol tabel om op teslaan welke functies al verwerkt zijn

Naast de sections willen we ook de Symbol Tabel opslaan, zodat we bij kunnen houden welke functies we al verwerkt hebben. We weten hoeveel memory we hiervoor moeten toewijzen door voor elke section over de Symbol Tabel te loopen en te kijken of de functienaam begint met __imp_, wat betekent dat het een import is. We vragen hiervoor de Name value op van elke entry in de Symbol Tabel, als deze __imp_ bevat moeten we extra memory toewijzen ter grootte van PVOID, aangezien hier de function pointer in moet passen.

Stap 3: Relocation

Nu de sections in memory geladen zijn willen we ervoor zorgen dat de functie adressen naar de correcte locatie wijzen.

Hiervoor loopen we weer over de geladen sections en vragen we alle function symbols op per section. Per symbol bepalen we vervolgens of het extern is en uit welke library het moet komen aan de hand van de string. Deze string ziet er bijvoorbeeld als volgt uit:

__imp_KERNEL32$GetCurrentProcess

Hier zien we als eerste de __imp_ tag, wat betekent dat dit een externe functie is. Vervolgens zien we KERNEL32, dit is de library waar de functie uit komt. Als laatste hebben we de functienaam zelf, wat in dit voorbeeld GetCurrentProcess is.

Voor interne functies kunnen we in de .text section kijken waar deze functie te vinden is. Voor externe functies is dit iets ingewikkelder. Hier moeten we de externe library inladen, waar we de locatie van de specifieke functie kunnen opvragen.

Op deze adressen willen we vervolgens de relocation toepassen, zodat we altijd het juiste adres hebben. Om dit uit te voeren hebben we per symbol het volgende nodig:

  1. Het type relocation, genoteerd in de relocation tabel.
  2. Het adres waar de waarde geschreven moet worden.
  3. Het adres waar het naar moet verwijzen.

1. Type

Het type relocation kan uit de relocation tabel gehaald worden, door de waarde voor Type op te vragen. Wat deze types betekenen staat uitgelegd in de Microsoft Documentatie.

 

2. Schrijfadres

Het adres waar de waarde geschreven moet worden, is de eerste byte in de section op de plek van de symbol die aangepast moet worden. Dit kan berekend worden gebaseerd op de COFF structuur:

Schrijfadres = VirtualAddress van de Section + VirtualAddress uit de relocation tabel voor de symbol

 

3. Verwijsadres

Het adres waar het naar moet verwijzen hangt af van het type relocation. Als het type bijvoorbeeld IMAGE_REL_AMD64_REL32 is, kan het als volgt berekend worden:

Verwijsadres = VirtualAddress van de Section + Relatieve adres in 32 bits van de symbol

Wanneer we het verwijsadres bepaald hebben, kunnen we deze kopiëren naar het schrijfadres. Als dit gedaan is voor alle sections en relocations, is het linken uitgevoerd. Nu is het mogelijk om het object in memory uit te voeren.

Stap 4: Uitvoeren

Om de object file uit te voeren moeten we de locatie van de startfunctie vinden. Als deze naam bijvoorbeeld “go” is, willen we zoeken in de geladen symbol tabel om deze functienaam te vinden. In deze tabel vinden we dan ook gelijk het adres wat we moeten hebben.

De memory van de functie moet uitvoerbaar gemaakt worden. Vervolgens casten we de locatie van de symbol naar een functie en voeren we deze uit.

Wanneer alles correct geïmplementeerd is, zou het object uitgevoerd moeten worden.

BOF ondersteuning

Cobalt Strike en meerdere andere C2 platforms maken gebruik van zogenaamde Beacon Object Files (BOF). Deze BOF’s hebben een extra functionaliteit wat het mogelijk maakt om onder andere data van en naar de Object Files te schrijven.

Cobalt Strike heeft een header beschikbaar gesteld waar alle beschikbare functies in te zien zijn. Deze header is te zien in de documentatie. De bijbehorende functies kunnen we zelf implementeren, of we gebruiken de volledige implementatie van de API, beschikbaar gesteld door TrustedSec op GitHub.

Voor de meeste BOF’s moet in ieder geval de volgende functies beschikbaar zijn:

  • BeaconDataInt
  • BeaconDataShort
  • BeaconDataLength
  • BeaconDataExtract
  • BeaconOutput
  • BeaconPrintF

Deze functies moeten beschikbaar zijn binnen het programma waarmee de BOF uitgevoerd zal worden, in ons geval dus in de agent.

Net zoals de andere functies willen we ervoor zorgen dat de adressen te vinden zijn binnen de Object File wanneer deze in memory geladen wordt. Gelukkig kunnen we deze case vrij eenvoudig afvangen, aangezien ze allemaal beginnen met de “Beacon” string. Dit handelen we dan verder af op dezelfde manier als de andere functie symbols.

 

Conclusie

Surelock heeft deze methodes als standalone programma geïmplementeerd en toegevoegd aan de agents. Als voorbeeld werd het dir object uitgevoerd in een testomgeving. Dit object is afkomstig van de TrustedSec Situational Awareness BOF collectie. Dit object leest de bestanden in de gekozen locatie uit.

De bofloader wordt uitgevoerd, welke het dir object in memory injecteert en uitvoert.

Dit gaf het volgende resultaat:


> .\bofloader.exe .\dir.x64.o z C:\

Geladen Object File: .\dir.x64.o
Virtual Size: 28672
Object file toegewezen op 00000285A6470000 met een grootte van 28672
Section .text - Adres: 00000285A6470000 - 2160 bytes gekopieerd
Section .data - Adres: 00000285A6471000 - 32 bytes gekopieerd
Section .bss - Adres: 00000285A6472000 - 0 bytes gekopieerd
Section .xdata - Adres: 00000285A6472000 - 144 bytes gekopieerd
Section .pdata - Adres: 00000285A6473000 - 132 bytes gekopieerd
Section .rdata - Adres: 00000285A6474000 - 208 bytes gekopieerd
Section /4 - Adres: 00000285A6475000 - 64 bytes gekopieerd
Section 0 heeft 85 relocations
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapAlloc gevonden op locatie: 00007FFD9568CB40
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapFree gevonden op locatie: 00007FFD94BD0D30
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapFree gevonden op locatie: 00007FFD94BD0D30
MSVCRT$calloc gevonden op locatie: 00007FFD950ECA70
BeaconOutput gevonden op locatie: 00007FF77B8614E0
MSVCRT$free gevonden op locatie: 00007FFD950ECAC0
MSVCRT$vsnprintf gevonden op locatie: 00007FFD9511E880
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapAlloc gevonden op locatie: 00007FFD9568CB40
KERNEL32$HeapFree gevonden op locatie: 00007FFD94BD0D30
Kernel32$WideCharToMultiByte gevonden op locatie: 00007FFD94BD0E30
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapAlloc gevonden op locatie: 00007FFD9568CB40
KERNEL32$HeapFree gevonden op locatie: 00007FFD94BD0D30
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapAlloc gevonden op locatie: 00007FFD9568CB40
MSVCRT$strlen gevonden op locatie: 00007FFD95132040
MSVCRT$_strnicmp gevonden op locatie: 00007FFD9512FC10
MSVCRT$strstr gevonden op locatie: 00007FFD95132900
MSVCRT$strcat gevonden op locatie: 00007FFD9512EF60
MSVCRT$strcmp gevonden op locatie: 00007FFD95131E40
MSVCRT$strcat gevonden op locatie: 00007FFD9512EF60
KERNEL32$FindFirstFileA gevonden op locatie: 00007FFD94BE06A0
KERNEL32$GetLastError gevonden op locatie: 00007FFD94BD0E10
BeaconPrintf gevonden op locatie: 00007FF77B861510
MSVCRT$strstr gevonden op locatie: 00007FFD95132900
MSVCRT$strcat gevonden op locatie: 00007FFD9512EF60
KERNEL32$FindClose gevonden op locatie: 00007FFD94BE0660
KERNEL32$FileTimeToSystemTime gevonden op locatie: 00007FFD94BE0B30
KERNEL32$SystemTimeToTzSpecificLocalTime gevonden op locatie: 00007FFD94BC2430
MSVCRT$strcmp gevonden op locatie: 00007FFD95131E40
MSVCRT$strlen gevonden op locatie: 00007FFD95132040
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapAlloc gevonden op locatie: 00007FFD9568CB40
MSVCRT$strncat gevonden op locatie: 00007FFD95132110
MSVCRT$strcat gevonden op locatie: 00007FFD9512EF60
KERNEL32$FindNextFileA gevonden op locatie: 00007FFD94BE0710
KERNEL32$GetLastError gevonden op locatie: 00007FFD94BD0E10
KERNEL32$FindClose gevonden op locatie: 00007FFD94BE0660
BeaconPrintf gevonden op locatie: 00007FF77B861510
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapFree gevonden op locatie: 00007FFD94BD0D30
BeaconDataParse gevonden op locatie: 00007FF77B861260
BeaconDataExtract gevonden op locatie: 00007FF77B8613F0
BeaconDataShort gevonden op locatie: 00007FF77B861350
KERNEL32$GetProcessHeap gevonden op locatie: 00007FFD94BD0DF0
KERNEL32$HeapAlloc gevonden op locatie: 00007FFD9568CB40
MSVCRT$strncat gevonden op locatie: 00007FFD95132110
KERNEL32$HeapFree gevonden op locatie: 00007FFD94BD0D30
Section 1 heeft 0 relocations
Section 2 heeft 0 relocations
Section 3 heeft 0 relocations
Section 4 heeft 33 relocations
Section 5 heeft 0 relocations
Section 6 heeft 0 relocations

Contents of C:\*:
        07/21/2025 02:34         <dir> $Recycle.Bin 
        02/20/2026 03:12             0 $WINRE_BACKUP_PARTITION.MARKER 
        02/22/2024 00:33        112136 appverifUI.dll 
        07/31/2024 19:59    <junction> Documents and Settings 
        03/16/2026 02:14         12288 DumpStack.log.tmp 
        08/11/2025 05:48         <dir> inetpub 
        03/16/2026 02:14    1342177280 pagefile.sys 
        05/06/2022 22:24         <dir> PerfLogs 
        09/26/2025 06:40         <dir> Program Files 
        07/31/2024 15:06         <dir> Program Files (x86) 
        09/26/2025 07:07         <dir> ProgramData 
        07/21/2025 11:30         <dir> Recovery 
        03/16/2026 02:14      16777216 swapfile.sys 
        10/02/2025 07:26         <dir> Symbols 
        07/31/2024 13:01         <dir> System Volume Information 
        07/21/2025 04:59         <dir> Temp 
        07/21/2025 02:32         <dir> Users 
        02/22/2024 00:34         66328 vfcompat.dll 
        02/20/2026 05:53         <dir> Windows 
                            1359145248 Total File Size for 6 File(s) 
                                                          13 Dir(s)

In de debug output is te zien dat alle symbols afgehandeld worden en uiteindelijk de BOF correct uitgevoerd wordt. Hoe deze implementatie precies in zijn werk gaat, zal misschien in een toekomstig artikel verder toegelicht worden.

De functionaliteit is toegevoegd aan de C2 agent. In combinatie met de andere bestaande functionaliteiten, kunnen nu object files als data gedownload en in memory geïnjecteerd worden. Dit maakt de agent modulair en zorgt ervoor dat de objecten geen voetafdruk achterlaten op de schijf van het systeem. Daarnaast kunnen we nu BOF’s gebruiken van Cobalt Strike, waar al een brede ondersteuning voor is.

Neem contact met ons op

Hoe werkt een Red Teaming

  1. 1
    Vul je gegevens in op het aanvraagformulier
  2. 2
    Wij nemen binnen 24 uur contact met je op
  3. 3
    Kennismaking met Surelock
  4. 4
    Je ontvangt een gratis plan op maat
  5. 5
    Aan de slag!
Jorn Heibrink

Jorn Heibrink

We zijn van maandag t/m vrijdag
bereikbaar van 8:00 tot 18:00.

 0348 796 146