Практика
Изменение номера тома логического раздела диска

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


Иногда иметь такую возможность бывает крайне полезно. Допустим, у нас есть лицензия на некую программу, защита которой привязана к номеру тома логического раздела диска. И надо же было случиться такой беде – полетел диск (как правило – системный) к которому эта программа привязана. Мы поставили новый диск, переустановили Винду, установили свою “любимую” программу и ... облом! Понятно, что о покупке еще одной лицензии речи и быть не может. Что же делать в такой ситуации? Поможет смена номера логического раздела (конечно, при условии, что мы предварительно его запомнили). Этот способ примечателен еще и тем, что он абсолютно легальный, так как не нарушает никаких условий лицензионных соглашений!

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

Поле/Файловая система FAT16 FAT32 NTFS EXFAT
Метка тома 0x2B 0x47 ? ?
Файловая система 0x36 0x52 0x3 0x3
Серийный номер 0x27 0x43 0x48 0x64

Итак, вот план наших действий. Определяем файловую систему, показываем пользователю текущий номер и даем ему возможность этот номер сменить.

Для реализации задуманного напишем класс, который будет иметь следующий вид:

TVolumeSN = class
private
  hD: THandle;
  function DiskOpen( Vol: PChar ): Boolean;
  procedure DiskClose;
  function ReadSector ( var Buffer: TVolumeSNBuf ): Boolean;
  function WriteSector( var Buffer: TVolumeSNBuf ): Boolean;
public
  function GetVolumeSN( Volume: string ): string;
  function SetVolumeSN( Volume: string; NewSN: DWORD ): Boolean; overload;
  function SetVolumeSN( Volume: string; NewSN: string ): Boolean; overload;
end;

DiskOpen – открыть диск
DiskClose – закрыть диск
ReadSector – прочитать сектор
WriteSector – записать сектор
GetVolumeSN – получить серийный номер
SetVolumeSN – изменить серийный номер


Первые 4 метода просты и не нуждаются в объяснении, остальные имеют подробные комментарии в коде.

function TVolumeSN.DiskOpen(Vol: PChar): Boolean;
begin
   hD := CreateFile( Vol, GENERIC_READ or GENERIC_WRITE,
                     FILE_SHARE_READ or FILE_SHARE_WRITE,
                     nil, OPEN_EXISTING, 0, 0 );
   Result := hD <> INVALID_HANDLE_VALUE;
end;

procedure TVolumeSN.DiskClose;
begin
   if hD <> INVALID_HANDLE_VALUE then
      CloseHandle( hD );
end;

function TVolumeSN.ReadSector(var Buffer: TVolumeSNBuf): Boolean;
var
  Read: DWORD;
begin
   Read := 0;

   // Перемещаем курсор в начало диска
   if SetFilePointer( hD, 0, nil, FILE_BEGIN ) = INVALID_SET_FILE_POINTER then
      Exit( False );

   if not ReadFile( hD, Buffer, VOLUME_SN_BUF_SIZE, Read, nil ) then
      Exit( False );

   Result := True;
end;

function TVolumeSN.WriteSector(var Buffer: TVolumeSNBuf): Boolean;
var
  Write: DWORD;
begin
   // Перемещаем курсор в начало диска
   if SetFilePointer( hD, 0, nil, FILE_BEGIN ) = INVALID_SET_FILE_POINTER then
      Exit( False );

   if not WriteFile( hD, Buffer, VOLUME_SN_BUF_SIZE, Write, nil ) then
      Exit( False );

   Result := True;
end;

function TVolumeSN.GetVolumeSN(Volume: string): string;
var
  Drive: PChar;
  Sector: TVolumeSNBuf;
  i: Integer;
  Serial: PDWORD;
  FS: string;
begin
   Drive := PChar( '\\.\' + Volume );

   // Открываем диск
   if not DiskOpen( Drive ) then
      raise Exception.Create( 'Невозможно открыть диск.' + #13 +
                               IntToStr( GetLastError ) + ': ' +
                               SysErrorMessage( GetLastError ) );

   // Читаем загрузочную область
   if not ReadSector( Sector ) then
   begin
      DiskClose;
      raise Exception.Create( 'Невозможно считать данные.' + #13 +
                               IntToStr( GetLastError ) + ': ' +
                               SysErrorMessage( GetLastError ) );
   end;

   // Поиск сигнатуры загрузочной области
   for i := 1 to MAX_PBSI do
   begin
      // Определяем нужную сигнатуру
      FS := Copy( ConvertToString( Sector ), PartialBootSectorInfo[i].FsOffs, Length( PartialBootSectorInfo[i].Fs ) );
      if PartialBootSectorInfo[i].Fs = FS then
      begin
         Serial := @Sector[PartialBootSectorInfo[i].SerialOffs];
         Result := IntToHex( DWORD( Serial^ ), 8 );
         Break;
      end;
   end;

   // Закрываем диск
   DiskClose;
end;

function TVolumeSN.SetVolumeSN(Volume: string; NewSN: DWORD): Boolean;
var
  Drive: PChar;
  Sector: TVolumeSNBuf;
  i: Integer;
  FS: string;
begin
   Result := False;
   Drive := PChar( '\\.\' + Volume );

   // Открываем диск
   if not DiskOpen( Drive ) then
      raise Exception.Create( 'Невозможно открыть диск.' + #13 +
                               IntToStr( GetLastError ) + ': ' +
                               SysErrorMessage( GetLastError ) );

   // Читаем загрузочную область
   if not ReadSector( Sector ) then
   begin
      DiskClose;
      raise Exception.Create( 'Невозможно считать данные.' + #13 +
                               IntToStr( GetLastError ) + ': ' +
                               SysErrorMessage( GetLastError ) );
   end;

   // Поиск сигнатуры загрузочной области
   i := 1;
   while i <= MAX_PBSI do
   begin
      // Определяем нужную сигнатуру
      FS := Copy( ConvertToString( Sector ), PartialBootSectorInfo[i].FsOffs, Length( PartialBootSectorInfo[i].Fs ) );
      if PartialBootSectorInfo[i].Fs = FS then
         Break;
      Inc( i );
   end;

   if i > MAX_PBSI then
   begin
      DiskClose;
      raise Exception.Create( 'Невозможно сменить серийный номер тома для этой файловой системы.' );
   end;

   // Меняем номер
   PDWORD( Sector + PartialBootSectorInfo[i].SerialOffs )^ := NewSN;

   // Записываем в загрузочную область
   if not WriteSector( Sector ) then
   begin
      DiskClose;
      raise Exception.Create( 'Невозможно записать данные.' + #13 +
                               IntToStr( GetLastError ) + ': ' +
                               SysErrorMessage( GetLastError ) );
   end;

   // Закрываем диск
   DiskClose;
   Result := True;
end;

function TVolumeSN.SetVolumeSN(Volume, NewSN: string): Boolean;
var
  i: Integer;
begin
   Result := False;

   if Length( NewSN ) = 0 then
      raise Exception.Create( 'Не введен серийный номер.' );

   if Length( NewSN ) <> 8 then
      raise Exception.Create( 'Серийный номер должен состоять из 8 символов.' );

   for i := 1 to Length( NewSN ) do
      if not CharInSet( NewSN[i], ['0'..'9', 'A'..'F', 'a'..'f'] ) then
         raise Exception.Create( 'Некорректный формат серийного номера (XXXXXXXX, X = [0..9,A..F]).' );

   SetVolumeSN( Volume, HexToInt( NewSN ) );
end;

Как видите, все просто. Полный исходный код, как всегда, находится в конце статьи.

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

На этом все, успехов в лицензировании!

.: Пример к данной статье :.


При использовании материала - ссылка на сайт обязательна