Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b124671
DNS transport 🔥
KaliPatriot Nov 28, 2025
211ad96
Merge branch 'main' into feat/dns-transport
KaliPatriot Nov 30, 2025
ba2e7b7
Merge branch 'main' into feat/dns-transport
KaliPatriot Nov 30, 2025
237da97
Add unit tests
KaliPatriot Dec 5, 2025
518b384
Fixed DNS resolver to use local system resolver
KaliPatriot Dec 6, 2025
dfe0ee8
fmt, fixed more hardcoded values
KaliPatriot Dec 6, 2025
e422db7
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 6, 2025
ac2fd4e
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 7, 2025
d40d3d1
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 11, 2025
7399bc8
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 22, 2025
49f8099
updated to dns protobuf
KaliPatriot Dec 23, 2025
66cbc55
working, thanks ai!
KaliPatriot Dec 23, 2025
6b9ab1c
fix
KaliPatriot Dec 23, 2025
e9725c2
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 24, 2025
b1ad861
update to support new transport ent
KaliPatriot Dec 25, 2025
04bbaf6
more updates
KaliPatriot Dec 25, 2025
54e8e0d
fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt fmt f…
KaliPatriot Dec 25, 2025
8535045
added dns to imixv2
KaliPatriot Dec 25, 2025
d71c172
line endings ?
KaliPatriot Dec 25, 2025
d0301aa
more line endings
KaliPatriot Dec 25, 2025
f2630c4
missed transport in schema
KaliPatriot Dec 25, 2025
fc66356
Line endings
KaliPatriot Dec 25, 2025
ac32da5
debug assertions, constant for dns buf size, move docs section
KaliPatriot Dec 25, 2025
5879081
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 25, 2025
877434c
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 25, 2025
4186ddb
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 26, 2025
9586fb8
Merge branch 'main' into feat/dns-transport
KaliPatriot Dec 27, 2025
8de4767
move url dep to workspace
KaliPatriot Dec 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion docs/_docs/admin-guide/tavern.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,74 @@ Below are some deployment gotchas and notes that we try to address with Terrafor

## Redirectors

By default Tavern only supports GRPC connections directly to the server. To Enable additional protocols or additional IPs / Domain names in your callbacks utilize tavern redirectors which recieve traffic using a specific protocol like HTTP/1.1 and then forward it to an upstream tavern server over GRPC. See: `tavern redirector -help`
By default Tavern only supports gRPC connections directly to the server. To enable additional protocols or additional IPs/domain names in your callbacks, utilize Tavern redirectors which receive traffic using a specific protocol (like HTTP/1.1 or DNS) and then forward it to an upstream Tavern server over gRPC.

### Available Redirectors

Realm includes three built-in redirector implementations:

- **`grpc`** - Direct gRPC passthrough redirector
- **`http1`** - HTTP/1.1 to gRPC redirector
- **`dns`** - DNS to gRPC redirector

### Basic Usage

List available redirectors:

```bash
tavern redirector list
```

Start a redirector:

```bash
tavern redirector --transport <TRANSPORT> --listen <LISTEN_ADDR> <UPSTREAM_GRPC_ADDR>
```

### HTTP/1.1 Redirector

The HTTP/1.1 redirector accepts HTTP/1.1 traffic from agents and forwards it to an upstream gRPC server.

```bash
# Start HTTP/1.1 redirector on port 8080
tavern redirector --transport http1 --listen ":8080" localhost:8000
```

### DNS Redirector

The DNS redirector tunnels C2 traffic through DNS queries and responses, providing a covert communication channel. It supports TXT, A, and AAAA record types.

```bash
# Start DNS redirector on UDP port 53 for domain c2.example.com
tavern redirector --transport dns --listen "0.0.0.0:53?domain=c2.example.com" localhost:8000

# Support multiple domains
tavern redirector --transport dns --listen "0.0.0.0:53?domain=c2.example.com&domain=backup.example.com" localhost:8000
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do multi-domain here.
We're going to do multi transport in the agent logic which can include multiple domains on the same transport.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for the redirector, not the agent.

```

**DNS Configuration Requirements:**

1. Configure your DNS server to delegate queries for your C2 domain to the redirector IP
2. Or run the redirector as your authoritative DNS server for the domain
3. Ensure UDP port 53 is accessible

**Server Behavior:**

- **Benign responses**: Non-C2 queries to A records return `0.0.0.0` instead of NXDOMAIN to avoid breaking recursive DNS lookups (e.g., when using Cloudflare as an intermediary)
- **Conversation tracking**: The server tracks up to 10,000 concurrent conversations
- **Timeout management**: Conversations timeout after 15 minutes of inactivity (reduced to 5 minutes when at capacity)
- **Maximum data size**: 50MB per request

See the [DNS Transport Configuration](/user-guide/imix#dns-transport-configuration) section in the Imix user guide for more details on agent-side configuration.

### gRPC Redirector

The gRPC redirector provides a passthrough for gRPC traffic, useful for deploying multiple Tavern endpoints or load balancing.

```bash
# Start gRPC redirector on port 9000
tavern redirector --transport grpc --listen ":9000" localhost:8000
```

## Configuration

Expand Down
115 changes: 94 additions & 21 deletions docs/_docs/dev-guide/imix.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,29 +91,45 @@ pub use mac_address::MacAddress;

## Develop a New Transport

We've tried to make Imix super extensible for transport development. In fact, all of the transport specific logic is complete abstracted from how Imix operates for callbacks/tome excution. For Imix all Transports live in the `realm/implants/lib/transport/src` directory.
We've tried to make Imix super extensible for transport development. In fact, all of the transport specific logic is completely abstracted from how Imix operates for callbacks/tome execution. For Imix all Transports live in the `realm/implants/lib/transport/src` directory.

If creating a new Transport create a new file in the directory and name it after the protocol you plan to use. For example, if writing a DNS Transport then call your file `dns.rs`. Then define your public struct where any connection state/clients will be. For example,
### Current Available Transports

Realm currently includes three transport implementations:

- **`grpc`** - Default gRPC transport (with optional DoH support via `grpc-doh` feature)
- **`http1`** - HTTP/1.1 transport
- **`dns`** - DNS-based covert channel transport

**Note:** Only one transport may be selected at compile time. The build will fail if multiple transport features are enabled simultaneously.

### Creating a New Transport

If creating a new Transport, create a new file in the `realm/implants/lib/transport/src` directory and name it after the protocol you plan to use. For example, if writing a new protocol called "Custom" then call your file `custom.rs`. Then define your public struct where any connection state/clients will be stored. For example,

```rust
#[derive(Debug, Clone)]
pub struct DNS {
dns_client: Option<hickory_dns::Client>
pub struct Custom {
// Your connection state here
// e.g., client: Option<CustomClient>
}
```

NOTE: Depending on the struct you build, you may need to derive certain features, see above we derive `Debug` and `Clone`.
**NOTE:** Your struct **must** derive `Clone` and `Send` as these are required by the Transport trait. Deriving `Debug` is also recommended for troubleshooting.

Next, we need to implement the Transport trait for our new struct. This will look like:

```rust
impl Transport for DNS {
impl Transport for Custom {
fn init() -> Self {
DNS{ dns_client: None }
Custom {
// Initialize your connection state here
// e.g., client: None
}
}
fn new(callback: String, proxy_uri: Option<String>) -> Result<Self> {
// TODO: setup connection/client hook in proxy, anything else needed
// before fuctions get called.
// before functions get called.
Err(anyhow!("Unimplemented!"))
}
async fn claim_tasks(&mut self, request: ClaimTasksRequest) -> Result<ClaimTasksResponse> {
Expand Down Expand Up @@ -169,44 +185,101 @@ impl Transport for DNS {

NOTE: Be Aware that currently `reverse_shell` uses tokio's sender/reciever while the rest of the methods rely on mpsc's. This is an artifact of some implementation details under the hood of Imix. Some day we may wish to move completely over to tokio's but currenlty it would just result in performance loss/less maintainable code.

After you implement all the functions/write in a decent error message for operators to understand why the function call failed then you need to import the Transport to the broader lib scope. To do this open up `realm/implants/lib/transport/src/lib.rs` and add in your new Transport like so:
After you implement all the functions and write descriptive error messages for operators to understand why function calls failed, you need to:

```rust
// more stuff above
#### 1. Add Compile-Time Exclusivity Checks

#[cfg(feature = "dns")]
mod dns;
#[cfg(feature = "dns")]
pub use dns::DNS as ActiveTransport;
In `realm/implants/lib/transport/src/lib.rs`, add compile-time checks to ensure your new transport cannot be compiled alongside others:

// more stuff below
```rust
// Add your transport to the mutual exclusivity checks
#[cfg(all(feature = "grpc", feature = "custom"))]
compile_error!("only one transport may be selected");
#[cfg(all(feature = "http1", feature = "custom"))]
compile_error!("only one transport may be selected");
#[cfg(all(feature = "dns", feature = "custom"))]
compile_error!("only one transport may be selected");

// ... existing checks above ...

// Add your transport module and export
#[cfg(feature = "custom")]
mod custom;
#[cfg(feature = "custom")]
pub use custom::Custom as ActiveTransport;
```

Also add your new feature to the Transport Cargo.toml at `realm/implants/lib/transport/Cargo.toml`.
**Important:** The transport is exported as `ActiveTransport`, not by its type name. This allows the imix agent code to remain transport-agnostic.

#### 2. Update Transport Library Dependencies

Add your new feature and any required dependencies to `realm/implants/lib/transport/Cargo.toml`:

```toml
# more stuff above

[features]
default = []
grpc = []
dns = [] # <-- see here
grpc-doh = ["grpc", "dep:hickory-resolver"]
http1 = []
dns = ["dep:data-encoding", "dep:rand"]
custom = ["dep:your-custom-dependency"] # <-- Add your feature here
mock = ["dep:mockall"]

[dependencies]
# ... existing dependencies ...

# Add any dependencies needed by your transport
your-custom-dependency = { version = "1.0", optional = true }

# more stuff below
```

Then make sure the feature flag is populated down from the imix crate `realm/implants/imix/Cargo.toml`
#### 3. Enable Your Transport in Imix

To use your new transport, update the imix Cargo.toml at `realm/implants/imix/Cargo.toml`:

```toml
# more stuff above

[features]
default = ["transport/grpc"]
# Check if compiled by imix
win_service = []
default = ["transport/grpc"] # Default transport
http1 = ["transport/http1"]
dns = ["transport/dns"]
custom = ["transport/custom"] # <-- Add your feature here
transport-grpc-doh = ["transport/grpc-doh"]

# more stuff below
```

And that's all that is needed for Imix to use a new Transport! Now all there is to do is setup the Tarver redirector see the [tavern dev docs here](/dev-guide/tavern#transport-development)
#### 4. Build Imix with Your Transport

Compile imix with your custom transport:

```bash
# From the repository root
cd implants/imix

# Build with your transport feature
cargo build --release --features custom --no-default-features

# Or for the default transport (grpc)
cargo build --release
```

**Important:** Only specify one transport feature at a time. The build will fail if multiple transport features are enabled. Ensure you include `--no-default-features` when building with a non-default transport.

#### 5. Set Up the Corresponding Redirector

For your agent to communicate, you'll need to implement a corresponding redirector in Tavern. See the redirector implementations in `tavern/internal/redirectors/` for examples:

- `tavern/internal/redirectors/grpc/` - gRPC redirector
- `tavern/internal/redirectors/http1/` - HTTP/1.1 redirector
- `tavern/internal/redirectors/dns/` - DNS redirector

Your redirector must implement the `Redirector` interface and register itself in the redirector registry. See `tavern/internal/redirectors/redirector.go` for the interface definition.

And that's all that is needed for Imix to use a new Transport! The agent code automatically uses whichever transport is enabled at compile time via the `ActiveTransport` type alias.
62 changes: 61 additions & 1 deletion docs/_docs/user-guide/imix.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Building in the dev container limits variables that might cause issues and is th

| Env Var | Description | Default | Required |
| ------- | ----------- | ------- | -------- |
| IMIX_CALLBACK_URI | URI for initial callbacks (must specify a scheme, e.g. `http://`) | `http://127.0.0.1:8000` | No |
| IMIX_CALLBACK_URI | URI for initial callbacks (must specify a scheme, e.g. `http://` or `dns://`) | `http://127.0.0.1:8000` | No |
| IMIX_SERVER_PUBKEY | The public key for the tavern server (obtain from server using `curl $IMIX_CALLBACK_URI/status`). | automatic | Yes |
| IMIX_CALLBACK_INTERVAL | Duration between callbacks, in seconds. | `5` | No |
| IMIX_RETRY_INTERVAL | Duration to wait before restarting the agent loop if an error occurs, in seconds. | `5` | No |
Expand All @@ -33,6 +33,65 @@ Imix has run-time configuration, that may be specified using environment variabl
| IMIX_BEACON_ID | The identifier to be used during callback (must be globally unique) | Random UUIDv4 | No |
| IMIX_LOG | Log message level for debug builds. See below for more information. | INFO | No |

## DNS Transport Configuration

The DNS transport enables covert C2 communication by tunneling traffic through DNS queries and responses. This transport supports multiple DNS record types (TXT, A, AAAA) and can use either specific DNS servers or the system's default resolver with automatic fallback.

### DNS URI Format

When using the DNS transport, configure `IMIX_CALLBACK_URI` with the following format:

```
dns://<server>?domain=<DOMAIN>[&type=<TYPE>]
```

**Parameters:**
- `<server>` - DNS server address(es), `*` to use system resolver, or comma-separated list (e.g., `8.8.8.8:53,1.1.1.1:53`)
- `domain` - Base domain for DNS queries (e.g., `c2.example.com`)
- `type` (optional) - DNS record type: `txt` (default), `a`, or `aaaa`

**Examples:**

```bash
# Use specific DNS server with TXT records (default)
export IMIX_CALLBACK_URI="dns://8.8.8.8:53?domain=c2.example.com"

# Use system resolver with fallbacks
export IMIX_CALLBACK_URI="dns://*?domain=c2.example.com"

# Use multiple DNS servers with A records
export IMIX_CALLBACK_URI="dns://8.8.8.8:53,1.1.1.1:53?domain=c2.example.com&type=a"

# Use AAAA records
export IMIX_CALLBACK_URI="dns://8.8.8.8:53?domain=c2.example.com&type=aaaa"
```

### DNS Resolver Fallback

When using `*` as the server, the agent uses system DNS servers followed by public resolvers (1.1.1.1, 8.8.8.8) as fallbacks. If system configuration cannot be read, only the public resolvers are used. When multiple servers are configured, the agent tries each server in order on every failed request until one succeeds, then uses the working server for subsequent requests.

### Record Types

| Type | Description | Use Case |
|------|-------------|----------|
| TXT | Text records (default) | Best throughput, data encoded in TXT RDATA |
| A | IPv4 address records | Lower profile, data encoded across multiple A records |
| AAAA | IPv6 address records | Medium profile, more data per record than A |

### Protocol Details

The DNS transport uses an async windowed protocol to handle UDP unreliability:

- **Chunked transmission**: Large requests are split into chunks that fit within DNS query limits (253 bytes total domain length)
- **Windowed sending**: Up to 10 packets are sent concurrently
- **ACK/NACK protocol**: The server responds with acknowledgments for received chunks and requests retransmission of missing chunks
- **Automatic retries**: Failed chunks are retried up to 3 times before the request fails
- **CRC32 verification**: Data integrity is verified using CRC32 checksums

**Limits:**
- Maximum data size: 50MB per request
- Maximum concurrent conversations on server: 10,000

## Logging

At runtime, you may use the `IMIX_LOG` environment variable to control log levels and verbosity. See [these docs](https://docs.rs/pretty_env_logger/latest/pretty_env_logger/) for more information. **When building a release version of imix, logging is disabled** and is not included in the released binary.
Expand Down Expand Up @@ -100,6 +159,7 @@ These flags are passed to cargo build Eg.:

- `--features grpc-doh` - Enable DNS over HTTP using cloudflare DNS for the grpc transport
- `--features http1 --no-default-features` - Changes the default grpc transport to use HTTP/1.1. Requires running the http redirector.
- `--features dns --no-default-features` - Changes the default grpc transport to use DNS. Requires running the dns redirector. See the [DNS Transport Configuration](#dns-transport-configuration) section for more information on how to configure the DNS transport URI.

## Setting encryption key

Expand Down
2 changes: 2 additions & 0 deletions implants/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ anyhow = "1.0.65"
assert_cmd = "2.0.6"
async-recursion = "1.0.0"
async-trait = "0.1.68"
base32 = "0.5"
base64 = "0.21.4"
chrono = "0.4.34"
const-decoder = "0.3.0"
Expand Down Expand Up @@ -126,6 +127,7 @@ tonic-build = { git = "https://github.com/hyperium/tonic.git", rev = "c783652" }
trait-variant = "0.1.1"
uuid = "1.5.0"
static_vcruntime = "2.0"
url = "2.5"
which = "4.4.2"
whoami = { version = "1.5.1", default-features = false }
windows-service = "0.6.0"
Expand Down
1 change: 1 addition & 0 deletions implants/imix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ crate-type = ["cdylib"]
win_service = []
default = ["transport/grpc"]
http1 = ["transport/http1"]
dns = ["transport/dns"]
transport-grpc-doh = ["transport/grpc-doh"]

[dependencies]
Expand Down
3 changes: 2 additions & 1 deletion implants/imixv2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ edition = "2024"
crate-type = ["cdylib"]

[features]
default = ["install", "grpc", "http1"]
default = ["install", "grpc", "http1", "dns"]
grpc = ["transport/grpc"]
http1 = ["transport/http1"]
dns = ["transport/dns"]
win_service = []
install = []

Expand Down
1 change: 1 addition & 0 deletions implants/lib/pb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ default = []
imix = []
grpc = []
http1 = []
dns = []


[dependencies]
Expand Down
14 changes: 14 additions & 0 deletions implants/lib/pb/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(_) => println!("generated c2 protos"),
};

// Build DNS Protos (no encryption codec - used for transport layer only)
match tonic_build::configure()
.out_dir("./src/generated")
.build_server(false)
.build_client(false)
.compile(&["dns.proto"], &["../../../tavern/internal/c2/proto/"])
{
Err(err) => {
println!("WARNING: Failed to compile dns protos: {}", err);
panic!("{}", err);
}
Ok(_) => println!("generated dns protos"),
};

Ok(())
}
Loading
Loading