Skip to main content

windows_bridge/deploy/
bridge.rs

1use vmi::{
2    VmiContext,
3    arch::amd64::Amd64,
4    driver::VmiRead,
5    os::windows::WindowsOs,
6    trace::Hex,
7    utils::{
8        bridge::{BridgeHandler, BridgePacket, BridgeResponse},
9        shellcode::{Status, impl_bridge_contract, impl_stage},
10    },
11};
12
13use crate::bridge::BridgeOutput;
14
15/// Stage reported by the `deploy` shellcode.
16#[derive(Clone, Copy, PartialEq, Eq)]
17pub struct DeployStage(u8);
18
19impl_stage!(DeployStage);
20
21impl DeployStage {
22    /// No deploy operation was started.
23    pub const NONE: Self = Self(0x00);
24
25    /// Parameter parsing stage.
26    pub const PARAMETERS: Self = Self(0x01);
27
28    /// Initialization stage.
29    pub const INITIALIZATION: Self = Self(0x02);
30
31    /// Download stage.
32    pub const DOWNLOAD: Self = Self(0x03);
33
34    /// Extraction stage.
35    pub const EXTRACT: Self = Self(0x04);
36
37    /// Execution stage.
38    pub const EXECUTE: Self = Self(0x05);
39}
40
41impl std::fmt::Debug for DeployStage {
42    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43        let name = match *self {
44            Self::NONE => "None",
45            Self::PARAMETERS => "Parameters",
46            Self::INITIALIZATION => "Initialization",
47            Self::DOWNLOAD => "Download",
48            Self::EXTRACT => "Extract",
49            Self::EXECUTE => "Execute",
50            _ => return self.0.fmt(f),
51        };
52        f.write_str(name)
53    }
54}
55
56/// Status reported by the `deploy` shellcode.
57pub type DeployStatus = Status<DeployStage>;
58
59/// Host response when the shellcode reaches the execution gate.
60#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
61pub enum ExecuteResponse {
62    /// Allows the shellcode to run the configured executable.
63    Continue,
64
65    /// Allows execution and reports [`BridgeOutput::DeployExecuting`].
66    ContinueAndNotify,
67
68    /// Aborts the shellcode before process execution.
69    #[default]
70    Abort,
71
72    /// Defers execution and reports [`BridgeOutput::DeployWaiting`].
73    ///
74    /// The shellcode sleeps and retries the gate until allowed or aborted.
75    Wait,
76}
77
78/// Policy applied to a `deploy` request.
79#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
80pub struct DeployPolicy {
81    /// Number of retries allowed after failed download attempts.
82    max_download_retries: u64,
83
84    /// Response returned when the shellcode reaches the execution gate.
85    execute_response: ExecuteResponse,
86}
87
88impl DeployPolicy {
89    /// Sets the number of retries allowed after failed download attempts.
90    pub fn max_download_retries(self, max_download_retries: u64) -> Self {
91        Self {
92            max_download_retries,
93            ..self
94        }
95    }
96
97    /// Sets the response returned at the execution gate.
98    pub fn execute_response(self, execute_response: ExecuteResponse) -> Self {
99        Self {
100            execute_response,
101            ..self
102        }
103    }
104
105    /// Allows process execution.
106    #[expect(dead_code, reason = "retained as a convenience")]
107    pub fn allow_execute(self) -> Self {
108        self.execute_response(ExecuteResponse::Continue)
109    }
110
111    /// Allows process execution when `allow_execute` is true.
112    pub fn maybe_allow_execute(self, allow_execute: bool) -> Self {
113        self.execute_response(if allow_execute {
114            ExecuteResponse::Continue
115        }
116        else {
117            ExecuteResponse::Abort
118        })
119    }
120}
121
122/// Host-side bridge handler for the `deploy` shellcode.
123#[derive(Debug)]
124pub struct DeployBridge {
125    /// Policy applied to shellcode requests.
126    policy: DeployPolicy,
127}
128
129impl_bridge_contract!(DeployBridge);
130
131impl DeployBridge {
132    /// Method used to report download readiness or failure.
133    const METHOD_DOWNLOAD: u16 = 0x0001;
134
135    /// Method used to request permission to execute.
136    const METHOD_EXECUTE: u16 = 0x0002;
137
138    /// Method used to report the final status.
139    const METHOD_EXIT: u16 = 0xffff;
140
141    /// Allows the shellcode to continue its current stage.
142    const RESPONSE_CONTINUE: u64 = 0x0000_0000;
143
144    /// Leaves the shellcode waiting at its current stage.
145    const RESPONSE_WAIT: u64 = 0x0000_0001;
146
147    /// Aborts the shellcode's current stage.
148    const RESPONSE_ABORT: u64 = 0xffff_ffff;
149
150    /// Creates a `deploy` bridge with the given policy.
151    pub fn new(policy: DeployPolicy) -> Self {
152        Self { policy }
153    }
154
155    /// Handles a `deploy` bridge packet.
156    fn handle_packet(&self, packet: BridgePacket) -> Option<BridgeResponse<BridgeOutput>> {
157        match packet.method() {
158            Self::METHOD_DOWNLOAD => self.handle_download(packet),
159            Self::METHOD_EXECUTE => self.handle_execute(packet),
160            Self::METHOD_EXIT => self.handle_exit(packet),
161            _ => self.handle_unknown(packet),
162        }
163    }
164
165    /// Handles the [`METHOD_DOWNLOAD`] bridge method.
166    ///
167    /// Applies the download retry limit to a readiness or failure report.
168    ///
169    /// [`METHOD_DOWNLOAD`]: Self::METHOD_DOWNLOAD
170    fn handle_download(&self, packet: BridgePacket) -> Option<BridgeResponse<BridgeOutput>> {
171        let attempt = packet.value1();
172        let native_code = packet.value2() as u32;
173
174        let response = if attempt == 0 || attempt <= self.policy.max_download_retries {
175            Self::RESPONSE_CONTINUE
176        }
177        else {
178            Self::RESPONSE_ABORT
179        };
180
181        tracing::debug!(attempt, native_code, response, "download gate");
182
183        Some(BridgeResponse::new(response))
184    }
185
186    /// Handles the [`METHOD_EXECUTE`] bridge method.
187    ///
188    /// Returns the configured execution response.
189    ///
190    /// [`METHOD_EXECUTE`]: Self::METHOD_EXECUTE
191    fn handle_execute(&self, _packet: BridgePacket) -> Option<BridgeResponse<BridgeOutput>> {
192        let response = match self.policy.execute_response {
193            ExecuteResponse::Continue => BridgeResponse::new(Self::RESPONSE_CONTINUE),
194            ExecuteResponse::ContinueAndNotify => BridgeResponse::new(Self::RESPONSE_CONTINUE)
195                .with_output(BridgeOutput::DeployExecuting),
196            ExecuteResponse::Abort => BridgeResponse::new(Self::RESPONSE_ABORT),
197            ExecuteResponse::Wait => {
198                BridgeResponse::new(Self::RESPONSE_WAIT).with_output(BridgeOutput::DeployWaiting)
199            }
200        };
201
202        tracing::debug!(
203            response = ?self.policy.execute_response,
204            "execute gate"
205        );
206
207        Some(response)
208    }
209
210    /// Handles the [`METHOD_EXIT`] bridge method.
211    ///
212    /// [`METHOD_EXIT`]: Self::METHOD_EXIT
213    fn handle_exit(&self, packet: BridgePacket) -> Option<BridgeResponse<BridgeOutput>> {
214        let status = DeployStatus::decode(packet.value1());
215
216        tracing::debug!(
217            stage = ?status.stage(),
218            kind = ?status.kind(),
219            code = status.code(),
220            native_code = status.native_code(),
221            "shellcode completed"
222        );
223
224        Some(BridgeResponse::default().with_output(BridgeOutput::DeployFinished(status)))
225    }
226
227    /// Handles a bridge packet with an unknown method.
228    ///
229    /// Logs the packet details and returns no response.
230    fn handle_unknown(&self, packet: BridgePacket) -> Option<BridgeResponse<BridgeOutput>> {
231        tracing::warn!(
232            request = %Hex(packet.request()),
233            method = %Hex(packet.method()),
234            value1 = %Hex(packet.value1()),
235            value2 = %Hex(packet.value2()),
236            value3 = %Hex(packet.value3()),
237            value4 = %Hex(packet.value4()),
238            "unknown bridge method"
239        );
240
241        None
242    }
243}
244
245impl<Driver> BridgeHandler<WindowsOs<Driver>> for DeployBridge
246where
247    Driver: VmiRead<Architecture = Amd64>,
248{
249    type Output = BridgeOutput;
250
251    /// Request code for the `deploy` shellcode.
252    const REQUEST: u16 = 0x0011;
253
254    #[tracing::instrument(name = "deploy", skip_all)]
255    fn handle(
256        &mut self,
257        _vmi: &VmiContext<'_, WindowsOs<Driver>>,
258        packet: BridgePacket,
259    ) -> Option<BridgeResponse<BridgeOutput>> {
260        debug_assert_eq!(
261            packet.request(),
262            <Self as BridgeHandler<WindowsOs<Driver>>>::REQUEST
263        );
264
265        self.handle_packet(packet)
266    }
267}