Direct I/O — Архив WASM.RU

Все статьи

Direct I/O — Архив WASM.RU

«Direct I/O is the world's first device driver for Microsoft and which enables the direct hardware access for your existing software without any programming efforts on your side».

Введение

Из вышеприведенной цитаты, думаю понятно, что объектом исследования является приложение, позволяющее другим программам обращаться напрямую к оборудованию. С этим не возникало проблем, когда программы работали под управлением операционных систем Windows, предшественников Windows NT, но времена идут, архитектура меняется, а переделывать старые программы, зачастую просто нет возможности. Принцип, по которому работает драйвер Direct I/O (и аналогичные программы) не нов, и был опубликован в журнале Dr.Dobb's за май 1996 года, автором статьи (русский перевод которой можно найти на void.ru) является Дейл Робертс.

Видимо многим low-level программистам не составит особого труда реализовать аналогичную программу, времени на это может уйти меньше, чем на написание этой статьи и окажется это более достойным занятием, чем- то, которым будем заниматься мы, а именно - исследованием той части программы, которая касается её защиты от нелицензионного использования. Используемая при этом технология довольно простая, но может кому-то показаться интересной, т.к. статья имеет исключительно, и по возможности максимально, познавательный характер. Это путь авторам, который покажет слабые места их программ, покажет, чем пользуются, и что руководит людьми, когда нужная им программа отказывается работать больше месяца, когда нет денег, кредитной карты или просто рядом нет банка.

Знакомство

Итак, что происходит, когда нет вышеперечисленных проблем:

  • Вы покупаете программу и сообщаете автору Name или Company, на которые желаете зарегистрировать программу, после чего получаете Key

«The license information consists of three entries: Name, Company and Key. Name or Company may be empty, but Key always is a 128 character long string formatted into two lines as shown in the example below».
  • Пример регистрационных данных, мы попробуем их ввести в программу:
Name: John Doe
Company: ACME Corp.
Key: 143E6B6BA98DCE3D8833DC4FC67301D1173CEB17075906AE38B20EC289085BDE F65744EB4561936040A6B352ADC6ED4E585FC8075FA55927CAE6414A7215A9E7
  • После того, как в окне «License» (Рис.1) будут введены правильные данные (которых у нас нет), программа начинает работать в полнофункциональном режиме. Иначе она работает в 30-ти дневном trial-режиме и при каждой её инициализации выводит наг-скрин (Рис.2)
Рис.1 Рис.2

Это не трехзначный пароль, который можно подобрать даже руками, столько букв, цифр ... Давайте для начала соберем максимально возможную информацию о программе, о её обращениях к реестру, к файлам, о взаимодействие с операционной системой. Лучше всего это начинать ещё во время установки программы, но мы сделаем только в тот момент, когда будем нажимать кнопку «OK» (Рис.1)

Сокрытые ключи реестра

Начнём с реестра, обычно там хранятся настройки программ и регистрационные данные, для этого воспользуемся утилитой Regmon, и вот что интересного мы сразу замечаем:

Process Path Other
rundll32.exe HKLM\SOFTWARE\Paule\Direct I/O\CurrentVersion\Name John Doe
rundll32.exe HKLM\SOFTWARE\Paule\Direct I/O\CurrentVersion\Company ACME Corp.
rundll32.exe HKLM\SOFTWARE\Paule\Direct I/O\CurrentVersion\Key 14 3E 6B ...
rundll32.exe HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider \Microsoft Base Cryptographic Provider v1.0\Image Path rsabase.dll
rundll32.exe HKLM\SOFTWARE\Paule\Direct I/O\CurrentVersion\TimeLock\Licensed 0x0
rundll32.exe HKLM\SOFTWARE\Paule\Direct I/O\CurrentVersion\TimeLock\InstallTime 00 E9 6B ...

Обратите внимание, что процессом является rundll32.exe, а почему, ответ найдем в свойствах ярлыка программы, запускается она такой строкой: «C:\WINNT\system32\rundll32.exe shell32.dll,Control_RunDLL DirectIO.CPL». Расширение .CPL означает, что это аплет панели управления.

Из таблицы теперь понятно, в какой ветке реестра программа хранит регистрационные данные, также понятно, что задействована криптография и ... сначала не верим своим глазам, но «Licensed» - ведь это слово обозначает, зарегистрирована ли программа, предполагает два ответа: да или нет, true или false, 1 или 0. Наивно, но почему бы не открыть regedit и проставить 0x1? Конечно, пробуем и вот здесь мы сразу спотыкаемся на «первом уровне» защиты.

Regedit нам сообщает, что не удается открыть «TimeLock». Ошибка при открытии раздела. Пробуем другие программы для работы с реестром и снова неудача, ни одна не может открыть эту ветку реестра, хотя Regmon то, ведь все отлично показывает, все параметры мы в нем видим. Неужели программа контролирует доступ к своей ветке, маловероятно, но проводим эксперименты вплоть до ручного удаления всех файлов программы: directio.cpl, directio.drv, directio.vdd, directiocoinst.dll, directio.sys, directiofilter.sys, и это все равно ничего не дает. Переходим к поиску в сети любой информации, которая может касаться этой ошибки работы с реестром, попутно задаем вопрос на форуме wasm.ru, где и получаем точный ответ от Four-F: «На sysinternals.com в разделе "Systems internals tips and trivia" есть маленький параграф "Hidden Registry Keys?" и исходник reghide.zip.»

Так и есть, для защиты используется описанное Марком Руссиновичем (автор Regmon-а) различие работы со строками между Win32 API и Native API:

«How is this possible? The answer is that a name which is a counted Unicode string can explicitly include NULL characters (0) as part of the name. For example, "Key\0". To include the NULL at the end the length of the Unicode string is specified as 4. There is absolutely no way to specify this name using the Win32 API since if "Key\0" is passed as a name, the API will determine that the name is "Key" (3 characters in length) because the "\0" indicates the end of the name».

Коротко говоря, когда вы создаете ключ реестра с помощью RegCreateKey (Win32 API), то передаете строку с именем ключа (например «Key»), но длину строки вы не указываете, функция высчитывает ее сама (как только встретит 0x0). А при использовании функции NtCreateKey (Native API), мы обязаны явно указать какой длины хотим создать ключ, по этому для строки «Key» можно определить длину как 4 и потом эту ветку, из-за несовпадения длин, будет невозможно открыть функциями Win32 API.

Значит для того, чтобы изменить параметр «Licensed» нам нужно использовать Native API, в исходнике reghide.zip можно (достаточно для начала) почерпнуть информации, как это сделать.

К сожалению, я (возможно и вы) ещё не знакомы с языком C, но уже знаете или Вас больше привлекает ассемблер, по этому именно на нём сделаем программу, главной целью которой будет изменить параметр «Licensed» с 0x0 на 0x1 . Будем использовать при этом FASM. Ниже я сразу приведу готовый код, скажу только, что все необходимые структуры, константы и некоторые примеры для работы с Native API, можно найти в Kernel Mode Driver Development Kit by Four-F.

;================================================================
format      PE GUI 4.0
entry       start
;================================================================
include     '%fasminc%\win32a.inc'
;================================================================
KEY_SET_VALUE       =  2h
OBJ_CASE_INSENSITIVE=  40h
;================================================================
struct              OBJECT_ATTRIBUTES
      .length       dd sizeof.OBJECT_ATTRIBUTES
      .directory    dd 0
      .object       dd sregpath
      .attributes   dd OBJ_CASE_INSENSITIVE
      .descriptor   dd 0
      .service      dd 0
ends
oa                  OBJECT_ATTRIBUTES
;================================================================
sregpath    dd      sregparam-regpath            ; sizeof.regpath
oregpath    dd      regpath                      ; offset regpath
regpath     du      '\Registry\Machine\SOFTWARE\Paule\Direct I/O'
            du      '\CurrentVersion\TimeLock',0
;================================================================
sregparam   dd      ntdll-regparam              ; sizeof.regparam
oregparam   dd      regparam                    ; offset regparam
regparam    du      'Licensed'
;================================================================
ntdll       db      'ntdll.dll',0
ntok        db      'NtOpenKey',0
ntsk        db      'NtSetValueKey',0
ntc         db      'NtClose',0
;================================================================
hkey        dd      0
param       dd      1                                       ; 0x1
;================================================================
start:      invoke  GetModuleHandle,ntdll
            mov     esi,eax
            invoke  GetProcAddress,esi,ntok
            push    oa KEY_SET_VALUE hkey
            call    eax                               ; NtOpenKey
            invoke  GetProcAddress,esi,ntsk
            push    4 param REG_DWORD 0 sregparam [hkey]
            call    eax                           ; NtSetValueKey
            invoke  GetProcAddress,esi,ntc
            push    [hkey]
            call    eax                                 ; NtClose
            invoke  ExitProcess,0
;================================================================
data        import
library     kernel32,'kernel32.dll'
include     '%fasminc%\apia\kernel32.inc'
end         data
;================================================================

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

Ну а теперь, стоит посмотреть на результат работы этой программы :)

Рис.3 Рис.4

Сначала мы имеем лицензионную версию (Рис.3), но как только воспользуемся услугами драйвера или просто нажмем кнопку «License», то «лицензионность» сразу улетучивается (Рис.4), понятно, что программа, независимо от параметра «Licensed», все равно проверяет корректность регистрационных данных. Назовем эту проверку «вторым уровнем» защиты (криптографическим).

Перед его изучением на минуту вспомним о ещё одном параметре - «InstallTime». Назначение его понятно, от него отсчитывается 30 дней, теперь мы знаем, как его можно изменить, тем самым, продлив trial-режим. Но это ещё не тот выход, который спасет от наг-скрина (Рис.2)

Цифровая подпись RSA

Итак, на «второй уровень» мы имеем такие данные:

  • Key (128 hex-символов)
  • Microsoft Base Cryptographic Provider v1.0
  • rsabase.dll (Filemom подтверждает обращение к этому файлу)

Соберем ещё немного информации, а точнее посмотрим в секцию импорта directio.cpl, на предмет всяческих Crypto API функций. Инструментов для этого множество, я использовал PE Explorer (Рис.5), хотя можно обойтись простым поиском слова «Crypto» в файле directio.cpl .

Рис.5

Здесь, тем, кто не знает, что делают эти функции, предлагаю воспользоваться поиском в сети, чтобы пройти первоначальный ликбез, который, в свое время прошел я, но, вспоминая, что лень мы обычно перебарываем, только когда прижмет, коротко приведу мысли, которые могут крутиться в голове у ленивого человека:

  • CryptImportKey (куда-то импортирует какой-то ключ, какой?)
  • CryptVerifySignatureW (сверяет сигнатуру, подпись с чем-то, c чем?)

Не сладко ... Инструментов, призванных нам помочь очень много, но мы добавим в наш арсенал ещё несколько:

Сразу замечу, что человек, имеющий некоторый опыт, знание ассемблера, который знаком с функцией CryptVerifySignature, уже давно понял, где и что нужно поправить, затратив на всё не более получаса, а мы ленивые будем изучать то, без чего могли бы в данном случае обойтись.

Экскурсия к прову

Regmon нам показывал обращение к какому то провайдеру ... Естественно всемирная паутина здесь не при чем, и об этом коротко.

Криптопровайдер (CSP) это динамическая библиотека (.dll), содержащая различные функции (процедуры) соответственной тематики. Именно они выполняют шифрование, хеширование, проверку цифровой подписи и множество других вычислительных действий. С операционной системой Windows поставляется несколько провайдеров, в том числе и Microsoft Base Cryptographic Provider (rsabase.dll).

При создании своих собственных CSP можно воспользоваться «инструментом» разработчика - Microsoft Cryptographic Service Provider Development Kit, есть маленький, но существенный нюанс. Когда ваша dll будет готова, нужно ее «подписать» у Microsoft, и тогда любые приложения смогут обращаться к ее функциям, кроме того, если я не ошибаюсь, то она будет защищена операционной системой от изменений. А для ее инсталляции достаточно создать необходимые ключи и параметры, посмотрите у себя ветку HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider

Проследив в отладчике, куда ведет функция CryptVerifySignature, может возникнуть вопрос, почему advapi32.dll, а не rsabase.dll? Таким образом Microsoft избегает разнообразия, возможной запутанности при работе с разными CSP, а также появляется некая универсальность для приложений, они могут работать с разными криптопровайдерами не изменяя свой код. Приложению даже не обязательно знать, с каким CSP оно имеет дело, посредником между ними выступает advapi32.dll, она «преобразовывает» вызов, например CryptVerifySignature, в вызов CPVerifySignature, а последняя является, реально проверяющей подпись функцией (в нашем случае она из rsabase.dll). Такое же преобразование выполняется и для остальных.

Вызывая CryptAcquireContext, мы можем не указывать CSP, она сама выберет используемого в вашей ОС по-умолчанию криптопровайдера. Эта функция перебирает, упомянутую выше, ветку реестра (это обращение мы и поймали Regmon-ом) и в зависимости от переданных ей параметров, определяется с CSP и загружает его dll в адресное пространство нашего процесса. После чего вызывает CPAcquireContext и проверяет целостность криптопровайдера. Много действий выполняет CryptAcquireContext, по этому она довольно медленная.

Предварительный анализ

Давайте ознакомимся, как в программе вызываются эти функции, и с какими параметрами.

Открываем IDA, загружаем туда directio.cpl, после предварительного анализа переходим к вкладке Names и ищем функцию CryptVerifySignatureW, кликнув на ней, мы попадаем в секцию импорта. Перед именем функции будет слово BOOL, что означает «Boolean» и предполагает два варианта, возвращаемых функцией, о них мы уже говорили, помните true или false, 1 или 0. Именно так, если наш Key правильный, то она вернет истину, иначе ложь. Кто не верит, может посмотреть в MSDN :) Ниже имени функции будут такие строчки:

.idata:1000A060; DATAXREF: sub_10006240+275r
.idata:1000A060; DATAXREF: sub_10006940+C0r

sub_10006240 и sub_10006940 показывает, в каком месте программы вызывается эта Crypto API функция, и как видим, это происходит в двух разных местах, переходим к ним (с мыслью, почему их два). При долгом всматривании (найди различия), ответ будет очевиден, в одном месте используются параметры (данные), зашитые в программу, а в другом, возможно, наши данные из реестра.

Первый алгоритм проверки

Начнем с простого и интересного, какие данные зашиты в программу и зачем?

Данными здесь являются параметры, передаваемые в вышеупомянутые функции Crypto API, IDA нам их покажет, логика этой проверки такая:

;==========================================================================
cspname     db      'Microsoft Base Cryptographic Provider v1.0',0
csp         dd      0
hash        dd      0
hkey        dd      0
pubkey      db      (Рис.6)
Name        db      (Рис.6)
Company     db      (Рис.6)
Key         db      (Рис.6)
;==========================================================================
invoke  CryptAcquireContext,csp,0,cspname,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT
invoke  CryptImportKey,[csp],pubkey,54h,0,0,hkey
invoke  CryptCreateHash,[csp],CALG_MD5,0,0,hash
invoke  CryptHashData,[hash],Name,1Ch,0
invoke  CryptHashData,[hash],Company,0Ah,0
invoke  CryptVerifySignature,[hash],Key,40h,[hkey],0,0
        test eax,eax
        jz   false
;==========================================================================

Сейчас мы можем наглядно посмотреть на зашитые в directio.cpl данные (Рис.6) и сопоставить их с логикой проверки.

Рис.6

Более того, в Hex Workshop, вы можете их «вырезать», а потом, используя (входящую в пакет MASM32) утилиту Bintodb, конвертировать эти данные в массив байт, пригодный для компиляции с вышеприведенным исходным текстом. Сделав это, мы убеждаемся, что восстановили алгоритм проверки верно, вшитые данные полностью корректны, т.к. функция CryptVerifySignature возвращает 1 (true), и нам это здорово помогло осмыслить алгоритм.

Теперь мы понимаем, что CryptImportKey импортирует RSA public.key 2, а наш 128-ми символьный ключ это сигнатура, которую проверяет CryptVerifySignature, по два hex-символа на байт (максимальное его значение FFh), получится 128/2=64 байт, т.е. параметр «40h», это длина подписи.

Наивно полагая, а вдруг поможет, почему бы ни попробовать ввести эти «выдранные» и корректные данные в окне «License» (Рис.1) Пробуем, и улыбаемся до самых ушей :), какие же мы все-таки бываем наивными! Раз не проходит, значит, данные не верны, значит, не этим алгоритмом проверяются регистрационные данные. А этот алгоритм, получается, служит просто для проверки корректности работы Crypto API, ним проверяются только зашитые данные.

Вторая проверка, выводы и результаты

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

Что если попробовать переписать RSA public.key 2 на место RSA public.key 1 (Рис.6), а потом ввести зашитые в программе регистрационные данные? С этим нет проблем, два клика в Hex Workshop, сохраняем файл. Теперь вводим в качестве Name строку «Direct I/O API», в качестве Company «Check», а в качестве ключа 128 символов, помеченных красным. Если сделали всё правильно, можем забыть о наивности, программа полноценно работает!

А если «подправить» код так, чтобы результат, возвращаемый функцией CryptVerifySignature, для программы всегда был true? Тогда никаких регистрационных данных, не нужно будет вводить вообще.

Без проблем, для наглядности сделаем это в отладчике OllyDbg. Запустим Direct I/O, откроем окно «License» (Рис.1), чтобы нам осталось только нажать кнопку «OK», в OllyDbg сделаем «Attach» к процессу rundll32 (с окном «License»). Потоки процесса теперь будут временно приостановлены, а мы в отладчике поставим breakpoint на вызов CryptVerifySignature, это можно сделать разными способами, если установлен plug-in CommandBar, то в нем достаточно написать «bp ADVAPI32.CryptVerifySignatureW», после этого можно продолжить выполнение процесса. В Direct I/O нажимаем кнопку «OK», срабатывает брэкпоинт и вы должны наблюдать такую картину:

Рис.7

Присмотревшись, на Рис.7 можно увидеть все наши регистрационные данные, но давайте взглянем на участок кода, следующий сразу за вызовом CryptVerifySignature:

Смещение Опкод Мнемоника
100064B5 FF15 60A00010 Call CryptVerifySignatureW
100064BB 894424 38 mov [esp+38],eax

Думаю не секрет, что после вызова функции, её результат будет в регистре eax, т.е. именно там будет 0x1 в случае удачи, иначе 0x0, по этому коду видно, что потом результат заноситься в стековую переменную. После вызова функции мы можем, прямо в отладчике записать в регистр eax единицу, продолжить выполнение программы и снова убедиться, что она считает себя «зарегистрированной».

Чтобы сделать этот эффект постоянным, нам нужно изменить код, на что-то вроде «mov [esp+38],1», но мы должны следить за размерами опкодов, одним байтом в сторону и «Стой, стреляю!». Здесь нужно четко знать, где и на что можно править, для этого пригодятся мануалы Intel и Книга Опкодов от The Svin. Вскоре мы понимаем, такая запись единицы в переменную будет стоить 8 байт, что равносильно расстрелу. Не долго думая, мы приходим к другому варианту, что проще и безболезненно для программы, сделать инкремент (увеличение на единицу) стековой переменной, к тому же интуитивно уверены, что до этого там всегда должен быть ноль, правим один байт (89- >FF), получается такой код:

Смещение Опкод Мнемоника
100064B5 FF15 60A00010 Call CryptVerifySignatureW
100064BB FF4424 38 inc [esp+38]

Сохраняем файл, и снова убеждаемся в полноценной работе программы уже без отладчика, за исключением маленького нюанса. Это произойдет не автоматически, т.к. никаких данных в окне «License» (Рис.1) мы не вводили, то проверка не запускалась и в реестре не записано 0x1 для параметра «Licensed». Это не беда, у нас уже давно готов исходный код для этого, осталось только добавить его кодом патчера и процесс можно считать полностью автоматизированным.

Есть ли у вас план, мистер Фикс?

Раскуривая очередную сигарету, думаем ... Можно ли считать дело сделанным? Нет. Мы так и не выяснили, по какому принципу автор формирует ключ, можно ли реализовать программу генерации правильного ключа, и вообще обойтись без вмешательства в код программы?

О-о ... У меня целый мешок плана, Фокс. Можно с уверенностью сказать: «Не изменяя файлы программы, повлиять на ход ее выполнения не просто возможно, а для этого есть уйма средств, в руках программиста!». Чего нам стоит сделать лоадер, который запустит программу и уже в её памяти поправит этот байт, можно даже поправить не в directio.cpl, а в advapi32.dll . И вообще, если его правильно поправить на диске, то абсолютно все приложения, которые надеяться на достоверность результата CryptVerifySignature, будут нагло обмануты.

Возможно, теперь вы не захотите ничего слышать об этой функции и никогда не будете ее использовать в своих программах. Может, и будете правы, но если копнете ее глубже, и не только ее, то узнаете, как использовать и проверять цифровую подпись средствами Crypto API, причем без advapi32.dll, так что лопату вам в руки и семь футов под землей.

Алгоритм создания подписи

Ниже я приведу код, примерно таким он должен быть у автора (только с обработкой ошибок естественно):

;=============================================================================
cspname     db      'Microsoft Base Cryptographic Provider v1.0',0
private     file    'private.key'
sprivate    =       $-private
csp         dd      0
hash        dd      0
hkey        dd      0
Name        db      'John Doe'
sName       =       $-Name
Company     db      'ACME Corp.'
sCompany    =       $-Company
signature   rb      40h
ssignature  dd      $-signature
;=============================================================================
sigfile     db      'rsa.sig',0
hfile       dd      0
count       dd      0
;=============================================================================
start:      invoke  CryptAcquireContext,csp,0,cspname,\
                    PROV_RSA_FULL,CRYPT_VERIFYCONTEXT
            invoke  CryptImportKey,[csp],private,sprivate,0,0,hkey
            invoke  CryptCreateHash,[csp],CALG_MD5,0,0,hash
            invoke  CryptHashData,[hash],Name,sName,0
            invoke  CryptHashData,[hash],Company,sCompany,0
            invoke  CryptSignHash,[hash],AT_SIGNATURE,0,0,signature,ssignature
;=============================================================================
            invoke  CreateFile,sigfile,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0
            push    eax
            invoke  WriteFile,eax,signature,[ssignature],count,0
            invoke  CloseHandle
;=============================================================================

Хм... Код есть, а почему мы не можем создать правильный ключ для программы? А потому, что не хватает одного параметра, у нас нет файла private.key, у нас нет секретного ключа! Хм... А как он генерируется? Очень просто, вот код (думаю, вы сами догадаетесь обработать ошибки и подключить wincrypt.inc, я уже не говорю об его оптимизации)

;=============================================================================
RSA512      =       0x02000000
;=============================================================================
publickey   db      'public.key',0
privatekey  db      'private.key',0
csp         dd      0
key         dd      0
blob        dd      0
blobsize    dd      0
pheap       dd      0
hfile       dd      0
count       dd      0
;=============================================================================
start:      invoke  CryptAcquireContext,csp,0,0,PROV_RSA_FULL,CRYPT_NEWKEYSET
            invoke  CryptGenKey,[csp],AT_SIGNATURE,RSA512+CRYPT_EXPORTABLE,key
            call    [GetProcessHeap]
            mov     [pheap],eax
            push    PUBLICKEYBLOB
            push    publickey
            call    exportkey
            push    PRIVATEKEYBLOB
            push    privatekey
            call    exportkey
            invoke  CryptDestroyKey,[key]
            invoke  CryptReleaseContext,[csp],0
            jmp     exit
;=============================================================================
proc        exportkey,keyfile,keytype
            invoke  CryptExportKey,[key],0,[keytype],0,0,blobsize
            invoke  HeapAlloc,[pheap],HEAP_ZERO_MEMORY,[blobsize]
            mov     [blob],eax
            invoke  CryptExportKey,[key],0,[keytype],0,[blob],blobsize
            invoke  CreateFile,[keyfile],GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0
            mov     [hfile],eax
            invoke  WriteFile,[hfile],[blob],[blobsize],count,0
            invoke  HeapFree,[pheap],0,[blob]
            invoke  CloseHandle,[hfile]
            return
endp
;=============================================================================

Ключи

Но толку от этого мало, ключ всегда генерируется разный. Сколько бы мы его не генерировали, получить такой же, как у автора не реально! Более того, именно от этого, от генератора случайных чисел (ГСЧ), зависит стойкость секретно ключа. Ну, может быть ещё немного от «утюга и паяльника». А вообще то, очень зависит от длины ключа (секретной экспоненты), скажем ключи до 384 бит, может и реально «подобрать» в домашних условиях, но на ключи 512 бит и более, нужны огромные вычислительные мощности и время. Хотя, говорят (на форуме wasm.ru проскакивала такая информация), были случаи «взлома» 2048 битного ключа RSA, при создании которого использовался плохой ГСЧ. Здесь я мало чего знаю и мало что могу сказать, я не профессор математики, а криптография требует недюжинных знаний в ней.

Наверное, существует много атак на эти схемы, даже теоретически подумать, ведь у автора есть только один private.key (соответствующий ему public.key зашит в программу), и он им генерирует регистрационные ключи для многих пользователей. Значит, в этой схеме с цифровой подписью существует множество правильных данных. Нужно найти любые, хеш-объект от которых даст нам подпись (Key), которая, в свою очередь, совпадет с подписью, полученной (после расшифровки публичным ключом) от хеш-объекта, взятого от тех же данных. И тогда CryptVerifySignature вернет нам истину. В этом случае мы упираемся в стойкость алгоритма хеширования MD5.

Да, довольно не просто все это ... А вот если бы мы столкнулись с ключом в половину меньшей длины, то посмотрите, что нам всего лишь нужно было сделать, чтобы найти секретный ключ, имея публичный (Рис.6 – RSA public.key 1):

1. Разбор ключа
blobheader.bType PUBLICKEYBLOB 06
blobheader.bVersion CUR_BLOB_VERSION 02
blobheader.reserved 0 0000
blobheader.aiKeyAlg CALG_RSA_SIGN 00240000
rsapubkey.magic 0x31415352;"RSA1" 52534131
rsapubkey.bitlen 512 00020000
rsapubkey.pubexp 65537 01000100
rsapubkey.pubmod  (prime1 * prime2) 53CDFE887979A94989F1F60109176ED7D1D06DB45578 EF54B7DB3B799912736629A29AD7F3229D450EB8125E 4FB548F214053845BEDD88CC9D2316A26D21D1C5
2. rsapubkey.pubmod - hex переворот
C5D1216DA216239DCC88DDBE45380514F248B54F5E12 B80E459D22F3D79AA22966731299793BDBB754EF7855 B46DD0D1D76E170901F6F18949A9797988FECD53
3. rsapubkey.pubmod - hex2dec
10360512575683163663328330543073246223858236 83635078640493871720526037819053132311873501 21493858515539327642796816590641211984997427 18195448002379037723987

Осталось только вот это длинное число «впихнуть» в одну из многих программ факторизации и ждать результата, а потом из полученного числа попытаться «спаять» private.key, соответствующий формату Crypto API.

PRIVATEKEYBLOB
blobheader.bType PRIVATEKEYBLOB 07
blobheader.bVersion CUR_BLOB_VERSION 02
blobheader.reserved 0 0000
blobheader.aiKeyAlg CALG_RSA_SIGN 00240000
rsapubkey.magic 0x31415352;"RSA2" 52534132
rsapubkey.bitlen 512 00020000
rsapubkey.pubexp 65537 01000100
modulus Значение "prime1 * prime2", число "n"
prime1 Число "p"
prime2 Число "q"
exponent1 Значение "d mod (p - 1)"
exponent2 Значение "d mod (q - 1)"
coefficient Значение "(inverse of q) mod p"
privateExponent Число "d"

Думаю, что это бы у нас получилось, и потом, используя этот ключ, могли бы спокойно сгенерировать правильные регистрационные данные. Напомню, что только при условии немного меньшей длины rsapubkey.pubmod , иначе мы никогда не дождемся результата ?. Но, стоит заметить, это будет все же не универсальный вариант, т.к. автор может сменять public.key с выходом новых версий программы (что он, кстати, иногда и делает).

2002-2013 (c) wasm.ru