Target: timeleg’s CrackMe4
URL: http://www.crackmes.de/users/timeleg/crackme4/
Protection: Keyfile
Description: Crackme with a keyfile protection.
Tools: .NET Decompiler.
Load the crackme in your .NET decompiler and lets take a look at what happens when we load our chosen file.
private void button1_Click(object sender, EventArgs e)
{
if (this.openFileDialog1.ShowDialog() != DialogResult.OK)
return;
string fileName = this.openFileDialog1.FileName;
try
{
if (this.reg.Testuj(fileName))
{
this.button1.Visible = false;
this.label2.Visible = false;
Label label = this.label3;
string str = label.Text + this.reg.ZistiMeno();
label.Text = str;
this.label3.Visible = true;
}
else
{
int num = (int) MessageBox.Show(" Wrong file !", "Unregistered", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
if ((int) this.reg.VratPocetPokusov() == 0)
this.Close();
else
this.reg.Odober();
}
catch
{
int num = (int) MessageBox.Show("I can't open file!");
}
}
The file check routine is in the reg.Testuj method that gets passed the file name, so lets check it out.
public bool Testuj(string subor)
{
this.meno = new StreamReader(subor).ReadToEnd();
string str = this.meno;
try
{
this.meno = this.meno.Remove(0, 56);
}
catch
{
this.meno = "";
}
if (string.IsNullOrEmpty(this.meno))
str = str.Insert(0, "0x38");
return this.KryptujDES(str.Remove(38, str.Length - 38)) == "n6oAtBbtvN2lmnwIguxE9MBZlpqdtQrf68pThEpfB8Zmzmvpu5TPPw==" && this.KryptujDES(subor.Remove(0, subor.Length - 15)) == "2x2S4WJlj7vAkXgGnN+WCQ==";
}
First of all the contents of our file is being read to the variable meno and then copied to the variable str.
Then it tries to remove the 56 first characters in the string meno and check if the remaining string is null or empty, and if it is, insert 0x38 at the beginning of the str variable.
At the last row we see some comparisons of the results of a method call. Lets see what that method does.
private string KryptujDES(string retazec)
{
byte[] numArray = new byte[8]
{
(byte) 68,
(byte) 105,
(byte) 112,
(byte) 108,
(byte) 111,
(byte) 109,
(byte) 107,
(byte) 65
};
DESCryptoServiceProvider cryptoServiceProvider = new DESCryptoServiceProvider();
MemoryStream memoryStream = new MemoryStream();
memoryStream.Flush();
CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, cryptoServiceProvider.CreateEncryptor(numArray, numArray), CryptoStreamMode.Write);
StreamWriter streamWriter = new StreamWriter((Stream) cryptoStream);
streamWriter.Write(retazec);
((TextWriter) streamWriter).Flush();
cryptoStream.FlushFinalBlock();
return Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int) memoryStream.Length);
}
The result of this method is a Base64-encoded representation of a DES encrypted string. And since we have the encrypted values plus the key we can decrypt those values to generate a valid key-file.
Lets see what the input to the encryption routine is.
//str == contents of our key-file, subor == file name
return this.KryptujDES(str.Remove(38, str.Length - 38)) == "n6oAtBbtvN2lmnwIguxE9MBZlpqdtQrf68pThEpfB8Zmzmvpu5TPPw==" && this.KryptujDES(subor.Remove(0, subor.Length - 15)) == "2x2S4WJlj7vAkXgGnN+WCQ==";
The first part takes the 38 first characters of the file and encrypts them, then compares the result to the valid string. The second part removes the first x – 15 characters from the file name and encrypts them, then compares to the valid string.
Now we can write a decrypter for the strings to see what we are comparing to.
Following is the C# code for a decrypter.
using System;
using System.IO;
using System.Security.Cryptography;
namespace timelegs_CrackMe4
{
class Decrypter
{
static void Main(string[] args)
{
const string encrypted1 = "n6oAtBbtvN2lmnwIguxE9MBZlpqdtQrf68pThEpfB8Zmzmvpu5TPPw==";
const string encrypted2 = "2x2S4WJlj7vAkXgGnN+WCQ==";
Console.WriteLine("File data:");
Console.WriteLine(DecodeDes(encrypted1));
Console.WriteLine("File name:");
Console.WriteLine(DecodeDes(encrypted2));
Console.ReadKey();
}
private static string DecodeDes(string input)
{
var key = new byte[] { 68, 105, 112, 108, 111, 109, 107, 65};
var encrypted = Convert.FromBase64String(input);
var cryptoServiceProvider = new DESCryptoServiceProvider();
var memoryStream = new MemoryStream(encrypted);
var cryptoStream = new CryptoStream(memoryStream, cryptoServiceProvider.CreateDecryptor(key, key), CryptoStreamMode.Read);
var streamReader = new StreamReader(cryptoStream);
return streamReader.ReadToEnd();
}
}
}
And when we run this we get the output:
File data:
* Licencny subor *
* Diplomka 2015 *
File name:
\Licencny_subor
Now we got the first part of our key-file and it seems to be the only file content check done on the key-file. So lets save this data to a file called Licencny_subor and add some characters to get the file length up to 57. (Remember the first string.Remove call?)
Here is the contents of my Licencny_subor file.
* Licencny subor *
* Diplomka 2015 *1111111111111111111
When we load this file into the crackme we get a message telling us ‘You’ve done it!, 1’. Ok, so it seems that the last part of the key-file is the name, lets take another look at the button1_Click method.
if (this.reg.Testuj(fileName))
{
this.button1.Visible = false;
this.label2.Visible = false;
Label label = this.label3;
string str = label.Text + this.reg.ZistiMeno();
label.Text = str;
this.label3.Visible = true;
}
The name seems to come from the reg.ZistiMeno method. Lets check that out.
public string ZistiMeno()
{
return this.meno;
}
All it does is return the variable meno, and the contents of that variable are the result of the this.meno.Remove(0, 56) call which means the last characters in the key-file. We can try this out by changing the last ‘1’ of the file to something else and see if that shows up in the message. And it does!
To summarize, a valid key-file has to be named Licencny_subor and it has to be at least 57 bytes long. The first 38 bytes has to be the header and the rest can be anything, with the name starting from byte 57.
Lets improve our decrypter to generate a valid key-file for us instead.
using System;
using System.IO;
using System.Security.Cryptography;
namespace timelegs_CrackMe4
{
class Keygen
{
static void Main(string[] args)
{
Console.Write("Name: ");
var name = Console.ReadLine();
if (string.IsNullOrEmpty(name))
return;
var keyFileName = SaveKeyFile(name);
Console.WriteLine("Keyfile saved as: " + keyFileName);
Console.ReadKey();
}
private static string SaveKeyFile(string name)
{
const string encrypted1 = "n6oAtBbtvN2lmnwIguxE9MBZlpqdtQrf68pThEpfB8Zmzmvpu5TPPw==";
const string encrypted2 = "2x2S4WJlj7vAkXgGnN+WCQ==";
var keyFileData = DecodeDes(encrypted1);
var keyFileName = DecodeDes(encrypted2);
keyFileName = ".\\" + keyFileName;
var randomKeyFileData = GenerateRandomString(54 - keyFileData.Length);
keyFileData += "\n" + randomKeyFileData + "\n" + name;
File.WriteAllText(keyFileName, keyFileData);
return keyFileName;
}
private static string GenerateRandomString(int length)
{
var random = new Random();
var randomKeyFileData = "";
for (var i = 0; i < length; i++)
{
randomKeyFileData += (char)('a' + random.Next(0, 26));
}
return randomKeyFileData;
}
private static string DecodeDes(string input)
{
var key = new byte[] { 68, 105, 112, 108, 111, 109, 107, 65};
var encrypted = Convert.FromBase64String(input);
var cryptoServiceProvider = new DESCryptoServiceProvider();
var memoryStream = new MemoryStream(encrypted);
var cryptoStream = new CryptoStream(memoryStream, cryptoServiceProvider.CreateDecryptor(key, key), CryptoStreamMode.Read);
var streamReader = new StreamReader(cryptoStream);
return streamReader.ReadToEnd();
}
}
}