Development of an application that verifies its own integrity presents unique challenges. Using a cryptographic hash function to detect potentially malicious modifications to an assembly is a common approach. Embedding the the digest of an assembly within it and then, at runtime, comparing the embedded digest to a newly computed hash will achieve this. However, attempting to embed the assembly digest by adding it to the source of the application and recompiling will alter the hash value of the resulting file. Because of this, the value must be patched into the assembly after compilation; also, during the integrity check the embedded hash within the message needs to somehow be ignored before the hash is computed, otherwise it too will affect the result.
The following C# solution shows how to create an application that detects modifications through the use of the RIPEMD-160 cryptographic hash function. PatchDigest (configured to run as a post-build event of Calculator) searches Calculator.exe for the _embeddedDigest value, zeroes it, hashes the result, then overwrites the array with the actual digest. When Calculator is run it reads itself, zeroes _embeddedDigest, then hashes the result just as PatchDigest does before comparing the computed digest to the embedded one. If they don't match it is assumed the application has been modified in a malicious manner and, in an attempt to throw the attacker off, the output of the application is corrupted. This behavior can be observed by removing Calculator's post-build event and recompiling.
DownloadCalculator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Security.Cryptography;
namespace Calculator
{
class Program
{
private static byte[] _embeddedDigest = new byte[20]
{
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
};
private static Random _rnd = new Random();
static bool CheckIntegrity()
{
string asmLocation = Assembly.GetExecutingAssembly().Location;
byte[] buffer = File.ReadAllBytes(asmLocation);
bool match;
for (int x = 0; x < buffer.Length; x++)
{
match = true;
for (int y = 0; y < _embeddedDigest.Length; y++)
{
if (_embeddedDigest[y] != buffer[x + y])
{
match = false;
break;
}
}
if (match)
{
for (int y = 0; y < _embeddedDigest.Length; y++)
buffer[x + y] = 0;
break;
}
}
RIPEMD160Managed ripemd = new RIPEMD160Managed();
byte[] digest = ripemd.ComputeHash(buffer);
match = true;
for (int x = 0; x < digest.Length; x++)
{
if (_embeddedDigest[x] != digest[x])
{
match = false;
break;
}
}
return match;
}
static void Main(string[] args)
{
Console.Write("X: ");
int x = int.Parse(Console.ReadLine());
Console.Write("Y: ");
int y = int.Parse(Console.ReadLine());
// if the integrity check fails replace y with a random value
y = CheckIntegrity() ? y : _rnd.Next();
Console.WriteLine("X + Y = {0}\r\n" +
"Press any key to continue...", x + y);
Console.ReadKey();
}
}
}
PatchDigest
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Security.Cryptography;
namespace PatchDigest
{
/// <summary>
/// Usage: PatchDigest [Target Application]
/// </summary>
class Program
{
private static byte[] _embeddedDigest = new byte[20]
{
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
0x00, 0x0F, 0xF0, 0xFF,
};
static void Main(string[] args)
{
string file = args[0];
byte[] buffer = File.ReadAllBytes(file);
int index = -1;
bool patched = false;
for (int x = 0; x < buffer.Length; x++)
{
bool match = true;
for (int y = 0; y < _embeddedDigest.Length; y++)
{
if (_embeddedDigest[y] != buffer[x + y])
{
match = false;
break;
}
}
if (match)
{
index = x;
for (int y = 0; y < _embeddedDigest.Length; y++)
buffer[x + y] = 0;
RIPEMD160Managed ripemd = new RIPEMD160Managed();
byte[] digest = ripemd.ComputeHash(buffer);
for (int y = 0; y < _embeddedDigest.Length; y++)
buffer[x + y] = digest[y];
File.WriteAllBytes(file, buffer);
patched = true;
break;
}
}
Console.WriteLine((patched ? "Patched" : "Patch Failed") +
"Press any key to continue...");
}
}
}