electron_hardener/
fuses.rs1use crate::{BinaryError, ElectronApp, PatcherError};
6use std::ops::Range;
7
8#[cfg(test)]
9use enum_iterator::IntoEnumIterator;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(test, derive(IntoEnumIterator))]
26#[non_exhaustive]
27pub enum Fuse {
28 RunAsNode,
30 EncryptedCookies,
33 NodeOptions,
37 NodeCliInspect,
41 EmbeddedAsarIntegrityValidation,
50 OnlyLoadAppFromAsar,
53}
54
55#[derive(Debug, PartialEq)]
56#[non_exhaustive]
57pub enum FuseStatus {
59 Present(bool),
61 Modified,
63 Removed,
67}
68
69impl Fuse {
70 const SENTINEL: &'static [u8] = b"dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX";
72
73 const DISABLED: u8 = b'0';
75 const ENABLED: u8 = b'1';
77 const REMOVED: u8 = b'r';
83
84 const EXPECTED_VERSION: u8 = 1;
86
87 fn schema_pos(&self) -> usize {
89 let wire_pos = match self {
90 Self::RunAsNode => 1,
91 Self::EncryptedCookies => 2,
92 Self::NodeOptions => 3,
93 Self::NodeCliInspect => 4,
94 Self::EmbeddedAsarIntegrityValidation => 5,
95 Self::OnlyLoadAppFromAsar => 6,
96 };
97
98 wire_pos - 1
99 }
100
101 pub(crate) fn find_wire(binary: &[u8]) -> Result<Range<usize>, PatcherError> {
105 let sentinel_len = Self::SENTINEL.len();
106
107 let pos = binary
108 .windows(sentinel_len)
109 .position(|slice| slice == Self::SENTINEL)
110 .ok_or(BinaryError::NoSentinel)?;
111
112 let start = pos + sentinel_len;
113
114 let version = binary.get(start).ok_or(BinaryError::NoFuseVersion)?;
115
116 if *version != Self::EXPECTED_VERSION {
117 return Err(PatcherError::FuseVersion {
118 expected: Self::EXPECTED_VERSION,
119 found: *version,
120 });
121 }
122
123 let len_pos = start + 1;
124 let wire_len = binary.get(len_pos).ok_or(BinaryError::NoFuseLength)?;
125
126 let wire_start = len_pos + 1;
127 let fuse_bytes = (wire_start)..(wire_start + usize::from(*wire_len));
128
129 Ok(fuse_bytes)
130 }
131
132 fn fuse_status(&self, wire: &[u8]) -> Result<FuseStatus, PatcherError> {
133 let status = wire
134 .get(self.schema_pos())
135 .ok_or(BinaryError::FuseDoesNotExist(*self))?;
136
137 let status = match *status {
138 Self::ENABLED => FuseStatus::Present(true),
139 Self::DISABLED => FuseStatus::Present(false),
140 Self::REMOVED => FuseStatus::Removed,
141 s => {
142 return Err(BinaryError::UnknownFuse {
143 fuse: *self,
144 value: s,
145 }
146 .into())
147 }
148 };
149
150 Ok(status)
151 }
152
153 fn disable(&self, wire: &mut [u8]) -> Result<FuseStatus, PatcherError> {
154 let mut enabled = self.fuse_status(wire)?;
155
156 match enabled {
157 FuseStatus::Present(e) if e => {
158 wire[self.schema_pos()] = Self::DISABLED;
159 enabled = FuseStatus::Modified
160 }
161 FuseStatus::Removed => return Err(PatcherError::RemovedFuse(*self)),
162 _ => {}
163 }
164
165 Ok(enabled)
166 }
167
168 fn enable(&self, wire: &mut [u8]) -> Result<FuseStatus, PatcherError> {
169 let mut enabled = self.fuse_status(wire)?;
170
171 match enabled {
172 FuseStatus::Present(e) if !e => {
173 wire[self.schema_pos()] = Self::ENABLED;
174 enabled = FuseStatus::Modified
175 }
176 FuseStatus::Removed => return Err(PatcherError::RemovedFuse(*self)),
177 _ => {}
178 }
179
180 Ok(enabled)
181 }
182}
183
184impl<'a> ElectronApp<'a> {
185 pub fn from_bytes(application_bytes: &'a mut [u8]) -> Result<ElectronApp<'a>, PatcherError> {
192 let wire_pos = Fuse::find_wire(application_bytes)?;
193
194 Ok(Self {
195 contents: application_bytes,
196 wire_start: wire_pos.start,
197 wire_end: wire_pos.end,
198 })
199 }
200
201 pub fn get_fuse_status(&self, fuse: Fuse) -> Result<FuseStatus, PatcherError> {
211 let wire = &self.contents[self.wire_start..self.wire_end];
212 fuse.fuse_status(wire)
213 }
214
215 pub fn set_fuse_status(
226 &mut self,
227 fuse: Fuse,
228 enabled: bool,
229 ) -> Result<FuseStatus, PatcherError> {
230 let wire = &mut self.contents[self.wire_start..self.wire_end];
231
232 if enabled {
233 fuse.enable(wire)
234 } else {
235 fuse.disable(wire)
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 const TEST_BYTES: &[u8] = include_bytes!("../examples/fake_electron_fuses.bin");
245 const FUSE: Fuse = Fuse::RunAsNode;
246
247 fn get_wire() -> &'static [u8] {
248 let wire_pos = Fuse::find_wire(TEST_BYTES).unwrap();
249 &TEST_BYTES[wire_pos]
250 }
251
252 #[test]
253 fn sentinal_is_found() {
254 assert!(Fuse::find_wire(TEST_BYTES).is_ok());
255 }
256
257 #[test]
258 fn enabled_fuse_is_correct() {
259 assert_eq!(
260 FUSE.fuse_status(get_wire()).unwrap(),
261 FuseStatus::Present(true)
262 );
263 }
264
265 #[test]
266 fn disabled_fuse_is_correct() {
267 let mut wire = get_wire().to_vec();
268 assert_eq!(FUSE.disable(&mut wire).unwrap(), FuseStatus::Modified);
269 assert_eq!(FUSE.fuse_status(&wire).unwrap(), FuseStatus::Present(false));
270 }
271
272 #[test]
273 fn removed_fuse_is_correct() {
274 let mut wire = get_wire().to_vec();
275 wire[FUSE.schema_pos()] = Fuse::REMOVED;
276
277 assert_eq!(FUSE.fuse_status(&wire).unwrap(), FuseStatus::Removed);
278 }
279
280 #[test]
281 fn unknown_fuse_value_is_correct() {
282 let value = 9;
283 let mut wire = get_wire().to_vec();
284 wire[FUSE.schema_pos()] = value;
285
286 assert_eq!(
287 FUSE.fuse_status(&wire),
288 Err(PatcherError::Binary(BinaryError::UnknownFuse {
289 fuse: FUSE,
290 value,
291 }))
292 );
293 }
294
295 #[test]
296 fn modfying_removed_fuse_errors() {
297 let mut wire = get_wire().to_vec();
298 wire[FUSE.schema_pos()] = Fuse::REMOVED;
299
300 assert_eq!(
301 FUSE.disable(&mut wire),
302 Err(PatcherError::RemovedFuse(FUSE))
303 );
304 assert_eq!(FUSE.enable(&mut wire), Err(PatcherError::RemovedFuse(FUSE)));
305 }
306
307 #[test]
308 fn test_app_fuse_actions() {
309 let mut application_bytes = TEST_BYTES.to_vec();
310 let mut app = ElectronApp::from_bytes(&mut application_bytes).unwrap();
311
312 assert_eq!(
313 app.get_fuse_status(FUSE).unwrap(),
314 FuseStatus::Present(true)
315 );
316
317 assert_eq!(
319 app.set_fuse_status(FUSE, true).unwrap(),
320 FuseStatus::Present(true)
321 );
322
323 assert_eq!(
324 app.set_fuse_status(FUSE, false).unwrap(),
325 FuseStatus::Modified
326 );
327 assert_eq!(
328 app.get_fuse_status(FUSE).unwrap(),
329 FuseStatus::Present(false)
330 );
331 }
332
333 #[test]
334 fn can_read_all_fuses() {
335 let wire = get_wire();
336
337 for fuse in Fuse::into_enum_iter() {
338 assert!(matches!(
339 fuse.fuse_status(wire).unwrap(),
340 FuseStatus::Present(_)
341 ));
342 }
343 }
344
345 #[test]
346 fn fuse_modifies_correct_position() {
347 let mut wire = get_wire().to_vec();
348
349 let fuse1 = Fuse::RunAsNode;
350 let fuse2 = Fuse::EncryptedCookies;
351 let fuse3 = Fuse::NodeOptions;
352
353 let fuse_2_original_status = fuse2.fuse_status(&wire).unwrap();
354
355 fuse1.disable(&mut wire).unwrap();
356
357 assert_eq!(fuse2.fuse_status(&wire).unwrap(), fuse_2_original_status);
359
360 let fuse_1_original_status = fuse1.fuse_status(&wire).unwrap();
361
362 fuse2.disable(&mut wire).unwrap();
363
364 assert_eq!(fuse1.fuse_status(&wire).unwrap(), fuse_1_original_status);
365
366 let left_fuse_original_status = fuse1.fuse_status(&wire).unwrap();
367 let right_fuse_original_status = fuse3.fuse_status(&wire).unwrap();
368
369 fuse2.enable(&mut wire).unwrap();
370
371 assert_eq!(fuse1.fuse_status(&wire).unwrap(), left_fuse_original_status);
372 assert_eq!(
373 fuse3.fuse_status(&wire).unwrap(),
374 right_fuse_original_status
375 );
376 }
377}