Sunday, January 19, 2020

For the last(??) time, .NET code isn't safe!

Recently a friend asked me to have a look at an application that he uses.  So I downloaded and installed it, and set about having a look.  A quick glance at the file listing shows a likely candidate, "Common.Licensing.dll".  Could you be any more obvious?  And the filesize?  30kb.  (closes eyes, shakes head).  I toss it into IDA Pro, and discover that it's a .NET assembly.  So, I exit IDA, and toss it into dnspy.  (If you've not yet seen the power of dnspy, I HIGHLY recommend it.  This applies not only to us reversers, but to anyone who ships a protected product using .NET.  Have a look at what I can see about your product!)

For instance, if you have a licensing object, and associated fields, I might see something like this:

        public int TrialDays { get; set; }

        public bool IsTrial { get; set; }

This means that I can simply replace the accessor functions to always say that this ISN'T a trial by returning FALSE on the get, and just ignoring what happens in the set.  Or, if I'm feeling especially funny, I'll change the TrialDays get to return "69", or "420" or some other funny number.  Or if you store the code that the user enters when trying to register your application in an obvious place, I might see something like this:
public string ActivationCode
        {
            get
            {
                return this._settings.ActivationCode;
            }
        }

So now I can just look through the rest of the code for references to ActivationCode, and find all sorts of things.  Like, I could find out that you compute this activation code via MD5:
public string GetHash(string activationCode, string email, bool isExtended)
        {
            return LicenseChecker.GetMd5Hash(CryptHelper.Encrypt(this.GetHwId(isExtended), this.Aggregate(new string[]
            {
                activationCode,
                email,
                this._md5HashKey
            }, "")));
        } But, you think that's OK, because you encrypt it before you store it, right?  Like this:  
public Func<string, string> Encrypt
        {
            get
            {
                return (string value) => CryptHelper.Encrypt(value, this._md5HashKey);
            }
        }

I heard that! You said "Big deal, you know it's MD5, but you don't know the hash key! Ahem: public readonly string _md5HashKey = "kPW6ib49Q1mUaPJBmD2OKaXLo9B7eKCf";

But, it's not all bad news! (I lied, it's all bad). Even your idea that "it's OK to include the code to compute the activation code locally, as you plan to VALIDATE it online, and THAT will certainly stop me" is a bad one. As you included all of THAT code in the same file, so it became this:
public bool CheckOnline(UserSettings settings, Action onOnlineReCheckFailed = null, bool waitResult = false)
{
    return true;
}
And that's just not very useful is it? You also started some threads that should, in theory, validate the activation from time to time right?
        public async Task<SerialValidationResultEnum> CheckOnlineAsync(UserSettings settings)
        {
            TaskAwaiter<bool> taskAwaiter = this.IsOnline().GetAwaiter();
            if (!taskAwaiter.IsCompleted)
            {
                await taskAwaiter;
                TaskAwaiter<bool> taskAwaiter2;
                taskAwaiter = taskAwaiter2;
                taskAwaiter2 = default(TaskAwaiter<bool>);
            }
            SerialValidationResultEnum result;
            if (!taskAwaiter.GetResult())
            {
                result = SerialValidationResultEnum.ConnectionToServerFailed;
            }
            else
            {
                string url = (!settings.IsValid()) ? this._registrationURL : this._checkURL;
                string requestData = this.GetRequestData(settings);
                result = await this.GetServerResult(url, requestData, false);
            }
            return result;
        }
Well, I see from this that you return an enum. And, you include that in the source, so this whole function becomes:
       public async Task<SerialValidationResultEnum> CheckOnlineAsync(UserSettings settings)
        {
            return SerialValidationResultEnum.SuccessfullyValidated;
        }
And you included, as strings! The server URLs that you plan to try to contact, have all been changed. This is what they look like now:
        public readonly string _registrationURL = "localhost";
        public readonly string _checkURL = "localhost";
        public readonly string _trialURL = "localhost";

So, good luck getting any data out that way either. And last but not least, all those other "background tasks" that you run that will validate things, and update member variables of the class should something not look right? Yeah, they don't do much anymore:
        public async Task<SerialValidationResultEnum> CheckTrial()
        {
            return SerialValidationResultEnum.SuccessfullyValidated;
        }

        public async Task<SerialValidationResultEnum> CheckAsync(string activationCode, string email)
        {
            return SerialValidationResultEnum.SuccessfullyValidated;
        }

        public async Task<SerialValidationResultEnum> CheckAsync(UserSettings settings)
        {
            return SerialValidationResultEnum.SuccessfullyValidated;
        }

        public async Task<SerialValidationResultEnum> GetServerResult(string url, string requestData, bool isTrialResponse = false)
        {
            return SerialValidationResultEnum.SuccessfullyValidated;
        }

        public async Task<bool> IsOnline()
        {
            return false;
        }

        public async Task<bool> SendAnalytics(AnalyticsEventsEnum analyticsEvent)
        {
            return true;
        }
And, as the cherry on this sundae of fail, I couldn't resist a little graffiti.

        public string ActivationCode
        {
            get
            {
                return "The Humble Guys!";
            }
        }
This shows up when you do Help|About. 

So, in closing, be aware of your surroundings.  If you plan to write an app in .NET be aware that there are some KICKASS tools out there for decompiling your code, and in the case of dnspy, it even allowed me to make all those changes you see above in a "Visual Studio feeling" editor, and one click later, it had compiled all my changes, and had written them to MY version of Common.Licensing.dll.  So, no hex-editing required.