orph3usLyre / muddy-waters

A static string obfuscation library for rust projects
Apache License 2.0
71 stars 4 forks source link

Panic when decrypting if use in different crates #24

Open xuxiaocheng0201 opened 4 days ago

xuxiaocheng0201 commented 4 days ago

I wrapped muddy as a workspace crate to add some pretreatments like trim, but paniced when decrypting at runtime. File tree:

.
├── Cargo.toml
├── obscure
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── obscure_macro
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── src
    └── main.rs

Cargo.toml:

[package]
name = "tester"
version = "0.1.0"
edition = "2021"

[dependencies]
obscure = { path = "obscure" }

[workspace]
members = [
    "obscure",
    "obscure_macro",
]

obscure/Cargo.toml:

[package]
name = "obscure"
version = "0.1.0"
edition = "2021"

[dependencies]
muddy = "~0.2"
obscure_macro = { path = "../obscure_macro" }

obscure/src/lib.rs:

pub extern crate muddy;

muddy::muddy_init!();

pub use obscure_macro::e;

obscure_macro/Cargo.toml:

[package]
name = "obscure_macro"
version = "0.1.0"
edition = "2021"

[dependencies]
syn = "^2"
quote = "^1"

[lib]
proc-macro = true

obscure_macro/src/lib.rs:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};

#[proc_macro]
pub fn e(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as LitStr);
    let secret = input.value();
    let secret = trim(secret);
    quote!({
        use ::obscure::muddy_internal;
        let secret = ::obscure::muddy::m!(#secret);
        debug_assert_eq!(&secret, #secret);
        secret
    }).into()
}

fn trim(secret: String) -> String {
    let mut trim = String::new();
    let mut whitespace = false;
    for ch in secret.trim().chars() {
        if ch.is_ascii_whitespace() {
            whitespace = true;
            continue;
        } else {
            if whitespace {
                trim.push(' ');
                whitespace = false;
            }
            trim.push(ch);
        }
    }
    trim
}

src/main.rs

use obscure::e;

fn main() {
    let str = e!("Hello world");
    println!("{str}");
}

Output:

thread 'main' panicked at obscure/src/lib.rs:3:1:
called `Result::unwrap()` on an `Err` value: Error
stack backtrace:
   0: rust_begin_unwind
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:665:5
   1: core::panicking::panic_fmt
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/panicking.rs:74:14
   2: core::result::unwrap_failed
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/result.rs:1679:5
   3: core::result::Result<T,E>::unwrap
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/result.rs:1102:23
   4: obscure::muddy_internal::decrypt
             at ./obscure/src/lib.rs:3:1
   5: tester::main
             at ./src/main.rs:4:15
   6: core::ops::function::FnOnce::call_once
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

The error is when verifying mac: code. It seems different keys and nonces will be generated when used in different crates?

orph3usLyre commented 4 days ago

Interesting. Thanks for opening an issue! I'll try and take a look at this over the weekend and see if I can figure this out.

orph3usLyre commented 18 hours ago

I've poked at this over the weekend and can't say that I've made too much progress. I've confirmed that the data for both the nonce and the encrypted data is the same at build time/run time. The panic occurs from the call to cipher.decrypt(&nonce, ciphertext.as_ref()) (see https://docs.rs/chacha20poly1305/latest/chacha20poly1305/type.ChaCha20Poly1305.html), and both the embedded and the env-based obfuscation fail to decrypt. There is no associated error data since errors provided by chacha20poly1305 are purposefully opaque. My first suspicion was that it was related to the associated data of the cipher (see Payload) but I don't use this feature in the crate.

I'll have to keep digging. Thanks again for bringing this up.

xuxiaocheng0201 commented 17 hours ago

I run the code in debug mode, and found the error is returned when verifying mac (see this code) (I have told at the end of this issue).

I don't think the Payload is the key, because its implementation is very simple.

xuxiaocheng0201 commented 16 hours ago

I found the reason: The key generate twice, one is proc_macro time, and one is build time.

To reproduce, you can change this line to this:

pub(crate) static KEY: Lazy<Key> = Lazy::new(|| {
    eprintln!("Run once");
    ChaCha20Poly1305::generate_key(&mut OsRng)
});

and run cargo build. You will see Run once twice.

A solution is to split the KEY to another crate, like muddy_key. And muddy_macro crate depends the new crate to read the key.

BTW, a crate like chacha20poly1305 is written by rust-lang owners group, and it has been running stably for two years with no update. It shouldn't have such obvious errors.

orph3usLyre commented 15 hours ago

Good catch! I'll test this out throughout the week. Thanks for the contributions :tada: