Защита Qt приложения от модификации

Рано или поздно при распостранении приложения, написанного с помощью Qt возникают два вопроса:

  • Цифровая подпись приложения, удостоверяющая вашу личность или привязывающую программу к названию компании. Особенно актуально для программ с открытым исходным кодом. Позволяет определить, что именно Вы или Ваша компания выпустила очередную версию программы.
  • Защита файлов программы от изменения.

В этой статье рассматривается второй вопрос, а именно проверка на целостность исполняемого файла. Зачем это нужно? Вы не можете быть уверены, что скомпилированный вами файл останется без изменения на машине пользователя. Вирусы, нежелательное ПО, которое встраивается в файлы, банально неисправное оборудование пользователя. Во всех этих случаях ваша программа может работать не так как следует, сбоить, а то и вовсе крэшится. А это влияет на репутацию как программы, так и вас, вашей компании. Встроить проверку на целостность файла не займет много времени, и в итоге вы поимеете только плюсы.

Как же это работает? Очень просто. Подсчитываем CRC исполняемого файла за исключением области, где хранится эталонное значение, и сравниваем с ним. Если сходится - все Ок, в противном случае нужно уведомить пользователя, и порекомендовать скачать исправную версию с сайта. Для CRC есть специальная область в заголовке exe файла - байты 18 и 19, считая от нуля. Туда и будем записывать эталонное значение.

Пример кода из программы UnoSDR подсчета CRC:

bool App::checkSelfCRC()
{
#ifdef Q_OS_WIN
    QByteArray ba;
    quint16 hash = 0, selfHash = 0;
    qint64 time = QDateTime::currentMSecsSinceEpoch();
    //
    QFile file(qApp->applicationFilePath());
    if(file.open(QIODevice::ReadOnly))
    {
        ba = file.readAll();
        file.close();
        //
        if(ba.size() > 20)
        {
            for(qint64 i = 0; i < 18; i++)
            {
                hash += ba.at(i);
                hash += (hash << 10);
                hash ^= (hash >> 6);
            }
            //
            for(qint64 i = 20; i < ba.size(); i++)
            {
                hash += ba.at(i);
                hash += (hash << 10);
                hash ^= (hash >> 6);
            }
            // read saved crc
            selfHash = ba.at(18) | (quint16)ba.at(19) << 8;
            qDebug() << "calculate self crc" << hash << selfHash << "time, ms:" << QDateTime::currentMSecsSinceEpoch() - time;
        }
        else
        {
            qDebug() << "Error calc self crc - error ba size <= 20" << ba.size();
        }
    }
    else
    {
        qDebug() << "Error calc self crc - error open exe file" << file.errorString();
    }
    return selfHash == hash;
#else
    return true;
#endif
}

Используется хэш Дженкинса. В случае, если возникает ошибка открытия файла, что не исключено, функция возвращает true. Так же, если операционная система не Windows, потому что мы используем специфичный для Windows заголовок исполняемого файла.

Это еще не все. Нам нужно как то записать эталонную CRC при деплое приложения. Консольная утилита:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray ba;
    quint16 hash = 0;
    //
    QFile file("../bin/UnoSDR.exe");
    if(file.open(QIODevice::ReadWrite))
    {
        ba = file.readAll();
        if(ba.size() > 20)
        {
            for(qint64 i = 0; i < 18; i++)
            {
                hash += ba.at(i);
                hash += (hash << 10);
                hash ^= (hash >> 6);
            }
            //
            for(qint64 i = 20; i < ba.size(); i++)
            {
                hash += ba.at(i);
                hash += (hash << 10);
                hash ^= (hash >> 6);
            }
            // write crc
            ba.clear();
            ba.append(hash & 255);
            ba.append(hash >> 8);
            file.seek(18);
            file.write(ba);
        }
        else
        {
            qDebug() << "Error calc self crc - error ba size <= 20" << ba.size();
        }
        file.close();
    }
    else
    {
        qDebug() << "Error calc self crc - error open exe file" << file.errorString();
    }

    return a.exec();
}

Следует заметить, что данный метод работает только при непреднамеренном изменении исполняемого файла, и не защищает от взлома.