CodeNet / Языки программирования / C / C++ / Microsoft Visual C++ / Система
Смена серийного номера тома
11 марта 2009 года
В статье “WinAPI: Определение типа носителя и его серийного номера” я показывал простой пример как реализовать защиту программ путем привязки к железу. Но все бы хорошо, да только бывают моменты когда просто необходимо “отвязаться” от железки.
Ну а теперь от слов к делу. Вся информация для корректной работы накопителя хранится в его загрузочной области. Нас будет интересовать используемая файловая система, от которой зависит расположения нужных байт относительно начала загрузочной области. Поэтому основная наша задача это определить тип файловой системы, и сместившись на нужный адрес переписать значение серийного номера. Всё просто ;). Изучим таблицу.
Таблица:
Поле/ФС | FAT | FAT32 | NTFS |
---|---|---|---|
Метка тома | 0x2B | 0x47 | ? |
Файловая система | 0x36 | 0x52 | 0x3 |
Серийный номер | 0x27 | 0x43 | 0x48 |
В приведенной таблице показаны основные файловые системы и соответствующие для них смещения. Оперируя этой информацией мы и будем реализовывать нашу программу. На необходимо реализовать следующие функции:
- DiskOpen - открыть диск
- DiskClose - закрыть диск
- ReadSector - прочитать сектор
- WriteSector - записать сектор
Реализовывать нашу программу мы будем С++ под семейство Windows NT. В частности, в среде разработки С++ Builder 6. Нам понадобиться глобальная переменная, в которой будем хранить указатель на используемый диск:
HANDLE hD;
Начнем c описания функции DiskOpen():
bool DiskOpen(char *vol) { char szDrive[10]; sprintf(szDrive, "\\\\.\\%c:", vol[0]); hD = CreateFile( szDrive, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); return hD != INVALID_HANDLE_VALUE; }
Данной функции в качестве параметра передаётся буква диска, которому нужно сменить серийный номер. С помощью API-функции CreateFile(), мы получаем указатель на диск (подробней о CreateFile можно прочитать в статье “WinAPI: Работа с файлами (основные функции)“). В случае успеха возвращает “истину”, иначе - “ложь”.
Далее опишем функцию DiskClose():
void DiskClose() { if (hD != INVALID_HANDLE_VALUE) CloseHandle(hD); }
Эта функция достаточно простая, она освобождает используемый указатель на диск.
Следующая функция ReadSector():
bool ReadSector (DWORD sector, char *Buffer, int sectorSize) { DWORD read = 0; if (SetFilePointer(hD, sector, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return false; if (!ReadFile(hD, Buffer, sectorSize, &read, NULL)) return false; return true; }
Эта функция уже посложней. Исользует 3 параметра:
- первый - указатель на диск.
- второй - буфер, куда будут записаны данные, считанные из загрузочной области.
- третий - размер загрузочной области, а следовательно и буфера.
Функция переводит указатель смещения в файле на нужную позицию с помощью API-функции SetFilePointer(), а потом с помощью ReadFile() считывает в буфер данные из загрузочной области (подробней о ReadFile() можно прочитать в статье “WinAPI: Работа с файлами (основные функции)“). В случае успеха возвращает “истину”, иначе - “ложь”.
И последняя функция WriteSector():
bool WriteSector(DWORD sector, char *Buffer, int sectorSize) { DWORD wrote = 0; if (SetFilePointer(hD, sector, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return false; if (!WriteFile(hD, Buffer, sectorSize, &wrote, NULL)) return false; return true; }
Эта функция аналогична предыдущей. Только вместо API-функции ReadFile() используется WriteFile() (подробней о WriteFile() можно прочитать в статье “WinAPI: Работа с файлами (основные функции)“). В случае успеха возвращает “истину”, иначе - “ложь”.
Ну что, базовые функции мы реализовали, пора приступить и к завершающей стадии. Скинем на форму 1 кнопку, 1 поле ввода (сюда будем вводить новый серийный номер), и 1 ComboBox (определим выбор основных букв дисков). И для кнопки опишем следующий код:
void __fastcall TForm1::Button1Click(TObject *Sender) { //проверяем не пусты ли необходимые поля if ((ComboBox1->Text !="") && (Edit1->Text !="")) { DWORD newSerial = StrToInt(Edit1->Text); const max_pbsi = 3; //определяем структуру для сигнатуры загрузочной области struct partial_boot_sector_info { LPSTR Fs; // название файловой системы DWORD FsOffs; // смещение системного имени в загрузочной области DWORD SerialOffs; // смещение серийного номера в загрузочной области }; //описываем часто используемые сигнатуры partial_boot_sector_info pbsi[max_pbsi] = { {"FAT32", 0x52, 0x43}, {"FAT", 0x36, 0x27}, {"NTFS", 0x03, 0x48} }; //инициализируем необходимые переменные TCHAR Drive[12]={0}; char Sector[512]; DWORD i; sprintf(Drive, "%s:\\", ComboBox1->Text); //открываем диск if (!DiskOpen(Drive)) {//если ошибка оповещаем пользователя и выходим из процелуры Application->MessageBoxA("Невозможно открыть диск!","Ошибка"); return; } // читаем загрузочную область if (!ReadSector(0, Sector,512)) { Application->MessageBoxA("Невозможно считать данные","Ошибка"); return; } // посик сигнатуры загрузочной области for (i=0;i= max_pbsi) { Application->MessageBoxA("Невозможно сменить номер для этой файловой системы!","Ошибка"); return; } // меняем номер *(PDWORD)(Sector+pbsi[i].SerialOffs) = newSerial; // записываем в загрузочную область if (!WriteSector(0, Sector,512)) { Application->MessageBoxA("Невозможно записать на диск!","Ошибка"); return; } DiskClose(); Application->MessageBoxA("Серийный номер успешно изменен! Необходимо перезагрзить компьютер.","Внимание"); }else Application->MessageBoxA("Не указан диск или серийный номер!","Внимание"); }
Если Вы меняете серийный номер тома флешки, то компьютер перезагружать не обязательно, достаточно отключить и заново подключить флешку. В других случаях результат буде виден только после перезагрузки. На этом всё, замечания и вопросы можете высказать на форуме. Спасибо за внимание! ;)
Рабочий пример: скачать (4 Кб).
Рекомендуемые статьи для прочтения:
- WinAPI: Работа с файлами (основные функции).
- WinAPI: Определение типа носителя и его серийного номера.