Защита файлов в Excel — Архив WASM.RU
Как создать защищенный файл в Excel
Предположим, вы работаете с файлом (таблицами) в Excel и при этом хотите, чтобы кроме вас (или доверенных лиц) никто не мог бы иметь доступа к нему. Для этого вы можете защитить файл паролем (Файл - Сохранить как - Параметры - Ввод пароля). После всех подтверждений файл будет сохранен. При последующих попытках открыть его, Excel будет предлагать ввести пароль, и если тот окажется неверен, то вы (или кто-то другой) содержимое файла увидеть не сможете. Примечание: речь идет о версии Excel 97.
Что происходит с файлом при сохранении его с паролем?
Создадим новый файл. В первое поле таблицы занесем отладочную строку, например 50 букв "A". Сохраним файл с паролем. Пусть он будет тоже характерным, например "123456789012345" (максимальная длина, которую позволяет ввести программа - 15 символов). Создадим также файл с той же строкой из букв "A", но защищать его паролем не будем.
Теперь попытаемся сравнить оба файла. Длины защищенного и незащищенного файлов не отличаются. Попытаемся найти отличия в их содержимом. Проще сверять файлы не визуально, а с помощью небольшой программы, которая считывает в два буфера содержимое файлов, а потом побайтно сравнивает их, выводя несовпадающие байты и их номер в файле в протокол.
Оказыватеся, что файлы Excel имеют что-то вроде 512-байтного заголовка, который абсолютно одинаков для защищенного и незащищенного файлов. Далее, начиная с байта в позиции 0214h в файле начинаются отличия:
Код (Text):
0214h: защищенный: 86h, незащищенный: E1h 0216h: защищенный: 00h, незащищенный: 02h 0218h: защищенный: 2Fh, незащищенный: B0h ...и так далее. Попробуем найти нашу тестовую строку в файле. Оказывается, ее там нет. Как нет и пароля в явном виде. Что ж, на такой легкий вариант никто и не рассчитывал. Один из простейших способов зашифровать данные - это выполнить операцию xor (исключающее ИЛИ) над байтами шифруемой информации. В самом простом случае на каждый байт накладывается одна и та же xor-маска. Для расшифровки достаточно повторить ту же самую операцию xor с той же маской - поскольку эта операция при выполнении четное число раз приводит к начальному результату:
Код (Text):
(ЛюбойБайт xor Mask) xor Mask = ЛюбойБайтУбедимся, что Excel не так прост. Для этого достаточно пройти все 256 байтов масок, накладывая их на все байты файла и создавая новый файл. Неудачным поиском тестовой строки по таким новым файлам легко проверить, что программа все еще хитрее нас.
Итак, мы имеем множество отличающихся байтов в защищенном и незащищенном файлах. Точнее, их более 60%. Очевидно, что сам пароль сейчас как "иголка в стоге сена", поэтому нам не хватает дополнительных экспериментальных данных, для того чтобы хотя бы знать, где примерно находится то, во что Excel превратил исходный пароль.
Напрашивается следующий опыт - сохранить множество одинаковых файлов с одним и тем же паролем и содержимым (скажем, все с той же тестовой строкой). Создадим, к примеру, 10 таких файлов. Сравнивать их опять же удобно по вышеприведенному принципу, немного усложнив алгоритм - сначала читаем в некий буфер один из файлов, а потом сравниваем второй файл с первым, несовпадающие байты фиксируем, для остальных файлов просто выводим байты в несовпадающих позициях. В протокол выведем строки типа:
Код (Text):
Номер_байта Байт1 Байт2 ... Байт10,где Байт1 Байт2 ... Байт10 - байты 10 файлов, причем Байт1 всегда отличен от Байт2.
Что же обнаруживается в результате такого сравнения? Оказывается, что очень значительная доля одинаковых с точки зрения пароля и содержащейся в них информации файлов, зашифрованных одной и той же копией Excel, оказалась различной (около 30%)! Выявленное многообразие шифрования только на первый взгляд кажется осложняющим дальнейший анализ. В самом деле, вот у нас есть блоки байтов, которые сильно отличаются от файла к файлу, а вот есть наборы байтов, которые остаются неизменными, причем их расположение и длина от файла к файлу не меняются. Тут может быть два варианта - либо измененный "пароль" принадлежит неизменной части (частям), либо переменной. Остальные варианты пока можно отбросить из-за их сложной реализации с точки зрения программиста.
Преположим, что измененный "пароль" принадлежит к постоянной части (частям) файла. Но это находится в противоречии с тем фактом, что значительная часть файла, а следовательно и информация, по крайней мере частично, изменяется. Ведь если "пароль" постоянен, то неоткуда взять дополнительный источник, вносящий изменения - дата или время создания файлов на эту роль не годятся, ибо при копировании защищенных файлов они могут быть изменены.
Сделав такое допущение, будем искать характерные изменяющиеся блоки в протоколе. Вероятно, что длина их должна быть порядка длины пароля, как вероятно и то, что расположение их находится в "любимых" программистами местах файла - либо в начале файла, либо в его конце.
Остановимся пока на первом варианте, ибо мы уже обнаружили, что структура файла сделана по принципу заголовок (информация о файле) - информативное содержание, а не наоборот.
Раз сам заголовок не изменяется, перейдем к несовпадающим байтам после него. Сразу можно заметить, что с позиции 0222h начинается постоянная по длине (48 байтов) изменяющаяся группа байтов. Далее можно обнаружить и другие такие группы. Например, для пароля длиной в один символ на месте данных файла (тестовой строки) находятся видоизменяющиеся группы длиной 14 байтов, перемежаемые постоянными группами длиной 8 байтов (найти в зашифрованном файле позицию тестовой строки легко - достаточно узнать ее позицию в незашифрованном файле).
Как же можно "вручную" выбрать из таких групп ту, которая содержит в искаженном виде пароль? Предположим, что программа сначала считывает такую группу, запрашивает пароль, сверяет его по внутреннему алгоритму с этой группой, и только в случае успешной проверки продолжает распаковывать файл. Для примера изменим любой один байт из первой группы в 48 байтов и попробуем открыть его в Excel.
Оказывается, в этом случае Excel стабильно выдает сообщение - неверный пароль! А вот если мы изменим другие переменные байты, скажем, в позиции 025Ch, он выдаст сообщение типа "Не могу прочитать файл", или вообще появится традиционное "Программа выполнила :".
На этом месте явно наступило время обдумать полученные "в лоб" результаты и обогатиться новыми знаниями. Ведь нам совершенно не известно, как программа обрабатывает эти 48 байтов вместе с паролем.
Криптография и все-все-все
Пусть мы пока не знаем, как поступает программа с этими 48 байтами, но можно предположить, что существует некоторая однозначная функция проверки пароля и этого блока:
Код (Text):
F = F(пароль, 48 байтов с позиции 0222h).Например, CRC (контрольная сумма) 48 байтов - скажем, просто их побайтное сложение по два должно образовывать последовательность из 16 байтов. Эти байты, например, представляют собой сам пароль. Или не сам пароль, а пароль, на который наложена 16-байтная постоянная xor-маска, взятая из последних 16 байтов.
В случае использования алгоритмов, подобных этому, сразу возникает вопрос - а что, если кто-то узнает (скажем, украдет или подсмотрит исходный код Excel, или просмотрит его под отладчиком) принцип действия алгоритма ? Тогда любой пароль элементарно вычисляем - достаточно проделать вышеописанные операции над этими 48 байтами, повторив действия программы, чтобы получить пароль для любого файла. Иначе говоря, существует функция G, обратная к функции F:
Код (Text):
Пароль = G(48 байтов с позиции 0222h).Очевидно, что в этом случае хакерами становились бы уже в детском саду. Да и парни из команд, подобных MS, знают, с кем имеют дело. В чем же тут хитрость?
Обогатимся, так сказать, знаниями, которые накопило к настоящему времени человечество в области выстраивания заборов и капканов. Скорее всего, "против нас играют" не программеры, а какие-нибудь ученые-математики с их заумными формулами и доказательствами.
Наберем в поисковике чего-нибудь со словами crypto. Крипто - это криптография, так по науке называются способы защиты данных. Искать лучше на русскоязычных сайтах - лишних трудностей нам не нужно. Вот, скажем, попался нам www.cryptography.ru - сайт МГУ по этой самой криптграфии.
Начинаем читать статью "Введение в криптографию" под редакцией В.В.Ященко. Оказывается, многие современные алгоритмы шифрования (да, скорее всего, и наш) строится вот по какому принципу - если используется некий алгоритм создания и проверки некоторой измененной информации (пароля), то не должно существовать однозначной функции, позволяющей получить сам пароль по этой зашифрованной информации. То есть, все что можно - это брать пароль и сравнивать его каким-нибудь способом с зашифрованными данными (функция F), а вот из самой зашифрованной информации однозначно пароль получить нельзя (обратная функция G). Причем отсутствие этой самой обратной функции строго не доказано (!), но сейчас все же полагают, что способы выбора функций F достаточно надежны. Проверяют надежность того или иного способа многократными попытками взлома (!).
Хорошо, поверим в то, что не существует способа сразу вычислить пароль. Но ведь никто нам не мешает узнать алгоритм проверки программой пароля, т.е. узнать функцию F. Узнав эту функцию, мы можем методом перебора поискать пароль, "подкидывая" все множество возможных паролей на вход функции F - как только сравнение окажется верным, мы нашли пароль. Такой алгоритм прост до неприличия, но в чем же хитрость защиты - ведь перебором однозначно можно найти нужный пароль? Смысл в том, что время, необходимое на перебор всех-всех вариантов огромно, хотя и конечно.
Пусть процедура проверки одного пароля занимает 1 мкс. Пусть также мы ищем пароль длиной 10 символов. Число возможных символов, которые можно ввести с клавиатуры, будет примерно 100. Тогда число всех возможных вариантов пароля составит:
Код (Text):
10010 = 1020 вариантов.А время, необходимое на перебор всех вариантов, будет равно:
Код (Text):
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-таблицу) заполним паролем примерно так:
Код (Text):
K = "PasswordPassword..."(пароль ведь может быть меньше 256 байтов). После этого введем индексы i и j, j вначале равен 0. Выполним 256 замен между таблицами:
Код (Text):
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[] выполняем следующие действия:
Код (Text):
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],и, наконец - вот оно:
Код (Text):
Info[..] = Info[..] xor Gamma.Таким образом, мы формируем таблицу подстановок S и с ее помощью проводим операции "исключающее ИЛИ" (xor) над элементами шифруемой информации. Как уже было сказано ранее, четное число операций xor над байтом дает исходный байт. Поэтому достаточно провести еще раз ту же самую операцию над зашифрованным алгоритмом RC4 массивом Info[], чтобы получить исходный Info[].
С алгоритмом MD5 все гораздо сложнее. Но случайно мне под руку подвернулся его исходник, правда, на Cи, но читать можно. MD5 - это по сути не алгоритм шифрования, а так называемая хэш-функция. Слово громкое, но это всего лишь сверхуникальная последовательность из 16 байтов для любой последовательности байтов любой длины. Грубо говоря, это некая особая выжимка из массива чисел. Работает даже тогда, когда массив пуст. Алгоритм поражает своей тяжестью - именно не сложностью! В нем, так же как и в RC4, есть секция инициализации, которая состоит в заполнении 4 двойных слов (32 бита) - пусть это будет массив state[1..4] - следующими числами:
Код (Text):
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. Формат вызова у этих функций примерно такой:
Код (Text):
SetFilePointer(FileHandle: DWORD; Pos: DWORD; Rezerv: DWORD; FromWhat: DWORD): DWORD;Где:
- FileHandle - дескриптор файла;
- Pos - указатель в файле;
- Rezerv - (резерв?) передают NULL;
- FromWhat - с начала файла (FILE_BEGIN = 0;), с конца файла (FILE_CURRENT = 1;), с текущей позиции (FILE_END = 2;).
Код (Text):
ReadFile(FileHandle: DWORD; AddrBuffer: DWORD; BuffSize: DWORD; AddrForBytesRead: DWORD; Rezerv: DWORD): DWORD;Где:
- FileHandle - дескриптор файла;
- AddrBuffer - адрес буфера чтения;
- BuffSize - сколько байтов читать;
- AddrForBytesRead - сколько байтов будет прочитано.
Устанавливаем брейкпойнты на эти две функции:
Код (Text):
bpx SetFilePointer bpx ReadFileи продолжаем выполнение программы (Ctrl-D). После клика на нашем файле в браузере мы почти сразу же "выпадаем" в отладчик на первый breakpoint по SetFilePointer. Ага, смотрим переданные параметры и видим, что произошел следующий вызов:
Код (Text):
SetFilePointer( Handle: ...; Pos: 0; Rez: 62E2D4; FromWhat: FILE_BEGIN)T.е. мы собираемся читать что-то с начала файла. Пропускаем этот и еще несколько вызовов, пока не обнаруживаем вот это:
Код (Text):
015F:7FF66B88 call ReadFile ( ReadFile( Handle: ...; AddrBuffer: 64107Ah; BuffSize: 10h; AddrForBytesRead: 62B8F0h; Rezerv: 0;) ).Т.е читаем 16 байтов в буфер 64107Ah. Выполняем эту функцию:
Код (Text):
G @SS:ESP,и видим, что прочли как раз первые 16 байтов с позиции 0222h. Далее мы видим еще два интересующих нас вызова:
Код (Text):
015F:7FF66B88 call ReadFile ( ReadFile( Handle: :; AddrBuffer: 62BA54h; BuffSize: 10h; AddrForBytesRead: 62B8F0h; Rezerv: 0;) )и
Код (Text):
015F:7FF66B88 call ReadFile ( ReadFile( Handle: :; AddrBuffer: 62BA44h; BuffSize: 10h; AddrForBytesRead: 62B8F0h; Rezerv: 0;) ).А это, собственно, и есть чтение второго и третьего блока, по 16 байтов каждый, из тех самых 48 байтов.
Продолжая дальше выполнять программу, мы видим, как на экране появляется приглашение к вводу пароля. Введем характерный пароль, чтобы потом можно было легко искать его в памяти. После подтверждения ввода повторно "вываливаемся" в отладчик на тех же самых чтениях 48 байтов в той же самой последовательности.
Итак, мы получили 3 вызова с одной и той же точки(:7FF66B88) на чтение 16 байтов 48-байтного блока. Это явно подпрограмма, одна и та же подпрограмма. Поэтому нужно найти код, который делает что-то вроде этого:
Код (Text):
; Прочесть первые 16 байтов ; Что-то сделать ; Прочесть вторые 16 байтов ; Что-то сделать ; Прочесть третьи 16 байтов ; Что-то сделать ; Видимо, сравнить парольПоследовательно возвращаясь из вложенных процедур с точки ":7FF66B88", находим именно такой код (buff2, buff3 и buff4 - 1-й, 2-й и 3-й блоки по 16 байтов):
Код (Text):
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 символа необходимо:
Код (Text):
~100x100x0.0008 = 8 секунд!А как же без "дырок" ?
Итак, мы создали простой код, подбирающий пароли к файлам Excel методом "грубой силы". Очевидно, перебор всех паролей достаточно серьезной длины (например, 10 символов) займет огромное время. Попытаемся проанализировать оригинальный код Excel на предмет "багов", наличие которых может существенно сократить время подбора пароля.
Обратим внимание на тот факт, что сам пароль используется в процедуре проверки ВСЕГО ОДИН РАЗ, а именно - в подпрограмме 308B75D5: сначала всего лишь вычисляется MD5-хэш от пароля:
Код (Text):
; Подпрограмма 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:
Код (Text):
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:
Код (Text):
bpx cs:308B7616Только что сформирован хэш hp, он находится по адресу [ebp-10h]:
Код (Text):
d ss:ebp-10И мы видим первые пять байтов хэша от пароля "1":
Код (Text):
06 D4 96 32 C9 ...Запомним их и продолжим выполнение программы. Естественно, раз пароль был верным, файл нормально откроется. А теперь соль эксперимента: откроем еще раз этот же файл, только вот вместо правильного пароля введем какую-нибудь чепуху. Опять попадем в отладчик в той же точке 308B7616 и заменим первые пять байт нового хэша(видно, что он отличается от прежнего) на те самые первые пять байт от оригинального пароля(data 0 > заменить пять байт по адресу [ebp-10h]). И что мы видим ? Файл открылся как ни в чем не бывало !
Итак, мы нашли очевидный баг. Для подбора пароля, оказывается, нет нужды перебирать все пароли длиной от 1 до 15, достаточно перебрать первые 5 байтов хэша hp ! А это уже совсем другое число вариантов: 256^5 ~= 10^12 ! Теперь алгоритм перебора может быть переписан в следующем виде: вызываем нашу процедуру обработки пароля, передаем ей какой-нибудь пароль (произвольный !), но дописываем небольшой код, подменяющий создаваемые оригинальным кодом первые пять байт hp на перебираемые значения:
Код (Text):
.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 байтов.
- если сравнение окажется верным, нам необходимо запомнить найденные пять байт;
- далее при открытии файла ввести ЛЮБОЙ пароль и подставить найденные байты в нужное место.
Начнем, пожалуй.
Код (Text):
; Это программа - подборщик паролей к файлам 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.Это было вступление. Лирическое
Код (Text):
.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-хэша.
Код (Text):
; Макрос 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Данные программы, мессаджи ...
Код (Text):
.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.
Код (Text):
; Буфер для передачи его MAIN StrangeBuffer db 80h, 15 dup(0) db 16 dup(0) db 16 dup(0) db 16 dup(0) LocBuffer db 4000h dup(0)Пошел код. Приветственное сообщение...
Код (Text):
.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.
Код (Text):
; Вызов главного кода проверки. .data ; Тестовый "пароль". ; На самом деле это первые пять байт хэша от (см. выше). ; Реально, конечно, нужно начинать с пяти нулей ! :) ; На этих байтах я тренировался, так что не обращайте внимания ;) ; Найдите Sice-ом место, куда нужно их подставить, введите правильный ; пароль на файл и смотрите, что у вас там ... TestFiveBytes db 09Eh,0D1h,02Fh,084h,07Dh db 4 dup(0) ; Эти четрые нуля ОБЯЗАТЕЛЬНЫ !!! LogFileName db "pass_ex.txt",0 .codeОткроем файл, куда будем писать результат.
Код (Text):
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 - так немного быстрее.
Код (Text):
; 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Собственно, небольшие пояснения по отладочным кодам...
Код (Text):
; Будем выполнять проверку тестового пароля, введя ; начальное значение-99 - всего в данном примере выполнится 100 ; раз код проверки пароля sub word ptr TestFiveBytes,99 cli @@FindPassword:Зовем процедуру проверки. Передаем ей адрсе буфера, где она будет развлекаться: строить RC4-таблицу, и т.д.
Код (Text):
push offset LocBuffer ; <- буфер для RC4 & хэша MD5 от пароля call P308B774C ; Вызов главного кода test eax,eax jz @@PasswordFoundВыполняем переход к очередному подборочному хэшу...
Код (Text):
xor ebx,ebx @@NextLetter: inc byte ptr TestFiveBytes[ebx] jnz @@FindPassword inc ebx cmp ebx,5 jb @@NextLetter jmp @@Exit @@PasswordFound:Нашли "пароль" - сохраним его и выдадим сообщение.
Код (Text):
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Процедура проверки, которая вызывает этот код, работает с переменными в стеке, куда просит нас в очередной раз записать блоки защиты из файла. Мы их один раз прочли и передаем ей.
Код (Text):
; "Чтение" 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Собственно, начало процедуры проверки пассворда.
Код (Text):
; Точка входа в проверку кода, адрес буфера в стеке. P308B774C proc push ebp mov ebp,esp ; Выделим место для локальных переменных: контекста MD5, ... etc sub esp,08Ch mov edi,[ebp+08h] ; <- адресом буфера lea ecx,[ebp-8Ch] call P309AD4D2 ; MD5InitПодставляем переборочный "хэш", причем ТОЛЬКО 5 байт !!!
Код (Text):
push 5+4 ; Длина буфера для MD5Update mov edx,offset TestFiveBytes ; <- адрес переборочного хэша lea ecx,[ebp-8Ch] call P309AD4FC ; MD5Update lea ecx,[ebp-8Ch] call P309AD5A9 ; MD5FinalСформировали хэш от переборочных пяти байт плюс 4 нуля. Теперь создадим таблицу замен RC4 на основе полученного хэша.
Код (Text):
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.
Код (Text):
; Прочитать 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
Код (Text):
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Сравнить вычисленные данные.
Код (Text):
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.
Код (Text):
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.
Код (Text):
;*** 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.
Код (Text):
; Подпрограмма 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-хэшу.
Код (Text):
; Подпрограмма 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 формирование конечного хэша по контексту.
Код (Text):
; (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.
Код (Text):
; Подпрограмма 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. Т.е. при условии, что только последний вариант окажется удачным, необходимо
Код (Text):
10^12 / 2x10^4 ~= 5x10^7 секунд,или
Код (Text):
5*10^7 / 3600 ~= 1,5x10^4 часов,или
Код (Text):
1.5*10^4 / 24 ~= 625 дней ~= 21 месяц.В среднем, конечно, будет примерно 11 месяцев. Что ж, реальный срок, хотя ОЧЕНЬ большой.
Что может улучшить время подбора ?
Во-первых, техника. Если взять компьютеры 1.6 ГГц(уже есть такие), то ориентировочно можно принять повышение производительности в 10 раз, это дает полное время перебора
Код (Text):
~ 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% случаев будет составлять диапазон
Код (Text):
20 +/- sqrt(20) ~= 20 +/- 5Соответственно, число каждого варианта двоек бит будет лежать в диапазоне
Код (Text):
5 +/- sqrt(5) ~= 5 +/- 3А число каждого варианта четверок бит будет лежать в диапазоне
Код (Text):
1-2 +/- sqrt(1-2) ~= 1-2 +/- 1-2Вот число нулевых/единичных бит, пар и четверок реальных пяти байт подобранного хэша:
Код (Text):
Число 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 раза в сравнении с "тупым" перебором.
В-общем, тут еще есть над чем подумать. Проще, разумеется, включить парк компьютеров и запустить параллельный подбор на непересекающихся диапазонах искомых пяти байт. Удачи! © Chingachguk / HI-TECH
Защита файлов в Excel
Дата публикации 15 авг 2002