
Open Source C&C Specification
Rascunho: Solicitação de Discussão
Este documento fornece uma visão geral da Versão 1 da Especificação OST de C&C. O objetivo é fornecer uma descrição detalhada das mensagens e dos campos dentro dessas mensagens.
A motivação por trás desta especificação é fornecer um protocolo de mensagens C&C (incluindo atribuição de tarefas, saída estruturada e roteamento ponto a ponto) que possa ser implementado literalmente, ou simplesmente servir de inspiração para desenvolvedores de projetos. Este documento não tem a intenção de descrever o que é C&C. Presume-se que o leitor entenda o que é e para que é usado.
As palavras-chave "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" e "OPTIONAL" devem ser interpretadas conforme descrito na [RFC2119].
Esta especificação parte das seguintes premissas:
Abaixo está uma lista de termos usados ao longo deste documento.
Metadados do Implante: Informações que um implante relata sobre si mesmo a um servidor de equipe.
Solicitação de Tarefa: Uma tarefa atribuída a um implante para execução.
Resposta de Tarefa: O status e a saída (se houver) de uma determinada tarefa.
Chave de Sessão: Uma chave de criptografia exclusiva usada por um implante para criptografar suas mensagens.
Cada mensagem de solicitação de tarefa e de resposta DEVE ter o seguinte cabeçalho de 16 bytes.```text | Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | -------------------------------------------------------------| | 0 | Type | Code | Flags | Label | | -------------------------------------------------------------| | 1 | Identifier | Length | | -------------------------------------------------------------|
- **Type**: inteiro de 1 byte. O 'tipo' da tarefa em questão. Consulte [[Tipos e Códigos de Tarefas](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#task-types-and-codes)].
- **Code**: inteiro de 1 byte. Um 'subcódigo' para o Tipo fornecido. Consulte [[Tipos e Códigos de Tarefas](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#task-types-and-codes)].
- **Flags**: inteiro de 2 bytes. Um conjunto de flags bit a bit para descrever o estado da mensagem. Consulte [[Flags de Tarefa](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#task-flags)].
- **Label**: inteiro de 4 bytes. Um rótulo único para correlacionar várias mensagens relacionadas à mesma tarefa.
- **Identifier**: inteiro de 4 bytes. Um identificador sequencial usado para construir mensagens fragmentadas na ordem correta.
- **Length**: inteiro de 4 bytes. O comprimento total dos dados da tarefa.
## Tipos e Códigos de Tarefas```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 |
|---------------------------------------------|
Algumas flags são mutuamente exclusivas e NÃO DEVEM ser definidas em conjunto. Se nenhuma flag for definida, uma tarefa DEVERIA ser considerada como concluída com êxito e a saída associada (se houver) NÃO é fragmentada.```text
| Value | Description |
|---|---|
| 0 | No flags |
| 1 | Task Error |
| 2 | Task Running (as job) |
| 4 | Message is fragmented, more to follow |
## Dados da Tarefa
Os dados da tarefa são anexados ao cabeçalho e consistem em uma estrutura binária, dependendo do tipo e código específicos da tarefa. Cada tipo de mensagem de solicitação e resposta de tarefa é definido em [[Definições de Mensagens](https://github.com/rasta-mouse/ost-c2-spec?tab=readme-ov-file#message-definitions)].
NÃO É OBRIGATÓRIO que uma solicitação ou resposta de tarefa tenha quaisquer dados se não forem necessários.
## Mensagem de Tarefa Criptografada
Antes da transmissão, o cabeçalho da tarefa e os dados da tarefa são combinados e criptografados com AES usando a chave de sessão do implant.```text
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| ------------------------------------ |
| 0 | Iv |
| 8 | |
| ------------------------------------ |
| 16 | Checksum |
| 24 | |
| 32 | |
| 40 | |
| ------------------------------------ |
| 48 | Data |
| .. | |
| ------------------------------------ |
Um implant DEVE se registrar em um servidor de equipe antes de poder receber ou enviar qualquer dado de tarefa.
O implant gera uma mensagem [IMPLANT-METADATA], criptografa-a com a chave pública RSA do servidor de equipe e a envia para o servidor de equipe.
O servidor de equipe usa sua chave privada RSA para descriptografar a [IMPLANT-METADATA] do implant e DEVE registrá-la como uma nova sessão/callback.
Um implant DEVE fazer "check-in" com o servidor de equipe para receber quaisquer dados de tarefa pendentes para si mesmo ou para filhos.
O método de check-in é específico do canal C2 e não é coberto por esta especificação. Um implant registrado PODE enviar apenas seu ID para fazer check-in. No entanto, se o implant alterou sua chave de sessão, sleep ou configuração de jitter desde então, ele DEVE também reenviar seus metadados.
Se não houver tarefas pendentes, um servidor de equipe PODE responder sem dados, ou com dados dummy na forma de uma ou mais mensagens [NOP]. Caso contrário, ele DEVE responder com uma coleção de solicitações de tarefas criptografadas com AES usando a chave de sessão do implant.
Um implant filho DEVE escrever seus metadados no canal P2P (ex.: named pipe ou socket TCP) assim que uma conexão for estabelecida com um novo pai.
O pai DEVE ler esses metadados e enviá-los de volta ao servidor de equipe em uma mensagem [LINK-REP].
O servidor de equipe DEVE descriptografar os metadados do filho e registrá-los como uma nova sessão/callback ou atualizar as relações pai-filho existentes no caso de um unlink & link.
O servidor de equipe DEVE enviar uma mensagem [LINK-ACK] de volta ao novo pai para confirmar o ID do filho. O pai DEVERIA usar o Label da mensagem para correlacionar esse processo.
As tarefas para implants filhos são encapsuladas em uma ou mais mensagens [LINK-PASS-THRU]. Estas serão criptografadas com a chave de sessão do pai. Ao recebê-las, o pai DEVE descriptografar a mensagem e encaminhar os dados encapsulados para o implant filho indicado pelo campo child-id.
Os dados encapsulados podem ser a própria tarefa ou outro LINK-PASS-THRU se o filho estiver em outro nível abaixo na cadeia.
Todos os campos Timestamp são transmitidos como inteiros com sinal de 64 bits (Int64) que representam a época UNIX (o número de segundos que se passaram desde 1 de janeiro de 1970).
Algumas linguagens não distinguem entre um valor omitido e um valor transmitido igual a zero. Para consistência, as implementações DEVEM prefixar campos OPCIONAIS com um byte 1 ou 0 (ou seja, TRUE ou FALSE) para indicar se o valor está presente ou não.
Nem sempre é possível saber quando um campo termina e outro começa ao ler dados de um fluxo binário. Esta especificação determina o uso de um valor de comprimento prefixado a esses campos, para que as implementações saibam quantos bytes, ou quantos elementos, esse campo contém. Os seguintes tipos de dados DEVEM ter prefixo de comprimento:
As implementações PODEM incluir tipos de mensagem, códigos de controle e flags que não são definidos nesta especificação, de acordo com seu design e funcionalidade exclusivos. No entanto, é RECOMENDADO usar valores que estejam no extremo superior do pool não reservado para reduzir a chance de serem atribuídos em uma revisão futura. As implementações NÃO DEVEM usar um tipo, código ou flag definido para qualquer coisa que não seja sua finalidade pretendida.
As implementações DEVERIAM lidar adequadamente com o recebimento de uma mensagem com campos ou flags que não reconhecem e retornar uma mensagem de erro apropriada.
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) }
Endereços IP DEVEM ser transmitidos em ordem de bytes de rede.
### Plataforma```text
Platform {
Linux = 0,
MacOS = 1,
Windows = 2
}
TASK-ERROR { error-code [1] UInt32 message [2] String OPTIONAL }
## Definições de 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 o campo spawnto não estiver definido, o implante SHOULD reverter para sua configuração padrão.
SET-BLOCKDLLS-REQ { blockdlls [1] Boolean OPTIONAL }
Se o campo `blockdlls` *não* estiver definido, o implante DEVE reverter para sua configuração padrão.
### SET-PPID-REQ```text
SET-PPID-REQ {
ppid [1] UInt32 OPTIONAL
}
Se o campo ppid não estiver definido, o implant DEVERIA reverter para a sua configuração padrão.
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 o campo path não estiver definido, o implante DEVERIA mudar seu diretório de trabalho para um local 'padrão' (por exemplo, o diretório home do usuário).
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 o campo `path` *não* estiver definido, o implante DEVERIA listar seu diretório de trabalho atual.
### 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
}
Flags bit a bit.```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
}
Flags de bits.```text Inheritance { None = 0, ContainerInherit = 1, ObjectInherit = 2, }
### Propagação
Flags bit a bit.```text
Propagation {
None = 0,
NoPropagateInherit = 1,
InheritOnly = 2,
}
WHOAMI-REP { primary [1] String impersonation [2] String OPTIONAL }
## Definições de Processos
### 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 }
### Arquitetura```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 }
## Definições do 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 }
## Definições 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] Sequência de [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 |