Чтобы "привязать" программу к компьютеру, необходимо уметь генерировать уникальный идентификатор для его "железа" - hardware id. По hardware id генерируется серийный номер, который будет работать только на заданному компьютере. Такой подход активно используется для защиты ПО, хотя для пользователей он порой не слишком удобен - при апгрейде компьютера серийный номер может перестать работать.
Вопрос - как сгенерировать hardware id?
Изучение интернета показало, что имеется три основных варианта.
- Использовать MAC-адрес сетевой карты.
- Использовать информацию из WMI.
- Использовать серийный номер жесткого диска, на котором установлена операционная система.
- Сетевых карт может быть несколько.
- При включении bluetooth/wi-fi появляется новая "сетевая карта".
- При отключенном сетевом проводе MAC-адрес не определяется.
- В Me/2000/XP эта служба встроена, но в более ранних версиях Windows ее нужно доставлять.
- Под Vista, с включенным UAC, без прав администратора доступ к WMI не получить.
- Cлужба WMI может быть отключена.
- Получение информации из WMI - очень медленный процесс. Несколько секунд задержки при запуске гарантированы.
Беда только в том, что у нас приложение написано на Delphi. Значит и процедура получения hardware id нужна на Delphi (вариант с DLL не подходит). А DiskID32 написан на С++...
Делать нечего. Вооружился отладчиком и перевел исходные коды DiskID32 с C++ на Delphi. Основные проблемы, с которыми столкнулся: в Delphi нет битовый полей (они использовались при описании ряда структур) и нет полноценной адресной арифметики. К счастью, битовые поля оказались не принципиальны и я их просто отбросил. Операции над указателями (инкремент, декремент) заменил на аналогичные операции с массивами. Не слишком изящно, но работает. Код старался транслировать один в один, ничего не удаляя. Оригинальные исходники DiskID содержат код для определения MAC-адресов сетевых карт. В моем случае MAC-адрес не требовался, поэтому эти функции я не транслировал.
Код был протестирован на Windows XP, Vista, Server 2003 (с рейдом и без), а так же под Vista 64-bit. Под 9x код не тестировался, поэтому гарантий, что он заработает, нет никаких. Если вы найдете ошибку в коде - пожалуйста, сообщите мне о ней на email, указанный в исходных кодах.
Остается выразить благодарность датской компании Efaktum, для которой была выполнена эта трансляция кода, и которая любезно разрешила мне выложить переведенный код в открытый доступ.
Update: В первой версии исходных кодов нашлась ошибка, см. комментарии. Исправил ее и, заодно, перевел проект на Delphi 2010 (совместимость с Delphi 7 сохранена):
Download source codes of DiskId32 for Delphi (updated 16.01.2012)
View source codes of DiskId32 for Delphi.
Short description in English.
P.s. если вы не хотите, чтобы diskID выводил информацию на консоль, закомментируйте
макрос {$DEFINE PRINTING_TO_CONSOLE_ALLOWED}. В таком виде исходники пригодны для использования в неконсольных приложениях.
Update: Под x64 нет crtdll.dll. Вместо нее следует использовать msvcrt.dll. Т.е. в crtdll_wrapper объявить crt-функции следующим образом:
function crt_isspace(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isspace'; function crt_isalpha(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isalpha'; function crt_tolower(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'tolower'; function crt_isprint(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isprint'; function crt_isalnum(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isalnum';Библиотека crtdll.dll нужна только если требуется совместимость с win95.
Update: август 2013: Прислали на email обновление: "1. Сделал рабочей ReadIdeDriveAsScsiDriveInNT - char/ansichar ошибка; 2. Избавился от msvcrt.dll (переписал функции на дельфи); 3. Немного оптимизации." Обновленную версию следует брать в svn.
Большое спасибо. А ошибка небольшая есть:
ОтветитьУдалитьВ этом месте:
if ('-' = HardDriveSerialNumber[ip]) then continue;
Если попадается тире то происходит зацикливаение, потому что переменная ip не меняется :)
Исправляется так:
ОтветитьУдалитьif ('-' = HardDriveSerialNumber[ip]) then
begin
Inc(ip);
continue;
end;
Блин, а визуальных исходников нет?
ОтветитьУдалитьНа семерке почему-то не работает, только в режиме совместимости с ХР
ОтветитьУдалитьА оригинал работает? Выше правильно указали на ошибку в коде - без этого исправления программа может зависать. Например, у меня на 64-битной семерке с исправлением работает, без него - зависает.
ОтветитьУдалитьДа сорри, тестил не я, оригинал работает. Не работала версия переведенная на 2009 делфи. Сейчас пытаюсь разобраться, перевести на 2010, при компиле ругается на
ОтветитьУдалитьStrCopy(@HardDriveSerialNumber,@serialNumber);
StrCopy(@HardDriveModelNumber,@modelNumber);
[DCC Error] hwid_impl.pas(1300): E2251 Ambiguous overloaded call to 'StrCopy';
Я если честно нуб, не понимаю что тут делают @ки, ведь они возвращают адрес вроде.
потом после компила уже ругается на
assert(SIZE_rt_DiskInfo =92);
assert(SIZE_IDSECTOR =256);
и
assert(sizeof(HardDriveSerialNumber) = 1024);
В общем не работает попытка с нулевыми правами. Насчет совместимости с вин7 пока нет возможности попробовать)
ОтветитьУдалитьСимволы @ нужны, чтобы привести массив символов к указателю на строку, оканчиваюующся нулем. Перевел проект на Delphi 2010, по идее, должен и на Delphi 2009 работать.
ОтветитьУдалитьИсходничег для 2010 не качаетсо...Спасибо
ОтветитьУдалитьПоправил ссылку, спасибо.
ОтветитьУдалитьТолько вот теперь с выводом что-то не то - выдаёт только первые символы...
ОтветитьУдалитьТоесть если серийник JDJSH2D выдаёт только J=)
Функцию ConvertToString не добил. Поправил.
ОтветитьУдалитьпопробовал определить серийник под Windows 7.
ОтветитьУдалитьПод админовскими правами все выдает правильно.
Если как обычный пользователь, то выдает какую-то ерунду.
А оригинал то не ерунду выдает?
ОтветитьУдалитьХорошая работа, спасибо. Но есть пара нюансов:
ОтветитьУдалить1. При вызове функций
ReadPhysicalDriveInNTWithAdminRights(Dest)
ReadIdeDriveAsScsiDriveInNT(Dest);
ReadPhysicalDriveInNTWithZeroRights(Dest);
ReadPhysicalDriveInNTUsingSmart(Dest);
ReadDrivePortsInWin9X (Dest);
массив Dest НЕ будет сохранять предыдущий результат; моя реализация выглядела так: вместо Dest в функциях используем
ThisDest: tresults_array_dv;
В конце каждой функции что-то типа
if Result then begin
drive := Length(Dest); //offset; using "drive" variable to memory saving
SetLength(Dest, drive + count_drives_dv);
for ijk := 0 to count_drives_dv - 1 do
Dest[drive + ijk] := ThisDest[ijk];
ThisDest := nil;
end; //if Result
2. Серийные номера жестких дисков не обязательно состоят только из цифр; на моих hdd samsung, hitachi и toshiba серийный номер содержит и латинские буквы
В связи с этим заменил проверку
if ((Chr(0) = HardDriveSerialNumber [0]) and
(isalnum (serialNumber [0]) or isalnum (serialNumber [19])))
на
if (Chr(0) = HardDriveSerialNumber [0])
3. Генерация id компьютера с привязкой только к одному параметру (hdd) не даст нормального результата - накрылся винт у клиента, он его сменил - а саппорт говорит, что программу вы не покупали... (думаю, именно для этого используют данный код) - плохо. Я делал привязку к CPU, hdd, video, MB и RAM. CPU brand string (и, возможно, S/N) узнается через ассемблер (в сети есть пример - им и воспользовался), hdd - пример выше, MB и RAM (производитель + модель, а для памяти и обьем) через WMI (при включенном UAC и отключенной службе на Windows 7 x64 определяется нормально). +параметры MB можно считать из реестра. Информация о видео также есть в сети - используя WinAPI без прав админа.
А суть защиты - определяем количество допустимых замен (у меня 2) - и тогда определяем легальность использования. Думаю, идея ясна =)
Этот комментарий был удален автором.
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьИ еще маленькое дополнение: вместо
ОтветитьУдалитьSetLength(Dest, MAX_IDE_DRIVES - 1);
нужно
SetLength(Dest, MAX_IDE_DRIVES);
думаю, ясно почему ;)
Еще мелкий баг вылез
ОтветитьУдалить"The only things i changed was a comment on (file: hwid_impl.pas, line n° 1328):
Write(Format ('Controller Buffer Size on Drive___: %s bytes'+#$0D#$0A, [bufferSize]));
because bufferSize was in comment (file: hwid_impl.pas, line n°1284):
// bufferSize: array [0..32-1] of AnsiChar;"
В SVN - поправил.
При использовании данного кода заметил, что если программа висит в памяти, то невозможно корректно извлечь флешку через безопасное отключение. Может кто подскажет как с этим можно бороться?
ОтветитьУдалитьОшибка в коде была - handle не закрывался в функции ReadPhysicalDriveInNTUsingSmart. Исправил, проблема должна уйти, проверьте. Изменения в модуле:
ОтветитьУдалитьhttps://dvsrc.googlecode.com/svn/trunk/Delphi/DiskId32Port/hwid_impl.pas
Изменения помечены тегом 20120115. Если все в порядке, обновлю архив.
Да, проблема устранена, огромное спасибо!
УдалитьЗдравствуйте! А можно получить исходник проекта с формой. Чтобы вся информация выводилась непосредственно на форму, а не в консоль.
ОтветитьУдалитьСпасибо!
У меня нет таких исходников. Сделать можно, например, так: завести функцию
ОтветитьУдалитьprocedure MyWrite(s: String);
которая пишет строки в TMemo, а не в консоль. Заменить в файле hwid_impl.pas все вызовы Write() на MyWrite()
Функция MyWrite должна быть глобальной. TMemo в нее придется передавать так же через глобальную переменную.
Здравствуйте. Заметил, что функция ReadPhysicalDriveInNTWithZeroRights не определяет vendorId для жестких дисков (по крайней мере на 4 ноутбуках). А для 1 ноута не вообще не определила серийный номер винчестера.
ОтветитьУдалитьВот результат небольшого приложения, использующего только ReadPhysicalDriveInNTWithZeroRights
HDD #0 of 0
vendorId:
modelNumber: HTS541060G9AT00
productRevision: MB3OA60A
serialNumber:
DriveType: FixedMedia
DriveSizeBytes: 60011642880
STorageBUSTYPE: BusTypeAta
К сожалению, протестировать Ваше приложения на этом ноутбуке не удалось, т.к. пользователи не очень опытные, а сам я к нему доберусь не очень скоро.
Какие могут быть причины?
Спасибо заранее.
Добрый день. А оригинальный DiskID32 на этих ноутбуках результат выдает?
ОтветитьУдалитьМне кажется, проблема может быть связана с типом HDD. DiskID32 не работает, как минимум, c RAID.
Кроме того, вот здесь пишут: "дисковая подсистема может быть представлена рейдом, а там серийный номер стандартными средствами всё равно не получишь. И экзотические SSD тоже могут быть разные, которые могут и не поддерживать SMART. Обычно поддерживают, но ведь есть же SSD на PCI экспресе."
уж не в SSD ли дело?
К сожалению, SSD здесь не причем.
ОтветитьУдалитькратко о проблемах.
Задача - получить серийник винчестера с НУЛЕВЫМИ правами
1. Проблема №1 - ни оригинальный DiskID32 , ни Ваш порт не выдает Vendor Id именно в функции ReadPhysicalDriveInNTWithZeroRights
2. Проблема №2 - на одном из проверяемых винчестеров (HTS541060G9AT00 - это HITACHI на стареньком ACER TravelMate 2490) не определился серийный номер именно в функции ReadPhysicalDriveInNTWithZeroRights.
Винчестер точно не ссд и точно не рейд.
В ближайшие несколько недель попробую компонент от артсофт (ссылка есть там же)
Если оригинальный не выдает, то порт тоже не выдаст.
ОтветитьУдалитьПосмотрел - действительно, Vendor ID на некоторых дисках пустой. Судя по всему, ошибки тут нет - просто производитель не указывает эту информацию.
Что касается серийника, может дело в версии windows?
Earlier versions of Windows did not
provide the serial number and you have to go fetch the appropriate page using scsi passthrough.
Замечу следующее. Функция ReadPhysicalDriveInNTWithZeroRights основана на использовании функции DeviceIoControl с кодом IOCTL_STORAGE_QUERY_PROPERTY. При этом используется STORAGE_PROPERTY_QUERY.PropertyId=StorageDeviceProperty. Между тем, этот параметр может принимать и другие значения.
Вот здесь приведен пример программы, которая умеет получать уникальные идентификаторы SCSI дисков, используя STORAGE_PROPERTY_QUERY.QueryType = StorageDeviceIdProperty.
А ведь есть еще STORAGE_PROPERTY_QUERY.QueryType=StorageDeviceUniqueIdProperty и другие значения...
Вряд ли XP SP3 можно считать "ранней" версией :).
УдалитьКак нибудь попробую DeviceIoControl с другими значениями. Потом отпишусь.
Здравствуйте. У меня несколько жестких дисков, и мне нужно получить серийный номер только того из них, на котором в данный момент запущена операционная система.
ОтветитьУдалитьНасколько я понимаю, сложность в том, как связать логический раздел с конкретным жестким диском? Попробуйте воспользоваться функцией QueryDosDevice. Более подробно написано здесь и здесь.
УдалитьВот здесь предлагают альтернативный способ - через анализ ключа реестра HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices, причем на delphi.
Спасибо за ответ.
Удалитьdiskid32 выводит серийный номер, порядок контроллера (primary, secondary...) и тип (master, slave) для каждого физического диска. Мне надо вывести только серийный номер для того физического диска, который содержит логический диск, на котором запущена Windows в данный момент.
Я могу вывести физический диск для того логического диска, на котором запущена Windows, например, используя WMI. Например получу такой вывод "DeviceID: \\.\PHYSICALDRIVE0".
Проблема в том, что я не могу связать это с diskid32.
Так если есть "\\.\PHYSICALDRIVE0", в чем проблема? Во всех функциях (ReadPhysicalDriveInNTWithAdminRights, ReadPhysicalDriveInNTWithZeroRights, ReadPhysicalDriveInNTUsingSmart) есть строка типа
УдалитьhPhysicalDriveIOCTL := CreateFile(PWideChar('\\.\PhysicalDrive' + IntToStr(drive)), 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
Возможно, ето не то, что в оригинале - я модули менял под себя, но в оригинале обязательно есть что-то подобное. Так вот, там такой вызов стоит в цикле, а Вам нужно будет сделать всего один вызов =)
Функция getHardDriveComputerID заполняет глобальные переменные HardDriveSerialNumber и HardDriveModelNumber информацией о первом найденном HDD. Затем эти данные используются для генерации hardware id.
УдалитьФункция getHardDriveComputerID внутри себя содержит вызовы вспомогательных функций, использующих различные методы для поиска информации о дисках:
ReadIdeDriveAsScsiDriveInNT, ReadDrivePortsInWin9X, ReadPhysicalDriveInNTWithZeroRights и т.д. Каждая из них возвращает массив типа tresults_array_dv. Массив содержит подробную информацию обо всех жестких дисках. К сожалению, в оригинале номер диска, который используется в пути "\\.\PhysicalDriveXXX" в этой структуре не сохраняется.
Это легко исправить. Достаточно добавить в tresults_dv поле DriveId. В функции PrintIdeInfo, где заполняются все записи типа tresults_dv, после строки:
Result.ControllerType := drive div 2;
добавить строку
Result.DriveId := drive;
Теперь, при вызове любой из функций ReadIdeDriveAsScsiDriveInNT, ReadDrivePortsInWin9X и т.д. вы будете получать полную информацию обо всех дисках, причем для каждого диска будут указаны DriveModelNumber, DriveSerialNumber и соответствующий им driveId, используемый в "\\.\PhysicalDriveXXX".
Остальное дело техники. Или можно, действительно, как предложили выше, перекроить код - вместо цикла делать один вызов. Тогда придется немножко изменить код во всех функциях ReadIdeDriveAsScsiDriveInNT, ReadDrivePortsInWin9X и т.д.
Этот комментарий был удален автором.
ОтветитьУдалитьЗдравствуйте. Подскажите, как можно определить кеш память диска?
ОтветитьУдалитьСпасибо автору за программу! Нашел то, что нужно)
ОтветитьУдалитьВопрос, что будет если юзер запустит прогу на ОС без прав администратора и с включенным UAC, что вернет функция getHardDriveComputerID
ОтветитьУдалитьСпасибо автору !
ОтветитьУдалитьНа Windows Server 2008 x64 не работает и diskid32 тоже, ID = 0.
Посмотрел логи, постоянно выводится INVALID_HANDLE видимо при открытии устройства.
Предлагаю написать все варианты ОС в сочетании с правами на которых программа работает/не работает.
ОтветитьУдалитьЯ проверил (SSD, HDD):
1. Windows 7 x64 Admin UAC off - ok
2. Windows 7 x64 User UAC off - ok
3. Windows 7 x64 Admin UAC on - ok
4. Windows 7 x64 User UAC on - ok
5. Windows Server 2008 R2 x64 Admin - error
Интересно узнать про Windows XP, Windows 8, Windows Server 2003.
Добрый день. Столкнулся с одной неприятной особенностью. Пи считывании серийного номера жесткого диска в DiskId32 вызывается процедура flipAndCodeBytes с параметром flip = 1 (при получении других свойств flip = 0). Так вот. Иногда получается так, что серийный номер возвращается не так, как ожидалось и для получения ожидаемого результата лучше вызывать flipAndCodeBytes с flip = 0. Такое поведение обнаружилось в Windows 8.1 x64. На берусь утверждать, но есть мысли, что на всех x64 версиях Windows. Как с этим лучше бороться???
ОтветитьУдалитьЭтот комментарий был удален автором.
УдалитьПроблема описана здесь
Удалитьhttp://stackoverflow.com/questions/14623397/hdd-serial-number-flipped-every-2-bytes-in-windows-xp-vista-and-7-but-not-in-wi
Похоже вариант только один - проверять версию windows и, если это win8 или выше, то вызывать flipAndCodeBytes с flip = 0.
сделал фикс
Удалитьhttps://www.dropbox.com/s/lrdsy5yujurawez/DiskId32Port.7z?dl=0
дойдут руки - перенесу проект на github
Спасибо, разобрался. На самом деле, надо копнуть чуть глубже. Вызывать flipAndCodeBytes с flip = 0 нужно начиная с Windows Server 2008 R2, по крайней мере на доступных мне машинах дела обстоят именно так. Это Windows 6.1, ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER]
ОтветитьУдалить