Skip to content

Commit

Permalink
Add support for Electron 15 (#7)
Browse files Browse the repository at this point in the history
* Add support for new Node debugging fuses

Increase minimum tested Electron version to 15

* Add support for new ASAR integrity fuses
  • Loading branch information
complexspaces authored Sep 22, 2021
1 parent 7c20492 commit 2caa53d
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 43 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ Notable changes to this project will be documented in the [keep a changelog](htt

## [Unreleased]

### Changed
* Updated minimum supported Electron version to 15.
* Deprecated patching with `NodeJsCommandLineFlag`. This has been superseded by the `NodeCliInspect` fuse.
* Deprecated patching with `DevToolsMessage`. It is no longer needed due to the functionality provided by the `NodeCliInspect` fuse.

### New
* Added support for Electron's experimental cookie encryption fuse added in version 13.
* Added suport for Electron's new fuses to disable NodeJS debugging flags and environment variables.
* Added support for Electron's new ASAR integrity fuses to protect against unknown code from being ran.

## [0.2.1] - 2021-06-02

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ cargo install electron-hardener
```

## Electron compatibility
`electron-harder` tracks the latest stable version of Electron. Functionality is currently tested on a minimum version of Electron 13. Older versions may partially work but this is not guaranteed.
`electron-harder` tracks the latest stable version of Electron. Functionality is currently tested on a minimum version of Electron 15. Older versions may partially work but this is not guaranteed.

## MSRV

Expand Down
Binary file modified examples/fake_electron_flags.bin
Binary file not shown.
Binary file modified examples/fake_electron_fuses.bin
Binary file not shown.
14 changes: 6 additions & 8 deletions examples/usage.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! A small and basic sample of how to use the library's functionality, without needing an Electron app present.
use electron_hardener::{
patcher::NodeJsCommandLineFlag, BinaryError, ElectronApp, Fuse, PatcherError,
};
use electron_hardener::{patcher::ElectronOption, BinaryError, ElectronApp, Fuse, PatcherError};

fn main() {
let mut application_bytes = {
Expand All @@ -16,22 +14,22 @@ fn main() {
let fuse = Fuse::RunAsNode;

let original_status = app.get_fuse_status(fuse).unwrap();
println!("The unmodified fuse status is {:?}", original_status);
println!("The unmodified fuse status is `{:?}`", original_status);

println!("Removing RUN_AS_NODE functionality");
app.set_fuse_status(fuse, false).unwrap();

let new_status = app.get_fuse_status(fuse).unwrap();
println!("The new fuse status is now {:?}", new_status);
println!("The new fuse status is now `{:?}`", new_status);

let flag = NodeJsCommandLineFlag::Inspect;
let flag = ElectronOption::JsFlags;

println!("Removing {:?} functionality from the app", flag);
app.patch_option(flag).unwrap();

match app.patch_option(flag) {
Err(PatcherError::Binary(BinaryError::NodeJsFlagNotPresent(_))) => {
println!("Removed the Node.JS flag!")
Err(PatcherError::Binary(BinaryError::ElectronOptionNotPresent(_))) => {
println!("Removed the Electron flag!")
}
_ => println!("Didn't remove the flag!"),
}
Expand Down
32 changes: 6 additions & 26 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
//! A re-implementation of the `electron-evil-feature-patcher` CLI tool that works nearly identically.
use electron_hardener::{
patcher::{DevToolsMessage, ElectronOption, NodeJsCommandLineFlag},
ElectronApp, Fuse,
};
use electron_hardener::{patcher::ElectronOption, ElectronApp, Fuse};
use std::{env, fs};

const FUSES: &[Fuse] = &[Fuse::RunAsNode];

const NODEJS_FLAGS: &[NodeJsCommandLineFlag] = &[
NodeJsCommandLineFlag::Inspect,
NodeJsCommandLineFlag::InspectBrk,
NodeJsCommandLineFlag::InspectPort,
NodeJsCommandLineFlag::Debug,
NodeJsCommandLineFlag::DebugBrk,
NodeJsCommandLineFlag::DebugPort,
NodeJsCommandLineFlag::InspectBrkNode,
NodeJsCommandLineFlag::InspectPublishUid,
];
const FUSES_TO_DISABLE: &[Fuse] = &[Fuse::RunAsNode, Fuse::NodeOptions, Fuse::NodeCliInspect];
const FUSES_TO_ENABLE: &[Fuse] = &[Fuse::OnlyLoadAppFromAsar];

const ELECTRON_FLAGS: &[ElectronOption] = &[
ElectronOption::JsFlags,
Expand All @@ -26,9 +13,6 @@ const ELECTRON_FLAGS: &[ElectronOption] = &[
ElectronOption::WaitForDebuggerChildren,
];

const DEVTOOLS_MESSAGES: &[DevToolsMessage] =
&[DevToolsMessage::Listening, DevToolsMessage::ListeningWs];

fn main() -> Result<(), Box<dyn std::error::Error>> {
let application_path = env::args()
.nth(1)
Expand All @@ -38,22 +22,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let mut app = ElectronApp::from_bytes(&mut application_bytes)?;

for fuse in FUSES.iter().copied() {
for fuse in FUSES_TO_DISABLE.iter().copied() {
app.set_fuse_status(fuse, false)?;
}

for flag in NODEJS_FLAGS.iter().copied() {
app.patch_option(flag)?;
for fuse in FUSES_TO_ENABLE.iter().copied() {
app.set_fuse_status(fuse, true)?;
}

for flag in ELECTRON_FLAGS.iter().copied() {
app.patch_option(flag)?;
}

for msg in DEVTOOLS_MESSAGES.iter().copied() {
app.patch_option(msg)?;
}

fs::write(application_path, application_bytes)?;

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ pub enum BinaryError {
/// The value found querying the fuse.
value: u8,
},
#[allow(deprecated)]
/// The Node.JS command line flag attempted to be disabled wasn't present.
NodeJsFlagNotPresent(crate::patcher::NodeJsCommandLineFlag),
/// The Electron command line flag attempted to be disabled wasn't present.
ElectronOptionNotPresent(crate::patcher::ElectronOption),
#[allow(deprecated)]
/// The Node.JS debugging message attempted to be disabled wasn't present.
MessageNotPresent(crate::patcher::DevToolsMessage),
}
Expand Down
36 changes: 36 additions & 0 deletions src/fuses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ pub enum Fuse {
/// Enables [experimental cookie encryption](https://github.com/electron/electron/pull/27524) support
/// in the application.
EncryptedCookies,
/// Disbles the ability to use the [NODE_OPTIONS] environment variable on the application.
///
/// [NODE_OPTIONS]: (https://nodejs.org/api/cli.html#cli_node_options_options)
NodeOptions,
/// Disables the ability to use the [debugging command-line flags] on the application.
///
/// [debugging command-line flags](https://nodejs.org/en/docs/guides/debugging-getting-started/#command-line-options)
NodeCliInspect,
/// Enables the integrity validation of the `app.asar` file when it and resources inside are loaded by Electron.
///
/// This is designed to prevent tampering with application code on supported platforms.
///
/// To use this, an Electron packaging tool must create the correct checksum and embed it into the application.
/// Otherwise, this will have no effect on custom Electron apps.
///
/// **Note**: This fuse currently only affects macOS. It is a no-op on other operating systems.
EmbeddedAsarIntegrityValidation,
/// Forces Electron to only load the application from `app.asar`. Other files and folders will be ignored
/// if they exist in the search path.
OnlyLoadAppFromAsar,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -69,6 +89,10 @@ impl Fuse {
let wire_pos = match self {
Self::RunAsNode => 1,
Self::EncryptedCookies => 2,
Self::NodeOptions => 3,
Self::NodeCliInspect => 4,
Self::EmbeddedAsarIntegrityValidation => 5,
Self::OnlyLoadAppFromAsar => 6,
};

wire_pos - 1
Expand Down Expand Up @@ -324,6 +348,7 @@ mod tests {

let fuse1 = Fuse::RunAsNode;
let fuse2 = Fuse::EncryptedCookies;
let fuse3 = Fuse::NodeOptions;

let fuse_2_original_status = fuse2.fuse_status(&wire).unwrap();

Expand All @@ -337,5 +362,16 @@ mod tests {
fuse2.disable(&mut wire).unwrap();

assert_eq!(fuse1.fuse_status(&wire).unwrap(), fuse_1_original_status);

let left_fuse_original_status = fuse1.fuse_status(&wire).unwrap();
let right_fuse_original_status = fuse3.fuse_status(&wire).unwrap();

fuse2.enable(&mut wire).unwrap();

assert_eq!(fuse1.fuse_status(&wire).unwrap(), left_fuse_original_status);
assert_eq!(
fuse3.fuse_status(&wire).unwrap(),
right_fuse_original_status
);
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! - A fast and configurable alternative implementation of the [electron-evil-feature-patcher] tool created by [Dimitri Witkowski].
//! All patches it can perform are also exposed in this crate. See its README for more details on how it works.
//!
//! Functionality is tested on a minimum version of Electron 13. Older versions may partially work but this is not guaranteed.
//! Functionality is tested on a minimum version of Electron 15. Older versions may partially work but this is not guaranteed.
//!
//! ### A Note on Effectiveness
//!
Expand Down
43 changes: 36 additions & 7 deletions src/patcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub trait Patchable: private::Sealed {
fn disable(&self, binary: &mut [u8]) -> Result<(), PatcherError>;
}

#[allow(deprecated)]
mod private {
use super::{DevToolsMessage, ElectronOption, NodeJsCommandLineFlag};

Expand All @@ -32,9 +33,12 @@ mod private {
/// See the [Node.JS documentation] for details on what each flag does.
///
/// [Node.JS documentation]: https://nodejs.org/en/docs/guides/debugging-getting-started/#command-line-options
#[deprecated(
since = "0.2.2",
note = "This has been superseded by the NodeCliInspect fuse."
)]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(IntoEnumIterator))]
#[non_exhaustive]
pub enum NodeJsCommandLineFlag {
Inspect,
Expand All @@ -47,6 +51,7 @@ pub enum NodeJsCommandLineFlag {
InspectPublishUid,
}

#[allow(deprecated)]
impl NodeJsCommandLineFlag {
const fn search_string(&self) -> &'static str {
match self {
Expand All @@ -71,6 +76,7 @@ impl NodeJsCommandLineFlag {
}
}

#[allow(deprecated)]
impl Patchable for NodeJsCommandLineFlag {
fn disable(&self, binary: &mut [u8]) -> Result<(), PatcherError> {
let search = Regex::new(self.search_string()).expect("all regex patterns should be valid");
Expand Down Expand Up @@ -153,8 +159,11 @@ impl Patchable for ElectronOption {
/// that Chromium/Electron/Node.JS handle parsing command line arguments. If something is changed
/// and a debugging flag slips through, modifying one of these will cause the application to trigger a segemntation fault
/// and be terminated by the OS, exiting immediately.
#[deprecated(
since = "0.2.2",
note = "This is no longer necessary due to the NodeCliInspect fuse's functionality."
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(IntoEnumIterator))]
#[non_exhaustive]
pub enum DevToolsMessage {
/// The message printed to standard out when Node.JS listens on TCP port.
Expand All @@ -167,15 +176,18 @@ pub enum DevToolsMessage {
ListeningWs,
}

#[allow(deprecated)]
impl DevToolsMessage {
const fn search_string(&self) -> &'static str {
match self {
#[allow(deprecated)]
Self::Listening => "\0Debugger listening on %s\n\0",
Self::ListeningWs => "\0\nDevTools listening on ws://%s%s\n\0",
}
}
}

#[allow(deprecated)]
impl Patchable for DevToolsMessage {
fn disable(&self, binary: &mut [u8]) -> Result<(), PatcherError> {
let search = Regex::new(self.search_string()).expect("all regex patterns should be valid");
Expand Down Expand Up @@ -218,11 +230,24 @@ mod tests {
const TEST_DATA: &[u8] = include_bytes!("../examples/fake_electron_flags.bin");

#[test]
#[allow(deprecated)]
fn disabling_nodejs_flags_works() {
use NodeJsCommandLineFlag::*;
let mut data = TEST_DATA.to_vec();

const ALL_FLAGS: &[NodeJsCommandLineFlag] = &[
Inspect,
InspectBrk,
InspectPort,
Debug,
DebugBrk,
DebugPort,
InspectBrkNode,
InspectPublishUid,
];

// Remove all the flags supported.
for flag in NodeJsCommandLineFlag::into_enum_iter() {
for flag in ALL_FLAGS {
flag.disable(&mut data).unwrap();

if flag.fallback_search_string().is_some() {
Expand All @@ -231,11 +256,11 @@ mod tests {
}

// Ensure they no longer exist
for flag in NodeJsCommandLineFlag::into_enum_iter() {
for flag in ALL_FLAGS {
assert_eq!(
flag.disable(&mut data),
Err(PatcherError::Binary(BinaryError::NodeJsFlagNotPresent(
flag
*flag
)))
);
}
Expand All @@ -261,17 +286,21 @@ mod tests {
}
}

#[allow(deprecated)]
#[test]
fn disabling_debugging_messages_works() {
let mut data = TEST_DATA.to_vec();

const ALL_MESSAGES: &[DevToolsMessage] =
&[DevToolsMessage::ListeningWs, DevToolsMessage::Listening];

// Remove all the options supported.
for msg in DevToolsMessage::into_enum_iter() {
for msg in ALL_MESSAGES.iter().copied() {
msg.disable(&mut data).unwrap();
}

// Ensure they no longer exist
for msg in DevToolsMessage::into_enum_iter() {
for msg in ALL_MESSAGES.iter().copied() {
assert_eq!(
msg.disable(&mut data),
Err(PatcherError::Binary(BinaryError::MessageNotPresent(msg)))
Expand Down

0 comments on commit 2caa53d

Please sign in to comment.