Skip to content

Commit 70f622e

Browse files
authored
Merge branch 'main' into tests/coverage-expansion-3584061628779474263
2 parents e163c68 + 8ef8ea8 commit 70f622e

File tree

36 files changed

+5133
-278
lines changed

36 files changed

+5133
-278
lines changed

docs/_docs/admin-guide/tavern.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,74 @@ Below are some deployment gotchas and notes that we try to address with Terrafor
104104

105105
## Redirectors
106106

107-
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`
107+
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.
108+
109+
### Available Redirectors
110+
111+
Realm includes three built-in redirector implementations:
112+
113+
- **`grpc`** - Direct gRPC passthrough redirector
114+
- **`http1`** - HTTP/1.1 to gRPC redirector
115+
- **`dns`** - DNS to gRPC redirector
116+
117+
### Basic Usage
118+
119+
List available redirectors:
120+
121+
```bash
122+
tavern redirector list
123+
```
124+
125+
Start a redirector:
126+
127+
```bash
128+
tavern redirector --transport <TRANSPORT> --listen <LISTEN_ADDR> <UPSTREAM_GRPC_ADDR>
129+
```
130+
131+
### HTTP/1.1 Redirector
132+
133+
The HTTP/1.1 redirector accepts HTTP/1.1 traffic from agents and forwards it to an upstream gRPC server.
134+
135+
```bash
136+
# Start HTTP/1.1 redirector on port 8080
137+
tavern redirector --transport http1 --listen ":8080" localhost:8000
138+
```
139+
140+
### DNS Redirector
141+
142+
The DNS redirector tunnels C2 traffic through DNS queries and responses, providing a covert communication channel. It supports TXT, A, and AAAA record types.
143+
144+
```bash
145+
# Start DNS redirector on UDP port 53 for domain c2.example.com
146+
tavern redirector --transport dns --listen "0.0.0.0:53?domain=c2.example.com" localhost:8000
147+
148+
# Support multiple domains
149+
tavern redirector --transport dns --listen "0.0.0.0:53?domain=c2.example.com&domain=backup.example.com" localhost:8000
150+
```
151+
152+
**DNS Configuration Requirements:**
153+
154+
1. Configure your DNS server to delegate queries for your C2 domain to the redirector IP
155+
2. Or run the redirector as your authoritative DNS server for the domain
156+
3. Ensure UDP port 53 is accessible
157+
158+
**Server Behavior:**
159+
160+
- **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)
161+
- **Conversation tracking**: The server tracks up to 10,000 concurrent conversations
162+
- **Timeout management**: Conversations timeout after 15 minutes of inactivity (reduced to 5 minutes when at capacity)
163+
- **Maximum data size**: 50MB per request
164+
165+
See the [DNS Transport Configuration](/user-guide/imix#dns-transport-configuration) section in the Imix user guide for more details on agent-side configuration.
166+
167+
### gRPC Redirector
168+
169+
The gRPC redirector provides a passthrough for gRPC traffic, useful for deploying multiple Tavern endpoints or load balancing.
170+
171+
```bash
172+
# Start gRPC redirector on port 9000
173+
tavern redirector --transport grpc --listen ":9000" localhost:8000
174+
```
108175

109176
## Configuration
110177

docs/_docs/dev-guide/imix.md

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -91,29 +91,45 @@ pub use mac_address::MacAddress;
9191

9292
## Develop a New Transport
9393

94-
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.
94+
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.
9595

96-
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,
96+
### Current Available Transports
97+
98+
Realm currently includes three transport implementations:
99+
100+
- **`grpc`** - Default gRPC transport (with optional DoH support via `grpc-doh` feature)
101+
- **`http1`** - HTTP/1.1 transport
102+
- **`dns`** - DNS-based covert channel transport
103+
104+
**Note:** Only one transport may be selected at compile time. The build will fail if multiple transport features are enabled simultaneously.
105+
106+
### Creating a New Transport
107+
108+
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,
97109

98110
```rust
99111
#[derive(Debug, Clone)]
100-
pub struct DNS {
101-
dns_client: Option<hickory_dns::Client>
112+
pub struct Custom {
113+
// Your connection state here
114+
// e.g., client: Option<CustomClient>
102115
}
103116
```
104117

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

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

109122
```rust
110-
impl Transport for DNS {
123+
impl Transport for Custom {
111124
fn init() -> Self {
112-
DNS{ dns_client: None }
125+
Custom {
126+
// Initialize your connection state here
127+
// e.g., client: None
128+
}
113129
}
114130
fn new(callback: String, proxy_uri: Option<String>) -> Result<Self> {
115131
// TODO: setup connection/client hook in proxy, anything else needed
116-
// before fuctions get called.
132+
// before functions get called.
117133
Err(anyhow!("Unimplemented!"))
118134
}
119135
async fn claim_tasks(&mut self, request: ClaimTasksRequest) -> Result<ClaimTasksResponse> {
@@ -169,44 +185,101 @@ impl Transport for DNS {
169185

170186
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.
171187

172-
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:
188+
After you implement all the functions and write descriptive error messages for operators to understand why function calls failed, you need to:
173189

174-
```rust
175-
// more stuff above
190+
#### 1. Add Compile-Time Exclusivity Checks
176191

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

182-
// more stuff below
194+
```rust
195+
// Add your transport to the mutual exclusivity checks
196+
#[cfg(all(feature = "grpc", feature = "custom"))]
197+
compile_error!("only one transport may be selected");
198+
#[cfg(all(feature = "http1", feature = "custom"))]
199+
compile_error!("only one transport may be selected");
200+
#[cfg(all(feature = "dns", feature = "custom"))]
201+
compile_error!("only one transport may be selected");
202+
203+
// ... existing checks above ...
204+
205+
// Add your transport module and export
206+
#[cfg(feature = "custom")]
207+
mod custom;
208+
#[cfg(feature = "custom")]
209+
pub use custom::Custom as ActiveTransport;
183210
```
184211

185-
Also add your new feature to the Transport Cargo.toml at `realm/implants/lib/transport/Cargo.toml`.
212+
**Important:** The transport is exported as `ActiveTransport`, not by its type name. This allows the imix agent code to remain transport-agnostic.
213+
214+
#### 2. Update Transport Library Dependencies
215+
216+
Add your new feature and any required dependencies to `realm/implants/lib/transport/Cargo.toml`:
186217

187218
```toml
188219
# more stuff above
189220

190221
[features]
191222
default = []
192223
grpc = []
193-
dns = [] # <-- see here
224+
grpc-doh = ["grpc", "dep:hickory-resolver"]
225+
http1 = []
226+
dns = ["dep:data-encoding", "dep:rand"]
227+
custom = ["dep:your-custom-dependency"] # <-- Add your feature here
194228
mock = ["dep:mockall"]
195229

230+
[dependencies]
231+
# ... existing dependencies ...
232+
233+
# Add any dependencies needed by your transport
234+
your-custom-dependency = { version = "1.0", optional = true }
235+
196236
# more stuff below
197237
```
198238

199-
Then make sure the feature flag is populated down from the imix crate `realm/implants/imix/Cargo.toml`
239+
#### 3. Enable Your Transport in Imix
240+
241+
To use your new transport, update the imix Cargo.toml at `realm/implants/imix/Cargo.toml`:
242+
200243
```toml
201244
# more stuff above
202245

203246
[features]
204-
default = ["transport/grpc"]
247+
# Check if compiled by imix
248+
win_service = []
249+
default = ["transport/grpc"] # Default transport
205250
http1 = ["transport/http1"]
206251
dns = ["transport/dns"]
252+
custom = ["transport/custom"] # <-- Add your feature here
207253
transport-grpc-doh = ["transport/grpc-doh"]
208254

209255
# more stuff below
210256
```
211257

212-
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)
258+
#### 4. Build Imix with Your Transport
259+
260+
Compile imix with your custom transport:
261+
262+
```bash
263+
# From the repository root
264+
cd implants/imix
265+
266+
# Build with your transport feature
267+
cargo build --release --features custom --no-default-features
268+
269+
# Or for the default transport (grpc)
270+
cargo build --release
271+
```
272+
273+
**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.
274+
275+
#### 5. Set Up the Corresponding Redirector
276+
277+
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:
278+
279+
- `tavern/internal/redirectors/grpc/` - gRPC redirector
280+
- `tavern/internal/redirectors/http1/` - HTTP/1.1 redirector
281+
- `tavern/internal/redirectors/dns/` - DNS redirector
282+
283+
Your redirector must implement the `Redirector` interface and register itself in the redirector registry. See `tavern/internal/redirectors/redirector.go` for the interface definition.
284+
285+
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.

docs/_docs/user-guide/eldritch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,12 @@ The <b>file.moveto</b> method moves a file or directory from `src` to `dst`. If
455455

456456
The <b>file.parent_dir</b> method returns the parent directory of a give path. Eg `/etc/ssh/sshd_config` -> `/etc/ssh`
457457

458+
### file.pwd (V2-Only)
459+
460+
`file.pwd() -> Option<str>`
461+
462+
The <b>file.pwd</b> method returns the current working directory of the process. If it could not be determined, `None` is returned.
463+
458464
### file.read
459465

460466
`file.read(path: str) -> str`

docs/_docs/user-guide/imix.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Building in the dev container limits variables that might cause issues and is th
1818

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

36+
## DNS Transport Configuration
37+
38+
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.
39+
40+
### DNS URI Format
41+
42+
When using the DNS transport, configure `IMIX_CALLBACK_URI` with the following format:
43+
44+
```
45+
dns://<server>?domain=<DOMAIN>[&type=<TYPE>]
46+
```
47+
48+
**Parameters:**
49+
- `<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`)
50+
- `domain` - Base domain for DNS queries (e.g., `c2.example.com`)
51+
- `type` (optional) - DNS record type: `txt` (default), `a`, or `aaaa`
52+
53+
**Examples:**
54+
55+
```bash
56+
# Use specific DNS server with TXT records (default)
57+
export IMIX_CALLBACK_URI="dns://8.8.8.8:53?domain=c2.example.com"
58+
59+
# Use system resolver with fallbacks
60+
export IMIX_CALLBACK_URI="dns://*?domain=c2.example.com"
61+
62+
# Use multiple DNS servers with A records
63+
export IMIX_CALLBACK_URI="dns://8.8.8.8:53,1.1.1.1:53?domain=c2.example.com&type=a"
64+
65+
# Use AAAA records
66+
export IMIX_CALLBACK_URI="dns://8.8.8.8:53?domain=c2.example.com&type=aaaa"
67+
```
68+
69+
### DNS Resolver Fallback
70+
71+
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.
72+
73+
### Record Types
74+
75+
| Type | Description | Use Case |
76+
|------|-------------|----------|
77+
| TXT | Text records (default) | Best throughput, data encoded in TXT RDATA |
78+
| A | IPv4 address records | Lower profile, data encoded across multiple A records |
79+
| AAAA | IPv6 address records | Medium profile, more data per record than A |
80+
81+
### Protocol Details
82+
83+
The DNS transport uses an async windowed protocol to handle UDP unreliability:
84+
85+
- **Chunked transmission**: Large requests are split into chunks that fit within DNS query limits (253 bytes total domain length)
86+
- **Windowed sending**: Up to 10 packets are sent concurrently
87+
- **ACK/NACK protocol**: The server responds with acknowledgments for received chunks and requests retransmission of missing chunks
88+
- **Automatic retries**: Failed chunks are retried up to 3 times before the request fails
89+
- **CRC32 verification**: Data integrity is verified using CRC32 checksums
90+
91+
**Limits:**
92+
- Maximum data size: 50MB per request
93+
- Maximum concurrent conversations on server: 10,000
94+
3695
## Logging
3796

3897
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.
@@ -100,6 +159,7 @@ These flags are passed to cargo build Eg.:
100159

101160
- `--features grpc-doh` - Enable DNS over HTTP using cloudflare DNS for the grpc transport
102161
- `--features http1 --no-default-features` - Changes the default grpc transport to use HTTP/1.1. Requires running the http redirector.
162+
- `--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.
103163

104164
## Setting encryption key
105165

implants/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ anyhow = "1.0.65"
6161
assert_cmd = "2.0.6"
6262
async-recursion = "1.0.0"
6363
async-trait = "0.1.68"
64+
base32 = "0.5"
6465
base64 = "0.21.4"
6566
chrono = "0.4.34"
6667
const-decoder = "0.3.0"
@@ -126,6 +127,7 @@ tonic-build = { git = "https://github.com/hyperium/tonic.git", rev = "c783652" }
126127
trait-variant = "0.1.1"
127128
uuid = "1.5.0"
128129
static_vcruntime = "2.0"
130+
url = "2.5"
129131
which = "4.4.2"
130132
whoami = { version = "1.5.1", default-features = false }
131133
windows-service = "0.6.0"

implants/imix/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ crate-type = ["cdylib"]
1111
win_service = []
1212
default = ["transport/grpc"]
1313
http1 = ["transport/http1"]
14+
dns = ["transport/dns"]
1415
transport-grpc-doh = ["transport/grpc-doh"]
1516

1617
[dependencies]

implants/imixv2/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ edition = "2024"
77
crate-type = ["cdylib"]
88

99
[features]
10-
default = ["install", "grpc", "http1"]
10+
default = ["install", "grpc", "http1", "dns"]
1111
grpc = ["transport/grpc"]
1212
http1 = ["transport/http1"]
13+
dns = ["transport/dns"]
1314
win_service = []
1415
install = []
1516

implants/lib/eldritchv2/eldritchv2/src/bindings_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ fn test_file_bindings() {
6060
"mkdir",
6161
"move",
6262
"parent_dir",
63+
"pwd",
6364
"read",
6465
"read_binary",
6566
"remove",

implants/lib/eldritchv2/stdlib/eldritch-libfile/src/fake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ impl FileLibrary for FileLibraryFake {
253253
}
254254
}
255255

256+
fn pwd(&self) -> Result<Option<String>, String> {
257+
Ok(Some("/home/user".to_string()))
258+
}
259+
256260
fn remove(&self, path: String) -> Result<(), String> {
257261
let mut root = self.root.lock();
258262
let parts = Self::normalize_path(&path);

0 commit comments

Comments
 (0)