惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
量子位
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
IT之家
IT之家
F
Fortinet All Blogs
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Register - Security
The Register - Security
NISL@THU
NISL@THU
Webroot Blog
Webroot Blog
A
Arctic Wolf
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
Recent Announcements
Recent Announcements
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Blog — PlanetScale
Blog — PlanetScale
L
LangChain Blog
P
Palo Alto Networks Blog
Y
Y Combinator Blog
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
AWS News Blog
AWS News Blog
有赞技术团队
有赞技术团队
Engineering at Meta
Engineering at Meta
C
Cybersecurity and Infrastructure Security Agency CISA
aimingoo的专栏
aimingoo的专栏
Know Your Adversary
Know Your Adversary
Cyberwarzone
Cyberwarzone
Martin Fowler
Martin Fowler
The Hacker News
The Hacker News
P
Privacy International News Feed
T
Threat Research - Cisco Blogs
G
GRAHAM CLULEY
宝玉的分享
宝玉的分享
博客园 - 聂微东
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
S
Securelist
T
The Exploit Database - CXSecurity.com
T
Threatpost
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
F
Full Disclosure

Black Hills Information Security, Inc.

Bad Habits: An ANTISOC Operation Same Problem, Different Angles: When Red Team and Blue Team Actually Talk to Each Other How to Identify and Exploit New Vulnerabilities Swapper – A Pure Regex Match/Replace Burp Extension A Practical Guide to BloodHound Data Collection Network Engineering Basics Signed, Trusted, and Abused: Proxy Execution via WebView2 Getting Started In Pentesting – Advice From The BHIS Pentest Lead Cloud Security: Tips and Resources for Securing the Cloud Lessons From A Chatbot Incident How to Lead Effective Tabletops Understanding GRC: How to Navigate Risks and Compliance Standards The “P” in PAM is for Persistence: Linux Persistence Technique Malware Analysis: How to Analyze and Understand Malware OSINT: How to Find, Use, and Control Open-Source Intelligence What to Do with Your First Home Lab When the SOC Goes to Deadwood: A Night to Remember Social Engineering and Microsoft SSPR: The Road to Pwnage is Paved with Good Intentions Common Cyber Threats Finding the Right Penetration Testing Company Deceptive-Auditing: An Active Directory Honeypots Tool The Curious Case of the Comburglar How to Set Smart Goals (That Actually Work For You) Inside the BHIS SOC: A Conversation with Hayden Covington Abusing Delegation with Impacket (Part 3): Resource-Based Constrained Delegation Why You Got Hacked – 2025 Super Edition Abusing Delegation with Impacket (Part 2): Constrained Delegation Abusing Delegation with Impacket (Part 1): Unconstrained Delegation GoSpoof – Turning Attacks into Intel Model Context Protocol (MCP) Bypassing WAFs Using Oversized Requests Getting Started with AI Hacking Part 2: Prompt Injection Wrangling Windows Event Logs with Hayabusa & SOF-ELK (Part 2) DomCat: A Domain Categorization Tool Wrangling Windows Event Logs with Hayabusa & SOF-ELK (Part 1) Microsoft Store and WinGet: Security Risks for Corporate Environments Default Web Content MailFail Commonly Abused Administrative Utilities: A Hidden Risk to Enterprise Security Stop Spoofing Yourself! Disabling M365 Direct Send Bypassing CSP with JSONP: Introducing JSONPeek and CSP B Gone Offensive Tooling Cheatsheets: An Infosec Survival Guide Resource DNS Triage Cheatsheet GraphRunner Cheatsheet Burp Suite Cheatsheet Impacket Cheatsheet Wireshark Cheatsheet Hashcat Cheatsheet EyeWitness Cheatsheet Nmap Cheatsheet Netcat (nc) Cheatsheet Hunt for Weak Spots in Your Wireless Network with Airodump-ng from the Aircrack-ng Suite Detecting ADCS Privilege Escalation Vulnerability Scanning with Nmap Getting Started with NetExec: Streamlining Network Discovery and Access How to Use Dirsearch Augmenting Penetration Testing Methodology with Artificial Intelligence – Part 3: Arcanum Cyber Security Bot How to Design and Execute Effective Social Engineering Attacks by Phone Abusing S4U2Self for Active Directory Pivoting Why Use a Macro Pad? Espanso: Text Replacement, the Easy Way Caging Copilot: Lessons Learned in LLM Security Augmenting Penetration Testing Methodology with Artificial Intelligence – Part 2: Copilot Augmenting Penetration Testing Methodology with Artificial Intelligence – Part 1: Burpference Intercepting Traffic for Mobile Applications that Bypass the System Proxy How to Root Android Phones Communicating Security to the C-Suite: A Strategic Approach Offline Memory Forensics With Volatility Getting Started with AI Hacking: Part 1 Go-Spoof: A Tool for Cyber Deception How to Test Adversary-in-the-Middle Without Hacking Tools Canary in the Code: Alert()-ing on XSS Exploits How to Hack Wi-Fi with No Wi-Fi Why Your Org Needs a Penetration Test Program Burp Suite Extension: Copy For Light at the End of the Dark Web Wi-Fi Forge: Practice Wi-Fi Security Without Hardware Avoiding Dirty RAGs: Retrieval-Augmented Generation with Ollama and LangChain Gone Phishing: Installing GoPhish and Creating a Campaign 5 Things We Are Going to Continue to Ignore in 2025 John Strand’s 5 Phase Plan For Starting in Computer Security Questions From a Beginner Threat Hunter GRC for Security Managers: From Checklists to Influence AI Large Language Models and Supervised Fine Tuning Attack Tactics 9: Shadow Creds for PrivEsc w/ Kent & Jordan One Active Directory Account Can Be Your Best Early Warning Introduction to Zeek Log Analysis Creating Burp Extensions: A Beginner’s Guide Pitting AI Against AI: Using PyRIT to Assess Large Language Models (LLMs) The Top Ten List of Why You Got Hacked This Year (2023/2024) ICS Hard Knocks: Mitigations to Scenarios Found in ICS/OT Backdoors & Breaches Intro to Data Analytics Using SQL Finding Access Control Vulnerabilities with Autorize The Detection Engineering Process Cyber Risk Lessons We Can Learn From Hurricane Preparedness Intro to Desktop Application Testing Methodology What Is Penetration Testing? Adversary in the Middle (AitM): Post-Exploitation Pentesting, Threat Hunting, and SOC: An Overview QEMU, MSYS2, and Emacs: Open-Source Solutions to Run Virtual Machines on Windows
Indecent Exposure: Your Secrets are Showing
BHIS · 2025-01-09 · via Black Hills Information Security, Inc.

by moth

Hard-coded cryptographic secrets? In my commercially purchased, closed-source software? It’s more likely than you think. Like, a lot more likely. 

This blog post details a true story of cryptographic secret discovery, DLL modification, password recovery, and software platform compromise. Please note that all references to specifically targeted software have been scrubbed and all cryptographically sensitive materials have been simulated for the sake of telling this story, to hopefully avoid pissing too many people off. 

Now, without further ado, on to our story… 

That’s No Hash…  

A few years ago, I was checking Teams notifications and saw the following message sent to all testers: 

“Hash challenge – step 1, probably identification of hash type. used hash generator, no results seemed to align. The username is dbadmin, the hash is I3bnJsdcK4qwstvVaekB5CzcT7ESjmR/xpB8IKNtMFc=. Any suggestions would be amazing” – Jordan Drysdale, 2022-07-25 

Continuing my trend of being one of BHIS’ most “nerd snipe-able” gremlins, I decided to at least give that “hash” value a quick smell test and see if I couldn’t give something useful to my good pal Jordan Drysdale. At first glance, it seemed obvious that the hash value was at least base64-encoded, so I used Python to base64 decode the value, then converted it to a hex string, before finally using the hashid Linux utility to determine what hash type, if any, the value could possibly be. From the hashid command output, it looked like the hash might have been something like SHA-256. 

Conversion of Possible Hash Value 

I tossed this back to Jordan on the off chance that it would be helpful, but something about the situation had me wondering: Was this really a hash at all? Where had Jordan found this value? At this point, I was fully invested and jumped on a call with him where he showed me a config file resembling the below screenshot. 

CryptKeeper Configuration File 

As alluded to in this blog post’s introduction, “CryptKeeper” here is a stand-in for a software named “[REDACTED]” that is used by [REDACTED] organizations to help them manage their [REDACTED]. You can perhaps imagine how much I would love to not have the previous sentence redacted, but alas that is not the situation we find ourselves in. Just know that, as contrived as this example might seem, it is a very close simulation of what we found in the wild on that fateful engagement. 

After talking it over, Jordan and I eventually arrived at a simple conclusion: That password value wasn’t hashed, it was encrypted. This was starting to get interesting. 

Sneaking a Peek 

At this point, Jordan opened a tool called dnSpy and opened one of the DLL files neighboring the configuration file that we found. In reality, this resulted in a bunch of assemblies being loaded, both from the third-party software and the .NET Framework itself. After some cursory browsing, Jordan eventually searched for the word “crypt”. This returned several results, many of which seemed especially enticing. 

CryptKeeper Search Results 

As we could see, there appeared to be encryption and decryption methods, as well as three methods to get key, salt, and IV values for what appeared to be AES encryption. With that in mind, we cracked open the RadicalRijndael class from the CryptKeeper.Security namespace. 

Cracking a Cold One with the Boys 

Well now, what have we here? Two public methods for encryption and decryption, which themselves retrieve what appeared to be static key, salt, and IV values before calling additional helper functions. We scrolled to the bottom of the class and observed the functions responsible for getting the cryptographic materials, which turned out to be methods simply returning byte arrays as strings. 

Key Retrieval Method 

Jordan sent me the three values and I threw them into a Python shell to see what we were dealing with. After some light processing, we found that all three values were human-readable ASCII strings. 

Decoded Cryptographic Secret Materials 

Straight from the Source 

With our understanding of the affected software, and the knowledge that the necessary cryptographic materials were baked into the compiled DLL, we now needed to decrypt our encrypted password value. After a quick naïve attempt at decryption using a variety of different tools failed, we had an idea on how to proceed. By loading a DLL into memory, you can then access resources from that DLL using a method called “reflective assembly”. 

Reflective Assembly Primer 

When a class is public, and ideally when a method is static (to allow for direct execution without an instance of the associated class), you can run something like the following PowerShell command to load and directly run .NET methods. 

[System.Reflection.Assembly]::LoadFrom("Path\To\Assembly\File.dll") 
[Namespace.Class]::Method() 

Or, in newer versions of PowerShell, you can use the new using command. 

using assembly ".\File.dll" 
using namespace Namespace 
[Class]::Method() 

This works great for loading and running assemblies in environments that have restrictive application control mechanisms but do not restrict PowerShell access, but it requires target classes to be public and target methods to be both public and static. From the cryptographic routines in CryptKeeper, we have a non-public, internal class and public but non-static methods. 

If a class is public but a method is not static, that’s no major problem at all. Take the simple public Greeter class from the CryptKeeper DLL, for example: 

Public Class, Public Non-Static Method 

So just to illustrate this, the following screenshot shows that we have visibility to the Greeter type but no visibility to a method named Hello

Visibility to Class, No Visibility to Method 

Because the Hello method isn’t static, but is public, we can access the method through an instance of the Greeter class. To do so, we can either run [Greeter]::New() or New-Object Greeter

Creating Instances of Greeter Class 

If we then save the results of either of those two commands to a PowerShell variable, we can then call the Hello method through the created object. 

Successful Non-Static Method Invocation 

Alright, cool. That’s the first half of the problem solved. However, we get stopped when we try to access the internal RadicalRijndael class. 

No Visibility to Internal Class 

Now, we just need to figure out how to get access to the RadicalRijndael class. 

Take and Bake 

The first way that I thought of to get access to our target class was to exfiltrate the DLL to a machine with both dnSpy and the .NET build tools installed. From there, it is possible to right-click a class name in the dnSpy interface and modify it to our liking. 

C# Class Modification Menu Entry 

In this case, all we need to do is change the class from “internal” to “public”, then click the “Compile” button. 

Class Modification and Recompilation 

With the class recompiled, we can then save the changes to a new module. In this case, to preserve the original, I thought it best to create a new DLL file. 

Saving Module as New DLL 

Finally, we can load the modified DLL with PowerShell reflective assembly and then access the newly public class to decrypt our password, as shown below. 

Successful Password Decryption using Modified DLL 

Off-Menu Items 

Now, while I am fond of the exfiltration, modification, and reflective assembly technique, something about it had been nagging at me in the few years since our original discovery: Could we possibly bypass .NET visibility checks and access private or internal code directly? If we were able to do that, we could leverage cryptographic code from within client environments without any exfiltration, with the added benefit of lowering both the required knowledge floor and software setup required to pull this off in the “conventional” way. 

With this nagging question in my mind, I began my search. To recap what we already know, the following screenshot shows a traditional reflective assembly DLL load followed by attempts to access our public Greeter and internal RadicalRijndael classes, with success and failure respectively. 

Type Visibility 

When researching, I found references to the Import-Module and Add-Type PowerShell cmdlets, but neither of them seemed to give visibility to internal or private types, so I looked through the fields of the loaded assembly and eventually found a method named GetType with several overloads. 

Assembly GetType Function Overloads 

Reading through the Microsoft GetType documentation, I observed the documentation for the overload shown below. 

Microsoft GetType Documentation Excerpt 

Maybe we can use GetType(String) to get a handle on classes in our DLL using the absolute names… 

$greeter = $asm.GetType("CryptKeeper.Security.Greeter")
$radical = $asm.GetTYpe("CryptKeeper.Security.RadicalRijndael")

Yep, absolutely! 

Visibility to Both Classes with GetType 

However, we still run into problems when trying to create an instance of the objects. As seen below, we can use the New-Object cmdlet to create an instance of Greeter, but not one of RadicalRijndael

Still Cannot Find RadicalRijndael Type 

So, let’s dig into the type that we loaded into the $radical variable, looking for anything with the word “constructor” in it. Eventually, we land on the GetConstructors() method, documented here

GetConstructors Method Documentation Excerpt 

Note that there is also a GetConstructor method to search for and get a specific constructor, but I was unable to get a good grasp on it. Instead, let’s look at the constructors of the RadicalRijndael class. As we didn’t see any explicit constructors in dnSpy, we should expect to see only the default constructor, and indeed this is the case. 

RadicalRijndael Default Constructor 

Since the GetConstructors method returns a list, we can get the default constructor by saving the zeroth element of the result to a PowerShell variable. From there, digging into the constructor object we come across the Invoke method, which has several overloads. 

Assignment of Default Constructor to Variable, Summary of Invoke Method Overloads 

We only have one constructor, and we know it accepts no arguments, so we should hopefully not need to do anything too fancy here. I spent some time looking up documentation for the Invoke method but didn’t find much. Eventually, on a whim, I tried running the Invoke method with a parameter value of $null, and lo and behold… 

Successful Constructor Invocation 

Woah. Look at that, we have our object right there! Capturing the results into a variable, we can use the Get-Member cmdlet and filter for a member type of “Method”, and we can see our public Encrypt and Decrypt methods! 

Public Method Visibility 

Which means that we can decrypt our password without having first modified our DLL. 

Successful Password Decryption using Unmodified DLL 

Ok, so taking this from the top, we end up with the following sequence of PowerShell commands. 

$asm = [System.Reflection.Assembly]::LoadFrom("C:\Users\moth\CryptKeeper\CryptKeeper.dll")
$rr_type = $asm.GetType("CryptKeeper.Security.RadicalRijndael")
$rr_ctor = $rr_type.GetConstructors()[0]
$rr = $rr_ctor.Invoke($null)
$rr.Decrypt("I3bnJsdcK4qwstvVaekB5CzcT7ESjmR/xpB8IKNtMFc=")

And we get our decrypted password back as a result. 

Putting it All Together 

Further Work and Conclusion 

Looking ahead to where this methodology could take us, I envision (and honestly had hoped to work on for this blog post) an extension to a tool like Snaffler to facilitate discovery of potentially noteworthy references to cryptographic libraries and the like. The Snaffler repository has an alternative tool named “UltraSnaffler”, but I was unable to get it compiled before running out of time and was also unable to think of a good methodology for looking through DLL files that wouldn’t be time- and resource-prohibitive. 

You might think this kind of thing is a solved problem: don’t store secrets in code like this. And it is. But there’s a long way to go between ‘the world has an answer’ to ‘every development team has it implemented correctly’. Who knows what you might find tucked away in source code or compiled DLLs, kept around due to bad practices, lax legacy code hygiene, or even good-intentioned backwards compatibility efforts? It definitely pays to look. We certainly will be



Ready to learn more?

Level up your skills with affordable classes from Antisyphon!

Pay-What-You-Can Training

Available live/virtual and on-demand