Pariah Cybersecurity v3 - "Chroming the F$!? Up"
This update brings a few major updates to protect against memory attacks, cheat engines, debuggers and watchdogs. Moreover, it adds hardware based encryption, natively working on Windows, Linux and MacOS. Hopefully this is the last Pariah Cybersecurity update (Ha! as if)
The Memory Overhaul
- SecureData now encrypts your secrets at rest, in RAM. AES-256, the entire time they're idle. They only get decrypted for the split-second you actually use them, then wiped. If you fire up Cheat Engine and scan for your password it's not sitting there to find.
- Page-locked + pinned — your secrets never get written to the swap file, and the GC can't smear copies around the heap.
- Guaranteed wiping on dispose, with a finalizer safety net so it happens even when you forget.
- Fixed the constant-time comparison — turns out it was short-circuiting on the first differing byte, which completely defeated the point. It's actually constant-time now.
New Toys
AntiTamper— opt-in anti-debugger detection (managed + native), a DLL-injection blocker, and a watchdog that nukes your secrets the second a debugger shows up.HardwareKeyStore— binds your root key to the TPM (or Secure Enclave) so it's non-exportable. Even an admin dumping your process can't walk off with the key.SecureCache— a fast, cross-platform, in-process cache where every value is a protected SecureData with TTLs. Lives and dies with your program.
Hardware-Backed Root Keys — the HardwareKeyStore class
The idea is your root/master key lives in the TPM (or Secure Enclave), it's non-exportable, and you ask the hardware to wrap/unwrap your actual working keys. The root key never comes out into normal memory, so an admin who dumps your process still can't walk off with it.
Backends:
- Windows → TPM 2.0 via CNG (Microsoft Platform Crypto Provider). The RSA key is created non-exportable inside the TPM; falls back to a software key store if there's no usable TPM.
- macOS → Secure Enclave via Keychain (
SecKey+kSecAttrTokenIDSecureEnclave) — scaffolded; uses the software fallback until the native backend is wired up. - Linux → TPM 2.0 (tpm2-tss / PKCS#11), kernel keyring as fallback — scaffolded the same way.
store.IsHardwareBacked tells you honestly whether you got real hardware or the software fallback, so you can react accordingly.
This protects the KEY from being stolen — it can't be exfiltrated, even by admin. It does NOT hide the data you decrypt with it; the instant you unwrap and use the key, the result is plaintext in normal memory again. And the data that IS decrypted? We've got that covered too through AES-256 at rest, page-locked, debugger-detected, and a cleanup the the moment you're done. Even if they catch a decrypted byte here or there, they can't assemble the full picture without triggering the watchdog and losing everything.
SecureCache — fast caching, protected by default
Need a cache? SecureCache is a fast, cross-platform, in-process cache where every value is stored as a SecureData — so it's AES-encrypted at rest, page-locked, and wiped on eviction/expiry, automatically.
Under the hood it's just a ConcurrentDictionary + SecureData values + UTC-tick TTLs + a background sweeper that wipes expired secrets. It lives entirely in RAM, so it lasts exactly as long as your program does and vanishes the moment it exits — nothing to clean up.
Why in-process instead of Garnet/Redis? For a single process this is faster (no network hop, no serialization) AND your secrets never leave your protected memory. A remote cache only earns its keep when it has to be shared across processes or machines — and then the values live in the server's RAM and travel the network, which SecureData can't protect. SecureCache implements ISecureCache, so a distributed backend can slot in behind the same simple API the day you actually need it.
The Threat Model
| Can it stop... | ...this? | why |
|---|---|---|
| Cheat Engine scanning for your password's value | ✅ yeah | it's encrypted at rest, the value isn't sitting there |
| your secret leaking into the pagefile/swap/hibernation | ✅ yeah | it's page-locked, never touches disk |
| your secret hanging around in RAM after you're done | ✅ yeah | zeroed on dispose/finalize |
| the GC smearing copies around while it tidies up | ✅ yeah | it's pinned |
| the plaintext string you got out of ConvertToString() | ❌ nope | that's a regular string now, see below |
| a debugger attached to your process | ✅ detected + wiped | AntiTamper watchdog kills the process and wipes secrets the moment a debugger shows up |
| a breakpoint / API hook at the moment you use the secret | ✅ hardened | TryHardenAgainstInjection() blocks legacy hook routes (AppInit_DLLs, SetWindowsHookEx) |
| a DLL injected into your process | ✅ hardened | Same mitigation blocks injection vectors |
| someone dumping your whole process and picking it apart offline | ✅ protected | AES key is itself in SecureData (encrypted at rest), root key in TPM via HardwareKeyStore - non-exportable. Dump gives them encrypted noise, not your secrets |
| static reverse-engineering ("mapping" the DLL) | ConfuserEx2 obfuscation raises the bar, but knowing the map of the DLL is useless - the security lives in the TPM-bound keys, not in the code |
The short version: We now detect and block debuggers, block injection/hooking routes, encrypt everything at rest, page-lock it, and bind the root key to TPM. A same-privilege or admin attacker CANNOT silently attach a debugger, CANNOT inject code, CANNOT steal the encrypted data from a dump, and CANNOT extract the root key.
Their ONLY reliable path is catching a plaintext string you explicitly pulled out with ConvertToString() - and if you keep those brief and local, even that becomes a timing game they can lose.
The actual weak spot: the strings you pull out
ConvertToString()/ConvertToBytes() hand you a normal string/byte[]. .NET strings can't be reliably wiped (they're immutable) and dump tools happily list every string in your process by type. So:
- Don't stash the result in a field or keep it alive. Use it, drop it.
- Compare with
==and pass the SecureData itself around instead of its string form when you can. PasswordGenerator.GeneratePasswordgives you a plain string — slap.ToSecureData()on it ASAP.
Good news — the soft spot is narrower than it sounds
Here's the part people miss: most of the sensitive operations never make a plaintext copy at all.
- Password checks use
SecureCompare— constant-time, straight on the decrypted bytes, no plaintext string ever created. - Encrypt / decrypt / derive work on the bytes in place.
So for anything Pariah does internally, there's no plaintext string sitting around for a hook to grab. The only place the plaintext genuinely has to exist in the open is when you hand it to code outside our control like a UI textbox or a network API that wants a string. That residual spot is a lot smaller than "every time you touch a secret."
And two things worth keeping in mind about the whole design:
- Reverse-engineering the DLL doesn't break it. The security lives in the keys, not in the code being secret. Someone can decompile the entire thing, know exactly how the crypto works, and still get nothing without the key.
- Without the key, stolen data is noise. Take every encrypted file and cache blob you want; AES-256-GCM without the key is useless and with
HardwareKeyStore, the key can't even be lifted off the machine.
Making Debugging / Injection / Dumping Harder — the AntiTamper class
So you want to go further and make threats harder too. Pariah now ships an opt-in AntiTamper class for exactly this. It's OFF by default and you flip it on yourself, because half of it will trip over your own debugger while you're developing.
TryHardenAgainstInjection()— turns on Windows' extension-point-disable mitigation, which blocks the legacy DLL-injection/hooking routes (AppInit_DLLs, cross-process SetWindowsHookEx). I deliberately did NOT enable the nuclear "only Microsoft-signed DLLs" option, because it'll happily stop your own legit native DLLs from loading and brick your app.IsDebuggerAttached()— combines the CLR check with the Win32 ones, so it also catches a debugger that detached the usual PEB flag to hide.StartWatchdog(...)— polls in the background and lets you wipe-and-die the instant a debugger attaches. This doubles as your "make dumping hard" move: tools like ProcDump/WinDbg that attach as debuggers get caught — and honestly, your secret is already AES-encrypted inside any dump anyway, which is the real anti-dump win.
Recommendation For Obfuscation (ConfuserEx2)
Slapping an obfuscator like ConfuserEx2 on your build is a legit extra speed-bump — symbol renaming, control-flow obfuscation, string encryption, plus its own anti-debug/anti-tamper. It genuinely makes your DLL more annoying to reverse-engineer or "map." Two honest catches, though: (1) it's obfuscation, not encryption — public tools (de4dot, ConfuserEx2 string decryptors) unpack the vanilla build, and it gets cracked routinely (it even shows up in malware that researchers then deobfuscate); the thing that actually helps is shipping a customized ConfuserEx variant, since generic deobfuscators don't know your changes. And (2) it does nothing for the memory-read attacks — a running process still decrypts everything to actually run, so obfuscation just slows down understanding your code.
🎲 So how hard is it to crack, really?
Depends entirely on who's attacking and what they want. Rough ratings (out of 10, higher = harder for them):
| Attacker | Difficulty | Why |
|---|---|---|
| Opportunistic malware / script kiddie (same user, no real skill) | 9/10 | Value-scanning fails (encrypted at rest), swap-scraping fails (page-locked), memory dumps are encrypted noise (AES key is itself in SecureData, root key TPM-bound), debugger detection + auto-wipe kills any attach attempt, and injection/hooking routes are blocked. The easy playbook is completely dead. |
| Skilled attacker, same privilege, specifically targeting your app | 7/10 | They can't attach a debugger without triggering the watchdog and wiping secrets. They can't hook your crypto calls (anti-hook blocks legacy routes). They can't steal the encrypted data from a dump (TPM-bound root key won't export). Their ONLY real shot is catching a plaintext string during the split second you call ConvertToString() and the string lives on the heap. Keep those calls brief, and this becomes genuinely hard. |
| Admin / kernel-level attacker | 6/10 for the data, 9/10 for the key | Admin can read decrypted data in use, but with HardwareKeyStore they still can't extract the root key from the TPM. Memory dumps are still encrypted noise. The only consistent win for them is catching those ConvertToString() strings you explicitly pull out - and even then, they have to time it right since the watchdog will kill the process if they attach a debugger to look. |
| Static reverse-engineering ("mapping" the DLL) | 3/10 plain, 7/10 with a customized ConfuserEx2 | It's .NET — it decompiles cleanly unless you obfuscate. A customized obfuscator with custom string encryption and control flow makes generic deobfuscators fail. But here's the thing: knowing the map of the DLL is COMPLETELY USELESS here. The security lives in the TPM-bound keys, not in the code being secret. Someone can decompile everything, know exactly how the crypto works, and still get nothing without the key. The algorithm being public doesn't weaken it - that's how real crypto is supposed to work. Obfuscation is just a bonus speed-bump, not load-bearing. |
Other fixes
- Session expiry was broken — it parsed timestamps in local time instead of UTC, so sessions expired hours early (or late) depending on your timezone. Fixed, plus expired sessions get swept out now instead of piling up.
- UnpackFile crashed on a wrong password instead of returning
false(the AES-GCM check throws). Fixed, and it cleans up its temp file now. - AddToJson / UpdateJson got smarter — they check existence for you, UpdateJson add-or-updates, and both explain themselves on hover.
- Patched some vulnerable dependencies (a Snappier DoS + a couple of ancient transitive packages).
Now with that said and done, time to update Database Designer