Cracking SmarterMail hashes

This week I encountered a password hash I hadnt seen in a while. Base64 with a twist. SmarterTools.com has a mail server called SmarterMail written in all .net. It stores its passwords in xml files in what looks to be base64, but not quite.

Unlike most companies, I assume SmarterTools doesn’t know how to encrypt or obfuscate its binaries, instead, they store all of their code in a managed assembly DLL and call it with an exe. That said, reversing it was rather simple since I only needed a decent decompiler and grep to find its hashing algorithm. And here it is:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;
namespace TicketCounter
{
public class CryptographyHelper
{
protected internal byte[] salt = new byte[4]
{
(byte) 155,
(byte) 26,
(byte) 93,
(byte) 86
};
protected SymmetricAlgorithm Coder;
protected byte[] Key;
protected byte[] IV;
protected int Method;
protected CryptographyHelper()
{
}
public CryptographyHelper(int methodIn)
{
this.Method = methodIn;
if (this.Method == 0)
this.Coder = (SymmetricAlgorithm) DES.Create();
else
this.Coder = (SymmetricAlgorithm) RC2.Create();
}
public void SetKey(string key)
{
int cb = this.Method != 0 ? 5 : 8;
PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(key, this.salt);
this.Key = passwordDeriveBytes.GetBytes(cb);
this.IV = passwordDeriveBytes.GetBytes(cb);
}
public void SetKey(ref Random rnd)
{
int length = this.Method != 0 ? 5 : 8;
this.Key = new byte[length];
this.IV = new byte[length];
rnd.NextBytes(this.Key);
rnd.NextBytes(this.IV);
}
public void SetKey(byte[] keyIn, byte[] ivIn)
{
int length = this.Method != 0 ? 5 : 8;
this.Key = new byte[length];
this.IV = new byte[length];
Array.Copy((Array) keyIn, 0, (Array) this.Key, 0, length);
Array.Copy((Array) ivIn, 0, (Array) this.IV, 0, length);
}
public string EncodeToBase64(string val)
{
if (val.Length == 0)
return val;
else
return Convert.ToBase64String(this.Encode(Encoding.UTF8.GetBytes(val)));
}
public string DecodeFromBase64(string val)
{
if (val.Length == 0)
return val;
else
return Encoding.UTF8.GetString(this.Decode(Convert.FromBase64String(val)));
}
public static string EncodeToBase64(int method, int key, string val)
{
if (val.Length == 0)
return val;
CryptographyHelper cryptographyHelper = new CryptographyHelper(method);
Random rnd = new Random(key);
cryptographyHelper.SetKey(ref rnd);
return cryptographyHelper.EncodeToBase64(val);
}
public static string DecodeFromBase64(int method, int key, string val)
{
if (val.Length == 0)
return val;
CryptographyHelper cryptographyHelper = new CryptographyHelper(method);
Random rnd = new Random(key);
cryptographyHelper.SetKey(ref rnd);
return cryptographyHelper.DecodeFromBase64(val);
}
public static string EncodeToBase64(int method, string key, string val)
{
if (val.Length == 0)
return val;
CryptographyHelper cryptographyHelper = new CryptographyHelper(method);
cryptographyHelper.SetKey(key);
return cryptographyHelper.EncodeToBase64(val);
}
public static string DecodeFromBase64(int method, string key, string val)
{
if (val.Length == 0)
return val;
CryptographyHelper cryptographyHelper = new CryptographyHelper(method);
cryptographyHelper.SetKey(key);
return cryptographyHelper.DecodeFromBase64(val);
}
public byte[] Encode(byte[] buf)
{
return this.PassThrough(buf, this.Coder.CreateEncryptor(this.Key, this.IV));
}
public byte[] Decode(byte[] buf)
{
return this.PassThrough(buf, this.Coder.CreateDecryptor(this.Key, this.IV));
}
protected byte[] PassThrough(byte[] buf, ICryptoTransform transformation)
{
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, transformation, CryptoStreamMode.Write);
cryptoStream.Write(buf, 0, buf.Length);
cryptoStream.FlushFinalBlock();
memoryStream.Seek(0L, SeekOrigin.Begin);
byte[] buffer = new byte[memoryStream.Length];
memoryStream.Read(buffer, 0, (int) memoryStream.Length);
cryptoStream.Close();
memoryStream.Close();
return buffer;
}
}
}

See the key?  No you don’t because the key was elsewhere in the app – specifically in the config area that deals with description:


//namespace SmarterMail.Config
public class SystemAdminLogin
{
public static string PasswordKey = 03a8ur98qhfa9h; //  assume quotes
public string ID { get; set; }
public string Username { get; set; }
public List<IpAccess> IpAccessRestrictions { get; private set; }
public bool IsPrimaryAdmin { get; set; }
public bool EnableIpAccessRestriction { get; set; }
public DateTime DateCreated { get; set; }
public string Description { get; set; }
public string Password
{
get
{
return CryptographyHelper.
DecodeFromBase64(0,SystemAdminLogin.PasswordKey, this.get_EncryptedPassword());
}
set
{
this.set_EncryptedPassword(CryptographyHelper.EncodeToBase64(0,SystemAdminLogin.PasswordKey, value));
}
}static SystemAdminLogin()
{
}public SystemAdminLogin()
{
this.IpAccessRestrictions = new List<IpAccess>();
this.DateCreated = DateTime.Now;
this.ID = string.Empty;
}public void ToXml(XmlWriter writer)
{
writer.WriteStartElement(“SystemAdmin”);
writer.WriteElementString(“ID”, this.ID);
writer.WriteElementString(“Username”, this.Username);
// ISSUE: reference to a compiler-generated method
writer.WriteElementString(“Password”, this.get_EncryptedPassword());
writer.WriteElementString(“EnableIpAccessRestriction”,
this.EnableIpAccessRestriction.ToString());
writer.WriteElementString(“DateCreated”,
this.DateCreated.ToString((IFormatProvider)
CultureInfo.InvariantCulture));
writer.WriteElementString(“Description”, this.Description);
if (this.IpAccessRestrictions.Count > 0)
{
writer.WriteStartElement(“IpAccess”);
foreach (IpAccess ipAccess in this.IpAccessRestrictions)
{
writer.WriteStartElement(“Allowed”);
writer.WriteElementString(“Description”, ipAccess.Description);
writer.WriteElementString(“StartIp”, ipAccess.StartIP);
if (System_ExtensionMethods7BCA73B06BAB478aA3AC6AC60979BA25.HasValue(ipAccess.EndIP))
writer.WriteElementString(“EndIp”, ipAccess.EndIP);
writer.WriteElementString(“Type”, ((int) ipAccess.Type).ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
public void FromXml(XmlReader reader)
{
string str = string.Empty;
label_27:
while (reader.Read())
{
int num = (int) reader.MoveToContent();
if (reader.NodeType == XmlNodeType.EndElement &&
reader.Name.ToLowerInvariant() == “systemadmin”)
break;
if (reader.NodeType == XmlNodeType.Element &&
reader.Name.ToLowerInvariant() == “ipaccess”)
{
IpAccess ipAccess = new IpAccess();
bool flag = false;
while (true)
{
do
{
if (reader.Read() && (reader.NodeType !=
XmlNodeType.EndElement || !(reader.Name.ToLowerInvariant() ==
“ipaccess”)))
{
if (reader.NodeType == XmlNodeType.EndElement &&
reader.Name.ToLowerInvariant() == “allowed” && flag)
{
this.IpAccessRestrictions.Add(ipAccess);
flag = false;
ipAccess = new IpAccess();
}
if (reader.NodeType == XmlNodeType.Element)
goto label_8;
}
else
goto label_27;
}
while (reader.NodeType != XmlNodeType.Text);
goto label_10;
label_8:
str = reader.Name.ToLowerInvariant();
continue;
label_10:
switch (str)
{
case “description”:
flag = true;
ipAccess.Description = reader.Value;
continue;
case “startip”:
flag = true;
ipAccess.StartIP = reader.Value;
continue;
case “endip”:
flag = true;
ipAccess.EndIP = reader.Value;
continue;
case “type”:
flag = true;
ipAccess.Type = (IPType) int.Parse(reader.Value);
continue;
default:
continue;
}
}
}
else if (reader.NodeType == XmlNodeType.Element)
str = reader.Name.ToLowerInvariant();
else if (reader.NodeType == XmlNodeType.Text)
{
switch (str)
{
case “id”:
this.ID = reader.Value;
continue;
case “enableipaccessrestriction”:
this.EnableIpAccessRestriction = bool.Parse(reader.Value);
continue;
case “username”:
this.Username = reader.Value;
continue;
case “password”:
// ISSUE: reference to a compiler-generated method
this.set_EncryptedPassword(reader.Value);
continue;
case “datecreated”:
this.DateCreated = DateTime.Parse(reader.Value,
(IFormatProvider) CultureInfo.InvariantCulture);
continue;
case “description”:
this.Description = reader.Value;
continue;
default:
continue;
}
}
}
}
}

That’s what we’re going to use to decrypt the binary.

I borrowed their code to make my own little decrypter app kind of like how a cracker makes a keygen – same principle really, except in this case, they include a function for decrypting so I don’t need much:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace TicketCounter
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public static string PasswordKey = “03a8ur98qhfa9h”;
private void btnDecode_Click(object sender, EventArgs e)
{
string enc = this.tbEnc.Text;
this.tbDec.Text = DecodeFromBase64(0, PasswordKey, enc);
}
public static string DecodeFromBase64(int method, string key, string val)
{
if (val.Length == 0)
return val;
CryptographyHelper cryptographyHelper = new CryptographyHelper(method);
cryptographyHelper.SetKey(key);
return cryptographyHelper.DecodeFromBase64(val);
}
}
}

Create the object and use it. Keep it simple. The project includes the class I ripped in the project.

And there you have it. Next time you take control of a Smarter Mail server, you can now decrypt the passwords with ease.

For those of you who are lazy, you can download the project here.

Happy hacking 🙂

4 thoughts on “Cracking SmarterMail hashes
  1. Since now, on latest version on Smartermail, you cannot retrieve passwords as admin…

    Could you check if is possible to update the program to decrypt xmls passwords for current version of Smartermail?

    1. I’ll have to re-download SmarterMail and update my code then. I’ll do this soon.

      Thanks!

Leave a Reply to averagejoe Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.