Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / C#

Check for Clock Tampering to Extend Licence Duration

Rate me:
Please Sign up or sign in to vote.
4.37/5 (13 votes)
26 May 2016CPOL5 min read 40.6K   1K   22   19
Help prevent clock tampering and enforce a software licence

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.

        /// <summary>
        /// check the creation date/time on an essential file.
        /// </summary>
        /// <returns></returns>
        public static bool EssentialFileDateCheck()
        {
            // use the config file:
            var me = Assembly.GetExecutingAssembly().Location + ".config";
            if (File.Exists(me))
            {
                // get the existing last-write
                var createdOn = File.GetCreationTime(me);
                if (createdOn < DateTime.Now)
                {
                    // set the last write time on the assembly:
                    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.

// compute a hash from assembly name and machine name, use this to create a folder name (like ms temp install folders)
var folder = (HashAlgorithmName.MD5.ComputeHash(Encoding.UTF8.GetBytes(Environment.MachineName + Assembly.GetEntryAssembly().FullName))).ToOneWayHex();

// work out a spot for the time file
var fn = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
                      folder, 
                      Path.ChangeExtension(Environment.MachineName.Clean(Path.GetInvalidFileNameChars()), ".dll"));

// create the path if it doesn't exist:
var path = Path.GetDirectoryName(fn);

if (!Directory.Exists(path))
{
    // create the folder if it doesn't already exist
    Directory.CreateDirectory(path);

    // come up with a date that's definitely older than today:
    var older = DateTime.Now - new TimeSpan(165, 165, 165, 0);

    // so the directory date doesn't distinguish it from other folders or put it at the top of a date-sort list:
    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.

// encrypt current time & date using ProtectedData (uses a machine access key)
var now  = DateTime.Now;
var time = ProtectedData.Protect(BitConverter.GetBytes(now.ToBinary()), entropy, DataProtectionScope.LocalMachine);

// delete the file if it already exists:
if (File.Exists(fn))
{
    File.Delete(fn);
}

// write the encrypted time to the file. the time is encrypted so it can't be changed.
File.WriteAllBytes(fn, time);

// set appropriate attributes on the file:
// hidden just makes it a little harder to find
// system gives a warning if the user tries to delete the file (cos if they do, it'll set off the tamper alarm)
// encrypted reflects the true state of the file
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.

// check using NTP:
networkTime = NTP.GetNetworkTime(100);

// remember we retrieved the time from the network
gotNetworkTime = true;

// compare the network time with the current system clock
if ((DateTime.Now - networkTime).Duration().TotalHours > 24)
{
     // the clock is out of sync by at least a day.
     // try to reset the system clock
     try
     {
         // set and verify system time clock:
         if (SystemClock.SetTime(networkTime) && (DateTime.Now - networkTime).Duration().TotalHours < 24)
         {
             // clock is now correct:
             return;
         }
         #if DEBUG
         // only include an exception message in debug releases for security purposes.
         throw new SecurityException("System Clock is incorrect by more than one day and cannot be adjusted automatically. Clock Tamper Check Failed");
         #else
         // couldn't set the clock and out by at least a day:
         throw new SecurityException();
         #endif
     }
     catch
     {
          #if DEBUG
          // only include an exception message in debug releases for security purposes.
          throw new SecurityException("System Clock is incorrect by more than one day and cannot be adjusted automatically. Clock Tamper Check Failed");
          #else
          // couldn't set the clock and out by at least a day:
          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();
        }
    }

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Decipha
Australia Australia
Wrote his first computer game in Microsoft Basic, on a Dragon 32 at age 7. It wasn't very good.
Has been working as a consultant and developer for the last 15 years,
Discovered C# shortly after it was created, and hasn't looked back.
Feels weird talking about himself in the third person.

Comments and Discussions

 
QuestionNice article! Pin
Bernhard Hiller1-Sep-16 1:08
Bernhard Hiller1-Sep-16 1:08 
AnswerProcessMonitor Pin
George I. Birbilis27-May-16 20:40
George I. Birbilis27-May-16 20:40 
GeneralRe: ProcessMonitor Pin
Simon Bridge30-May-16 18:03
Simon Bridge30-May-16 18:03 
GeneralRe: ProcessMonitor Pin
George I. Birbilis9-Sep-16 11:31
George I. Birbilis9-Sep-16 11:31 
Question[My vote of 1] Cool tips for the noobs Pin
jfriedman27-May-16 12:07
jfriedman27-May-16 12:07 
AnswerRe: [My vote of 1] Cool tips for the noobs Pin
Simon Bridge27-May-16 15:06
Simon Bridge27-May-16 15:06 
GeneralRe: [My vote of 1] Cool tips for the noobs Pin
jfriedman28-May-16 3:31
jfriedman28-May-16 3:31 
How are you going to read the file if you don't pass something to System.IO.File?

Stop being ignorant and thinking like your some type of genius when in fact you have nothing to offer.

There is no 100% sure fire way to stop cracks or reverse engineering.
GeneralRe: [My vote of 1] Cool tips for the noobs Pin
Simon Bridge30-May-16 17:14
Simon Bridge30-May-16 17:14 
GeneralRe: [My vote of 1] Cool tips for the noobs Pin
jfriedman7-Jun-16 19:18
jfriedman7-Jun-16 19:18 
QuestionI dont like this Pin
Merlin8727-May-16 7:55
Merlin8727-May-16 7:55 
AnswerRe: I dont like this Pin
Simon Bridge27-May-16 15:00
Simon Bridge27-May-16 15:00 
GeneralRe: I dont like this Pin
Merlin8728-May-16 2:01
Merlin8728-May-16 2:01 
GeneralRe: I dont like this Pin
Simon Bridge30-May-16 17:31
Simon Bridge30-May-16 17:31 
GeneralRe: I dont like this Pin
Merlin8730-May-16 21:10
Merlin8730-May-16 21:10 
GeneralRe: I dont like this Pin
Simon Bridge31-May-16 13:30
Simon Bridge31-May-16 13:30 
QuestionNice article. Pin
XH558v27-May-16 4:08
XH558v27-May-16 4:08 
AnswerRe: Nice article. Pin
Simon Bridge30-May-16 17:40
Simon Bridge30-May-16 17:40 
GeneralRe: Nice article. Pin
XH558v30-May-16 23:03
XH558v30-May-16 23:03 
GeneralRe: Nice article. Pin
Simon Bridge31-May-16 14:01
Simon Bridge31-May-16 14:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.