Introduction
If you are supplying an app that uses a time based licence, how do you prevent the user from simply changing the clock to work around your licence terms?
I have anti-virus software that I downloaded as a trial, but when I installed it I set my clock forward 20 years. Now the trial doesn't expire until 2035. The authors of the software could easily have prevented me from doing that... but how?
Background
A few years ago I wrote a quick and dirty system to implement licence checks using RSA asymmetrical encryption, whereby the licence data is signed using RSA and checked with a public key.
It was a very popular article, and spawned a lot of comments and requests for help implementing it.
One of the questions was how to prevent the user simply changing their clock to prevent the licence terms expiring.
Since then, internet connectivity has gone from being constantly available on some computers, to pretty much all computers, which is a big help with licence checks.
For example, if you were to set your clock to be years or months ahead of the current time, a fair number of internet services just wouldn't work. It also means that you have an avenue to check the time via internet time servers.
The Basic Check
The simplest form of clock-tamper checking I know of is to check the date on a required file, say the config file, ensure it's in the past, then set the date to the current date. Do this every time the app starts. If the user rolls the clock back, then the date on that file will be in the future, and you know the clock has been tampered with.
public static bool EssentialFileDateCheck()
{
var me = Assembly.GetExecutingAssembly().Location + ".config";
if (File.Exists(me))
{
var createdOn = File.GetCreationTime(me);
if (createdOn < DateTime.Now)
{
File.SetCreationTime(me, DateTime.Now);
return true;
}
}
return false;
}
This is a pretty naive implementation, and easily defeated - although it's tricky to set creation time on a file in the OS, it's obviously pretty easy to do it in .NET
More Advanced Check
I think we can assume that anyone really trying to sidestep the licensing process is able to look inside the assembly with .NET reflector or JustDecompile.
Thus the thing to do would be to check a file that is less easy to find and reset, so for starters, it shouldn't be in the app's directory, and it shouldn't have an easily predictable file name, and it shouldn't be easy to work out from the code exactly what that file will be.
I chose to create a file name by hashing the assembly-name and the computer name, then turning the hash into a hex string, and slotting it into the Environment.SpecialFolder.ApplicationData special path as a folder, with the file underneath, masquerading as a dll...
This is because Microsoft often leaves folders lying around that look very similiar, and contain dll's. The date on the folder is set to a point in time much earlier, and there is a difference applied to the file date so it isn't shown as created or modified today.
var folder = (HashAlgorithmName.MD5.ComputeHash(Encoding.UTF8.GetBytes(Environment.MachineName + Assembly.GetEntryAssembly().FullName))).ToOneWayHex();
var fn = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
folder,
Path.ChangeExtension(Environment.MachineName.Clean(Path.GetInvalidFileNameChars()), ".dll"));
var path = Path.GetDirectoryName(fn);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
var older = DateTime.Now - new TimeSpan(165, 165, 165, 0);
Directory.SetCreationTime(path, older);
Directory.SetLastAccessTime(path, older);
Directory.SetLastWriteTime(path, older);
}
NOTE
TE: The above code uses extension methods, "Clean", "ComputeHash" and "ToOneWayHex"
The point of this is that anyone looking through decompiled code is going to have a much harder time figuring out which file is the time-stamp, since the file-name itself won't appear as a string literal in the code.
Now that we have made the file harder to find, it's time to make it harder to reset. In addition to checking the file's creation date/last write date, the contents of the file itself will be a time-stamp. But not just "DD/MM/YYYY" as anyone could change that.
The contents of the file is written as the binary (64 bit integer) representation of a date-time, converted to a byte-array using BitConverter, and then encrypted via the ProtectedData class in System.Security.Cryptography.
var now = DateTime.Now;
var time = ProtectedData.Protect(BitConverter.GetBytes(now.ToBinary()), entropy, DataProtectionScope.LocalMachine);
if (File.Exists(fn))
{
File.Delete(fn);
}
File.WriteAllBytes(fn, time);
File.SetAttributes( fn, FileAttributes.Hidden | FileAttributes.Encrypted | FileAttributes.System );
File.SetCreationTime( fn, now - timeSlip);
File.SetLastWriteTime(fn, now - timeSlip);
This now makes it much much harder to alter the time-stamp file to defeat the clock tampering check, unless of course you just delete it.
I could make the tamper check fail if the file doesn't exist, but when the program first runs, the file won't exist. To circumvent this issue, I am setting a registry key when the program first runs. If the registry key is set, but the timestamp file is missing, the tamper check fails.
The registry key is constructed much like the file-name, and placed in a location that - even when looking for it and knowing what it's called - is quite hard to find or distinguish from other reg-keys.
Network Time Protocol
This offers the best and most fool proof method: independently check the current time on the internet, compare it to the system clock. If the difference exceeds an allowable threshold, reset the clock or fail the check.
Implementing NTP is fairly trivial, it just involves sending a 48 byte packet via UDP to a time-server, then decoding the result.
The trickiest part is that the MSB (most significant bit) and LSB (least significant bit) are reversed in the response from the time-server compared to most windows systems.
There is an implementation of a simple NTP call in the attached example.
When checking the time via the internet, there are a couple of factors to consider:
1) the computer may not be connected to the internet
2) the NTP ports may be blocked by firewalls or network rules.
3) despite getting the network time you may be unable to change the system clock.
The example solution attempts to get the current network time, if it fails to do so, it falls back to the time-stamp based tamper-check.
If it does get the network time, it compares it to the current system clock, and if out by more than one day, it attempts to change the system clock back to the real time. If it fails to do so, the tamper check fails. If it was able to reset the time, or the time is current then the tamper check passes.
networkTime = NTP.GetNetworkTime(100);
gotNetworkTime = true;
if ((DateTime.Now - networkTime).Duration().TotalHours > 24)
{
try
{
if (SystemClock.SetTime(networkTime) && (DateTime.Now - networkTime).Duration().TotalHours < 24)
{
return;
}
#if DEBUG
throw new SecurityException("System Clock is incorrect by more than one day and cannot be adjusted automatically. Clock Tamper Check Failed");
#else
throw new SecurityException();
#endif
}
catch
{
#if DEBUG
throw new SecurityException("System Clock is incorrect by more than one day and cannot be adjusted automatically. Clock Tamper Check Failed");
#else
throw new SecurityException();
#endif
}
}
Using the code
All code is within the Program.cs file of the attached example project.
The class NTP contains a simple Network-Time-Protocol implementation.
The class SystemClock provides a static method to set the time on the system.
The class ClockTampering provide the static method "CheckTimeTamper" this method returns void, and throws a System.Security.SecurityException if it detects the clock has been tampered.
The method "IsClockTampered" encapsulates "CheckTimeTamper" in a try..catch and returns true if it caught an exception.
class Program
{
static void Main(string[] args)
{
if (ClockTampering.IsClockTampered())
{
Console.WriteLine("Clock Tampering Detected!");
}
Console.ReadLine();
}
}