electron_hardener/
patcher.rs1use crate::{BinaryError, ElectronApp, PatcherError};
6use regex::bytes::Regex;
7
8#[cfg(test)]
9use enum_iterator::IntoEnumIterator;
10
11pub trait Patchable: private::Sealed {
13 #[doc(hidden)]
14 fn disable(&self, binary: &mut [u8]) -> Result<(), PatcherError>;
18}
19
20#[allow(deprecated)]
21mod private {
22 use super::{DevToolsMessage, ElectronOption, NodeJsCommandLineFlag};
23
24 pub trait Sealed {}
25
26 impl Sealed for NodeJsCommandLineFlag {}
27 impl Sealed for ElectronOption {}
28 impl Sealed for DevToolsMessage {}
29}
30
31#[deprecated(
37 since = "0.2.2",
38 note = "This has been superseded by the NodeCliInspect fuse."
39)]
40#[allow(missing_docs)]
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[non_exhaustive]
43pub enum NodeJsCommandLineFlag {
44 Inspect,
45 InspectBrk,
46 InspectPort,
47 Debug,
48 DebugBrk,
49 DebugPort,
50 InspectBrkNode,
51 InspectPublishUid,
52}
53
54#[allow(deprecated)]
55impl NodeJsCommandLineFlag {
56 const fn search_string(&self) -> &'static str {
57 match self {
58 Self::Inspect => "\0--inspect\0",
59 Self::InspectBrk => "\0--inspect-brk\0",
60 Self::InspectPort => "\0--inspect-port\0",
61 Self::Debug => "\0--debug\0",
62 Self::DebugBrk => "\0--debug-brk\0",
63 Self::DebugPort => "\0--debug-port\0",
64 Self::InspectBrkNode => "\0--inspect-brk-node\0",
65 Self::InspectPublishUid => "\0--inspect-publish-uid\0",
66 }
67 }
68
69 const fn fallback_search_string(&self) -> Option<&'static str> {
70 if matches!(self, Self::Inspect) {
72 Some(r"(?-u)\xAA--inspect\x00")
73 } else {
74 None
75 }
76 }
77}
78
79#[allow(deprecated)]
80impl Patchable for NodeJsCommandLineFlag {
81 fn disable(&self, binary: &mut [u8]) -> Result<(), PatcherError> {
82 let search = Regex::new(self.search_string()).expect("all regex patterns should be valid");
83 let found = search
84 .find(binary)
85 .or_else(|| {
86 self.fallback_search_string().and_then(|s| {
87 let search = Regex::new(s).expect("all regex patterns should be valid");
88 search.find(binary)
89 })
90 })
91 .ok_or(BinaryError::NodeJsFlagNotPresent(*self))?
92 .range();
93
94 for b in &mut binary[found] {
95 if *b == b'-' {
96 *b = b' '
97 }
98 }
99
100 Ok(())
101 }
102}
103
104#[allow(missing_docs)]
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111#[cfg_attr(test, derive(IntoEnumIterator))]
112#[non_exhaustive]
113pub enum ElectronOption {
114 JsFlags,
115 RemoteDebuggingPipe,
116 RemoteDebuggingPort,
117 WaitForDebuggerChildren,
118}
119
120impl ElectronOption {
121 const fn search_string(&self) -> &'static str {
122 match self {
123 Self::JsFlags => "\0js-flags\0",
124 Self::RemoteDebuggingPipe => "\0remote-debugging-pipe\0",
125 Self::RemoteDebuggingPort => "\0remote-debugging-port\0",
126 Self::WaitForDebuggerChildren => "\0wait-for-debugger-children\0",
127 }
128 }
129}
130
131impl Patchable for ElectronOption {
132 fn disable(&self, binary: &mut [u8]) -> Result<(), PatcherError> {
133 let search = Regex::new(self.search_string()).expect("all regex patterns should be valid");
134 let found = search
135 .find(binary)
136 .ok_or(BinaryError::ElectronOptionNotPresent(*self))?
137 .range();
138
139 let replacement = b"\0xx\r\n"
140 .iter()
141 .copied()
142 .chain(std::iter::repeat(0))
143 .take(found.len());
144
145 for (old, new) in binary[found].iter_mut().zip(replacement) {
146 *old = new;
147 }
148
149 Ok(())
150 }
151}
152
153#[deprecated(
163 since = "0.2.2",
164 note = "This is no longer necessary due to the NodeCliInspect fuse's functionality."
165)]
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167#[non_exhaustive]
168pub enum DevToolsMessage {
169 Listening,
173 ListeningWs,
177}
178
179#[allow(deprecated)]
180impl DevToolsMessage {
181 const fn search_string(&self) -> &'static str {
182 match self {
183 #[allow(deprecated)]
184 Self::Listening => "\0Debugger listening on %s\n\0",
185 Self::ListeningWs => "\0\nDevTools listening on ws://%s%s\n\0",
186 }
187 }
188}
189
190#[allow(deprecated)]
191impl Patchable for DevToolsMessage {
192 fn disable(&self, binary: &mut [u8]) -> Result<(), PatcherError> {
193 let search = Regex::new(self.search_string()).expect("all regex patterns should be valid");
194 let found = search
195 .find(binary)
196 .ok_or(BinaryError::MessageNotPresent(*self))?
197 .range();
198
199 let mut replacement = Vec::with_capacity(found.len());
200 replacement.push(b'\0');
201 let str_len = found.len() - 3;
202 for _ in (0..str_len).step_by(2) {
203 replacement.push(b'%');
204 replacement.push(b's');
205 }
206 replacement.extend_from_slice(b"\n\0");
207
208 for (old, new) in binary[found].iter_mut().zip(replacement) {
209 *old = new;
210 }
211
212 Ok(())
213 }
214}
215
216impl ElectronApp<'_> {
217 pub fn patch_option<P: Patchable>(&mut self, to_disable: P) -> Result<(), PatcherError> {
222 to_disable.disable(self.contents)
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 const TEST_DATA: &[u8] = include_bytes!("../examples/fake_electron_flags.bin");
231
232 #[test]
233 #[allow(deprecated)]
234 fn disabling_nodejs_flags_works() {
235 use NodeJsCommandLineFlag::*;
236 let mut data = TEST_DATA.to_vec();
237
238 const ALL_FLAGS: &[NodeJsCommandLineFlag] = &[
239 Inspect,
240 InspectBrk,
241 InspectPort,
242 Debug,
243 DebugBrk,
244 DebugPort,
245 InspectBrkNode,
246 InspectPublishUid,
247 ];
248
249 for flag in ALL_FLAGS {
251 flag.disable(&mut data).unwrap();
252
253 if flag.fallback_search_string().is_some() {
254 let _ = flag.disable(&mut data);
255 }
256 }
257
258 for flag in ALL_FLAGS {
260 assert_eq!(
261 flag.disable(&mut data),
262 Err(PatcherError::Binary(BinaryError::NodeJsFlagNotPresent(
263 *flag
264 )))
265 );
266 }
267 }
268
269 #[test]
270 fn disabling_electron_options_works() {
271 let mut data = TEST_DATA.to_vec();
272
273 for opt in ElectronOption::into_enum_iter() {
275 opt.disable(&mut data).unwrap();
276 }
277
278 for opt in ElectronOption::into_enum_iter() {
280 assert_eq!(
281 opt.disable(&mut data),
282 Err(PatcherError::Binary(BinaryError::ElectronOptionNotPresent(
283 opt
284 )))
285 );
286 }
287 }
288
289 #[allow(deprecated)]
290 #[test]
291 fn disabling_debugging_messages_works() {
292 let mut data = TEST_DATA.to_vec();
293
294 const ALL_MESSAGES: &[DevToolsMessage] =
295 &[DevToolsMessage::ListeningWs, DevToolsMessage::Listening];
296
297 for msg in ALL_MESSAGES.iter().copied() {
299 msg.disable(&mut data).unwrap();
300 }
301
302 for msg in ALL_MESSAGES.iter().copied() {
304 assert_eq!(
305 msg.disable(&mut data),
306 Err(PatcherError::Binary(BinaryError::MessageNotPresent(msg)))
307 );
308 }
309 }
310}