Hiding secrets in code

I'm writing some programs in pure Rust. The problem I want to solve is to hide hardcoded secrets. To that end, some code is extracted out to be an independent dylib project, and all other code only see .so binaries that yields. The crates "secrets" and "goldberg" are used for mem protection and obfuscation.

But, I still got some security concerns. Here is my code skeleton.

#[inline(always)]
fn f1()-> Aes256Gcm {
    let sb = SecretBox::somthing{
       // obfuscate secrets here
    };
    let aes:Aes256Gcm = Aes256Gcm::new_from_slice(&*sb.borrow()).unwrap();
    mem::forget(sb);
    aes
}

#[no_mangle]
pub unsafe extern "C" fn encrypt(base64_c_char: *mut c_char) -> *mut c_char{
    // some decoding work omitted here
   goldberg_stmts!{
        // something
        let aes = f1();
        let rtn= match aes.encrypt(nonce, s.as_ref()){
            // just some normal work
        };
        
        mem::forget(aes);
        rtn.into_raw()
    }
}

Q1, this is all in C world, can this lead to mem leaks?
Q2, should mem::drop() be preferred over mem::forget()? or even necessary? or something else better?
Q3, I'm really worried about the security, something like cracking this dylib *.so and mem leaks, pls help.
Q4, On the caller side, sure pure Rust also, what could be the best practice concerning security (in particular, hiding the secret something like an encryption key) and mem leaks? is there any other better ways of doing this (hiding secrets and avoiding mem leaks)

This is impossible, you can't solve this, and you shouldn't be trying.

No matter how hard you try to obfuscate/"encrypt"/otherwise hide hard-coded secrets, they can be found eventually. Most intermediate-level reverse engineers will be able to scrape it right from the disassembled code, but if all else fails, it can still be dumped from memory once it's in its decrypted form.

Hard-coding secrets is never the right solution, and splitting your executable in many parts isn't going to help. (Reverse engineering a dylib is not harder than reverse engineering an executable.)

Why are you trying to do this? What's your end goal?

13 Likes

It is said that "Immutably borrows the contents of the [SecretBox]. Returns a wrapper that ensures the underlying memory is [mprotect(2)][mprotect]ed once all borrows exit scope." I did not verify this.

If no obfuscation, the lib can be easily cracked, I tried. But after obfuscation, I couldn't, but I'm not good at reverse engineering.

Sorry I have no idea what you are talking about. No amount of memory protection helps if some secret is hard-coded in the executable. Memory protection occurs at runtime. It does not preclude reading the bits of the file itself.

5 Likes

mprotect makes it easier to find the secrets; it tells me where to look. It's also easy to defeat.

8 Likes

We need to know what your threat model is in order to help you; who's going to be trying to extract the secret? What do they gain by extracting it? How much effort are they willing to put in? What's their alternative to extracting the secrets from the code?

Answers are very, very different if the secret is something like a Google Maps API key used to display a map underneath a dataset your program has computed, versus the necessary information to control the world's nuclear arsenals.

Often, though, you'll find that once you've put your threat model together, the answer is that minimal obfuscation is plenty; anyone who's willing to run your app through Ghidra or IDA Pro is already beyond your ability to protect against, and you're really just aiming to protect the secret against someone who uses the strings tool to extract what they can.

10 Likes

Yes, strings did not work.

The attacker could be very strong, maybe like Lazarus Group. I just tried a free version of IDA, failed to easily find out some hidden secrets, but doesn't mean it is not easy for those experienced in something tools like IDA. The library I'm going to make is used only internally, living in highly secured servers, not spread out.

I thought of something like AWS KMS, but that seems to increase the attack surfaces and more things to consider, it's like nested safes, who holds the key of the outermost is always a question.

If the Lazarus Group obtaining a copy of the binary containing the secret is a plausible threat actor, then you're asking the sorts of questions that imply that you're in way over your head.

To protect against someone like them (or other government-sponsored groups), you need to ensure that they never have access to the secret at all, even in obfuscated form. If the group can get access to the code, in any form, then you've lost; they will bypass any obfuscation you have implemented.

17 Likes

As Schneider says, it's easy for anybody to write a security system so clever that they themselves cannot break it. It doesn't mean that others won't be able to break it.

If your opponent was some amateur game cheater or a lazy software pirate, you could play cat and mouse game of making code obfuscated and harder to analyze. However, if you have a skilled adversary, you can't hide anything in an executable. You would need an advanced DRM system, and these require kernel-level code, hardware support, and these still get broken and bypassed whenever any weakness is discovered.

18 Likes

I don't if that fits your use case, but you could embed some encrypted secret in your code as long as the key needed to decrypt it is not in your code, but somehow provided during runtime.

Or, if the code doesn't need to know the secret, but merely needs to validate that some one else has the secret, you could embed the encrypted secret in the code and then let the party that wants to prove that they have the secret enter the secret and then you encrypt the input and compare to the embedded encrypted secret. This is inherently vulnerable to brute-force attacks, though, if some one gets hold of your code.

1 Like

There are two mechanisms for doing this. I'd recommend against using either, and they're incredibly advanced code obfuscation techniques either way. One is good for checking a box but doesn't provide practical security. The other is "moon math" and not particularly practical either.

  1. Whitebox cryptography: a name for obfuscation techniques for embedding secrets in compiled binaries. The main thing to know about whiteboxes is that they're, as a rule, conceptually broken. It's primarily used for DRM, and the use case that you've handed someone a sort of decryption gadget that can't be misused except for its intended purposes, which as a general rule always fails against a determined attacker.
  2. Indistinguishability obfuscation is a sort of nuclear missile of cryptographic code obfuscation, based on the hardness of problems involving multilinear maps. To the extent those problems remain difficult the technique provides an actual cryptographic answer to obfuscating code with solid theoretical foundations. However executing such code is, to use the term-of-art, "ludicrously slow".
3 Likes

Physical protection? Software is not reliable.

I wonder why we store passwords as hashes?
That's right, because this never works out...

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.