allasm.ru

    Меню

 

Как создать защищенный файл в Excel

  Предположим, вы работаете с файлом (таблицами) в Excel и при этом хотите, чтобы кроме вас (или доверенных лиц) никто не мог бы иметь доступа к нему. Для этого вы можете защитить файл паролем (Файл - Сохранить как - Параметры - Ввод пароля). После всех подтверждений файл будет сохранен. При последующих попытках открыть его, Excel будет предлагать ввести пароль, и если тот окажется неверен, то вы (или кто-то другой) содержимое файла увидеть не сможете. Примечание: речь идет о версии Excel 97.

Что происходит с файлом при сохранении его с паролем?

  Создадим новый файл. В первое поле таблицы занесем отладочную строку, например 50 букв "A". Сохраним файл с паролем. Пусть он будет тоже характерным, например "123456789012345" (максимальная длина, которую позволяет ввести программа - 15 символов). Создадим также файл с той же строкой из букв "A", но защищать его паролем не будем.

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

  Оказыватеся, что файлы Excel имеют что-то вроде 512-байтного заголовка, который абсолютно одинаков для защищенного и незащищенного файлов. Далее, начиная с байта в позиции 0214h в файле начинаются отличия:

0214h: защищенный: 86h, незащищенный: E1h
0216h: защищенный: 00h, незащищенный: 02h
0218h: защищенный: 2Fh, незащищенный: B0h
...

  и так далее. Попробуем найти нашу тестовую строку в файле. Оказывается, ее там нет. Как нет и пароля в явном виде. Что ж, на такой легкий вариант никто и не рассчитывал. Один из простейших способов зашифровать данные - это выполнить операцию xor (исключающее ИЛИ) над байтами шифруемой информации. В самом простом случае на каждый байт накладывается одна и та же xor-маска. Для расшифровки достаточно повторить ту же самую операцию xor с той же маской - поскольку эта операция при выполнении четное число раз приводит к начальному результату:

(ЛюбойБайт xor Mask) xor Mask = ЛюбойБайт

  Убедимся, что Excel не так прост. Для этого достаточно пройти все 256 байтов масок, накладывая их на все байты файла и создавая новый файл. Неудачным поиском тестовой строки по таким новым файлам легко проверить, что программа все еще хитрее нас.

  Итак, мы имеем множество отличающихся байтов в защищенном и незащищенном файлах. Точнее, их более 60%. Очевидно, что сам пароль сейчас как "иголка в стоге сена", поэтому нам не хватает дополнительных экспериментальных данных, для того чтобы хотя бы знать, где примерно находится то, во что Excel превратил исходный пароль.

  Напрашивается следующий опыт - сохранить множество одинаковых файлов с одним и тем же паролем и содержимым (скажем, все с той же тестовой строкой). Создадим, к примеру, 10 таких файлов. Сравнивать их опять же удобно по вышеприведенному принципу, немного усложнив алгоритм - сначала читаем в некий буфер один из файлов, а потом сравниваем второй файл с первым, несовпадающие байты фиксируем, для остальных файлов просто выводим байты в несовпадающих позициях. В протокол выведем строки типа:

Номер_байта Байт1 Байт2 ... Байт10,

  где Байт1 Байт2 ... Байт10 - байты 10 файлов, причем Байт1 всегда отличен от Байт2.

  Что же обнаруживается в результате такого сравнения? Оказывается, что очень значительная доля одинаковых с точки зрения пароля и содержащейся в них информации файлов, зашифрованных одной и той же копией Excel, оказалась различной (около 30%)! Выявленное многообразие шифрования только на первый взгляд кажется осложняющим дальнейший анализ. В самом деле, вот у нас есть блоки байтов, которые сильно отличаются от файла к файлу, а вот есть наборы байтов, которые остаются неизменными, причем их расположение и длина от файла к файлу не меняются. Тут может быть два варианта - либо измененный "пароль" принадлежит неизменной части (частям), либо переменной. Остальные варианты пока можно отбросить из-за их сложной реализации с точки зрения программиста.

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

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

  Остановимся пока на первом варианте, ибо мы уже обнаружили, что структура файла сделана по принципу заголовок (информация о файле) - информативное содержание, а не наоборот.

  Раз сам заголовок не изменяется, перейдем к несовпадающим байтам после него. Сразу можно заметить, что с позиции 0222h начинается постоянная по длине (48 байтов) изменяющаяся группа байтов. Далее можно обнаружить и другие такие группы. Например, для пароля длиной в один символ на месте данных файла (тестовой строки) находятся видоизменяющиеся группы длиной 14 байтов, перемежаемые постоянными группами длиной 8 байтов (найти в зашифрованном файле позицию тестовой строки легко - достаточно узнать ее позицию в незашифрованном файле).

  Как же можно "вручную" выбрать из таких групп ту, которая содержит в искаженном виде пароль? Предположим, что программа сначала считывает такую группу, запрашивает пароль, сверяет его по внутреннему алгоритму с этой группой, и только в случае успешной проверки продолжает распаковывать файл. Для примера изменим любой один байт из первой группы в 48 байтов и попробуем открыть его в Excel.

  Оказывается, в этом случае Excel стабильно выдает сообщение - неверный пароль! А вот если мы изменим другие переменные байты, скажем, в позиции 025Ch, он выдаст сообщение типа "Не могу прочитать файл", или вообще появится традиционное "Программа выполнила :".

  На этом месте явно наступило время обдумать полученные "в лоб" результаты и обогатиться новыми знаниями. Ведь нам совершенно не известно, как программа обрабатывает эти 48 байтов вместе с паролем.

Криптография и все-все-все

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

F = F(пароль, 48 байтов с позиции 0222h).

  Например, CRC (контрольная сумма) 48 байтов - скажем, просто их побайтное сложение по два должно образовывать последовательность из 16 байтов. Эти байты, например, представляют собой сам пароль. Или не сам пароль, а пароль, на который наложена 16-байтная постоянная xor-маска, взятая из последних 16 байтов.

  В случае использования алгоритмов, подобных этому, сразу возникает вопрос - а что, если кто-то узнает (скажем, украдет или подсмотрит исходный код Excel, или просмотрит его под отладчиком) принцип действия алгоритма ? Тогда любой пароль элементарно вычисляем - достаточно проделать вышеописанные операции над этими 48 байтами, повторив действия программы, чтобы получить пароль для любого файла. Иначе говоря, существует функция G, обратная к функции F:

Пароль = G(48 байтов с позиции 0222h).

  Очевидно, что в этом случае хакерами становились бы уже в детском саду. Да и парни из команд, подобных MS, знают, с кем имеют дело. В чем же тут хитрость?

  Обогатимся, так сказать, знаниями, которые накопило к настоящему времени человечество в области выстраивания заборов и капканов. Скорее всего, "против нас играют" не программеры, а какие-нибудь ученые-математики с их заумными формулами и доказательствами.

  Наберем в поисковике чего-нибудь со словами crypto. Крипто - это криптография, так по науке называются способы защиты данных. Искать лучше на русскоязычных сайтах - лишних трудностей нам не нужно. Вот, скажем, попался нам www.cryptography.ru - сайт МГУ по этой самой криптграфии.

  Начинаем читать статью "Введение в криптографию" под редакцией В.В.Ященко. Оказывается, многие современные алгоритмы шифрования (да, скорее всего, и наш) строится вот по какому принципу - если используется некий алгоритм создания и проверки некоторой измененной информации (пароля), то не должно существовать однозначной функции, позволяющей получить сам пароль по этой зашифрованной информации. То есть, все что можно - это брать пароль и сравнивать его каким-нибудь способом с зашифрованными данными (функция F), а вот из самой зашифрованной информации однозначно пароль получить нельзя (обратная функция G). Причем отсутствие этой самой обратной функции строго не доказано (!), но сейчас все же полагают, что способы выбора функций F достаточно надежны. Проверяют надежность того или иного способа многократными попытками взлома (!).

  Хорошо, поверим в то, что не существует способа сразу вычислить пароль. Но ведь никто нам не мешает узнать алгоритм проверки программой пароля, т.е. узнать функцию F. Узнав эту функцию, мы можем методом перебора поискать пароль, "подкидывая" все множество возможных паролей на вход функции F - как только сравнение окажется верным, мы нашли пароль. Такой алгоритм прост до неприличия, но в чем же хитрость защиты - ведь перебором однозначно можно найти нужный пароль? Смысл в том, что время, необходимое на перебор всех-всех вариантов огромно, хотя и конечно.

  Пусть процедура проверки одного пароля занимает 1 мкс. Пусть также мы ищем пароль длиной 10 символов. Число возможных символов, которые можно ввести с клавиатуры, будет примерно 100. Тогда число всех возможных вариантов пароля составит:

10010 = 1020 вариантов.

  А время, необходимое на перебор всех вариантов, будет равно:

1020 x 10-6 = 10^14 секунд

  Увы, мы живем явно меньше. Тем не менее, интересно все же узнать как именно Excel применяет такие алгоритмы шифрования.

Алгоритмы шифрования, используемые в Excel

  Итак, перед нами явно какой-то из этих алгоритмов - или их комбинация. В той же статье В.В.Ященко читаем, что Word и другие MS-продукты используют некие алгоритмы RC4 и MD5. Использовать они их начали не сразу, раньше можно было, например, импортировать данные из Excel-файла в Access из любого защищенного файла, пароль при этом почему-то никто не спрашивал.

  Что же такое RC4, MD5 и тому подобные штучки? Схематично их алгоритмы можно представить таким образом - шифруемая информация (последовательность битов) должна быть, с одной стороны, скрыта во множестве "мусора" (тривиальный пример - сложение каждого байта строки с байтами строки-константы), а с другой стороны - рассеяна по полученному множеству таким образом, чтобы связи между шифруемыми битами были потеряны, но не окончательно - ведь еще предстоит проверка (например, меняем четные байты пароля на нечетные).

  Возьмем, к примеру, алгоритм шифрования RC4. Он состоит из подготовительной части и самого шифрования.

  Пусть у нас есть пароль "Password". Формируем две таблицы длиной 256 байтов. Первая (S-таблица) будет вначале содержать числа 0, 1, 2,.., 255, а вторую (K-таблицу) заполним паролем примерно так:

K = "PasswordPassword..."

  (пароль ведь может быть меньше 256 байтов). После этого введем индексы i и j, j вначале равен 0. Выполним 256 замен между таблицами:

i = 0,1.. 255, j = (j + S[i] + K[i]) and 255,  xchg S[i],S[j].

  Полученная таблица S называется таблицей подстановок.

  Теперь пусть есть некоторая информация, которую надо зашифровать - массив байтов Info[0,1,2:]. Определим два счетчика Q1 и Q2 с начальными значениями 0. При шифрации каждого байта Info[] выполняем следующие действия:

Q1 = (Q1 + 1) and 255, Q2 = (Q2 + S[Q2]) and 255,
xchg S[Q1], S[Q2], T = (S[Q1] + S[Q2]) and 255,
Gamma = S[T], 

  и, наконец - вот оно:

Info[..] = Info[..] xor Gamma.

  Таким образом, мы формируем таблицу подстановок S и с ее помощью проводим операции "исключающее ИЛИ" (xor) над элементами шифруемой информации. Как уже было сказано ранее, четное число операций xor над байтом дает исходный байт. Поэтому достаточно провести еще раз ту же самую операцию над зашифрованным алгоритмом RC4 массивом Info[], чтобы получить исходный Info[].

  С алгоритмом MD5 все гораздо сложнее. Но случайно мне под руку подвернулся его исходник, правда, на Cи, но читать можно. MD5 - это по сути не алгоритм шифрования, а так называемая хэш-функция. Слово громкое, но это всего лишь сверхуникальная последовательность из 16 байтов для любой последовательности байтов любой длины. Грубо говоря, это некая особая выжимка из массива чисел. Работает даже тогда, когда массив пуст. Алгоритм поражает своей тяжестью - именно не сложностью! В нем, так же как и в RC4, есть секция инициализации, которая состоит в заполнении 4 двойных слов (32 бита) - пусть это будет массив state[1..4] - следующими числами:

67452301	; state[1]:=MagValue1
EFCDAB89	; state[2]:=MagValue2
98BADCFE	; state[3]:=MagValue3
10325476	; state[4]:=MagValue4,

  а также в инициализации двух 32-битных счетчиков нулевыми значениями. Разобраться в MD5 гораздо сложнее, чем в RC4 - да это нам и не нужно. Нам пока важно иметь общее представление о механизмах, которые мы будем искать в Excel. А найдя их, мы найдем уже готовый код проверки пароля от MS.

"Микроскоп" SoftIce и Excel в роли "инфузории-туфельки"

  Итак, мы имеем смутное представление о том, что же мы хотим найти в Excel. Посмотрим в отладчике SoftIce на то, как программа работает с файлом и введенным паролем. Запускаем SoftIce. Выбираем в браузере зашифрованный файл. Нажимаем Сtrl-D. Сейчас файл будет открыт, и из него будет прочитано несколько блоков файла и, видимо, в том числе наш блок из 48 байтов. Чтение из файла осуществляется через api-функции SetFilePointer (Установить указатель в файле) и ReadFile (Прочитать блок байт из файла). Находятся эти функции в модуле kernel32.dll. Формат вызова у этих функций примерно такой:

SetFilePointer(FileHandle: DWORD; 
               Pos: DWORD; 
               Rezerv: DWORD; 
               FromWhat: DWORD): DWORD;

  Где:

  • FileHandle - дескриптор файла;
  • Pos - указатель в файле;
  • Rezerv - (резерв?) передают NULL;
  • FromWhat - с начала файла (FILE_BEGIN = 0;), с конца файла (FILE_CURRENT = 1;), с текущей позиции (FILE_END = 2;).
ReadFile(FileHandle: DWORD; 
         AddrBuffer: DWORD; 
         BuffSize: DWORD; 
         AddrForBytesRead: DWORD; 
         Rezerv: DWORD): DWORD;

  Где:

  • FileHandle - дескриптор файла;
  • AddrBuffer - адрес буфера чтения;
  • BuffSize - сколько байтов читать;
  • AddrForBytesRead - сколько байтов будет прочитано.

  Устанавливаем брейкпойнты на эти две функции:

bpx SetFilePointer
bpx ReadFile

  и продолжаем выполнение программы (Ctrl-D). После клика на нашем файле в браузере мы почти сразу же "выпадаем" в отладчик на первый breakpoint по SetFilePointer. Ага, смотрим переданные параметры и видим, что произошел следующий вызов:

SetFilePointer( Handle: ...; Pos: 0; Rez: 62E2D4; FromWhat: FILE_BEGIN)

  T.е. мы собираемся читать что-то с начала файла. Пропускаем этот и еще несколько вызовов, пока не обнаруживаем вот это:

015F:7FF66B88 call ReadFile
( ReadFile( Handle: ...; AddrBuffer: 64107Ah; BuffSize: 10h; 
AddrForBytesRead: 62B8F0h; Rezerv: 0;) ).

  Т.е читаем 16 байтов в буфер 64107Ah. Выполняем эту функцию:

G @SS:ESP,

  и видим, что прочли как раз первые 16 байтов с позиции 0222h. Далее мы видим еще два интересующих нас вызова:

015F:7FF66B88 call ReadFile
( ReadFile( Handle: :; AddrBuffer: 62BA54h; BuffSize: 10h; 
AddrForBytesRead: 62B8F0h; Rezerv: 0;) ) 

  и

015F:7FF66B88 call ReadFile
( ReadFile( Handle: :; AddrBuffer: 62BA44h; BuffSize: 10h; 
AddrForBytesRead: 62B8F0h; Rezerv: 0;) ).

  А это, собственно, и есть чтение второго и третьего блока, по 16 байтов каждый, из тех самых 48 байтов.

  Продолжая дальше выполнять программу, мы видим, как на экране появляется приглашение к вводу пароля. Введем характерный пароль, чтобы потом можно было легко искать его в памяти. После подтверждения ввода повторно "вываливаемся" в отладчик на тех же самых чтениях 48 байтов в той же самой последовательности.

  Итак, мы получили 3 вызова с одной и той же точки(:7FF66B88) на чтение 16 байтов 48-байтного блока. Это явно подпрограмма, одна и та же подпрограмма. Поэтому нужно найти код, который делает что-то вроде этого:

; Прочесть первые 16 байтов
; Что-то сделать
; Прочесть вторые 16 байтов
; Что-то сделать
; Прочесть третьи 16 байтов
; Что-то сделать
; Видимо, сравнить пароль

  Последовательно возвращаясь из вложенных процедур с точки ":7FF66B88", находим именно такой код (buff2, buff3 и buff4 - 1-й, 2-й и 3-й блоки по 16 байтов):

308B7793:
; ...
	lea edx,[edi+116h]	; <- 0064107A - buff2
; Прочитать buff2
308B77A3:
	call 3080AF24	; Прочесть buff2
; ...
	call [esi+10h]		; Выполнить код 308B75D5
308B77B5:
; ...
	call [esi+08]		; Вызвать 308B74FD
308B77BF:
; ...
; Прочитать buff3
	lea edx,[ebp-14h]	; <-0062BA54 - buff3 !!!
	call 3080AF24		; Прочесть там buff3
	test eax,eax		; Ошибка ?
	jz 308B778A		; Нет
; ...
308B77DA:
	call esi			; Вызвать 308B7548
; ...
; Прочитать buff4
	lea edx,[ebp-24h]	; <-0062BA44 - buff4 !!!
	call 3080AF24		; Прочесть там buff4
; ...
	call esi			; Вызвать 308B7548
; ...
	call 309AD4D2		; MD5Init( MD5_CTX - контекст [0062B9DC] )
; ...
	call 309AD4FC		; MD5Update( MD5_CTX - контекст MD5, буф., дл.буф. )
	lea ecx,[ebp-8Ch]	; <-0062B9DC - адрес контекста 
	call 309AD5A9		; MD5Final( контекст: 0062B9DC, digest: +58h)
; Сравнить вычисленные данные.
308B781B: 
	mov ecx,ebx	; <-10h
	lea edi,[ebp-24h]	; <- buff4, уже отксореный ранее по RC4
	lea esi,[ebp-34h]	; <- 0062BA34 - digest к 0062B9DC из 10h байтов
	xor eax,eax
	rep cmpsb			; Сравнить 3 блок из 16 байтов в 222h файла.
;	jnz ...			; Пароль не верен?

  Вот примерно и ясен каркас подпрограммы проверки пароля.

  Остается пройтись по подпрограммам, которые вызываются из этого участка кода (начиная с чтения первых 16 байтов и до последней проверки сравнением двух строк длиной по 16 символов).

  Полный текст процедуры проверки пароля Excel'ом приводится в приложении(ниже) с некоторыми комментариями.

  Оказывается, что алгоритм проверки довольно-таки запутанный. Можно сказать, что это дикая смесь RC4 и MD5. Схематично он выглядит так:

  • прочесть первые 16 байтов из 48;
  • получить MD5-хеш от пароля;
  • от этого хеш'а и первых 16 байтов образовывать новый MD5-хеш, фактически от ("MD5(пароль)+16 байтов" x 16) ;
  • еще раз(!) от полученного хеш'а и пароля образовать новый MD5-хеш;
  • полученный хеш использововать как пароль для формирования таблицы подстановок RC4;
  • прочесть вторые 16 байтов из 48;
  • выполнить RC4-расшифровку этих 16 байтов, таблица подстановок уже получена;
  • прочесть третьи 16 байтов из 48;
  • выполнить RC4-расшифровку и этих 16 байтов;
  • от уже расшифрованных RC4 двух 16 байтов получить MD5-хеш;
  • и только сейчас сравнить его с расшифрованным по RC4 третьим блоком из 16 байтов!!!

  Где-то внутри этого алгоритма также происходит формирование буфера, необходимого для расшифровки самого файла - например, той же таблицы подстановок для RC4.

  Время выполнения проверки одного пароля на P-120, windows98 составляет в среднем около 800 мкс. Т.е для проверки всех вариантов паролей длиной 2 символа необходимо:

~100x100x0.0008 = 8 секунд!

А как же без "дырок" ?

  Итак, мы создали простой код, подбирающий пароли к файлам Excel методом "грубой силы". Очевидно, перебор всех паролей достаточно серьезной длины (например, 10 символов) займет огромное время. Попытаемся проанализировать оригинальный код Excel на предмет "багов", наличие которых может существенно сократить время подбора пароля.

  Обратим внимание на тот факт, что сам пароль используется в процедуре проверки ВСЕГО ОДИН РАЗ, а именно - в подпрограмме 308B75D5: сначала всего лишь вычисляется MD5-хэш от пароля:

; Подпрограмма 308B75D5
P308B75D5 proc
	push ebp
	mov ebp,esp
	sub esp,68h		; Выделить локальный буффер в 68h=104байта
	push ebx		; 10h
	push esi
	push edi		
	lea ecx,[ebp-68h]	; Адрес локального буффера в 68h
	call P309AD4D2		; MD5Init
	mov eax,[ebp+0Ch]	
;	movz ecx,word ptr [eax] ; ECX=5, если пароль длиной 5 
        xor ecx,ecx  
	mov cx,word ptr [eax] 	
	shl ecx,1		; x 2
	push ecx
	lea edx,[eax+02]	; <- АДРЕС ПАРОЛЯ
	lea ecx,[ebp-68h]	; Адрес локального буффера в 68h
	call P309AD4FC	; MD5Update(лок.контекст[0062B958],Пароль,Длина пароля)
        lea ecx,[ebp-68h]	; Адрес лок.контекст[0062B958]
	call P309AD5A9	        ; MD5Final

  Назовем этот хэш hp.

  А что происходит дальше ? А дальше - самое интересное, программа формирует следующий хэш, но только от 5 байт первого, hp:

	lea ecx,[ebp-68h]
	call P309AD4D2		; MD5Init
	mov ebx,[ebp+08]
	push 10h
	lea esi,[ebx+116h]
	pop edi
L308B7616:
	push 5			; Длина буфера для MD5Update РАВНА ПЯТИ !
	lea edx,[ebp-10h]	
	lea ecx,[ebp-68h]	
	call P309AD4FC		; MD5Update
	push 10h		; Длина буфера для MD5Update
	mov edx,esi	      	; buff2 - буфер для MD5Update 
	lea ecx,[ebp-68h]	
	call P309AD4FC		; MD5Update
	dec edi			; 10h->0Fh->...
	jnz L308B7616		; Вызывать 10h раз MD5Update
	lea ecx,[ebp-68h]	
	call P309AD5A9		; MD5Final

  В первом приближении дальнейший код зависит ТОЛЬКО от этих пяти байтов hp ! Да, в начале формируется 16-ти байтный хэш hp от пароля, что никак не ослабляет мощность введенного пароля, но вот его усечение до пяти символов - это уже что-то. Давайте проверим эту гипотезу. Создадим некий файл, защищенный паролем "1". Откроем его, установив брейкпойнт в точке 308B7616:

bpx cs:308B7616

  Только что сформирован хэш hp, он находится по адресу [ebp-10h]:

d ss:ebp-10

  И мы видим первые пять байтов хэша от пароля "1":

06 D4 96 32 C9 ...

  Запомним их и продолжим выполнение программы. Естественно, раз пароль был верным, файл нормально откроется. А теперь соль эксперимента: откроем еще раз этот же файл, только вот вместо правильного пароля введем какую-нибудь чепуху. Опять попадем в отладчик в той же точке 308B7616 и заменим первые пять байт нового хэша(видно, что он отличается от прежнего) на те самые первые пять байт от оригинального пароля(data 0 > заменить пять байт по адресу [ebp-10h]). И что мы видим ? Файл открылся как ни в чем не бывало !

  Итак, мы нашли очевидный баг. Для подбора пароля, оказывается, нет нужды перебирать все пароли длиной от 1 до 15, достаточно перебрать первые 5 байтов хэша hp ! А это уже совсем другое число вариантов: 256^5 ~= 10^12 ! Теперь алгоритм перебора может быть переписан в следующем виде: вызываем нашу процедуру обработки пароля, передаем ей какой-нибудь пароль (произвольный !), но дописываем небольшой код, подменяющий создаваемые оригинальным кодом первые пять байт hp на перебираемые значения:

.data
; Тестовый "пароль"
TestFiveBytes db 000h,000h,000h,000h,000h

@@FindPassword:
; ...
  push 0826BB25Ch	      
  push offset Password	
  push offset LocBuffer	
  call P308B774C		; Вызов главного кода
  jz @@PasswordFound
  xor ebx,ebx
@@NextLetter:
  inc byte ptr TestFiveBytes[ebx]
  jnz @@FindPassword
  inc ebx
  cmp ebx,MaxPassLen
  jb  @@NextLetter
; ...
  call P309AD5A9	; MD5Final
; Подставляем переборочный хэш, причем ТОЛЬКО 5 байт !!!
  pushad
  lea edi,[ebp-10h]	; <-62B9B0 - UN1- адрес буфера для MD5Update
  mov esi,offset TestFiveBytes
  mov ecx,5
  cld
  rep movsb
  popad
; ...
  pop edi
L308B7616:

  Теперь нам надо перебирать "всего" 256^5 вариантов. Много это или мало ? Во-первых, существенно меньше первичной реализации нашего подбирателя. Число вариантов составляет примерно 10^12, при скорости подбора 10000 хэшей в секунду необходимо 10^8 секунд, что составляет примерно 40 месяцев, что не так уж и много для особо желающих, а, что самое главное - вполне обозримое время.

  Если подключить к делу сотни мощных компьютеров, то подбор может занять вообще несколько дней, что не говорит в пользу защиты от Excel.

  Следует добавить, что сам пароль можно будет найти после нахождения верного хэша путем атаки на MD5 перебором и сравнением первых 5-ти байт получаемых хэшей с найденными ранее. А также потенциальное наличие в алгоритме защиты ДУБЛИРУЮЩИХСЯ паролей (с вероятностью примерно 10^-12) - т.е. пароли, у которых первые пять байт MD5-хэша равны, для Excel будут эквиваленты !

Реализация подборщика паролей

  Итак, попробуем создать более-менее быстрый подборщик паролей. Еще раз в двух словах о том, что он должен делать.

  Как уже было написано выше, мы можем откинуть часть общего алгоритма проверки пароля и перебирать либо первые пять байт хэша от пароля, либо первые пять байт входного текста для одного из следующих MD5-хэшей: именно этот хэш будет подан в качестве ключа на вход RC4. Т.е., нам необходимо:

  • перебирать в цикле комбинацию из пяти байт: 00 00 00 00 00 -> FF FF FF FF FF;
  • сформировать от очередной комбинации + 4 нуля MD5-хэш;
  • полученный хеш использововать как пароль для формирования таблицы подстановок RC4;
  • прочесть вторые 16 байтов из 48;
  • выполнить RC4-расшифровку этих 16 байтов, таблица подстановок уже получена;
  • прочесть третьи 16 байтов из 48;
  • выполнить RC4-расшифровку и этих 16 байтов;
  • от уже расшифрованных RC4 двух 16 байтов получить MD5-хеш;
  • и сейчас сравнить его с расшифрованным по RC4 третьим блоком из 16 байтов.
  • если сравнение окажется верным, нам необходимо запомнить найденные пять байт;
  • далее при открытии файла ввести ЛЮБОЙ пароль и подставить найденные байты в нужное место.

  Начнем, пожалуй.

; Это программа - подборщик паролей к файлам Excel.
; На самом деле подбирает не сам пароль, а первые 5 байт некоторого хэша MD5.
; Затем необходимо при открытии файла разыскать в теле кода Excel процедуру
; формирования этого хэша(вызывается при открытии файла, защищенного паролем)
; и подставить вместо ПЕРВЫХ ПЯТИ байт полученного хэша найденные(пароль,
; введенный в диалоговом окне, при этом НЕВАЖЕН !)
; Как это сделать ? ;) Проще всего в софтайсе установить туда бряк и заменить 
; его по-быстрому ручками. Можно пропатчить Excel, именно это место, таким образом
; чтобы вводить с клавиатуры не сам пароль, а именно хэш(найденный, первые пять байт)
; Ну - это дело вкуса ;0) Адрес в Excel от Office-97
; находится в :308B74FD(чуть ниже, ВТОРОЙ ВЫЗОВ ПОСЛЕ ВВОДА ЛЮБОГО ПАРОЛЯ).
; А теперь о том, как устроен алгоритм проверки пароля(все это есть в коде, но слова
; не помешают):
; Итак, вначале читается пароль с диалогового окна и 16 байт из файла по
; адресу 222h(buff2). Далее образуется MD5-хэш от пароля(16 байт, кто не в курсе). 
; От полученного хэша(ВНИМАНИЕ !) h1 берутся первые пять байт
; (вот тут-то и нужна замена и подбор !) и дополняются 16-тью байтами 
; из файла, потом полученная последовательность дублируется 16 раз 
; и от нее получаем хэш h2 = MD5((h1(0..4)+buff2)x16).
; Далее от этого хэша и 4-х нулей формируем еще один хэш - h3,
; причем ОПЯТЬ БЕРЕМ первые пять байт(в принципе, тут тоже можно
; перебирать хэш, БУДЕТ ДАЖЕ в 2-3 раза БЫСТРЕЕ !).
; ИМЕННО ЭТО ТУТ И ДЕЛАЕТСЯ.
; Затем этот h3 используется как ключ для RC4.
; Потом читаются два буфера из файла - с адресов 232h и 242h 
; - назовем из buff3 и buff4. Оба расшифровываются по полученной
; таблице RC4 и от расшифрованного buff3 получаем хэш h4.
; Проверка верного пароля состоит в равенстве h4 и только
; что расшифрованного по RC4 buff4.
; Скорость подборки такая: тестирование проводилось
; на P120, 16МегRAM, win98, используются команды cli/sti.
; Код оптимизирован(мной) - было на мощном VC++, стало на asm.
; Один пароль занимает ~41.5 мкс.(~24000 вар/сек).
; При реальном использовании разумно будет дописать
; код заданием ИНТЕРВАЛА ПЕРЕБОРА(например, ищем с такого-то
; значения по такое-то).
; Ну вот и все. Удачи !
; Coded by Chingachguk, 2002.

  Это было вступление. Лирическое :)

.386
.model flat,stdcall
option casemap :none   ; case sensitive
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

  Инклюды, библиотеки ... подключаем. Компилировал на masm32.

  Далее идет описание макросов, использующихся в вычислении MD5-хэша.

; Макрос PFF. Необходим для вычисления MD5. Выполняет следующее:
; _a = ((((_b and _c) or (not(_b) and _d)) + a + _ac + _x) rol _s) + _b
PFF macro  _a,_b,_c,_d,_x,_s,_ac
  lea  ecx,[_a+_ac]
  mov  _a,_b
  and  _a,_c
  add  ecx,_x
  mov  edx,_b
  not  edx
  and  edx,_d
  or   _a,edx
  add  _a,ecx
  rol  _a,_s
  add  _a,_b
endm
; Макрос PGG. Необходим для вычисления MD5. Выполняет следующее:
; _a = (((_b and _d) or (_c and (not(_d)) + _a + _ac + _x) rol _s) + _b
PGG macro  _a,_b,_c,_d,_x,_s,_ac
  mov  ecx,_c
  mov  edx,_d
  not  edx
  and  ecx,edx
  mov  edx,_b
  and  edx,_d
  or   edx,ecx
  lea  _a,[_a+edx+_ac]
  add  _a,_x
  rol  _a,_s
  add  _a,_b
endm
; Макрос PHH. Необходим для вычисления MD5. Выполняет следующее:
; _a = ((((_b xor _c) xor _d) + _a + _ac + _x) rol _s) + _b
PHH macro  _a,_b,_c,_d,_x,_s,_ac
  mov  ecx,_b
  xor  ecx,_c
  xor  ecx,_d  
  lea  _a,[_a+ecx+_ac]
  add  _a,_x
  rol  _a,_s
  add  _a,_b
endm
; Макрос PII. Необходим для вычисления MD5. Выполняет следующее:
; _a = (((_c xor (_b or (not(_d)))) + _a + _ac + _x) rol _s) + b
PII macro  _a,_b,_c,_d,_x,_s,_ac
  mov  ecx,_b
  mov  edx,_d
  not  edx
  or   ecx,edx
  mov  edx,_c
  xor  edx,ecx
  add  _a,edx
  add  _a,_x
  add  _a,_ac
  rol  _a,_s
  add  _a,_b
endm

  Данные программы, мессаджи ...

.data
szDlgTitle    db "Check for Excel locked file",0
szMsg         db "  Welcome !!! ",0
PasswordTitle db "Check you Password",0
EXCELfile     db "C:\ASS_R\XLS\STATIS\1.xls",0 ; Имя файла для подбора 
Unknown       db 1000 dup(?)
EndUn         db 0
descr         dd ?
DebugFileOp   db "Debug Message",0
DebugOpOK     db "File not open !",0
; Буффера для чтения блоков защиты из файла
buffer1       db 10h dup(?)  ; Буфер для 222h-231h
buffer2       db 10h dup(?)  ; Буфер для 232h-241h
buffer3       db 10h dup(?)  ; Буфер для 242h-251h
bRead         dd ?

  А вот эта вещь является специфической. Используется MD5 в качестве добавления в процедуре MD5_Final как аргумент для MD5_Update.

; Буфер для передачи его MAIN
StrangeBuffer db 80h, 15 dup(0)
              db 16 dup(0)
              db 16 dup(0)
              db 16 dup(0)
LocBuffer     db 4000h dup(0)

  Пошел код. Приветственное сообщение...


.code
start:
; MessageBox 
    push MB_OK
    push offset szDlgTitle
    push offset szMsg
    push 0
    call MessageBox
; Open Excel file
    push OF_READ
    push offset Unknown
    push offset EXCELfile
    call OpenFile
    mov  descr,eax          ; Save file descriptor
    cmp  eax,0ffffffffh
    jnz  @@FileOpen
; File not open    
    push MB_OK
    push offset DebugFileOp
    push offset DebugOpOK
    push 0
    call MessageBox
    jmp  @@Exit
@@FileOpen:
; Read File Block
    push FILE_BEGIN
    push NULL
    push 222h
    push descr
    call SetFilePointer     ; iniHandle, Pointer, NULL, FILE_BEGIN
    push NULL
    push offset bRead
    push 10h*3
    push offset buffer1
    push descr
    call ReadFile           ; iniHandle, addr d_buffer, 2, addr bRead, NULL
    push descr
    call CloseHandle        ; Close excel file

  Только что мы прочли необходимые нам 16 x 2 байтов из файла - это блок защиты - в переменные buffer2 & buffer3.

; Вызов главного кода проверки.
.data
; Тестовый "пароль".
; На самом деле это первые пять байт хэша от (см. выше).
; Реально, конечно, нужно начинать с пяти нулей ! :)
; На этих байтах я тренировался, так что не обращайте внимания ;)
; Найдите Sice-ом место, куда нужно их подставить, введите правильный 
; пароль на файл и смотрите, что у вас там ...
TestFiveBytes db 09Eh,0D1h,02Fh,084h,07Dh
              db 4 dup(0) ; Эти четрые нуля ОБЯЗАТЕЛЬНЫ !!!
LogFileName db "pass_ex.txt",0
.code

  Откроем файл, куда будем писать результат.

 invoke CreateFile,offset LogFileName,GENERIC_WRITE,FILE_SHARE_WRITE,0,CREATE_ALWAYS,\
	FILE_ATTRIBUTE_NORMAL,0
 mov  descr,eax          ; Save file descriptor
 cmp  eax,0ffffffffh
 jnz  @@LogFileOpen
.data
OpenError db "File log.txt not open !",0
.code
 push MB_OK+MB_ICONERROR
 push offset szDlgTitle
 push offset OpenError
 push 0
 call MessageBox
 jmp  @@Exit
@@LogFileOpen:
.data?
StartRC4Table db 100h+4 dup(?)
.code

  Создадим исходную RC4 - таблицу замен: 0,1,..255 - так немного быстрее.

; Create Start RC4 Table
  xor  eax,eax
  mov  ecx,offset StartRC4Table
@@StartFillRC4:
  mov  [ecx+eax],al
  inc  eax
  cmp  eax,100h
  jb   @@StartFillRC4
  mov  dword ptr [ecx+100h],0

  Собственно, небольшие пояснения по отладочным кодам...

; Будем выполнять проверку тестового пароля, введя
; начальное значение-99 - всего в данном примере выполнится 100
; раз код проверки пароля
  sub  word ptr TestFiveBytes,99
  cli
@@FindPassword:

  Зовем процедуру проверки. Передаем ей адрсе буфера, где она будет развлекаться: строить RC4-таблицу, и т.д.

  push offset LocBuffer	; <- буфер для RC4 & хэша MD5 от пароля
  call P308B774C		; Вызов главного кода
  test eax,eax
  jz   @@PasswordFound

  Выполняем переход к очередному подборочному хэшу...

  xor  ebx,ebx
@@NextLetter:
  inc  byte ptr TestFiveBytes[ebx]
  jnz  @@FindPassword
  inc  ebx
  cmp  ebx,5
  jb   @@NextLetter
  jmp  @@Exit
@@PasswordFound:

  Нашли "пароль" - сохраним его и выдадим сообщение.

  sti
  push NULL
  push offset bRead
  push 5h
  push offset TestFiveBytes
  push descr
  call WriteFile
.data
PasswordMsg   db "You Password found ! See pass_ex.txt file.",0
.code
  push MB_OK
  push offset PasswordTitle
  push offset PasswordMsg
  push 0
  call MessageBox
@@Exit:
.data
EndWork db "All done ... ;)",0
.code
  sti
  push MB_OK
  push offset szDlgTitle
  push offset EndWork
  push 0
  call MessageBox
  invoke CloseHandle,descr
  push 0
  call ExitProcess

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

; "Чтение" buff3 & buff4
Read_buff3_buff4 proc
  push edi
  mov  edi,edx
  mov  esi,offset buffer2
  mov  ecx,10h*2/4
  rep  movsd
  pop  edi
  ret
Read_buff3_buff4 endp

  Собственно, начало процедуры проверки пассворда.

; Точка входа в проверку кода, адрес буфера в стеке.
P308B774C proc
  push ebp
  mov  ebp,esp
; Выделим место для локальных переменных: контекста MD5, ... etc
  sub  esp,08Ch
  mov  edi,[ebp+08h]	; <- адресом буфера
  lea  ecx,[ebp-8Ch]
  call P309AD4D2	      ; MD5Init

  Подставляем переборочный "хэш", причем ТОЛЬКО 5 байт !!!

  push 5+4		      ; Длина буфера для MD5Update
  mov  edx,offset TestFiveBytes   ; <- адрес переборочного хэша
  lea  ecx,[ebp-8Ch]
  call P309AD4FC	      ; MD5Update
  lea  ecx,[ebp-8Ch]
  call P309AD5A9	      ; MD5Final

  Сформировали хэш от переборочных пяти байт плюс 4 нуля. Теперь создадим таблицу замен RC4 на основе полученного хэша.

  lea  eax,[ebp-34h]	; <- есть заполненный digest
  push eax              ; Ключ 10h для RC4 - только что созданный хэш 
  mov  edx,10h
  lea  ecx,[edi+04]     ; <-[Переданный буфер+4]
  call RC4Table_10h     ; Инициализировать таблицу подстановок RC4 - Длина ключа 10h !

  Сформировали RC4-table. Теперь "прочтем" блоки защиты и расшифруем их на основе только что полученной таблицы замен RC4.

; Прочитать buff3 & buff4
  lea  edx,[ebp-24h]    ; buff3
  call Read_buff3_buff4
; Отксорить buff3 & buff4
  lea  eax,[ebp-24h]    ; <- buff3,buff4
  push eax ; Адрес buff3
  mov  edx,10h*2        ; Len of buff3 & buff4
  lea  ecx,[edi+04]     ; RC4Table
  call P309AD416

  Сформировать MD5-хэш от отксоренного buff3

  lea  ecx,[ebp-8Ch]
  call P309AD4D2	; MD5Init
  push 10h		; 10h - длина буфера
  lea  edx,[ebp-24h]	; buff3 !!! - адрес буфера
  lea  ecx,[ebp-8Ch]	; контекст MD5
  call P309AD4FC	; MD5Update
  lea  ecx,[ebp-8Ch]	; адрес контекста 
  call P309AD5A9	; MD5Final

  Сравнить вычисленные данные.

  mov  ecx,10h/4
  lea  edi,[ebp-14h]	; <- buff4, уже отксореный ранее по RC4
  lea  esi,[ebp-34h]	; <-0062BA34 -  digest к 0062B9DC из 10h байт
  xor  eax,eax
  repe cmpsd	      ; Сравнить 3 блок из 16 байт в 222h файла.
  jz   EXITMAIN		; Пароль не верен ?
  inc  eax
EXITMAIN:
  leave
  ret  04h		; Вернуться назад...
P308B774C endp
; Служебные подпрограммы. RC4-формирование таблицы ... MD5_Final

  Подпрограмма P309AD389. Инициализировать таблицу подстановок RC4.

Next_RC4 macro
  add  al,dl ; +key[i]
  mov  bl,[ecx+esi] ; S[i]
  shr  edx,8 ; Next key[i]
  add  al,bl
  mov  bh,[ecx+eax] ; bh=S[j]
  mov  [ecx+eax],bl ; S[j]=S[i]
  mov  [ecx+esi],bh ; S[j]=S[i]
  inc  esi
endm
Next4_RC4 macro
  Next_RC4  
  Next_RC4  
  Next_RC4  
  add  al,dl ; +key[i]
  mov  bl,[ecx+esi] ; S[i]
  add  al,bl
  mov  bh,[ecx+eax] ; bh=S[j]
  mov  [ecx+eax],bl ; S[j]=S[i]
  mov  [ecx+esi],bh ; S[j]=S[i]
  inc  esi
endm
; edx=len_key, ecx=addr of RC4Table, stack = key offset
; НО ТОЛЬКО ЕСЛИ КЛЮЧ ДЛИНОЙ 10h !!!
RC4Table_10h proc
  pushad 
  mov  esi,offset StartRC4Table
  mov  ebx,ecx
  mov  edi,ecx
  mov  ecx,100h/4
  rep  movsd
  movsw ; Два нулевых счетчика после 0,1,...255
  mov  ecx,ebx
  xor  esi,esi ; i
  xor  eax,eax ; j 
  mov  ebp,[esp+7*4+8]
@@FillRC4:
; Key[1..4]
  mov  edx,[ebp]
  Next4_RC4
; Key[5..8]
  mov  edx,[ebp+4]
  Next4_RC4
; Key[9..12]
  mov  edx,[ebp+8]
  Next4_RC4
; Key[13..16]
  mov  edx,[ebp+0Ch]
  Next4_RC4
  cmp  esi,100h
  jb   @@FillRC4
  popad
  ret  4
RC4Table_10h endp

  Подпрограмма - расшифровать байты по таблице подстановок RC4.

;*** EDX=len of buff, ecx=offset RC4 Table, stack = buff_addr
P309AD416 proc
  push ebp		    
  mov  ebp,esp
  push ebx
  push esi
  push edi		    
  mov  edi,[ebp+8] ; edi=Адрес буффера
  lea  ebp,[edi+edx] ; buff_addr+len_buff
  xor  ebx,ebx
  xor  edx,edx
  mov  bl,[ecx+100h] ; Q1
  mov  dl,[ecx+101h] ; Q2
@LOOP:
  xor  eax,eax
  inc  bl
  mov  al,[ecx+ebx] ; S[Q1]
  add  dl,al
  mov  ah,[ecx+edx] ; S[Q2]
  mov  [ecx+edx],al
  mov  [ecx+ebx],ah
  add  al,ah
  and  eax,0FFh
  mov  al,[ecx+eax]
  xor  [edi],al ; xor buffer[edi],gamma
  inc  edi
  cmp  edi,ebp
  jl   @LOOP
  mov  bh,dl
  mov  [ecx+100h],bx	; Запомнить счетчик 1,2
  pop  edi
  pop  esi
  pop  ebx
  pop  ebp  
  ret  4
P309AD416 endp

  Подпрограмма MD5_Init - инициализация контекста MD5.

; Подпрограмма MD5Init( MD5_CTX - контекст MD5 ).
P309AD4D2 proc			; Инициализация 24 байт контекста по ECX.
  mov dword ptr [ecx],00000000h	        ; count[1]:=0;
  mov dword ptr [ecx+04h],00000000h	; count[2]:=0;
  mov dword ptr [ecx+08h],67452301h	; state[1]:=MagValue1
  mov dword ptr [ecx+0Ch],0EFCDAB89h    ; state[2]:=MagValue2
  mov dword ptr [ecx+10h],98BADCFEh	; state[3]:=MagValue3
  mov dword ptr [ecx+14h],10325476h	; state[4]:=MagValue4
  ret
P309AD4D2 endp

  Подпрограмма MD5Update - добавление текста к MD5-хэшу.

; Подпрограмма MD5Update( MD5_CTX - контекст MD5, буфер, длина буфера )
; MD5_CTX - контекст MD5 - в ECX (его адрес)
; буфер - в EDX (его адрес)
; длина буфера - в стеке (первое слово за адресом возврата)
P309AD4FC proc	; Что-то делаем с 24 байтами по ECX
  push ebp
  mov  ebp,esp
  push edi		
  mov  esi,ecx	      ; Адрес переданного по ECX контекста
  mov  ebp,[ebp+8h]   ; Длина буффера, переданного нам > EBP
  mov  eax,ebp
  mov  ebx,edx	      ; адрес буффера -> EBX
  mov  edx,[esi]      ; 4 байта из переданного в ECX контекста
  mov  ecx,edx	      ; Положить их в ecx
  lea  eax,[edx+eax*8]  ; Длина массива * 8 + dd из контекста
  and  ecx,01F8h      ; Первые 4 байта из контекста and 01F8h
  shr  ecx,3	      ; разделить их на 8
  cmp  eax,edx	      ; Сравнить: (Длина массива * 8 + dd из контекста) и (dd из контекста)
  jae  L309AD522      ; Вышли за пределы ?
  inc  dword ptr [esi+04] ; Увеличить следующий dd из контекста
L309AD522:
  mov  [esi],eax      ; в контекст положить вычисленный eax
  mov  eax,ebp        ; Длина буффера, переданного нам > EAX
  shr  eax,1Dh	      ; Оставить 31,30 и 29 биты ? - 00,00,..
  add  [esi+04],eax   ; Добавить к следующему dd контекста
  dec  ebp            ; Уменьшить длину буффера, переданного нам
  js   L309AD59F      ; Равна 0 ? -> На выход
L309AD53A:
  mov  al,[ebx]	      ; Взять байт переданного буффера
  inc  ebx            ; Указать на следующий байт
  mov  [ecx+esi+18h],al ; Положить его в [4 байта из переданного в ((ECX and 01F8h) shr 3 + 
; + адрес переданного массива в 24 байта + следующие в нем 24 байта)
  inc  ecx	      ; Прирастить разделенный на 8 первый элемент массива
  cmp  ecx,40h	      ; Он вдруг стал 64 ?
  jnz  L309AD593      ; Да, да ...
  lea  edx,[esi+18h]
  lea  ecx,[esi+08h]
  call P309AD671      ; МАКРОСЫ
  xor  ecx,ecx
L309AD593:
  dec  ebp            ; Длина буффера, переданного нам
  jns  L309AD53A
L309AD59F:
  pop  edi
  pop  ebp
  ret  4
P309AD4FC endp

  Подпрограмма MD5_Final формирование конечного хэша по контексту.

; (MD5_CTX - контекст, digest - наполняемый буфер 10h)
; ECX - адрес контекста MD5
P309AD5A9 proc		; ECX=адрес контекста
  push edi
  mov  eax,[ecx+4]	; Взять второе слово контекста
  mov  esi,ecx	      ; Адрес контекста
  mov  [esi+18h+3Ch],eax ; <- второе слово контекста
  mov  ecx,[esi]	      ; Первое слово контекста
  mov  [esi+18h+38h],ecx ; <- Первое слово контекста
  and  ecx,1F8h	      ; с 1-м словом
  shr  ecx,3		; /8
  mov  eax,38h
  cmp  ecx,eax
  jl   L309AD5D5
  mov  eax,78h
L309AD5D5:
  sub  eax,ecx
  mov  edx,offset StrangeBuffer
  push eax
  mov  ecx,esi	      ; Адрес буфера
  call P309AD4FC	      ; MD5Update
  lea  edx,[esi+18h]
  add  esi,8h
  mov  ecx,esi
  call P309AD671	      ; Макроподстановки MD5
  lea  edi,[esi+50h]
  movsd
  movsd
  movsd
  movsd
  pop  edi
  ret
P309AD5A9 endp

  Подпрограмма макроподстановок в MD5.

; Подпрограмма 309AD671 - Макроподстановки MD5
; edx - адрес массива x[16] из long-ов
; ecx - адрес массива s[4] из long-ов
;// Константы для MD5Transform.
S11 equ 7
S12 equ 12
S13 equ 17
S14 equ 22
S21 equ 5
S22 equ 9
S23 equ 14
S24 equ 20
S31 equ 4
S32 equ 11
S33 equ 16
S34 equ 23
S41 equ 6
S42 equ 10
S43 equ 15
S44 equ 21
s0 equ eax  ; Элемент s[0]
s1 equ ebx  ; Элемент s[1]
s2 equ esi  ; Элемент s[2]
s3 equ ebp  ; Элемент s[3]
x0 equ dword ptr [edi]    ; Элемент x[0]
x1 equ dword ptr [edi+4]  ; Элемент x[1]
x2 equ dword ptr [edi+8]  ; Элемент x[2]
x3 equ dword ptr [edi+0Ch]; Элемент x[3]
x4 equ dword ptr [edi+10h]; Элемент x[4]
x5 equ dword ptr [edi+14h]; Элемент x[5]
x6 equ dword ptr [edi+18h]; Элемент x[6]
x7 equ dword ptr [edi+1Ch]; Элемент x[7]
x8 equ dword ptr [edi+20h]; Элемент x[8]
x9 equ dword ptr [edi+24h]; Элемент x[9]
x10 equ dword ptr [edi+28h] ; Элемент x[10]
x11 equ dword ptr [edi+2Ch] ; Элемент x[11]
x12 equ dword ptr [edi+30h] ; Элемент x[12]
x13 equ dword ptr [edi+34h] ; Элемент x[13]
x14 equ dword ptr [edi+38h] ; Элемент x[14]
x15 equ dword ptr [edi+3Ch] ; Элемент x[15]
P309AD671 proc
    pushad
    mov  edi,edx
    mov  s0,[ecx]       ; s[0]
    mov  s1,[ecx+4]     ; s[1]
    mov  s2,[ecx+8]     ; s[2]
    mov  s3,[ecx+0Ch]   ; s[3]
    push ecx
;static void MDTransformF (UINT4 s[4, UINT4 x16])
    PFF s0,s1,s2,s3,x0,S11,0d76aa478h ; //  1
    PFF s3,s0,s1,s2,x1,S12,0e8c7b756h ; //  2
    PFF s2,s3,s0,s1,x2,S13,0242070dbh ; //  3
    PFF s1,s2,s3,s0,x3,S14,0c1bdceeeh ; //  4
    PFF s0,s1,s2,s3,x4,S11,0f57c0fafh ; //  5
    PFF s3,s0,s1,s2,x5,S12,04787c62ah ; //  6
    PFF s2,s3,s0,s1,x6,S13,0a8304613h ; //  7
    PFF s1,s2,s3,s0,x7,S14,0fd469501h ; //  8
    PFF s0,s1,s2,s3,x8,S11,0698098d8h ; //  9
    PFF s3,s0,s1,s2,x9,S12,08b44f7afh ; // 10
    PFF s2,s3,s0,s1,x10,S13,0ffff5bb1h ; // 11
    PFF s1,s2,s3,s0,x11,S14,0895cd7beh ; // 12
    PFF s0,s1,s2,s3,x12,S11,06b901122h ; // 13
    PFF s3,s0,s1,s2,x13,S12,0fd987193h ; // 14
    PFF s2,s3,s0,s1,x14,S13,0a679438eh ; // 15
    PFF s1,s2,s3,s0,x15,S14,049b40821h ; // 16
; static void MDTransformG (UINT4 s[4, UINT4 x16])
    PGG s0,s1,s2,s3,x1,S21, 0f61e2562h ; // 17
    PGG s3,s0,s1,s2,x6,S22, 0c040b340h ; // 18
    PGG s2,s3,s0,s1,x11,S23,0265e5a51h ; // 19
    PGG s1,s2,s3,s0,x0,S24, 0e9b6c7aah ; // 20
    PGG s0,s1,s2,s3,x5,S21, 0d62f105dh ; // 21
    PGG s3,s0,s1,s2,x10,S22,002441453h ; // 22
    PGG s2,s3,s0,s1,x15,S23,0d8a1e681h ; // 23
    PGG s1,s2,s3,s0,x4,S24, 0e7d3fbc8h ; // 24
    PGG s0,s1,s2,s3,x9,S21, 021e1cde6h ; // 25
    PGG s3,s0,s1,s2,x14,S22,0c33707d6h ; // 26
    PGG s2,s3,s0,s1,x3,S23, 0f4d50d87h ; // 27
    PGG s1,s2,s3,s0,x8,S24, 0455a14edh ; // 28
    PGG s0,s1,s2,s3,x13,S21,0a9e3e905h ; // 29
    PGG s3,s0,s1,s2,x2,S22, 0fcefa3f8h ; // 30
    PGG s2,s3,s0,s1,x7,S23, 0676f02d9h ; // 31
    PGG s1,s2,s3,s0,x12,S24,08d2a4c8ah ; // 32
; static void MDTransformH (UINT4 s[4, UINT4 x16])
    PHH s0,s1,s2,s3,x5,S31, 0fffa3942h ; // 33
    PHH s3,s0,s1,s2,x8,S32, 08771f681h ; // 34
    PHH s2,s3,s0,s1,x11,S33,06d9d6122h ; // 35
    PHH s1,s2,s3,s0,x14,S34,0fde5380ch ; // 36
    PHH s0,s1,s2,s3,x1,S31, 0a4beea44h ; // 37
    PHH s3,s0,s1,s2,x4,S32, 04bdecfa9h ; // 38
    PHH s2,s3,s0,s1,x7,S33, 0f6bb4b60h ; // 39
    PHH s1,s2,s3,s0,x10,S34,0bebfbc70h ; // 40
    PHH s0,s1,s2,s3,x13,S31,0289b7ec6h ; // 41
    PHH s3,s0,s1,s2,x0,S32, 0eaa127fah ; // 42
    PHH s2,s3,s0,s1,x3,S33, 0d4ef3085h ; // 43
    PHH s1,s2,s3,s0,x6,S34, 004881d05h ; // 44
    PHH s0,s1,s2,s3,x9,S31, 0d9d4d039h ; // 45
    PHH s3,s0,s1,s2,x12,S32,0e6db99e5h ; // 46
    PHH s2,s3,s0,s1,x15,S33,01fa27cf8h ; // 47
    PHH s1,s2,s3,s0,x2,S34, 0c4ac5665h ; // 48
; static void MDTransformI (UINT4 s[4, UINT4 x16])
    PII s0,s1,s2,s3,x0,S41, 0f4292244h ; // 49
    PII s3,s0,s1,s2,x7,S42, 0432aff97h ; // 50
    PII s2,s3,s0,s1,x14,S43,0ab9423a7h ; // 51
    PII s1,s2,s3,s0,x5,S44, 0fc93a039h ; // 52
    PII s0,s1,s2,s3,x12,S41,0655b59c3h ; // 53
    PII s3,s0,s1,s2,x3,S42, 08f0ccc92h ; // 54
    PII s2,s3,s0,s1,x10,S43,0ffeff47dh ; // 55
    PII s1,s2,s3,s0,x1,S44, 085845dd1h ; // 56
    PII s0,s1,s2,s3,x8,S41, 06fa87e4fh ; // 57
    PII s3,s0,s1,s2,x15,S42,0fe2ce6e0h ; // 58
    PII s2,s3,s0,s1,x6,S43, 0a3014314h ; // 59
    PII s1,s2,s3,s0,x13,S44,04e0811a1h ; // 60
    PII s0,s1,s2,s3,x4,S41, 0f7537e82h ; // 61
    PII s3,s0,s1,s2,x11,S42,0bd3af235h ; // 62
    PII s2,s3,s0,s1,x2,S43, 02ad7d2bbh ; // 63
    PII s1,s2,s3,s0,x9,S44, 0eb86d391h ; // 64
; Добавить результат к первичным значениям !!!
    pop ecx
    add [ecx],s0       ; s[0]
    add [ecx+4],s1     ; s[1]
    add [ecx+8],s2     ; s[2]
    add [ecx+0Ch],s3   ; s[3]
    popad
    ret
P309AD671 endp

  Вот, собственно, и все. Совсем немного, правда ? :)

Оценка написанного кода подборщика

  Итак, выше приведен код, подбирающий некоторые пять байт, которые, если их найти, необходимо подставить при открытии защищенного файла Excel в процедуре по адресу :308B74FD перед формированием хэша MD5, второй вызов после ввода любого пароля.

  Оценим его эффективность. Скорость подбора составлет ~2x10^4 вариантов/сек на P120, общее число вариантов - 10^12. Т.е. при условии, что только последний вариант окажется удачным, необходимо

10^12 / 2x10^4 ~= 5x10^7 секунд, 

  или

5*10^7 / 3600 ~= 1,5x10^4 часов, 

  или

1.5*10^4 / 24 ~= 625 дней ~= 21 месяц.

  В среднем, конечно, будет примерно 11 месяцев. Что ж, реальный срок, хотя ОЧЕНЬ большой.

Что может улучшить время подбора ?

  Во-первых, техника. Если взять компьютеры 1.6 ГГц(уже есть такие), то ориентировочно можно принять повышение производительности в 10 раз, это дает полное время перебора

~ 62 дней (два месяца).

  Во-втрорых, более умный перебор. Что мы на самом деле подбираем ? Фактически, MD5-хэш. А хэш - это не просто какая-то последовательность байт, это практически ПСЕВДОСЛУЧАЙНАЯ последовательность. Т.е. не имеет практического смысла перебирать варианты вида 00 00 00 00 00 или 22 22 22 22 22 как явно неслучайные. Или (что надежнее) перебирать их в последнюю очередь.

  Как же можно оценить случайность очередного варианта ? Например, посчитав число единичных и нулевых бит. В псевдослучайной последовательности они равновероятны, т.е. всего 5x8 = 40 бит, примерно половина должна быть нулевой, половина - единичными. То же самое можно сказать и про двойки бит, т.е число сочетаний 00,01,10 и 11 должно быть примерно равно числу всех двоек в пяти байтах = 40/2 = 20 деленному на четыре - 5. А также число четверок бит 0000,0001..1111 должно быть в среднем равно (40/4)/16 ~= 1-2.

  Насколько велики могут быть отклонения числа битов, двоек, четверок от среднего ожидаемого ? Если принять, что распределение числа конкретного варианта нулевых(единичных) бит описывается распределением Гаусса со средним значением 20, то около 67% случаев будет составлять диапазон

20 +/- sqrt(20) ~= 20 +/- 5

  Соответственно, число каждого варианта двоек бит будет лежать в диапазоне

5 +/- sqrt(5) ~= 5 +/- 3

  А число каждого варианта четверок бит будет лежать в диапазоне

1-2 +/- sqrt(1-2) ~= 1-2 +/- 1-2

  Вот число нулевых/единичных бит, пар и четверок реальных пяти байт подобранного хэша:

Число 0/1 - 18/22
Число пар - 4/6/4/6
Число четверок - 0/1/1/0/1/0/0/1/1/1/0/0/0/2/1/1

  Мною был добавлен фильтр на перебираемые значения 5 байт, заключался он в проверке числа пар и четверок. Эффективность составила 2-4 раза в сравнении с "тупым" перебором.

  В-общем, тут еще есть над чем подумать. Проще, разумеется, включить парк компьютеров и запустить параллельный подбор на непересекающихся диапазонах искомых пяти байт. Удачи!

  [C] Chingachguk / HI-TECH