Blog. Just Blog

Работа с GUID на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Работа с GUID на Ассемблере
Работа с GUID на Ассемблере

GUID (Глобальный уникальный идентификатор) - это 128-битное значение, представленное в виде 32 шестнадцатеричных символов, разделенных дефисами на пять блоков. Формат стандартизирован для обеспечения читаемости, компактности и кроссплатформенной совместимости. Структура классического формата: 8-4-4-4-12 символов. Каждый блок содержит цифры и буквы латинского алфавита от A до F. Такая организация позволяет генерировать практически уникальные идентификаторы, что делает GUID надежным ключом для объектов и данных в различных областях программирования.

Причины выбора формата заключаются в том, что представление GUID с дефисами было принято в рамках технологий OLE/COM от Microsoft, где важна читаемость при отладке и хранении значений в текстовом виде (например, в реестре Windows). Шестнадцатеричная запись с разделителями упрощает визуальный контроль, снижает вероятность ошибок при ручном копировании и облегчает сравнение строк. Фиксированная длина (128 бит) обеспечивает теоретически огромное адресное пространство - порядка 2128 возможных значений, что делает коллизии практически невозможными при корректной генерации. Такой текстовый формат оказался удобен для логирования, конфигурационных файлов и текстовых протоколов, что способствовало его широкому распространению. Хотя изначально формат использовался в реализации Microsoft (GUID), он был стандартизирован как UUID в спецификации RFC 4122, где закреплен канонический вид записи: 8-4-4-4-12 шестнадцатеричных символов, разделенных дефисами (например, 550e8400-e29b-41d4-a716-446655440000). Сегодня этот формат стал де-факто стандартом для представления 128-битных уникальных идентификаторов.

Хотя на первый взгляд GUID может восприниматься как случайная последовательность, его структура содержит служебные поля. В каноническом представлении:

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
символ M (14-я позиция в строке, нумерация с 0) кодирует версию алгоритма генерации (4 бита), а символ N (19-я позиция) - вариант формата (старшие биты поля варианта). Например, в UUID 550e8400-e29b-41d4-a716-446655440000 значение "4" в позиции M указывает на версию 4 (генерация на основе случайных чисел), а значение "A" (бинарно 10xx) в позиции N соответствует варианту 1 по спецификации RFC 4122. Можете изучить какой-нибудь GUID или попробовать самостоятельно сделать валидный GUID. Для удобства можете использовать мою программу GUID Helper.

Теперь перейдем к программированию. Как я и обещал, мы будем использовать Ассемблер. Для удобства работы с GUID применяется следующая структура:
  1. ; Структура GUID (RFC 4122 / Windows)
  2. struct GUID
  3.     Data1 dd ?        ; 4 байта (32 бита)
  4.     Data2 dw ?        ; 2 байта (16 бит)
  5.     Data3 dw ?        ; 2 байта (16 бит)
  6.     Data4 db 8 dup(?) ; 8 байт (64 бита)
  7. ends
Начнем с проверки GUID. В структуре GUID поле версии занимает старшие 4 бита поля Data3 (смещение +6 в бинарном представлении). Это соответствует 14-й позиции в строковом формате xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx, где символ M отражает значение версии. Для извлечения версии из бинарного представления необходимо: загрузить 16-битное значение Data3, сдвинуть его вправо на 12 бит - останутся только биты версии (15-12), полученное 4-битное значение интерпретировать согласно спецификации RFC 4122:
  1.         ; my_guid - данные для работы с GUID
  2.  
  3.         ; Загружаем Data3 (смещение 6)
  4.         movzx   eax,word[my_guid.Data3]
  5.         ; Сдвигаем вправо на 12 бит, остаются биты 15-12
  6.         shr     eax,12
  7.  
  8.         ; EAX определяет версию
  9.         ; 0 -> Nil GUID (00000000-0000-0000-0000-000000000000)
  10.         ; 1 -> Time-based (на основе временной метки и MAC-адреса)
  11.         ; 2 -> DCE Security (устаревший, с UID домена)
  12.         ; 3 -> Name-based с хешем MD5
  13.         ; 4 -> Random (на основе криптографически стойких случайных чисел)
  14.         ; 5 -> Name-based с хешем SHA-1
  15.         ; 6 -> Reordered Time (альтернативный формат временной метки)
  16.         ; 7 -> Unix Time (временная метка в формате Unix)
  17.         ; 8 -> Custom (пользовательский формат)
  18.         ; 9-F -> Зарезервировано
Проверка версии позволяет определить алгоритм генерации идентификатора, что критично при валидации данных, отладке или выборе метода обработки GUID в приложении. Например, версия 4 требует криптографической случайности, а версия 1 - корректной временной метки и уникального узла.

Поле варианта (variant) определяет формат и происхождение идентификатора. Оно хранится в старших битах первого байта поля Data4 (смещение +8 в бинарном представлении), что соответствует 19-й позиции в строковом формате xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx, где символ N отражает значение варианта. Для извлечения варианта необходимо: загрузить первый байт поля Data4, сдвинуть его вправо на 6 бит - останутся старшие 2 бита (биты 7-6), интерпретировать полученное значение согласно спецификации:
  1.         ; my_guid - данные для работы с GUID
  2.  
  3.         ; Загружаем первый байт Data4
  4.         movzx   eax,byte[my_guid.Data4]
  5.         ; Оставляем только старшие 2 бита (биты 7-6)
  6.         shr     al,6                   
  7.  
  8.         ; EAX определяет вариант:
  9.         ; 0 -> NCS (устаревший формат, до стандартизации UUID)
  10.         ; 1 -> RFC 4122 (современный стандарт, наиболее распространенный)
  11.         ; 2 -> Microsoft COM/DCOM (устаревший формат Microsoft)
  12.         ; 3 -> Future (зарезервировано для будущего использования)
Проверка варианта позволяет определить, соответствует ли GUID современному стандарту RFC 4122 (вариант 1). Большинство систем, включая Windows API и современные базы данных, ожидают именно этот вариант. Идентификаторы с другими вариантами могут быть отвергнуты или обработаны некорректно. При генерации случайного GUID (версия 4) обязательно устанавливайте вариант 1, записывая в Data4[0] значение с битовой маской 10xx xxxx (диапазон 0x80-0xBF).

Теперь перейдем к генерации GUID в ручном режиме. Для этого сначала необходимо получить 16 байт (128 бит) случайных данных удобным для вас способом, например, с помощью функции BCryptGenRandom. Эта функция из API Windows Cryptography API, предоставляет криптографически стойкие случайные данные.
  1.         ; my_guid - данные для работы с GUID
  2.  
  3.         ; Делаем "чистый" случайный GUID
  4.         invoke  BCryptGenRandom,NULL,my_guid,GUID.sizeof,\
  5.                 BCRYPT_USE_SYSTEM_PREFERRED_RNG
  6.  
  7.         ; Установка версии Random (v4)
  8.         and     [my_guid.Data3],0x0FFF
  9.         or      [my_guid.Data3],0x4000
  10.         ; Установка варианта RFC 4122
  11.         and     byte [my_guid.Data4],0x3F
  12.         or      byte [my_guid.Data4],0x80
Полученные случайные данные нельзя сразу использовать как GUID - нужно правильно установить два служебных поля. Версия (поле M): старшие 4 бита байта по смещению +6 должны содержать значение 0100 (то есть цифру 4). Это указывает на версию 4, генерацию на основе случайных чисел. Вариант (поле N): старшие 2 бита байта по смещению +8 должны быть 10. Это дает диапазон значений 0x80-0xBF, который в строковом представлении проявляется как символы "8", "9", "a" или "b". Только после установки этих битов GUID будет соответствовать стандарту RFC 4122.

Для генерации GUID лучше использовать специальные системные функции: UuidCreate - основная функция из библиотеки rpcrt4.dll, генерирующая криптографически стойкий UUID (обычно версии 4 на основе случайных данных). CoCreateGuid - COM-обертка над UuidCreate из библиотеки ole32.dll. Функционально она не дает никаких преимуществ: просто вызывает UuidCreate и возвращает результат. Используется исключительно для удобства в COM-приложениях, когда ole32.dll уже загружена, а подключать дополнительно rpcrt4.dll не требуется. Обе функции возвращают валидный GUID, соответствующий стандарту RFC 4122. При ручной генерации через случайные числа (например, BCryptGenRandom) необходимо самостоятельно устанавливать биты версии и варианта, системные функции делают это автоматически.
  1.         ; my_guid - данные для работы с GUID
  2.  
  3.         ; Генерирует новый случайный UUID
  4.         invoke  UuidCreate,my_guid
  5.         ; Он же, COM-обертка над UuidCreate
  6.         invoke  CoCreateGuid,my_guid
Также в WinAPI есть особая функция UuidCreateNil, которая возвращает нулевой (пустой) GUID: {00000000-0000-0000-0000-000000000000}. Это "синтаксический сахар" - тривиальная функция, которая просто заполняет 16 байт по указанному адресу нулями. В производственном коде ее вызов избыточен.
  1.         ; my_guid - данные для работы с GUID
  2.  
  3.         ; GUID заполнен нулями {00000000-0000-0000-0000-000000000000}
  4.         invoke  UuidCreateNil,my_guid
Нулевой GUID, также называемый Nil UUID, используется как специальное значение-маркер: проверка инициализации - признак того, что переменная типа GUID еще не заполнена корректным значением; значение по умолчанию - инициализация полей структур или членов классов до получения реального идентификатора; а также безопасное и быстрое значение, не требующее генерации - достаточно обнулить 16 байт структуры или использовать предопределенную константу.

Для сравнения GUID используется функция IsEqualGUID, которая выполняет побайтовую проверку двух 128-битных идентификаторов. Функция игнорирует версию и вариант, сравнивает только "сырые" байты, поэтому два валидных GUID с разными версиями будут признаны разными. Такой подход широко применяется при работе с COM-интерфейсами, базами данных и любыми системами, где требуется явная проверка наличия уникального ключа перед его использованием.
  1.     ; Сравнить my_guid1 и my_guid2
  2.     invoke  IsEqualGUID,my_guid1,my_guid2
  3.     add     esp,8
  4.     ; Возвращает результат в AL (не BOOL!)
  5.     test    al,al
  6.     ; AL = 0, не равны
  7.     jz      .not_equal
  8.     ; AL != 0, равны
  9.     jnz     .equal
UuidCompare - функция для сравнения двух 128-битных идентификаторов с учетом их внутреннего представления. В отличие от простого побайтового сравнения IsEqualGUID, UuidCompare учитывает структуру поля UUID: порядок байт в Data1 (DWORD), Data2 (WORD) и Data3 (WORD) интерпретируется как единое знаковое целое. Функция возвращает: отрицательное значение, если первый UUID меньше второго; ноль, если равны; положительное значение, если первый больше. В Windows поля Data1, Data2, Data3 хранятся в формате little-endian, что важно при кросс-платформенной работе (например, сетевые UUID часто используют big-endian). Поэтому UuidCompare корректно сравнивает идентификаторы именно в контексте Windows-представления. Функция полезна для сортировки списков GUID или построения индексов, где требуется устойчивый порядок элементов. Для простой проверки равенства предпочтительнее использовать IsEqualGUID, она быстрее и не зависит от порядка байт.
  1.     ; Сравнение с учетом порядка байт
  2.     invoke  UuidCompare,my_guid,my_uuid2
  3.     add     esp,12
  4.     ; Возвращает результат в EAX
  5.     ; < 0 - my_guid1 < my_guid2
  6.     ; = 0 - my_guid1 == my_guid2
  7.     ; > 0 - my_guid1 > my_guid2
  8.     test    eax,eax
  9.     ; Равно
  10.     jz      .equal
  11.     ; Меньше
  12.     js      .less           
  13.     ; Больше
  14.     jg      .more
Для быстрого расчета хеша от GUID существует функция UuidHash, она возвращает 32-битное хеш-значение. Это позволяет ускорить сравнение и индексацию идентификаторов без хранения дополнительных метаданных. Однако для окончательной проверки равенства все равно требуется побайтовое сравнение через IsEqualGUID, так как 32-битный хеш может давать коллизии.
  1.     ; Указатель на UUID
  2.     invoke  UuidHash,my_guid,status
  3.     add     esp,8
  4.     ; EAX содержит 32-битный хеш
Для преобразования бинарного представления GUID в человекочитаемый формат в Windows существует несколько функций, каждая решает свою задачу и имеет свои особенности. Чтобы превратить "непонятный" набор байтов в привычную строку вида {550e8400-e29b-41d4-a716-446655440000}, удобнее всего использовать функцию StringFromGUID2. Она берет ваш идентификатор и аккуратно записывает его в заранее подготовленный буфер, расставляя фигурные скобки и дефисы в нужных местах. Буфер должен вмещать всего 39 символов (38 под текст и один завершающий ноль), а освобождать память не требуется, функция работает напрямую с вашим буфером и не выделяет память самостоятельно. Благодаря этому StringFromGUID2 идеально подходит для повседневных задач: логирования, отладки или отображения идентификатора пользователю.
  1.     ; Размер буфера (38 символов + нуль-терминатор)
  2.     invoke  StringFromGUID2,my_guid,szBuffer,39
  3.     add     esp,12
  4.     ; Возвращает количество записанных символов, включая 0
  5.     ; или 0 при ошибке
Есть и другие варианты. Функции StringFromCLSID и StringFromIID, первая предназначена для идентификаторов классов, вторая для интерфейсов, но по сути обе являются обертками над одной и той же реализацией. Они выполняют похожее преобразование, однако сами выделяют память под результирующую строку. Это удобно, когда не хочется заботиться о размере буфера, но требует обязательного освобождения памяти через CoTaskMemFree, иначе со временем программа начнет "утекать".
  1.     ; Указатель на UUID
  2.     invoke  StringFromCLSID,my_guid,pszResult
  3.     add     esp,8
  4.     test    eax,eax
  5.     jnz     .loc_error
  6.  
  7.     ; Использование результата, например, ESI - указатель на строку
  8.     mov     esi,[pszResult]
  9.  
  10.     ; Обязательно освобождение памяти
  11.     call    CoTaskMemFree,[pszResult]
Функция UuidToString представляет собой низкоуровневый вариант преобразования GUID, унаследованный из RPC/DCE. В отличие от StringFromGUID2, она возвращают строку без фигурных скобок, просто "голой" последовательностью символов вида 550e8400-e29b-41d4-a716-446655440000. Такой формат часто встречается в сетевых протоколах и кроссплатформенных системах. Эта функция сама выделяет память под результирующую строку средствами RPC. Это избавляет от заботы о размере буфера, но обязывает программиста освободить память после использования и только с помощью специальной функции RpcStringFree. Использование других методов освобождения (например, CoTaskMemFree) приведет к неопределенному поведению.
  1.     ; Указатель на UUID
  2.     invoke  UuidToString,my_guid,pwszResult
  3.     add     esp,8
  4.     test    eax,eax
  5.     jnz     .loc_error
  6.  
  7.     ; Использование
  8.     mov     esi,[pwszResult]
  9.  
  10.     ; Освобождение памяти через RpcStringFree
  11.     invoke  RpcStringFree,pwszResult
В программе часто возникает обратная задача: есть строка вида {550e8400-e29b-41d4-a716-446655440000}, прочитанная из файла, реестра или введенная пользователем, а нужен "настоящий" 128-битный идентификатор, 16 байт для передачи в API или сравнения. Для этого Windows предоставляет несколько функций, каждая со своими особенностями и областью применения.

Самый распространенный и надежный выбор - функция CLSIDFromString. Она принимает строку в формате Unicode (UTF-16) и заполняет 16-байтную структуру GUID, переданную по указателю. Главное преимущество - гибкость: функция одинаково хорошо понимает строки как с фигурными скобками "{...}", так и без них. Это особенно удобно, когда источник данных непредсказуем, например, один компонент сохраняет с скобками, другой без. Функция сама разбирает формат, проверяет корректность шестнадцатеричных цифр и расставляет байты в правильном порядке. Если используется обычная ANSI-строка из файла в кодировке Windows-1251 или UTF-8, ее сначала нужно преобразовать через MultiByteToWideChar, иначе результат будет неверным или функция вернет ошибку.
  1.     ; Строка в UTF-16
  2.     invoke  CLSIDFromString,szGuid,my_guid
  3.     ; Указатель на структуру GUID (16 байт)
  4.     add     esp,8
  5.     test    eax,eax
  6.     ; EAX = 0 - my_guid заполнен корректными данными
  7.     jz      .loc_ok
  8.     ; EAX != 0 - ошибка преобразования
Для работы с интерфейсами существует аналогичная функция IIDFromString. По сути, это та же реализация, что и CLSIDFromString, внутри вызывается один и тот же код. Различие чисто семантическое: в COM-коде принято использовать IIDFromString при парсинге идентификаторов интерфейсов, а CLSIDFromString - для классов. По работе отличий нет: обе функции принимают строки как с фигурными скобками "{...}", так и без них, работают с широкими строками в формате UTF-16 и заполняют 16-байтную структуру GUID без выделения памяти.
  1.     ; Строка в UTF-16
  2.     invoke  IIDFromString,szGuid,my_guid
  3.     ; Указатель на структуру GUID (16 байт)
  4.     add     esp,8
  5.     test    eax,eax
  6.     ; EAX = 0 - my_guid заполнен корректными данными
  7.     jz      .loc_ok
  8.     ; EAX != 0 - ошибка преобразования
UuidFromString - низкоуровневый RPC-вариант для низкоуровневой работы с UUID. Требования здесь строже: строка обязательно должна быть без фигурных скобок. Эти функции заполняют переданный буфер напрямую и не требуют освобождения памяти.
  1.     ; 550e8400-e29b-41d4-a716-446655440000
  2.     invoke  UuidFromString,szUuid,my_guid
  3.     add     esp,8
  4.     test    eax,eax
  5.     ; EAX = 0 - my_guid заполнен корректными данными
  6.     jz      .loc_ok
  7.     ; EAX != 0 - ошибка преобразования
GUIDFromString - устаревшая функция, оставшаяся от ранних версий Windows Shell. В отличие от современных аналогов, она ведет себя неочевидно: вместо заполнения 16-байтной структуры GUID она возвращает хэндл, который указывает на внутренний объект в памяти. Чтобы получить сами байты идентификатора, требуется дополнительное преобразование, а после использования необходимо освободить хэндл через GlobalFree. Из-за этой сложности, нестандартного поведения и наличия более простых и безопасных альтернатив, GUIDFromString рекомендуется избегать.
  1.     ; {550e8400-e29b-41d4-a716-446655440000}
  2.     invoke  GUIDFromString,szGuid
  3.     add     esp,4
  4.     test    eax,eax
  5.     ; EAX = 0 - ошибка
  6.     jz      .loc_error
  7.     ; EAX = HANDLE на внутренний объект
  8.     mov     [hResult],eax      
  9.     ; Требует освобождения памяти
  10.     invoke  GlobalFree,eax
Резюмируя сказанное: для большинства задач с GUID достаточно трех функций - UuidCreate для генерации, IsEqualGUID для сравнения и StringFromGUID2 / CLSIDFromString для преобразования в обе стороны. Они безопасны, не требуют ручного управления памятью и работают во всех версиях Windows без сюрпризов. Остальные функции нужны только в специфических сценариях: работе с COM, сетевыми протоколами или унаследованным кодом.

Поделиться ссылкой ВКонтакте
Просмотров: 174 | Комментариев: 1

Метки: Assembler

Комментарии

Отзывы посетителей сайта о статье
Лестер Глючный (10.08.2026 в 22:57):
Ещё GUID`ы представляются тупо "прямым" набором ASCII-HEX-цифр БЕЗ hyphen-minus`ов и фигурных скобок, только букво-цифры перетасованы точно такими же 4-2-2-2-6, тьфу, 8-4-4-4-12 группами, но записанными в обратном порядке — типично для MSInstaller`ов, мусорящие в Software\Microsoft\Installer\Features | Products | UpgradeCodes, а Гуглы (хотя скорее всего и не только они) умудрялись наследить и в других разделах реестра такими же перевёрнутасованном буквоцифрами (причём "вернув" их обратно в "фигурные скобки", находилось точно совпадение в каком-нибудь другом разделе реестра, напр. Browser Helper Object`ами ишака).
С тех пор захотелось заиметь такой "поисковик GUID`ов" (во всех подобных представлениях), чтоб искать их как в файловой системе, так и в реестре (+ в "динамически-подгружаемом" кусте COMPONENTS), причём и в "нестроковых" типах значений тоже, т.е. помимо REG_SZ | REG_MULTI_SZ | REG_EXPAND_SZ, искать в других типах как ASCII-текст введённого GUID`а, так и те самые 16 байт… Ну и банально для поиска того номера в Shell\Bags, в котором ColInfo или Sort содержит определённый PropID (без предварительного поиска имени папки в BagsMRU (тоже записанной в двоичном типе значения), и, соответственно, определения его номера)…

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2026
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0036 сек.) / Память: 4.5 Mb
Наверх