Защита файлов в Excel

Дата публикации 15 авг 2002

Защита файлов в Excel — Архив WASM.RU

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

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

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

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

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

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

Код (Text):
  1.  
  2. 0214h: защищенный: 86h, незащищенный: E1h
  3. 0216h: защищенный: 00h, незащищенный: 02h
  4. 0218h: защищенный: 2Fh, незащищенный: B0h
  5. ...
  6.  

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

Код (Text):
  1.  
  2. (ЛюбойБайт xor Mask) xor Mask = ЛюбойБайт
  3.  

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

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

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

Код (Text):
  1.  
  2. Номер_байта Байт1 Байт2 ... Байт10,
  3.  

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

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

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

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

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

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

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

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

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

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

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

Код (Text):
  1.  
  2. F = F(пароль, 48 байтов с позиции 0222h).
  3.  

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

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

Код (Text):
  1.  
  2. Пароль = G(48 байтов с позиции 0222h).
  3.  

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

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

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

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

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

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

Код (Text):
  1.  
  2. 10010 = 1020 вариантов.
  3.  

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

Код (Text):
  1.  
  2. 1020 x 10-6 = 10^14 секунд
  3.  

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

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

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

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

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

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

Код (Text):
  1.  
  2. K = "PasswordPassword..."
  3.  

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

Код (Text):
  1.  
  2. i = 0,1.. 255, j = (j + S[i] + K[i]) and 255,  xchg S[i],S[j].
  3.  

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

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

Код (Text):
  1.  
  2. Q1 = (Q1 + 1) and 255, Q2 = (Q2 + S[Q2]) and 255,
  3. xchg S[Q1], S[Q2], T = (S[Q1] + S[Q2]) and 255,
  4. Gamma = S[T],
  5.  

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

Код (Text):
  1.  
  2. Info[..] = Info[..] xor Gamma.
  3.  

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

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

Код (Text):
  1.  
  2. 67452301    ; state[1]:=MagValue1
  3. EFCDAB89    ; state[2]:=MagValue2
  4. 98BADCFE    ; state[3]:=MagValue3
  5. 10325476    ; state[4]:=MagValue4,
  6.  

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

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

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

Код (Text):
  1.  
  2. SetFilePointer(FileHandle: DWORD;
  3.                Pos: DWORD;
  4.                Rezerv: DWORD;
  5.                FromWhat: DWORD): DWORD;
  6.  

  Где:

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

  Где:

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

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

Код (Text):
  1.  
  2. bpx SetFilePointer
  3. bpx ReadFile
  4.  

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

Код (Text):
  1.  
  2. SetFilePointer( Handle: ...; Pos: 0; Rez: 62E2D4; FromWhat: FILE_BEGIN)
  3.  

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

Код (Text):
  1.  
  2. 015F:7FF66B88 call ReadFile
  3. ( ReadFile( Handle: ...; AddrBuffer: 64107Ah; BuffSize: 10h;
  4. AddrForBytesRead: 62B8F0h; Rezerv: 0;) ).
  5.  

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

Код (Text):
  1.  
  2. G @SS:ESP,
  3.  

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

Код (Text):
  1.  
  2. 015F:7FF66B88 call ReadFile
  3. ( ReadFile( Handle: :; AddrBuffer: 62BA54h; BuffSize: 10h;
  4. AddrForBytesRead: 62B8F0h; Rezerv: 0;) )
  5.  

  и

Код (Text):
  1.  
  2. 015F:7FF66B88 call ReadFile
  3. ( ReadFile( Handle: :; AddrBuffer: 62BA44h; BuffSize: 10h;
  4. AddrForBytesRead: 62B8F0h; Rezerv: 0;) ).
  5.  

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

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

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

Код (Text):
  1.  
  2. ; Прочесть первые 16 байтов
  3. ; Что-то сделать
  4. ; Прочесть вторые 16 байтов
  5. ; Что-то сделать
  6. ; Прочесть третьи 16 байтов
  7. ; Что-то сделать
  8. ; Видимо, сравнить пароль
  9.  

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

Код (Text):
  1.  
  2. 308B7793:
  3. ; ...
  4.     lea edx,[edi+116h]  ; <- 0064107A - buff2
  5. ; Прочитать buff2
  6. 308B77A3:
  7.     call 3080AF24   ; Прочесть buff2
  8. ; ...
  9.     call [esi+10h]      ; Выполнить код 308B75D5
  10. 308B77B5:
  11. ; ...
  12.     call [esi+08]       ; Вызвать 308B74FD
  13. 308B77BF:
  14. ; ...
  15. ; Прочитать buff3
  16.     lea edx,[ebp-14h]   ; <-0062BA54 - buff3 !!!
  17.     call 3080AF24       ; Прочесть там buff3
  18.     test eax,eax        ; Ошибка ?
  19.     jz 308B778A     ; Нет
  20. ; ...
  21. 308B77DA:
  22.     call esi            ; Вызвать 308B7548
  23. ; ...
  24. ; Прочитать buff4
  25.     lea edx,[ebp-24h]   ; <-0062BA44 - buff4 !!!
  26.     call 3080AF24       ; Прочесть там buff4
  27. ; ...
  28.     call esi            ; Вызвать 308B7548
  29. ; ...
  30.     call 309AD4D2       ; MD5Init( MD5_CTX - контекст [0062B9DC] )
  31. ; ...
  32.     call 309AD4FC       ; MD5Update( MD5_CTX - контекст MD5, буф., дл.буф. )
  33.     lea ecx,[ebp-8Ch]   ; <-0062B9DC - адрес контекста
  34.     call 309AD5A9       ; MD5Final( контекст: 0062B9DC, digest: +58h)
  35. ; Сравнить вычисленные данные.
  36. 308B781B:
  37.     mov ecx,ebx ; <-10h
  38.     lea edi,[ebp-24h]   ; <- buff4, уже отксореный ранее по RC4
  39.     lea esi,[ebp-34h]   ; <- 0062BA34 - digest к 0062B9DC из 10h байтов
  40.     xor eax,eax
  41.     rep cmpsb           ; Сравнить 3 блок из 16 байтов в 222h файла.
  42. ;   jnz ...         ; Пароль не верен?
  43.  

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

  Остается пройтись по подпрограммам, которые вызываются из этого участка кода (начиная с чтения первых 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):
  1.  
  2. ~100x100x0.0008 = 8 секунд!
  3.  

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

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

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

Код (Text):
  1.  
  2. ; Подпрограмма 308B75D5
  3. P308B75D5 proc
  4.     push ebp
  5.     mov ebp,esp
  6.     sub esp,68h     ; Выделить локальный буффер в 68h=104байта
  7.     push ebx        ; 10h
  8.     push esi
  9.     push edi       
  10.     lea ecx,[ebp-68h]   ; Адрес локального буффера в 68h
  11.     call P309AD4D2      ; MD5Init
  12.     mov eax,[ebp+0Ch]  
  13. ;   movz ecx,word ptr [eax] ; ECX=5, если пароль длиной 5
  14.         xor ecx,ecx  
  15.     mov cx,word ptr [eax]  
  16.     shl ecx,1       ; x 2
  17.     push ecx
  18.     lea edx,[eax+02]    ; <- АДРЕС ПАРОЛЯ
  19.     lea ecx,[ebp-68h]   ; Адрес локального буффера в 68h
  20.     call P309AD4FC  ; MD5Update(лок.контекст[0062B958],Пароль,Длина пароля)
  21.         lea ecx,[ebp-68h]   ; Адрес лок.контекст[0062B958]
  22.     call P309AD5A9          ; MD5Final
  23.  

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

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

Код (Text):
  1.  
  2.     lea ecx,[ebp-68h]
  3.     call P309AD4D2      ; MD5Init
  4.     mov ebx,[ebp+08]
  5.     push 10h
  6.     lea esi,[ebx+116h]
  7.     pop edi
  8. L308B7616:
  9.     push 5          ; Длина буфера для MD5Update РАВНА ПЯТИ !
  10.     lea edx,[ebp-10h]  
  11.     lea ecx,[ebp-68h]  
  12.     call P309AD4FC      ; MD5Update
  13.     push 10h        ; Длина буфера для MD5Update
  14.     mov edx,esi         ; buff2 - буфер для MD5Update
  15.     lea ecx,[ebp-68h]  
  16.     call P309AD4FC      ; MD5Update
  17.     dec edi         ; 10h->0Fh->...
  18.     jnz L308B7616       ; Вызывать 10h раз MD5Update
  19.     lea ecx,[ebp-68h]  
  20.     call P309AD5A9      ; MD5Final
  21.  

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

Код (Text):
  1.  
  2. bpx cs:308B7616
  3.  

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

Код (Text):
  1.  
  2. d ss:ebp-10
  3.  

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

Код (Text):
  1.  
  2. 06 D4 96 32 C9 ...
  3.  

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

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

Код (Text):
  1.  
  2. .data
  3. ; Тестовый "пароль"
  4. TestFiveBytes db 000h,000h,000h,000h,000h
  5.  
  6. @@FindPassword:
  7. ; ...
  8.   push 0826BB25Ch        
  9.   push offset Password 
  10.   push offset LocBuffer
  11.   call P308B774C        ; Вызов главного кода
  12.   jz @@PasswordFound
  13.   xor ebx,ebx
  14. @@NextLetter:
  15.   inc byte ptr TestFiveBytes[ebx]
  16.   jnz @@FindPassword
  17.   inc ebx
  18.   cmp ebx,MaxPassLen
  19.   jb  @@NextLetter
  20. ; ...
  21.   call P309AD5A9    ; MD5Final
  22. ; Подставляем переборочный хэш, причем ТОЛЬКО 5 байт !!!
  23.   pushad
  24.   lea edi,[ebp-10h] ; <-62B9B0 - UN1- адрес буфера для MD5Update
  25.   mov esi,offset TestFiveBytes
  26.   mov ecx,5
  27.   cld
  28.   rep movsb
  29.   popad
  30. ; ...
  31.   pop edi
  32. L308B7616:
  33.  

  Теперь нам надо перебирать "всего" 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):
  1.  
  2. ; Это программа - подборщик паролей к файлам Excel.
  3. ; На самом деле подбирает не сам пароль, а первые 5 байт некоторого хэша MD5.
  4. ; Затем необходимо при открытии файла разыскать в теле кода Excel процедуру
  5. ; формирования этого хэша(вызывается при открытии файла, защищенного паролем)
  6. ; и подставить вместо ПЕРВЫХ ПЯТИ байт полученного хэша найденные(пароль,
  7. ; введенный в диалоговом окне, при этом НЕВАЖЕН !)
  8. ; Как это сделать ? ;) Проще всего в софтайсе установить туда бряк и заменить
  9. ; его по-быстрому ручками. Можно пропатчить Excel, именно это место, таким образом
  10. ; чтобы вводить с клавиатуры не сам пароль, а именно хэш(найденный, первые пять байт)
  11. ; Ну - это дело вкуса ;0) Адрес в Excel от Office-97
  12. ; находится в :308B74FD(чуть ниже, ВТОРОЙ ВЫЗОВ ПОСЛЕ ВВОДА ЛЮБОГО ПАРОЛЯ).
  13. ; А теперь о том, как устроен алгоритм проверки пароля(все это есть в коде, но слова
  14. ; не помешают):
  15. ; Итак, вначале читается пароль с диалогового окна и 16 байт из файла по
  16. ; адресу 222h(buff2). Далее образуется MD5-хэш от пароля(16 байт, кто не в курсе).
  17. ; От полученного хэша(ВНИМАНИЕ !) h1 берутся первые пять байт
  18. ; (вот тут-то и нужна замена и подбор !) и дополняются 16-тью байтами
  19. ; из файла, потом полученная последовательность дублируется 16 раз
  20. ; и от нее получаем хэш h2 = MD5((h1(0..4)+buff2)x16).
  21. ; Далее от этого хэша и 4-х нулей формируем еще один хэш - h3,
  22. ; причем ОПЯТЬ БЕРЕМ первые пять байт(в принципе, тут тоже можно
  23. ; перебирать хэш, БУДЕТ ДАЖЕ в 2-3 раза БЫСТРЕЕ !).
  24. ; ИМЕННО ЭТО ТУТ И ДЕЛАЕТСЯ.
  25. ; Затем этот h3 используется как ключ для RC4.
  26. ; Потом читаются два буфера из файла - с адресов 232h и 242h
  27. ; - назовем из buff3 и buff4. Оба расшифровываются по полученной
  28. ; таблице RC4 и от расшифрованного buff3 получаем хэш h4.
  29. ; Проверка верного пароля состоит в равенстве h4 и только
  30. ; что расшифрованного по RC4 buff4.
  31. ; Скорость подборки такая: тестирование проводилось
  32. ; на P120, 16МегRAM, win98, используются команды cli/sti.
  33. ; Код оптимизирован(мной) - было на мощном VC++, стало на asm.
  34. ; Один пароль занимает ~41.5 мкс.(~24000 вар/сек).
  35. ; При реальном использовании разумно будет дописать
  36. ; код заданием ИНТЕРВАЛА ПЕРЕБОРА(например, ищем с такого-то
  37. ; значения по такое-то).
  38. ; Ну вот и все. Удачи !
  39. ; Coded by Chingachguk, 2002.
  40.  

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

Код (Text):
  1.  
  2. .386
  3. .model flat,stdcall
  4. option casemap :none   ; case sensitive
  5. include \masm32\include\windows.inc
  6. include \masm32\include\user32.inc
  7. include \masm32\include\kernel32.inc
  8. includelib \masm32\lib\user32.lib
  9. includelib \masm32\lib\kernel32.lib
  10.  

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

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

Код (Text):
  1.  
  2. ; Макрос PFF. Необходим для вычисления MD5. Выполняет следующее:
  3. ; _a = ((((_b and _c) or (not(_b) and _d)) + a + _ac + _x) rol _s) + _b
  4. PFF macro  _a,_b,_c,_d,_x,_s,_ac
  5.   lea  ecx,[_a+_ac]
  6.   mov  _a,_b
  7.   and  _a,_c
  8.   add  ecx,_x
  9.   mov  edx,_b
  10.   not  edx
  11.   and  edx,_d
  12.   or   _a,edx
  13.   add  _a,ecx
  14.   rol  _a,_s
  15.   add  _a,_b
  16. endm
  17. ; Макрос PGG. Необходим для вычисления MD5. Выполняет следующее:
  18. ; _a = (((_b and _d) or (_c and (not(_d)) + _a + _ac + _x) rol _s) + _b
  19. PGG macro  _a,_b,_c,_d,_x,_s,_ac
  20.   mov  ecx,_c
  21.   mov  edx,_d
  22.   not  edx
  23.   and  ecx,edx
  24.   mov  edx,_b
  25.   and  edx,_d
  26.   or   edx,ecx
  27.   lea  _a,[_a+edx+_ac]
  28.   add  _a,_x
  29.   rol  _a,_s
  30.   add  _a,_b
  31. endm
  32. ; Макрос PHH. Необходим для вычисления MD5. Выполняет следующее:
  33. ; _a = ((((_b xor _c) xor _d) + _a + _ac + _x) rol _s) + _b
  34. PHH macro  _a,_b,_c,_d,_x,_s,_ac
  35.   mov  ecx,_b
  36.   xor  ecx,_c
  37.   xor  ecx,_d  
  38.   lea  _a,[_a+ecx+_ac]
  39.   add  _a,_x
  40.   rol  _a,_s
  41.   add  _a,_b
  42. endm
  43. ; Макрос PII. Необходим для вычисления MD5. Выполняет следующее:
  44. ; _a = (((_c xor (_b or (not(_d)))) + _a + _ac + _x) rol _s) + b
  45. PII macro  _a,_b,_c,_d,_x,_s,_ac
  46.   mov  ecx,_b
  47.   mov  edx,_d
  48.   not  edx
  49.   or   ecx,edx
  50.   mov  edx,_c
  51.   xor  edx,ecx
  52.   add  _a,edx
  53.   add  _a,_x
  54.   add  _a,_ac
  55.   rol  _a,_s
  56.   add  _a,_b
  57. endm
  58.  

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

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

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

Код (Text):
  1.  
  2. ; Буфер для передачи его MAIN
  3. StrangeBuffer db 80h, 15 dup(0)
  4.               db 16 dup(0)
  5.               db 16 dup(0)
  6.               db 16 dup(0)
  7. LocBuffer     db 4000h dup(0)
  8.  

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

Код (Text):
  1.  
  2.  
  3. .code
  4. start:
  5. ; MessageBox
  6.     push MB_OK
  7.     push offset szDlgTitle
  8.     push offset szMsg
  9.     push 0
  10.     call MessageBox
  11. ; Open Excel file
  12.     push OF_READ
  13.     push offset Unknown
  14.     push offset EXCELfile
  15.     call OpenFile
  16.     mov  descr,eax          ; Save file descriptor
  17.     cmp  eax,0ffffffffh
  18.     jnz  @@FileOpen
  19. ; File not open    
  20.     push MB_OK
  21.     push offset DebugFileOp
  22.     push offset DebugOpOK
  23.     push 0
  24.     call MessageBox
  25.     jmp  @@Exit
  26. @@FileOpen:
  27. ; Read File Block
  28.     push FILE_BEGIN
  29.     push NULL
  30.     push 222h
  31.     push descr
  32.     call SetFilePointer     ; iniHandle, Pointer, NULL, FILE_BEGIN
  33.     push NULL
  34.     push offset bRead
  35.     push 10h*3
  36.     push offset buffer1
  37.     push descr
  38.     call ReadFile           ; iniHandle, addr d_buffer, 2, addr bRead, NULL
  39.     push descr
  40.     call CloseHandle        ; Close excel file
  41.  

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

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

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

Код (Text):
  1.  
  2.  invoke CreateFile,offset LogFileName,GENERIC_WRITE,FILE_SHARE_WRITE,0,CREATE_ALWAYS,\
  3.     FILE_ATTRIBUTE_NORMAL,0
  4.  mov  descr,eax          ; Save file descriptor
  5.  cmp  eax,0ffffffffh
  6.  jnz  @@LogFileOpen
  7. .data
  8. OpenError db "File log.txt not open !",0
  9. .code
  10.  push MB_OK+MB_ICONERROR
  11.  push offset szDlgTitle
  12.  push offset OpenError
  13.  push 0
  14.  call MessageBox
  15.  jmp  @@Exit
  16. @@LogFileOpen:
  17. .data?
  18. StartRC4Table db 100h+4 dup(?)
  19. .code
  20.  

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

Код (Text):
  1.  
  2. ; Create Start RC4 Table
  3.   xor  eax,eax
  4.   mov  ecx,offset StartRC4Table
  5. @@StartFillRC4:
  6.   mov  [ecx+eax],al
  7.   inc  eax
  8.   cmp  eax,100h
  9.   jb   @@StartFillRC4
  10.   mov  dword ptr [ecx+100h],0
  11.  

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

Код (Text):
  1.  
  2. ; Будем выполнять проверку тестового пароля, введя
  3. ; начальное значение-99 - всего в данном примере выполнится 100
  4. ; раз код проверки пароля
  5.   sub  word ptr TestFiveBytes,99
  6.   cli
  7. @@FindPassword:
  8.  

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

Код (Text):
  1.  
  2.   push offset LocBuffer ; <- буфер для RC4 & хэша MD5 от пароля
  3.   call P308B774C        ; Вызов главного кода
  4.   test eax,eax
  5.   jz   @@PasswordFound
  6.  

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

Код (Text):
  1.  
  2.   xor  ebx,ebx
  3. @@NextLetter:
  4.   inc  byte ptr TestFiveBytes[ebx]
  5.   jnz  @@FindPassword
  6.   inc  ebx
  7.   cmp  ebx,5
  8.   jb   @@NextLetter
  9.   jmp  @@Exit
  10. @@PasswordFound:
  11.  

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

Код (Text):
  1.  
  2.   sti
  3.   push NULL
  4.   push offset bRead
  5.   push 5h
  6.   push offset TestFiveBytes
  7.   push descr
  8.   call WriteFile
  9. .data
  10. PasswordMsg   db "You Password found ! See pass_ex.txt file.",0
  11. .code
  12.   push MB_OK
  13.   push offset PasswordTitle
  14.   push offset PasswordMsg
  15.   push 0
  16.   call MessageBox
  17. @@Exit:
  18. .data
  19. EndWork db "All done ... ;)",0
  20. .code
  21.   sti
  22.   push MB_OK
  23.   push offset szDlgTitle
  24.   push offset EndWork
  25.   push 0
  26.   call MessageBox
  27.   invoke CloseHandle,descr
  28.   push 0
  29.   call ExitProcess
  30.  

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

Код (Text):
  1.  
  2. ; "Чтение" buff3 & buff4
  3. Read_buff3_buff4 proc
  4.   push edi
  5.   mov  edi,edx
  6.   mov  esi,offset buffer2
  7.   mov  ecx,10h*2/4
  8.   rep  movsd
  9.   pop  edi
  10.   ret
  11. Read_buff3_buff4 endp
  12.  

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

Код (Text):
  1.  
  2. ; Точка входа в проверку кода, адрес буфера в стеке.
  3. P308B774C proc
  4.   push ebp
  5.   mov  ebp,esp
  6. ; Выделим место для локальных переменных: контекста MD5, ... etc
  7.   sub  esp,08Ch
  8.   mov  edi,[ebp+08h]    ; <- адресом буфера
  9.   lea  ecx,[ebp-8Ch]
  10.   call P309AD4D2          ; MD5Init
  11.  

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

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

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

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

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

Код (Text):
  1.  
  2. ; Прочитать buff3 & buff4
  3.   lea  edx,[ebp-24h]    ; buff3
  4.   call Read_buff3_buff4
  5. ; Отксорить buff3 & buff4
  6.   lea  eax,[ebp-24h]    ; <- buff3,buff4
  7.   push eax ; Адрес buff3
  8.   mov  edx,10h*2        ; Len of buff3 & buff4
  9.   lea  ecx,[edi+04]     ; RC4Table
  10.   call P309AD416
  11.  

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

Код (Text):
  1.  
  2.   lea  ecx,[ebp-8Ch]
  3.   call P309AD4D2    ; MD5Init
  4.   push 10h      ; 10h - длина буфера
  5.   lea  edx,[ebp-24h]    ; buff3 !!! - адрес буфера
  6.   lea  ecx,[ebp-8Ch]    ; контекст MD5
  7.   call P309AD4FC    ; MD5Update
  8.   lea  ecx,[ebp-8Ch]    ; адрес контекста
  9.   call P309AD5A9    ; MD5Final
  10.  

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

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

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

Код (Text):
  1.  
  2. Next_RC4 macro
  3.   add  al,dl ; +key[i]
  4.   mov  bl,[ecx+esi] ; S[i]
  5.   shr  edx,8 ; Next key[i]
  6.   add  al,bl
  7.   mov  bh,[ecx+eax] ; bh=S[j]
  8.   mov  [ecx+eax],bl ; S[j]=S[i]
  9.   mov  [ecx+esi],bh ; S[j]=S[i]
  10.   inc  esi
  11. endm
  12. Next4_RC4 macro
  13.   Next_RC4  
  14.   Next_RC4  
  15.   Next_RC4  
  16.   add  al,dl ; +key[i]
  17.   mov  bl,[ecx+esi] ; S[i]
  18.   add  al,bl
  19.   mov  bh,[ecx+eax] ; bh=S[j]
  20.   mov  [ecx+eax],bl ; S[j]=S[i]
  21.   mov  [ecx+esi],bh ; S[j]=S[i]
  22.   inc  esi
  23. endm
  24. ; edx=len_key, ecx=addr of RC4Table, stack = key offset
  25. ; НО ТОЛЬКО ЕСЛИ КЛЮЧ ДЛИНОЙ 10h !!!
  26. RC4Table_10h proc
  27.   pushad
  28.   mov  esi,offset StartRC4Table
  29.   mov  ebx,ecx
  30.   mov  edi,ecx
  31.   mov  ecx,100h/4
  32.   rep  movsd
  33.   movsw ; Два нулевых счетчика после 0,1,...255
  34.   mov  ecx,ebx
  35.   xor  esi,esi ; i
  36.   xor  eax,eax ; j
  37.   mov  ebp,[esp+7*4+8]
  38. @@FillRC4:
  39. ; Key[1..4]
  40.   mov  edx,[ebp]
  41.   Next4_RC4
  42. ; Key[5..8]
  43.   mov  edx,[ebp+4]
  44.   Next4_RC4
  45. ; Key[9..12]
  46.   mov  edx,[ebp+8]
  47.   Next4_RC4
  48. ; Key[13..16]
  49.   mov  edx,[ebp+0Ch]
  50.   Next4_RC4
  51.   cmp  esi,100h
  52.   jb   @@FillRC4
  53.   popad
  54.   ret  4
  55. RC4Table_10h endp
  56.  

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

Код (Text):
  1.  
  2. ;*** EDX=len of buff, ecx=offset RC4 Table, stack = buff_addr
  3. P309AD416 proc
  4.   push ebp         
  5.   mov  ebp,esp
  6.   push ebx
  7.   push esi
  8.   push edi         
  9.   mov  edi,[ebp+8] ; edi=Адрес буффера
  10.   lea  ebp,[edi+edx] ; buff_addr+len_buff
  11.   xor  ebx,ebx
  12.   xor  edx,edx
  13.   mov  bl,[ecx+100h] ; Q1
  14.   mov  dl,[ecx+101h] ; Q2
  15. @LOOP:
  16.   xor  eax,eax
  17.   inc  bl
  18.   mov  al,[ecx+ebx] ; S[Q1]
  19.   add  dl,al
  20.   mov  ah,[ecx+edx] ; S[Q2]
  21.   mov  [ecx+edx],al
  22.   mov  [ecx+ebx],ah
  23.   add  al,ah
  24.   and  eax,0FFh
  25.   mov  al,[ecx+eax]
  26.   xor  [edi],al ; xor buffer[edi],gamma
  27.   inc  edi
  28.   cmp  edi,ebp
  29.   jl   @LOOP
  30.   mov  bh,dl
  31.   mov  [ecx+100h],bx    ; Запомнить счетчик 1,2
  32.   pop  edi
  33.   pop  esi
  34.   pop  ebx
  35.   pop  ebp  
  36.   ret  4
  37. P309AD416 endp
  38.  

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

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

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

Код (Text):
  1.  
  2. ; Подпрограмма MD5Update( MD5_CTX - контекст MD5, буфер, длина буфера )
  3. ; MD5_CTX - контекст MD5 - в ECX (его адрес)
  4. ; буфер - в EDX (его адрес)
  5. ; длина буфера - в стеке (первое слово за адресом возврата)
  6. P309AD4FC proc  ; Что-то делаем с 24 байтами по ECX
  7.   push ebp
  8.   mov  ebp,esp
  9.   push edi     
  10.   mov  esi,ecx        ; Адрес переданного по ECX контекста
  11.   mov  ebp,[ebp+8h]   ; Длина буффера, переданного нам > EBP
  12.   mov  eax,ebp
  13.   mov  ebx,edx        ; адрес буффера -> EBX
  14.   mov  edx,[esi]      ; 4 байта из переданного в ECX контекста
  15.   mov  ecx,edx        ; Положить их в ecx
  16.   lea  eax,[edx+eax*8]  ; Длина массива * 8 + dd из контекста
  17.   and  ecx,01F8h      ; Первые 4 байта из контекста and 01F8h
  18.   shr  ecx,3          ; разделить их на 8
  19.   cmp  eax,edx        ; Сравнить: (Длина массива * 8 + dd из контекста) и (dd из контекста)
  20.   jae  L309AD522      ; Вышли за пределы ?
  21.   inc  dword ptr [esi+04] ; Увеличить следующий dd из контекста
  22. L309AD522:
  23.   mov  [esi],eax      ; в контекст положить вычисленный eax
  24.   mov  eax,ebp        ; Длина буффера, переданного нам > EAX
  25.   shr  eax,1Dh        ; Оставить 31,30 и 29 биты ? - 00,00,..
  26.   add  [esi+04],eax   ; Добавить к следующему dd контекста
  27.   dec  ebp            ; Уменьшить длину буффера, переданного нам
  28.   js   L309AD59F      ; Равна 0 ? -> На выход
  29. L309AD53A:
  30.   mov  al,[ebx]       ; Взять байт переданного буффера
  31.   inc  ebx            ; Указать на следующий байт
  32.   mov  [ecx+esi+18h],al ; Положить его в [4 байта из переданного в ((ECX and 01F8h) shr 3 +
  33. ; + адрес переданного массива в 24 байта + следующие в нем 24 байта)
  34.   inc  ecx        ; Прирастить разделенный на 8 первый элемент массива
  35.   cmp  ecx,40h        ; Он вдруг стал 64 ?
  36.   jnz  L309AD593      ; Да, да ...
  37.   lea  edx,[esi+18h]
  38.   lea  ecx,[esi+08h]
  39.   call P309AD671      ; МАКРОСЫ
  40.   xor  ecx,ecx
  41. L309AD593:
  42.   dec  ebp            ; Длина буффера, переданного нам
  43.   jns  L309AD53A
  44. L309AD59F:
  45.   pop  edi
  46.   pop  ebp
  47.   ret  4
  48. P309AD4FC endp
  49.  

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

Код (Text):
  1.  
  2. ; (MD5_CTX - контекст, digest - наполняемый буфер 10h)
  3. ; ECX - адрес контекста MD5
  4. P309AD5A9 proc      ; ECX=адрес контекста
  5.   push edi
  6.   mov  eax,[ecx+4]  ; Взять второе слово контекста
  7.   mov  esi,ecx        ; Адрес контекста
  8.   mov  [esi+18h+3Ch],eax ; <- второе слово контекста
  9.   mov  ecx,[esi]          ; Первое слово контекста
  10.   mov  [esi+18h+38h],ecx ; <- Первое слово контекста
  11.   and  ecx,1F8h       ; с 1-м словом
  12.   shr  ecx,3        ; /8
  13.   mov  eax,38h
  14.   cmp  ecx,eax
  15.   jl   L309AD5D5
  16.   mov  eax,78h
  17. L309AD5D5:
  18.   sub  eax,ecx
  19.   mov  edx,offset StrangeBuffer
  20.   push eax
  21.   mov  ecx,esi        ; Адрес буфера
  22.   call P309AD4FC          ; MD5Update
  23.   lea  edx,[esi+18h]
  24.   add  esi,8h
  25.   mov  ecx,esi
  26.   call P309AD671          ; Макроподстановки MD5
  27.   lea  edi,[esi+50h]
  28.   movsd
  29.   movsd
  30.   movsd
  31.   movsd
  32.   pop  edi
  33.   ret
  34. P309AD5A9 endp
  35.  

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

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

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

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

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

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

Код (Text):
  1.  
  2. 10^12 / 2x10^4 ~= 5x10^7 секунд,
  3.  

  или

Код (Text):
  1.  
  2. 5*10^7 / 3600 ~= 1,5x10^4 часов,
  3.  

  или

Код (Text):
  1.  
  2. 1.5*10^4 / 24 ~= 625 дней ~= 21 месяц.
  3.  

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

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

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

Код (Text):
  1.  
  2. ~ 62 дней (два месяца).
  3.  

  Во-втрорых, более умный перебор. Что мы на самом деле подбираем ? Фактически, 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):
  1.  
  2. 20 +/- sqrt(20) ~= 20 +/- 5
  3.  

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

Код (Text):
  1.  
  2. 5 +/- sqrt(5) ~= 5 +/- 3
  3.  

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

Код (Text):
  1.  
  2. 1-2 +/- sqrt(1-2) ~= 1-2 +/- 1-2
  3.  

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

Код (Text):
  1.  
  2. Число 0/1 - 18/22
  3. Число пар - 4/6/4/6
  4. Число четверок - 0/1/1/0/1/0/0/1/1/1/0/0/0/2/1/1
  5.  

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

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


0 1.445
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532