Сообщений 7 Оценка 10 [+0/-1] Оценить |
Вступление Идея Что дает и чего не дает данный способ Реализация Порядок применения CSelfSafe Дополнительная информация по CRC |
Автор должен чистосердечно раскаяться в том, что идея данного способа использования CRC для защиты исполняемых файлов от искажения целиком и полностью украдена им из книги Лу Гринзоу "Философия программирования Windows 95/NT" (Символ, Санкт-Петербург,1997), однако просит принять во внимание следующие, смягчающие его вину обстоятельства:
Что еще мог сделать в этой ситуации русский программист? Конечно, только одно – “переписать это все нафиг” :).
Для тех, кто не читал Лу Гринзоу (фи:( ) приведу идею метода. Для того, чтобы при подсчёте CRC учитывались только данные исполняемого файла и не учитывалась сама CRC, добавим в исходные тексты программы следующую глобальную структуру данных CRC_DATA:
struct CRC_DATA { BYTE label[ 16 ]; // метка (маячок) для поиска места CRC в файле DWORD crc; // собственно посчитанная CRC файла } CrcData = {{"0123456789ABCDE"}, 0}; // << ЗАДАЙТЕ ВАШУ МЕТКУ ЗДЕСЬ |
Придуманная нами уникальная (в пределах файла) метка label поможет найти в исполняемом файле нашей программы место (DWORD crc), где находится рассчитанная CRC, и которое поэтому не должно учитываться при подсчёте.
ПРИМЕЧАНИЕ В рассматриваемой далее реализации не имеет значения кратность длины метки установленному в свойствах проекта выравниванию – Struct Member Aligment, т.к. переменная CrcData.crc, как таковая, нигде в программе не используется. Она нужна только как гарантия наличия 4-х неиспользуемых байт после метки. Именно эти 4 байта будут использоваться для записи и чтения CRC. В зависимости от длины метки и используемого выравнивания они могут совпадать, а могут и не совпадать с 4-мя байтами переменной CrcData.crc. |
Начну с конца – использование CRC затрудняет, но не исключает полностью возможность искажения файла злоумышленником (см. например [1]), так что о 100%-й надежности определения факта искажения речь не идет. Утешимся, однако, тем, что под нашим контролем останутся искажения при передаче по каналам связи, записи/перезаписи, изменения, внесённые вирусами, а также малолетними «хацкерами» с редакторами ресурсов в руках.
Установив в свойствах проекта опцию Set CheckSum (ключ /RELEASE) мы заставим линкер после каждой перекомпиляции рассчитывать контрольную сумму для файла и записывать ее в соответствующее поле PE-заголовка. Следующий код демонстрирует способ проверки контрольной суммы при запуске exe-файла:
// checksumm.cpp : © Павел Блудов http://www.rsdn.ru/Users/Profile.aspx?uid=507 #include "stdafx.h" #include <tchar.h> #include <stdio.h> #include <windows.h> #include <Imagehlp.h> #pragma comment(lib, "Imagehlp.lib") int _tmain(int argc, _TCHAR* argv[]) { TCHAR szFullPath[MAX_PATH]; DWORD dwFileChecksum = 0, dwRealChecksum = 0; ::GetModuleFileName(::GetModuleHandle(NULL), szFullPath, MAX_PATH); ::MapFileAndCheckSum(szFullPath, &dwFileChecksum, &dwRealChecksum); tprintf(TEXT("File checksum %08X, real checksum %08X\n") , dwFileChecksum, dwRealChecksum); return 0; } |
В демонстрационном проекте (VC7) приведены исходные тексты класса CSelfSafe, делающего для нас необходимую минимальную работу по контролю целостности файла и расчету CRC:
#pragma once #include <sstream> #include "filemap.h" using namespace std; class CSelfSafe { public: // файл будет потом CSelfSafe(); // работать с файлом, переданным через HMODULE CSelfSafe( HMODULE hmod ); // работать с файлом с заданным именем CSelfSafe( string fname ); // новое имя файла для работы void NewFile( string fname ); void NewFile( HMODULE hmod ); ~CSelfSafe( void ); // получить строку с сообщением об ощибке string GetErrStrAnsi() const; // тоже для консольных приложений string GetErrStrOem() const; // подсчитать и сверить CRC для заданного файла BOOL CheckCRC(); // подсчитать и записать CRC в заданного файла BOOL WriteCRC(); protected: // имя проверяемого файла string filename; // сообщение об ошибке stringstream errstrm; // обработываемый файл, отображенный в память CFileMap targetfile; // найти место для CRC в файле BOOL OpenFileAndFindCRCpos( BYTE ** crcpos, BOOL toWrite = FALSE ); // посчитать CRC файла исключая собственно значение CRC в позиции crcpos DWORD SynCRC( BYTE * crcpos ); }; |
Оставим пока в стороне конструкторы и способы задания имени контролируемого файла, начнем с основных моментов.
ПРИМЕЧАНИЕ Ситуация с двумя метками регулярно возникает для Debug-версий исполняемых файлов. Помогает полная перекомпиляция. |
Ниже приведена реализация метода поиска места для CRC - OpenFileAndFindCRCpos():
// открыть файл и найти место для CRC BOOL CSelfSafe::OpenFileAndFindCRCpos( BYTE ** crcpos, BOOL toWrite ) { // открываем файл, отображаем в память if ( !targetfile.Open( filename.c_str(), toWrite ) ) { errstrm.clear(); errstrm.str( "" ); errstrm << "Невозможно открыть файл '" << filename << "'"; return FALSE; } // указатель на конец файла, будет нужен несколько раз BYTE* file_end = targetfile.Base() + targetfile.Size(); // ищем метку, после которой идет место для CRC BYTE* label_start = search( targetfile.Base(), file_end, CrcData.label, CrcData.label + sizeof( CrcData.label ) ); if ( label_start == file_end ) { errstrm.clear(); errstrm.str( "" ); errstrm << "В файле '" << filename << "' не найдено место хранения CRC"; return FALSE; } // CRC - сразу после метки и смещения *crcpos = label_start + sizeof( CrcData.label ); if ( ( *crcpos + sizeof( DWORD ) ) > file_end ) { // при попытке записи/чтения в это место, вылетим // за конец файла errstrm.clear(); errstrm.str( "" ); errstrm << "Недопустимое место для хранения CRC в файле '" << filename << "'"; return FALSE; } // метка найдена, на всякий случай ищем вторую, // начиная сразу после первой найденной метки if ( search( label_start + sizeof( CrcData.label ), file_end, CrcData.label, CrcData.label + sizeof( CrcData.label ) ) != file_end ) { // нашли две метки, это уже безобразие, метка в файле // должна быть одна, иначе непонятно, куда писать CRC errstrm.clear(); errstrm.str( "" ); errstrm << "В файле '" << filename << "' найдено 2 места для хранения CRC, должно быть только одно"; return FALSE; } return TRUE; } |
После получения от OpenFileAndFindCRCpos() указателя на место для хранения CRC в файле нам остается только подсчитать CRC32, исключив из подсчета те самые четыре байта, в которых будет храниться CRC, и записать на это место подсчитанную сумму:
// подсчитать и записать CRC для заданного файла BOOL CSelfSafe::WriteCRC() { // указатель на CRC, сохраненную в файле BYTE * crcpos = NULL; // результат записи CRC BOOL ret = FALSE; // ищем место, куда в файл нужно записать CRC if ( OpenFileAndFindCRCpos( &crcpos, TRUE ) ) { // нашли, пишем в это место только что подсчитанную CRC *reinterpret_cast<DWORD*>(crcpos) = SynCRC( crcpos ); ret = TRUE; } else { // расшифровка ошибки дана в OpenFileAndFindCRCpos ret = FALSE; } // закрываем файл targetfile.Close(); return ret; } // посчитать CRC, исключая собственно 32 бита CRC DWORD CSelfSafe::SynCRC( BYTE * crcpos ) { // первая половина файла, до места, где CRC DWORD CRC = accumulate( targetfile.Base(), crcpos, ( DWORD ) 0, // начальное значение CRC UpdateCRC ); // функция подсчета CRC // пропустили 32 байта места хранения CRC в файле и идем дальше return accumulate( crcpos + sizeof( DWORD ), // место после CRC targetfile.Base() + targetfile.Size(), // конец CRC, // CRC первой половины UpdateCRC ); // функция расчета } // пересчет CRC по таблице с учетом следующего байта DWORD UpdateCRC( DWORD crcSoFar, const BYTE& nextByte ) { return ( crcSoFar >> 8 ) ^ CRCtable[ ( ( BYTE ) ( crcSoFar & 0x000000ff ) ) ^ nextByte ]; } |
В данной реализации использована таблица для расчета CRC32, приведенная в [1].
Ищем место, где хранится CRC, считываем контрольную сумму, сохраненную в файле, и сравниваем с рассчитанной:
// подсчитать и сверить CRC для заданного файла BOOL CSelfSafe::CheckCRC() { // указатель на CRC, сохраненную в файле BYTE * crcpos = NULL; // результат сверки CRC BOOL ret = FALSE; // ищем место, где в файле записана CRC if ( OpenFileAndFindCRCpos( &crcpos ) ) { // нашли, сравниваем то, что записано в файле, // с CRC, подсчитанной только что if ( *reinterpret_cast<DWORD*>(crcpos) == SynCRC( crcpos ) ) ret = TRUE; else { // устанавливаем ошибку errstrm.clear(); errstrm.str( "" ); errstrm << "Файл '" << filename << "': неверное значение CRC"; ret = FALSE; } } else { // расшифровка ошибки дана в OpenFileAndFindCRCpos ret = FALSE; } // закрываем файл targetfile.Close(); return ret; } |
Пример использования метода дан в демонстрационном проекте.
Создайте объект класса CSelfSafe, передав ему в конструкторе или в методе NewFile() HMODULE или строку с именем контролируемого файла. В процедуре запуска exe или dll, а также, в порядке паранойи, по некоторым событиям в вашей программе вызывайте метод CheckCRC() для контроля целостности.
Естественно, после каждой перекомпиляции программы на нее надо натравливать утилитку, которая, используя тот же самый CSelfSafe, будет с помощью метода WriteCRC() подсчитывать CRC и записывать ее в нужное место. Это, пожалуй, единственное неудобство при использовании данного метода, но с другой стороны, зачем еще нужен Post Build Step? :-)
Не забудьте поменять для себя последовательность байтов в метке на нечто более редко встречающееся для затруднения обнаружения ее в исполняемом файле, с одной стороны, и для исключения ложных срабатываний при поиске, с другой стороны.
Тестовая программа из демонстрационного проекта при запуске с параметром (именем исполняемого файла) рассчитывает для него (файла) контрольную сумму и записывает в предназназначенное для этого место, а при запуске без параметра производит самопроверку CRC:
int _tmain( int argc, _TCHAR* argv[] ) { // инициализация CRTDBG для контроля утечек памяти // см. также // #define _CRTDBG_MAP_ALLOC // #include <crtdbg.h> // в stdafx.h _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); CSelfSafe selfs; if ( argc == 1 ) { // argv[0] в данном случае использовать нельзя, если // программу вызовут без полного пути к selfsecureexe, // и она запустится из одного из каталогов, доступных по // PATH, не найдем "сами себя" для самопроверки. char myname[_MAX_PATH]; ::GetModuleFileName( NULL, myname, sizeof(myname) ); selfs.NewFile( myname ); if ( !selfs.CheckCRC() ) { cout << selfs.GetErrStrOem() << endl; _getch(); return 0; } else { cout << "CRC in file " << myname << " is cheked out!" << endl; } } else { selfs.NewFile( argv[1] ); cout << "Writing CRC into file " << argv[1] << "..." << endl; if ( !selfs.WriteCRC() ) { cout << selfs.GetErrStrOem() << endl; _getch(); return 0; } else { cout << "CRC is written into file " << argv[1] << " !" << endl; } cout << "Checking CRC of " << argv[1] << "file..." << endl; if ( !selfs.CheckCRC() ) { cout << selfs.GetErrStrOem() << endl; _getch(); return 0; } else { cout << "CRC in file " << argv[1] << " is cheсked out!" << endl; } } _getch(); return 0; } |
Сообщений 7 Оценка 10 [+0/-1] Оценить |