
Open Source C&C Specification
Bozza: Richiesta di Discussione
Questo documento fornisce una panoramica della Versione 1 della specifica OST C&C. È inteso a fornire una descrizione dettagliata dei messaggi e dei campi all'interno di tali messaggi.
La motivazione alla base di questa specifica è fornire un protocollo di messaggistica C&C (inclusi tasking, output strutturato e routing peer-to-peer) che possa essere implementato testualmente, o semplicemente fungere da ispirazione per gli sviluppatori di progetti. Questo documento non intende descrivere cosa sia un C&C. Si presume che il lettore comprenda cosa sia e a cosa serva.
Le parole chiave "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" e "OPTIONAL" devono essere interpretate come descritto in [RFC2119].
Questa specifica assume quanto segue:
Di seguito è riportato un elenco di termini utilizzati in tutto questo documento.
Implant Metadata: Informazioni che un implant riporta su se stesso a un team server.
Task Request: Un compito assegnato a un implant da eseguire.
Task Response: Lo stato e l'output (se presente) di un determinato task.
Session Key: Una chiave di crittografia univoca utilizzata da un implant per crittografare i propri messaggi.
Ogni messaggio di richiesta e risposta di task MUST avere il seguente header di 16 byte.```text | Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | -------------------------------------------------------------| | 0 | Type | Code | Flags | Label | | -------------------------------------------------------------| | 1 | Identifier | Length | | -------------------------------------------------------------|
- **Type**: intero a 1 byte. Il 'tipo' di task di cui si tratta. Vedi [[Tipi di Task e Codici](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#task-types-and-codes)].
- **Code**: intero a 1 byte. Un 'sottocodice' per il Tipo specificato. Vedi [[Tipi di Task e Codici](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#task-types-and-codes)].
- **Flags**: intero a 2 byte. Un insieme di flag bitwise per descrivere lo stato del messaggio. Vedi [[Flag dei Task](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#task-flags)].
- **Label**: intero a 4 byte. Un'etichetta univoca per correlare più messaggi relativi allo stesso task.
- **Identifier**: intero a 4 byte. Un identificatore sequenziale utilizzato per costruire messaggi frammentati nell'ordine corretto.
- **Length**: intero a 4 byte. La lunghezza totale dei dati del task.
## Tipi di Task e Codici```text
|------------------|--------------------------|
| Type | Code |
|------------------|--------------------------|
| 0 - NOP | 0 |
|------------------|--------------------------|
| 1 - Exit | 0 |
|------------------|--------------------------|
| 2 - Set | 0 - Sleep/Jitter |
| | 1 - SpawnTo |
| | 2 - BlockDLLs |
| | 3 - PPID |
|------------------|--------------------------|
| 3 - File | 0 - Copy |
| | 1 - Move |
| | 2 - Delete |
| | 3 - Upload |
| | 4 - Download |
|------------------|--------------------------|
| 4 - Directory | 0 - Print |
| | 1 - Change |
| | 2 - Create |
| | 3 - Copy |
| | 4 - Move |
| | 5 - List |
| | 6 - Delete |
|------------------|--------------------------|
| 5 - WhoAmI | 0 |
|------------------|--------------------------|
| 6 - Process | 0 - List |
| | 1 - Kill |
| | 2 - Inject Spawn |
| | 3 - Inject Explicit |
|------------------|--------------------------|
| 7 - Registry | 0 - Query |
| | 1 - Add |
| | 2 - Delete |
|------------------|--------------------------|
| 8 - RPortFwd | 0 - Start |
| | 1 - Data |
|------------------|--------------------------|
| 9 - Environment | 0 - Get |
| | 1 - Set |
|------------------|--------------------------|
| 10 - SOCKS | 0 - Connect |
| | 1 - Data |
| | 2 - Close |
|------------------|--------------------------|
| 11 - Tokens | 0 - List |
| | 1 - Make |
| | 2 - Steal |
| | 3 - Use |
| | 4 - Revert |
| | 5 - Delete |
| | 6 - Purge |
|------------------|--------------------------|
| 12 - Run | 0 |
|------------------|--------------------------|
| 13 - ItemStore | 0 - List |
| | 1 - Add |
| | 2 - Delete |
| | 3 - Purge |
|------------------|--------------------------|
| 14 - LocalExec | 0 - .NET |
| | 1 - BOF |
| | 2 - Managed PowerShell |
| | 3 - Unmanaged PowerShell |
|------------------|--------------------------|
| 15 - PrintScreen | 0 |
|------------------|--------------------------|
| 16 - RemoteExec | 0 - WinRM |
| | 1 - WMI |
| | 2 - PsExec |
| | 3 - SSH |
|------------------|--------------------------|
| 17 - Link | 1 - Link SMB |
| | 2 - Link TCP |
|------------------|--------------------------|
| 18 - Unlink | 0 |
|------------------|--------------------------|
| 19 - P2P | 0 - Acknowledge |
| | 1 - PassThru |
|------------------|--------------------------|
| 20 - Jobs | 0 - List |
| | 1 - Kill |
|---------------------------------------------|
Alcuni flag si escludono a vicenda e NON DEVONO essere impostati insieme. Se non viene impostato alcun flag, si DOVREBBE presumere che l'attività sia stata completata con successo e che l'output associato (se presente) NON sia frammentato.```text
| Value | Description |
|---|---|
| 0 | No flags |
| 1 | Task Error |
| 2 | Task Running (as job) |
| 4 | Message is fragmented, more to follow |
## Dati del task
I dati del task vengono aggiunti all'header e consistono in una struttura binaria, a seconda del tipo e del codice specifici del task. Ogni tipo di messaggio di richiesta e risposta del task è definito in [[Message Definitions](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#message-definitions)].
NON È OBBLIGATORIO che una richiesta o una risposta del task contenga dati se non sono necessari.
## Messaggio di task crittografato
Prima della trasmissione, l'header del task e i dati del task vengono combinati e crittografati con AES utilizzando la chiave di sessione dell'impianto.```text
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| ------------------------------------ |
| 0 | Iv |
| 8 | |
| ------------------------------------ |
| 16 | Checksum |
| 24 | |
| 32 | |
| 40 | |
| ------------------------------------ |
| 48 | Data |
| .. | |
| ------------------------------------ |
Un implant DEVE registrarsi presso un team server prima di poter ricevere o inviare qualsiasi dato di task.
L'implant genera un messaggio [IMPLANT-METADATA], lo cifra con la chiave RSA pubblica del team server e lo invia al team server.
Il team server usa la sua chiave RSA privata per decifrare l'[IMPLANT-METADATA] dell'implant e DEVE registrarlo come una nuova sessione/callback.
Un implant DEVE effettuare il "check-in" con il team server per ricevere eventuali dati di task in sospeso per sé o per i figli.
Il metodo di check-in è specifico del canale C2 e non è trattato in questa specifica. Un implant registrato PUÒ inviare solo il proprio ID per effettuare il check-in. Tuttavia, se l'implant ha successivamente modificato la propria chiave di sessione, la configurazione di sleep o jitter, DEVE anche reinviare i propri metadati.
Se non ci sono task in sospeso, un team server PUÒ rispondere senza dati, o con dati fittizi sotto forma di uno o più messaggi [NOP]. In caso contrario, DEVE rispondere con una raccolta di richieste di task cifrate AES con la chiave di sessione dell'implant.
Un implant figlio DEVE scrivere i propri metadati sul canale P2P (ad es. named pipe o socket TCP) una volta stabilita una connessione con un nuovo parent.
Il parent DEVE leggere questi metadati e rinviarli al team server in un messaggio [LINK-REP].
Il team server DEVE decifrare i metadati del figlio e registrarli come una nuova sessione/callback oppure aggiornare le relazioni genitore-figlio esistenti in caso di unlink e link.
Il team server DEVE inviare un messaggio [LINK-ACK] al nuovo parent per confermare l'ID del figlio. Il parent DOVREBBE usare il Label del messaggio per correlare questo processo.
I task per gli implant figli sono incapsulati in uno o più messaggi [LINK-PASS-THRU]. Questi verranno cifrati con la chiave di sessione del parent. Alla ricezione, il parent DEVE decifrare il messaggio e inoltrare i dati incapsulati all'implant figlio indicato dal campo child-id.
I dati incapsulati possono essere il task stesso o un altro LINK-PASS-THRU se il figlio si trova a un altro livello più in basso nella catena.
Tutti i campi Timestamp vengono trasmessi come interi con segno a 64 bit (Int64) che rappresentano l'epoch UNIX (il numero di secondi trascorsi dal 1 gennaio 1970).
Alcuni linguaggi non distinguono tra un valore omesso e un valore trasmesso pari a zero. Per coerenza, le implementazioni DEVONO anteporre ai campi OPTIONAL un byte 1 o 0 (cioè TRUE o FALSE) per indicare se il valore è presente o meno.
Non è sempre possibile sapere quando un campo termina e un altro inizia leggendo dati da un flusso binario. Questa specifica impone l'uso di un prefisso di lunghezza per questi campi, in modo che le implementazioni sappiano quanti byte, o quanti elementi, contiene quel campo. I seguenti tipi di dati DEVONO avere un prefisso di lunghezza:
Le implementazioni POSSONO includere tipi di messaggio, codici di controllo e flag non definiti in questa specifica, secondo il loro design e la loro funzionalità unici. Tuttavia, è RACCOMANDATO usare valori che si trovano nella parte alta del pool non riservato per ridurre la probabilità che vengano assegnati in una revisione futura. Le implementazioni NON DEVONO usare un tipo, un codice o un flag definito per scopi diversi dal suo scopo previsto.
Le implementazioni DOVREBBERO gestire con garbo la ricezione di un messaggio con campi o flag non riconosciuti e restituire un messaggio di errore appropriato.
IMPLANT-METADATA { id [1] UInt32 session-key [2] SEQUENCE of Byte (32) sleep [3] UInt32 OPTIONAL jitter [4] UInt32 OPTIONAL username [5] String OPTIONAL host-id [6] String OPTIONAL hostname [7] String OPTIONAL domain [8] String OPTIONAL ipv4-ips [9] SEQUENCE of IPV4-ADDRESS OPTIONAL ipv6-ips [10] SEQUENCE of IPV6-ADDRESS OPTIONAL process-name [11] String OPTIONAL process-id [12] UInt32 OPTIONAL architecture [13] [Architecture] OPTIONAL platform [14] [Platform] OPTIONAL os-description [15] String OPTIONAL integrity [16] [Integrity] OPTIONAL }
### IPV4-ADDRESS```text
IPV4-ADDRESS {
address [1] SEQUENCE of Byte (4)
}
IPV6-ADDRESS { address [1] SEQUENCE of Byte (16) }
Gli indirizzi IP DEVONO essere trasmessi in ordine di byte di rete.
### Platform```text
Platform {
Linux = 0,
MacOS = 1,
Windows = 2
}
TASK-ERROR { error-code [1] UInt32 message [2] String OPTIONAL }
## Definizioni NOP
### NOP```text
NOP {
padding [1] SEQUENCE of Byte OPTIONAL
}
SET-SLEEP-REQ { interval [1] UInt32 jitter [2] Byte OPTIONAL }
### SET-SPAWNTO-REQ```text
SET-SPAWNTO-REQ {
spawnto [1] String OPTIONAL
}
Se il campo spawnto non è impostato, l'implant DOVREBBE tornare alla sua configurazione predefinita.
SET-BLOCKDLLS-REQ { blockdlls [1] Boolean OPTIONAL }
If the `blockdlls` field is *not* set, the implant SHOULD revert to its default configuration.
### SET-PPID-REQ```text
SET-PPID-REQ {
ppid [1] UInt32 OPTIONAL
}
Se il campo ppid non è impostato, l'impianto DOVREBBE tornare alla sua configurazione predefinita.
FILE-COPY-REQ { source [1] String destination [2] String force [3] Boolean OPTIONAL }
### FILE-MOVE-REQ```text
FILE-MOVE-REQ {
source [1] String
destination [2] String
}
FILE-DELETE-REQ { path [1] String }
### FILE-UPLOAD-REQ```text
FILE-UPLOAD-REQ {
destination [1] String
content [2] SEQUENCE of Byte
}
FILE-DOWNLOAD-REQ { path [1] String }
### FILE-DOWNLOAD-REP```text
FILE-DOWNLOAD-REP {
current-chuck [1] UInt16
total-chunks [2] UInt16
chunk-content [3] SEQUENCE of Byte
}
DIR-PRINT-REP { path [1] String }
### DIR-CHANGE-REQ```text
DIR-CHANGE-REQ {
path [1] String OPTIONAL
}
Se il campo path non è impostato, l'impianto DOVREBBE cambiare la propria directory di lavoro in una posizione 'predefinita' (ad es. la directory home dell'utente).
DIR-CREATE-REQ { path [1] String }
### DIR-CREATE-REP```text
DIR-CREATE-REP {
entry [1] [FileSystemEntry]
}
DIR-COPY-REQ { source [1] String destination [2] String }
### DIR-MOVE-REQ```text
DIR-MOVE-REQ {
source [1] String
destination [2] String
}
DIR-LIST-REQ { path [1] String OPTIONAL access-control [2] Boolean OPTIONAL }
Se il campo `path` *non* è impostato, l'implant DOVREBBE elencare la sua directory di lavoro corrente.
### DIR-LIST-REP```text
DIR-LIST-REP {
entries [1] SEQUENCE of [FileSystemEntry]
}
DIR-DELETE-REQ { path [1] String recurse [2] Boolean OPTIONAL }
### FileSystemEntry```text
FileSystemEntry {
path [1] String
length [2] UInt32 OPTIONAL
attributes [3] [FileAttributes] OPTIONAL
owner [4] String OPTIONAL
created [5] Timestamp OPTIONAL
last-accessed [6] Timestamp OPTIONAL
last-written [7] Timestamp OPTIONAL
access-control [8] SEQUENCE of [FileSecurity] OPTIONAL
}
Flag bitwise.```text FileAttributes { Normal = 1, Archive = 2, Compressed = 4, ReadOnly = 8, Hidden = 16, Directory = 32, System = 64 }
### FileSecurity```text
FileSecurity {
identity [1] String
access-mask [2] Int32
inheritance [3] [Inheritance] OPTIONAL
propagation [4] [Propagation] OPTIONAL
}
Flag bitwise.```text Inheritance { None = 0, ContainerInherit = 1, ObjectInherit = 2, }
### Propagazione
Flag bitwise.```text
Propagation {
None = 0,
NoPropagateInherit = 1,
InheritOnly = 2,
}
WHOAMI-REP { primary [1] String impersonation [2] String OPTIONAL }
## Definizioni dei processi
### PROC-LIST-REP```text
PROC-LIST-REP {
processes [1] SEQUENCE of [ProcessEntry]
}
PROC-KILL-REQ { process-id [1] UInt32 force [2] Boolean OPTIONAL }
### PROC-INJ-REQ```text
PROC-INJ-REQ {
shellcode [1] SEQUENCE of Byte
capability [2] SEQUENCE of Byte
process-id [3] UInt32 OPTIONAL
}
ProcessEntry { process-name [1] String process-id [2] UInt32 parent-process-id [3] UInt32 OPTIONAL session-id [4] Byte OPTIONAL owner [5] String OPTIONAL architecture [6] [Architecture] OPTIONAL integrity [7] [Integrity] OPTIONAL }
### Architettura```text
Architecture {
X86 = 0, // 32-bit Intel
X64 = 1, // 64-bit Intel
Arm = 2, // 32-bit ARM
Arm64 = 3, // 64-bit ARM
Wasm = 4 // WebAssembly
}
Integrity { Untrusted = 0, Low = 1, Medium = 2, // user High = 3, // sudoers System = 4 // root }
## Definizioni del Registro
### REG-QUERY-REQ```text
REG-QUERY-REQ {
hive [1] [RegistryHive]
key [2] String OPTIONAL
value [3] String OPTIONAL
access-control [4] Boolean OPTIONAL
}
REG-QUERY-REP { values [1] SEQUENCE of [RegistryValue] keys [2] SEQUENCE of [RegistryKey] }
### REG-ADD-REQ```text
REG-ADD-REQ {
hive [1] [RegistryHive]
key [2] String
name [3] String OPTIONAL
kind [4] [RegistryValueKind] OPTIONAL
value [5] SEQUENCE of Byte OPTIONAL
}
REG-DELETE-REQ { hive [1] [RegistryHive] key [2] String }
### RegistryHive```text
RegistryHive {
ClassesRoot = 0,
CurrentUser = 1,
LocalMachine = 2,
Users = 3,
CurrentConfig = 4
}
RegistryKey { name [1] String access-control [2] [RegistrySecurity] OPTIONAL }
### RegistryValue```text
RegistryValue {
name [1] String
type [2] [RegistryValueKind]
data [3] SEQUENCE of Byte
access-control [4] SEQUENCE of [RegistrySecurity] OPTIONAL
}
RegistryValueKind { None = 0, // REG_NONE String = 1, // REG_SZ ExpandString = 2, // REG_EXPAND_SZ Binary = 3, // REG_BINARY DWord = 4, // REG_DWORD MultiString = 5, // REG_MULTI_SZ Qword = 6 // REG_QWORD }
### RegistrySecurity```text
RegistrySecurity {
identity [1] String
access-mask [2] Int32
inheritance [3] [Inheritance] OPTIONAL
propagation [4] [Propagation] OPTIONAL
}
RPORTFWD-START { bind-port [1] UInt16 localhost-only [2] Boolean OPTIONAL forward-host [3] String forward-port [4] UInt16 }
### RPORTFWD-DATA```text
RPORTFWD-DATA {
data [1] SEQUENCE of Byte
}
ENV-GET-REQ { key [1] String }
### ENV-GET-REP```text
ENV-GET-REP {
value [1] String
}
ENV-SET-REQ { key [1] String value [2] String }
## Definizioni SOCKS
### SOCKS-CONNECT-REQ```text
SOCKS-CONNECT-REQ {
id [1] UInt32
target [2] SEQUENCE of Byte (4)
port [3] UInt16
}
SOCKS-DATA { id [1] UInt32 data [2] SEQUENCE of Byte }
### SOCKS-CLOSE-REQ```text
SOCKS-CLOSE-REQ {
id [1] UInt32
}
## Token Definitions
### TOKEN-LIST-REP
```text
TOKEN-LIST-REP {
tokens [1] SEQUENCE of [TokenEntry]
}```
### TOKEN-CREATE-REQ
```text
TOKEN-CREATE-REQ {
username [1] String
domain [2] String OPTIONAL
password [3] String OPTIONAL
}```
### TOKEN-STEAL-REQ
```text
TOKEN-STEAL-REQ {
process-id [1] UInt32
access-mask [2] UInt32 OPTIONAL
}```
### TOKEN-USE-REQ
```text
TOKEN-USE-REQ {
index [1] Byte
}```
### TOKEN-DELETE-REQ
```text
TOKEN-DELETE-REQ {
index [1] Byte
}```
### TokenEntry
```text
Token {
index [1] Byte
username [2] String
handle [3] String OPTIONAL
process-id [4] UInt32 OPTIONAL
}```
## Implant Store Definitions
### STORE-LIST-REP
```text
STORE-LIST-REP {
items [1] SEQUENCE of [StoreItem]
}```
### STORE-ADD-ITEM Definition
```text
STORE-ADD-ITEM-REQ {
item [1] SEQUENCE of Byte
name [2] String
type [3] [StoreItemType]
}```
### STORE-DELETE-ITEM Definition
```text
STORE-DELETE-ITEM-REQ {
index [1] Byte
}```
### StoreItem
```text
StoreItem {
index [1] Byte
name [2] String
type [3] [StoreItemType]
}```
### StoreItemType
```text
StoreItemType {
Assembly = 0,
BOF = 1,
Script = 2,
Generic = 3
}```
## Local Execution Definitions
### RUN-REQ
```text
RUN-REQ {
program [1] String
arguments [2] String OPTIONAL
token [3] Byte OPTIONAL
}```
### RUN-REP
```text
RUN-REP {
output [1] String
}```
### EXEC-ASM-REQ
Either store-index or assembly MUST be provided.
```text
EXEC-ASM-REQ {
store-index [1] Byte OPTIONAL
assembly [2] SEQUENCE of Byte OPTIONAL
arguments [3] SEQUENCE of String OPTIONAL
bypass-amsi [4] Boolean OPTIONAL
bypass-etw [5] Boolean OPTIONAL
}```
### EXEC-ASM-REP
```text
EXEC-ASM-REP {
output [1] String
}```
### EXEC-BOF-REQ
Either store-index or bof MUST be provided.
```text
EXEC-BOF-REQ {
store-index [1] Byte OPTIONAL
bof [2] SEQUENCE of Byte OPTIONAL
arguments [3] SEQUENCE of Byte OPTIONAL
bypass-amsi [4] Boolean OPTIONAL
bypass-etw [5] Boolean OPTIONAL
}```
### EXEC-BOF-REP
```text
EXEC-BOF-REP {
output [1] String
}```
### EXEC-POSH-REQ
Either store-index or script MUST be provided.
```text
EXEC-POSH-REQ {
cmdlet [1] String
store-index [2] Byte OPTIONAL
script [3] SEQUENCE of Byte OPTIONAL
bypass-amsi [3] Boolean OPTIONAL
bypass-etw [4] Boolean OPTIONAL
}```
### EXEC-POSH-REP
```text
EXEC-POSH-REP {
output [1] String
}```
## Screenshot Definitions
### SCRNSHOT-REP
```text
SCRNSHOT-REP {
data [1] SEQUENCE of Byte
}```
## Remote Execution Definitions
### WINRM-REQ
```text
WINRM-REQ {
target [1] String
program [2] String
arguments [3] String OPTIONAL
}```
### WMI-REQ
```text
WMI-REQ {
target [1] String
program [2] String
arguments [3] String OPTIONAL
}```
### PSEXEC-REQ
```text
PSEXEC-REQ {
target [1] String
service-name [2] String
service-description [3] String OPTIONAL
bin-path [4] String
}```
## Peer-to-Peer Definitions
### LINK-SMB-REQ
```text
LINK-SMB-REQ {
target [1] String
pipename [2] String
}```
### LINK-TCP-REQ
```text
LINK-TCP-REQ {
target [1] String
port [2] UInt32
}```
### LINK-REP
```text
LINK-SMB-REP {
child-metadata [1] SEQUENCE of Byte
}```
### LINK-ACK
```text
LINK-ACK {
child-id [1] UInt32
}```
### LINK-PASS-THRU
```text
LINK-PASS-THRU {
child-id [1] UInt32
message [2] SEQUENCE of Byte
}```
## JOB Definitions
### JOB-LIST-REP
```text
jobs [1] Sequenza di [JobEntry]```
### JOB-KILL-REQ
```text
index [1] UInt32```
### JobEntry
```text
index [1] UInt32
type [2] Byte
code [3] Byte```
| 8 | Message is fragmented, no more to follow |