Friday, March 8, 2019

Poor man's copy protection

As the title implies, this is a basic copy protection scheme. I start with a basic example and keep it as simple as possible. I have an intention to keep extending it over time.

Once a life ago (*grin*) I used to buy software dongles and kept my licenses under control. This is both good and bad at the same time. It is good because it needs a minimum effort to handle the scheme. Whether it is time-based demo or machine locked proprietary system, a dongle simply works. It keeps most of the promises it makes: your program only runs on a machine where the dongle is inserted.

One of the bad reasons for using a dongle is that when a hacker breaks its protection scheme it becomes trivial even for script kiddies. There are emulators and crackers. Just download the exploit, crack the software. Please note that I used to be a paid cracker at the time I went to the university and I used to cover my expenses this way. Sometimes people use dongles in a present/not-present fashion and they think they are safe. I really love this. I would make one thousand euro in a weekend's work. It is still a good amount of money for a student. Here is how:

An 8086 based CPU Assembly uses CMP mnemonic to compare a register with another one (or a memory value) and  store the result in the flags register.

The generic form of the instruction is:
cmp     dest, src     perform dest - src and set flags

The most important flag in the register to hack a present/not-present scheme is ZERO flag. You can take action using JZ (Jump if Zero) or JNZ (Jump if Not Zero) jump commands according to the bits of it. For example, if you check a number is in AX (or EAX, it is always B8 register) you would use this code:

cmp AX, 1234h ; or cmp EAX, 12345678h
jz theNumberIsOk
jmp theNumberIsWrong

The opcode for JZ is 74h and the opcode for JNZ is 75h. So all you need is to change one bit. Yes, with a hex editor, find the right JZ (74) and change it to JNZ (75) and you are done. It also works with the password entries. There is just one side effect: you cannot use the right password anymore. All the other words will work but the original password won't.

(Rumour has it that there is a secret jump code for 80x86 family. They say it is Jump if Programmer Is Not Looking and it is what you experience your debug code runs and production code doesn't)

Also it is the same with that dongle protection(!). If the dongle is on the machine, software won't work. By the way this is not a recommended use of copy protection dongles. They come with PKI based protection keys, binary encrypters, hash checksums, anti debuggers, anti tracers etc. Yet I've cracked lots of software due to the false sense of security the developer feels when they use a dongle. "Hey just check if it is present, we have lots of things to do!" says the manager. A dongle is not a magic bullet. It has to be used right. Remember: there is no 100% guarantee. Every software protection can be cracked IN TIME. A dongle gives you 6 months, for example. You should publish a new version in 6 months, a superior one, one with amazing abilities that your users would buy or upgrade to new one. You can eliminate the leeches this way. This kind of people are always greedy. They want it free and they want the NEW VERSION!

Our simple copy protection can save your software from unauthorized use of your little brother or sister. Nothing more.

Hence the title! We are going to develop a very basic copy protection scheme that a seasoned hacker can break in minutes. I said it. It is the poor man's copy protection.

We are so poor that we cannot afford a separate hardware for the present/not-present scheme. We will use Ethernet MAC address. Yes, I know, Ethernet MAC address can be changed. (In fact I used to change my Ethernet MAC address to a random value every time I boot my Ubuntu, I searched the bash script but couldn't find it, I must have deleted it) Here is the header file for the MAC address helper.

#ifndef CMACADDRESSHELPER_H
#define CMACADDRESSHELPER_H
#include <QtCore>
#include <QtNetwork/QNetworkInterface>

const int MACLoc = 3557; // a random number

class CMACAddressHelper
{
public:
    CMACAddressHelper();
    static QString GetFirstMACAddress();
    static QStringList GetAllMACAddresses();
    static QByteArray CreateControlBlock(QString MACAddress);
    static bool CheckMACAddress(QByteArray &ControlBlock, QString MACAddress);
};

#endif // CMACADDRESSHELPER_H
It has some static methods and an empty constructor that I forgot to remove. I developed a few different copy protection schemes in a few language e.g. Delphi, C, C++ and of course my beloved Assembly. One of my favourite copy protections was written in Assembly and it was self modifying its code while working. Try to step over a call and boom, you failed. I really like this kind of puzzles.

Ok, keep the long word short, here is how we get the Ethernet MAC address with Qt (code is taken from somewhere else, and honestly I don't remember)

QString CMACAddressHelper::GetFirstMACAddress()
{
    foreach(QNetworkInterface netInterface, QNetworkInterface::allInterfaces())
    {
        // Return only the first non-loopback MAC Address
        if (!(netInterface.flags() & QNetworkInterface::IsLoopBack))
            return netInterface.hardwareAddress();
    }
    return QString();
}

I love Qt for this simplicity. Qt has an answer for all kinds of everyday problems. QString is better that std::string, for example, especially with numbered arguments and such, which std::string lacks of. Last time I checked there were more than a few thousand classes in the framework, designed and implemented by smart people.

Now we need to save this MAC address to somewhere. A file, registry, a database. Just a persistent memory is enough. Next time our program runs, we will check if we are running on the same hardware.

Let's write this to a file for now. We are using a scheme that is very easy to crack (changing the file contents with the new MAC address) we would use a little bit privacy. I decided to fill the file with random values. And then I will place the MAC address into the file to a place I know, then XOR the file with a known value. Actually I do all this operations in the memory and then save the file at once. Here is the memory part. I get 8192 bytes of memory (2^13) and fill it with random bytes, place the MAC address to a place called MACLoc, and XORing whole buffer with 0xCC (11001100b)

QByteArray CMACAddressHelper::CreateControlBlock(QString MACAddress)
{
    QByteArray result(8192, static_cast<char>(0xFF));
    QByteArray macBytes = MACAddress.toLocal8Bit();
    for (int i = 0; i < result.size(); ++i)
    {
        int rand = qrand();
        result[i] = static_cast<char>(rand % 256);
    }

    for (int i = 0; i < macBytes.size(); ++i)
    {
        result[MACLoc + i] = macBytes[i];
    }

    for (int i = 0; i < result.size(); ++i)
    {
        result[i] = result[i] ^ static_cast<char>(0xCC);
    }

    return result;
}
I would write this memory buffer to a file. It is not a big buffer, so I can write it at once.

void MainWindow::on_recordButton_clicked()
{
    QFile
dossier("config.dat");
   
dossier.open(QIODevice::ReadWrite);
    auto macAddress = CMACAddressHelper::GetFirstMACAddress();
    if (macAddress.size() == 0)
        throw "No ethernet card found!";
    auto block = CMACAddressHelper::CreateControlBlock(macAddress);
   
dossier.write(block);
   
dossier.close();
    ui->label_Status->setText(macAddress+" saved...");
}
This program would run from a USB stick and then the file config.dat would be copied to hard disk. You cannot leave this program to the client. Now here is the code we will use to check if we are running on a licensed machine:

void MainWindow::on_pushButton_2_clicked()
{
    QFile
dossier("config.dat");
    dossier.open(QIODevice::ReadOnly);
    QByteArray block =
dossier.read(8192);
   
dossier.close();

    if (block.size() != 8192)
    {
        ui->
label_Status->setText("License not found!");
        return;
    }

    auto macAddress = CMACAddressHelper::GetFirstMACAddress();
    if (macAddress.size() != 17)
    {
        ui->
label_Status->setText("License not found!");
        return;
    }

    if (!CMACAddressHelper::CheckMACAddress(block, macAddress))
    {
        ui->
label_Status->setText("License not found!");
        return;
    }
    ui->
label_Status->setText("Thank you for using licensed software");
}



There are a few problems with this code. First of all, obfuscation is not protection. We basically filled a buffer with random data, put our MAC address to a place only we know (ha ha ha) and XORed the buffer to make the MAC address disappear (to the naked eye).

This is called obfuscation. This is why I hate writing proprietary systems in .NET. When I say it is prone to decompilation and reversing .NET advocates say the same "but there are good obfuscators". I was cracking binary code without any structural information (for loops, whiles, switch/case idioms, nothing, just disassembled binary code) and yet I was making money reversing algorithms. One of the oldest things I remember I had a hard time was Eizi Nakamura's Best Kit (or something like that, it was 26 years ago) where I learned a few good tricks.

What we did here is we obfuscated a basic MAC address data and saved it into a garbage-like-looking file. An experienced programmer would use AES or XTEA algorithms to protect it. A die hard coder would use public/private key encryption to protect the secret. But we have an Achilles' Heel here:

if (!CMACAddressHelper::CheckMACAddress(block, macAddress))


Remember the JZ/JNZ conversion? We can find the comparison, toggle the bit, and voila! Our program won't run in the original machine anymore but it will work everywhere else. Our code would work as if there is no (!) at the beginning, therefor not negating the result of CheckMACAddress.

Oh, by the way, here is the code for CheckMACAddress:

bool CMACAddressHelper::CheckMACAddress(QByteArray &ControlBlock, QString MACAddress)
{
    if (ControlBlock.size() != 8192)
        return false;

    if (MACAddress.size() != 17)
        return false;

    QByteArray macBytes = MACAddress.toLocal8Bit();

    for (int i = 0; i < ControlBlock.size(); ++i)
    {
        ControlBlock[i] = ControlBlock[i] ^ static_cast<char>(0xCC);
    }

    for (int i = 0; i < macBytes.size(); ++i)
    {
        if (ControlBlock[MACLoc + i] != macBytes[i])
            return false;
    }

    return true;
}

I think it is enough for today. I will improve this basic copy protection in time. At least I have this intention. This program has a purpose. A simple one. Teaching the first bit of copy protection. It has many flows. You can crack in minutes. Altering one bit to crack a copy protection is not a copy protection at all. The only thing that saves us from ordinary people is that they are too lazy to learn basic machine code :P

In my opinion, a good copy protection scheme should involve in encrypting the data and it should need a private key scheme to encrypt the data in order to protect it from altering. If you run the code on different hardware, you will not get the correct keys and your data would look like garbage. Yet I have seen many hard guys beaten by the kids on the block. Never trust one aspect of security. Always keep an eye on your enterprise. Security is a protection of self from the leeches.

Now I'm off to listen my dear Dolores, do I have to let it linger?

No comments:

Post a Comment

A Survey of Body Area Networks