windows_bridge/deploy/
bridge.rs1use 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#[derive(Clone, Copy, PartialEq, Eq)]
17pub struct DeployStage(u8);
18
19impl_stage!(DeployStage);
20
21impl DeployStage {
22 pub const NONE: Self = Self(0x00);
24
25 pub const PARAMETERS: Self = Self(0x01);
27
28 pub const INITIALIZATION: Self = Self(0x02);
30
31 pub const DOWNLOAD: Self = Self(0x03);
33
34 pub const EXTRACT: Self = Self(0x04);
36
37 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
56pub type DeployStatus = Status<DeployStage>;
58
59#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
61pub enum ExecuteResponse {
62 Continue,
64
65 ContinueAndNotify,
67
68 #[default]
70 Abort,
71
72 Wait,
76}
77
78#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
80pub struct DeployPolicy {
81 max_download_retries: u64,
83
84 execute_response: ExecuteResponse,
86}
87
88impl DeployPolicy {
89 pub fn max_download_retries(self, max_download_retries: u64) -> Self {
91 Self {
92 max_download_retries,
93 ..self
94 }
95 }
96
97 pub fn execute_response(self, execute_response: ExecuteResponse) -> Self {
99 Self {
100 execute_response,
101 ..self
102 }
103 }
104
105 #[expect(dead_code, reason = "retained as a convenience")]
107 pub fn allow_execute(self) -> Self {
108 self.execute_response(ExecuteResponse::Continue)
109 }
110
111 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#[derive(Debug)]
124pub struct DeployBridge {
125 policy: DeployPolicy,
127}
128
129impl_bridge_contract!(DeployBridge);
130
131impl DeployBridge {
132 const METHOD_DOWNLOAD: u16 = 0x0001;
134
135 const METHOD_EXECUTE: u16 = 0x0002;
137
138 const METHOD_EXIT: u16 = 0xffff;
140
141 const RESPONSE_CONTINUE: u64 = 0x0000_0000;
143
144 const RESPONSE_WAIT: u64 = 0x0000_0001;
146
147 const RESPONSE_ABORT: u64 = 0xffff_ffff;
149
150 pub fn new(policy: DeployPolicy) -> Self {
152 Self { policy }
153 }
154
155 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 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 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 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 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 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}