Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 12 ноября!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

"Гарант" или вскрытие выродка

Обсудить статью на форуме

Хорошая подборка видеоуроков, инструментов крэкера, книг и статей - здесь.

Автор: Devastator <trotsevskiy@u-b.ru>

Цель статьи: обучение мозга мыслительным процессам
Предмет исследования: Гарант Максимум за 2002 год.
Конечная цель исследования: изучение алгоритма генерации регестрационных данных.
Инструментарий: Отладчик SoftIce v4.05 (WinNT) или выше.
Дизассемблер IDA v4.3.0.740a или выше.
Мозги (желательно обученные ассемблеру)

FF. Вместо вступления.


Разгребал на работе завалы CD дисков и к своему удивлению нашел "Гарант - Максимум c региональным законодательством" аж на 2-х дисках и аж за 2002 год. Этот программный продукт и стал предметом моего исследования. Если кто-то скажет: "Старьё", то я его полностью поддержу. Однако организация заплатила за него около 300000 деревянных. И мне стало интересно, если контора закалачивает такие деньги, то и защищать свои программные продукты ОБЯЗАНА СООТВЕТСТВУЮЩИМ ОБРАЗОМ. Я ошибался...
Кстати на счёт дистрибутива, может выложить куда, а то я боюсь не найдёте. Короче пишите приватом в форум.

0. Знакомство с защитой.


Вставил диск и проинсталил за 1 минуту. Setup попросил ввести User Name, Org. Name и Serial Number. Вот думаю и добрался до того места, где интересы программы расходятся с моими. Ан нет. Ввёл серийный номер от балды и setup его проглотил.
Далее setup спросил запустить ли установку баз данных? Конечно. Setup запустил другой процесс (Breedr32.exe) и был таков.
Breeder32 - ну и имечко, созвучно с русским словом бред, а если с буржуинского переводить то получается толи отпрыск, толи выродок:). Учтиво следуя инструкциям выродка поподаем в диалог,в котором требуется ввести "Отзыв" при известном "Пароле".
Вводим от балды отзыв и... о боже: комп пищит как резаный и вываливается MessageBox, а затем процесс завершается. Нда...
Лезем в справку и читаем: Активация по схеме "Пароль-Отзыв"... Странно, а я что-то других схем и не пронаблюдал. Ладно читаем дальше: куда-то позвоните, и там вам сообщат на основе пароля ваш отзыв. Кстати при каждом новом запуске выродка генерится новый "пароль" вида 5F-9037(например).

1.Локализация защиты.


Из личного опыта: чем больше прога информирует юзера о неправильности/правильности его регистрационных данных, тем уязвимее защита. Т.е. в идеале после ввода регистрационных данных пользователью нужно сообщить минимум информации, а ещё лучше просто промолчать. А вы глянте на выродка: и пищит и MessageBoxA-ми отплёвывается. Конечно, чтобы локализовать защиту,есть множество способов, но если Вас приглашают в открытые ворота, то стоит в них заглянуть(сейчас хакеры(не крекеры) читают и смеются).
Итак при вводе не правильного "отзыва" сначала слышим писк, а затем видим MessageBox - отсюда вывод: код ответственный за писк исполняется перед вызовом MessageBox. Очень интересная ситуация,а вот представте и писк и бокс дают о себе знать одновременно. Поразмышляйте - оно очень полезно. Это будет значить что мы имеем или очень быстрый код(если задержка между сигналом и боксом практически не различима) или нас имеет многопоточность:). К слову сказать защиты, основанные на многопоточности редкость, во всяком случае для меня.
И так за писк скорее всего отвечает функция наподобее Beep из kernel32, а за бокс собственно сам MessageBoxA(W) из библиотеки User32 либо иной модальный диалог. Скорее всего и та и другая функции вызываются вызываются не напрямую, а через функции-переходники. Отловим в отладчике какую-нибудь функцию, подходящюю под описание. Для этого используем брекпоинты.
Переходим в адресное пространство исследуемого поцесса: addr breedr32
Устанавливаем брекпоинты:(хватит единственного) bpx User32!MessageBoxA
Отладчик всплывает на вызове функции User32!MessageBoxA. Посмотрим откуда произошол вызов - в отладчике исполним p ret
Оказывается по адресу cs:6С3СС8E1. Посмотрим в адресном пространстве какого модуля мы находимся: mfc42!.text.
Всётаки функция действительно вызывается не на прямую, а посредством функций библиотеки mfc42.dll.
Опять ищем вызов функции (до тех пор пока не окажемся в адресном пространстве исследуемого модуля - в нашем случае Breedr32).
Функция mfc вызывается по адресу cs:41646C в модуле Breedr32!.text.
Делаем логичное предположение: код, вызывающий функцию mfc вероято(но не очевидно) не получит управления, если пользователем введён правильный "отзыв".Основываясь на этом предположении можно с большой долей вероятности утверждать, что имеет место быть сравнение введённого нами "отзыва" с сгенерированым приложением, причём над этими строками возможно осуществлены преобразования, т.е. сравниваются hash-значения этих строк.
Таким образом мы локализовали код, ответственный за информирование пользователя о неверно введённом "отзыве". Как показывает практика и мой личный опыт функция сравнения будет той, после выполнения(и анализа возвращённого ей значения) которой будет в том или ином виде осуществлена передача управления коду, остветственному за обработку ошибки,т.е. вызовется функция mfc по адресу cs:41646C.

2. Анализ защиты или вскрытие покажет.


Просмотрим фрагмент кода в IDA. Адреса в IDA и отладчике будут совпадать. Подробнее об этом читайте мою предидущую статью.

.text:00416413 loc_416413:                             ; CODE XREF: sub_415B20+8DCj
 .text:00416413                 mov     eax, [ebp+18Ah]
 .text:00416419                 mov     edi, ds:atoi
 .text:0041641F                 mov     esi, [ebp+102h]
 .text:00416425                 push    eax
 .text:00416426                 call    edi ; atoi
 .text:00416428                 push    esi
 .text:00416429                 mov     ebx, eax
 .text:0041642B                 call    edi ; atoi
 .text:0041642D                 add     esp, 8
 .text:00416430                 cmp     eax, ebx
 .text:00416432                 jz      short loc_41647F		>>0
 .text:00416434                 mov     esi, ds:Beep
 .text:0041643A                 push    0C8h            ; dwDuration
 .text:0041643F                 push    3E8h            ; dwFreq
 .text:00416444                 call    esi ; Beep			>>1
 .text:00416446                 push    64h             ; dwMilliseconds
 .text:00416448                 call    ds:Sleep			>>2
 .text:0041644E                 push    0C8h            ; dwDuration
 .text:00416453                 push    3E8h            ; dwFreq
 .text:00416458                 call    esi ; Beep			>>3
 .text:0041645A                 mov     ecx, dword_445AB8
 .text:00416460                 mov     edx, dword_445AAC
 .text:00416466                 push    10h
 .text:00416468                 push    ecx
 .text:00416469                 push    edx
 .text:0041646A                 mov     ecx, ebp
 .text:0041646C                 call    CWnd::MessageBoxA(char const *,char const *,uint) 	>>4
 .text:00416471 
 .text:00416471 loc_416471:                             ; CODE XREF: sub_415B20+B5j
 .text:00416471                 push    0FFFFFFFFh
 .text:00416473                 mov     ecx, ebp
 .text:00416475                 call    CDialog::EndDialog(int)
 .text:0041647A                 jmp     loc_416C39

Бегло посмотрите на фрагмент, приведённый выше. Как и предполагалось: для писка приложение использует функцию Beep, а для бокса CWnd::MessageBoxA (MFC). Также наблюдаем функцию закрытия диалога.
>>0 А вот и условный переход. Если значения регистров еах и ebx не равны, мы имеем не верный "отзыв".
>>1 Пикаем.
>>2 Ждём.
>>3 Пикаем.
>>4 Показываем бокс.

Теперь давайте внимательно рассмотрим следующий фрагмент.

.text:00416413 loc_416413:                             ; CODE XREF: sub_415B20+8DCj
 .text:00416413                 mov     eax, [ebp+18Ah]	>>0
 .text:00416419                 mov     edi, ds:atoi
 .text:0041641F                 mov     esi, [ebp+102h]	>>1
 .text:00416425                 push    eax
 .text:00416426                 call    edi ; atoi		>>2
 .text:00416428                 push    esi
 .text:00416429                 mov     ebx, eax		>>3
 .text:0041642B                 call    edi ; atoi		>>4
 .text:0041642D                 add     esp, 8
 .text:00416430                 cmp     eax, ebx		>>5
 .text:00416432                 jz      short loc_41647F	>>6

>>0 В eax указатель на "строку 1". Почему указатель на строку? Потому что функция WinApi atoi принимает в качестве аргумента указатель на строку.
>>1 В esi указатель на "строку 2"
>>2 Вызываем функцию atoi (Ansi TO Integer) с целью преобразования "строки 1" к целочисленному виду.
>>3 Записываем результат функции в регистр ebx.
>>4 Производим аналогичные преобразования со "строкой 2". Результат как обычно в регистре eax.
>>5 Сравниваем результаты преобразований на предмет их идентичности.
>>6 Выполняем переход при равенстве.
Чтож, мы разобрали довольно оригинальное сравнение двух строк. Здесь реализован принцип не побайтового сравнения, а принцип хеширования. В качестве функции хеш-преобразования использована функция atoi.
Если кому не терпится можете пропатчить условный переход.

Теперь мне хотелось бы сказать несколко слов о двух строках кода, приведённых ниже.

.text:00416413                 mov     eax, [ebp+18Ah]
 .text:0041641F                 mov     esi, [ebp+102h]

Как видим, при расчёте эффективного адреса переменной в качестве базового регистра используется ebp. Существует такое понятие как ebp-based функция. Что это такое? Рассмотрим пример:

Test_Sub proc
 	push ebp		>>0
 	mov  ebp,esp	>>1
 	mov eax,[ebp+1Ch]	>>2
 	pop ebp		>>3
 endp	

>>0 Сохраним значение регистра ebp в стеке.
>>1 ebp содержит адрес вершины стека.
>>2 Обращение к переменной, используя в качестве базового регистра ebp. Очень удобно, так как вершина стека(esp) может меняться в ходе выполнения функции, а ebp обычно остаётся неизменным. Причём это удобно для компилятора в первую очередь, так как он ответствен за вычисление эффективных адресов переменных. Представте функцию, которая вызывает около 200(к примеру) других функций. Компилятор должен отслеживать значение esp(которое меняется при вызове функций) для того чтобы правильно расчитать эффективный адрес переменных находящихся в стеке. Однако это не эффективно - слишком много вычислений, гораздо легче сохранить значение esp, и обращаться к переменным просто прибавляя заранее просчитанное смещение.
>>3 восстановление значения ebp из стека.
Так к чему я всё это расказывал? Ах да... Я заметил в IDA, что при анализе ebp-based функций имеем такие вот интересные выражения.
Например:
mov eax,[ebp+arg_0+1Ch] - это обращение к аргументу функции.
mov eax,[ebp+var_0+1Ch] - это обращение к локальной переменной функции.
mov eax,[ebp+1Ch] - это обращение к глобальной переменной.

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

.text:00415B20 sub_415B20      proc near               ; CODE XREF: .text:00413C20p
 .text:00415B20                                         ; .text:0041483Bp
 .text:00415B20 var_1F8         = dword ptr -1F8h
 .text:00415B20 var_1F4         = dword ptr -1F4h
 .text:00415B20 var_1F0         = dword ptr -1F0h
 .text:00415B20 hMem            = dword ptr -1ECh
 .text:00415B20 Buffer          = byte ptr -1E8h
 .text:00415B20 var_1E4         = dword ptr -1E4h
 .text:00415B20 var_1D4         = byte ptr -1D4h
 .text:00415B20 var_1C0         = byte ptr -1C0h
 .text:00415B20 var_15C         = byte ptr -15Ch
 .text:00415B20 var_11C         = byte ptr -11Ch
 .text:00415B20 var_DC          = byte ptr -0DCh
 .text:00415B20 var_9C          = byte ptr -9Ch
 .text:00415B20 var_5C          = byte ptr -5Ch
 .text:00415B20 var_28          = dword ptr -28h
 .text:00415B20 var_1C          = dword ptr -1Ch
 .text:00415B20 var_18          = dword ptr -18h
 .text:00415B20 var_14          = dword ptr -14h
 .text:00415B20 var_C           = dword ptr -0Ch
 .text:00415B20 
 .text:00415B20                 mov     eax, large fs:0	>>0
 .text:00415B26                 push    0FFFFFFFFh
 .text:00415B28                 push    offset loc_438CE5
 .text:00415B2D                 push    eax
 .text:00415B2E                 mov     large fs:0, esp	>>0
 .text:00415B35                 sub     esp, 1D4h		>>1
 .text:00415B3B                 push    ebx
 .text:00415B3C                 push    ebp		>>2
 .text:00415B3D                 push    esi
 .text:00415B3E                 push    edi
 .text:00415B3F                 mov     ebp, ecx		>>3

>>0 Что-то непонятное. Много раз встречал в приложениях на MFC.
>>1 Выделяем место в стеке под локальные переменные.
>>2 Сохраняем значение ebp
>>3 Опаньки. В ebp не указатель на вершину стека, а нечто другое. Здесь имеет место быть передача аргументов функции через регистры общего назначения(этим кстати ОЧЕНЬ любит баловаться компилятор от Borland). Посмотрим на перекрёстные ссылки для вызовов sub_415B20 чтобы узнать чтоже передавалось через регистр ecx. Имеем:

.text:00414839                 mov     ecx, ebp
 .text:0041483B                 call    sub_415B20

Опять двадцать пять. Нда... если так и будет продолжаться мы просто утонем в коде. В функцию передаётся указатель на локальные переменные другой функции. Итак IDA нам здесь мало чем поможет. Конечно можно по полочкам разложить несколько мегабайт кода, но я к сожалению не проходил курсы камикадзе.
Поступим проще. Определим адреса строк в отладчике, а затем посмотрим изменится ли где нибудь в функции sub_415B20 их значение. Конкретно нас
интересует строка, содержащая сгенерированный "отзыв".
addr breedr32
bpx 416413
Итак имеем

.text:00416413                 mov     eax, [ebp+18Ah]  	>>0
 .text:00416419                 mov     edi, ds:atoi
 .text:0041641F                 mov     esi, [ebp+102h]	>>1

>>0 Указатель. eax = 0x1024AF0. Здесь наш "отзыв".
>>1 Указатель. esi = 0x1024910. Здесь сгенериный "отзыв".
Теперь точку останова на функцию sub_415B20.
addr breedr32
bpx 415B20
Опаньки, после того как сработал брекпоинт, по адресу ds:1024910 имеем уже сгенериный "отзыв". Он без изменений идёт на сравнение с введённым нами. В этом можно убедиться, если потрейсить(F10) код до момента сравнения. Отсюда вывод - генерится "отзыв" не в процедуре sub_415B20.
Чтож найдём место, откуда вызывалась функция sub_415B20 и повторим всё вышеописанное, дабы найти , где строка изменяется.
Итак вызов произошёл по адресу cs:41483B. Вот это да! Я устал искать (скролить) какой функции принадлежит сей кусок кода.
Чтож оставим это неблагодарное занятие.

Вернёмся в IDA. Давайте размышлять. [ebp+102h] содержит указатель на глобальную переменную. В большенстве случаев имеются ebp-based функции. Есть вероятность, что другие функции будут обращаться к этой переменной, а значит они будут использовать конструкции вида mov eax,[ebp+102h] или lea eax,[ebp+102h], причём в качестве базового регистра может быть использован отличный от ebp. Однако смещене(102h) должно быть одинаковам.
Поэтому можно попытаться простым поиском найти, где в листинге встречается константа 102h.
Итак смотрим первый результат поиска: .text:00410AA0 lea ecx,[esi+102h]

.text:00410AA0                 lea     ecx, [esi+102h]
 .text:00410AA6                 mov     byte ptr [esp+14h+var_4], 8
 .text:00410AAB                 call    CString::~CString(void)

Ага это вызывается деструктор для класса CString. И судя по всему в ecx указатель на наш буфер. Чтож не плохо. Теперь мы с небольшой долей вероятности можем утверждать, что строка относится к этому классу.
Далее: .text:00412DF2 код точно такой же

.text:00412DF2                 lea     ecx, [esi+102h]
 .text:00412DF8                 mov     byte ptr [esp+20h+var_4], 8
 .text:00412DFD                 call    CString::CString(void)

Стандартный конструктор для объекта класса CString.
Далее:.text:0041641F mov esi,[ebp+102h]

.text:00416426                 call    edi ; atoi
 .text:00416428                 push    esi
 .text:00416429                 mov     ebx, eax
 .text:0041642B                 call    edi ; atoi

Так этот фрагмент мы уже рассматривали.Смотри выше.
Далее:.text:004175DA lea edx, [esi+102h]

.text:004175D9                 push    ecx
 .text:004175DA                 lea     edx, [esi+102h]
 .text:004175E0                 push    offset aU_2     ; "%u"
 .text:004175E5                 push    edx
 .text:004175E6                 call    CString::Format(char const *,...)

Ух ты формирование строки. Это уже очень интересно. Возможно здесь из какого нибудь хеша делается строка.
Давайте рассмотрим поближе. Хотя поиск нашёл ещё около 20 вхождений. Если мы промахнулись, то рассмотрим их далее. Кстати если в [esi+102h] содержится указатель, то в edx будет адрес этого указателя.
Поставте брекпоинт на исполнение кода, до вызова CString::Format.
После выполнения функции регистры процессора содержат следующие данные: eax - длина получившейся строки,ecx - начало буфера,edx - конец буфера.
Итак на выходе мы имеем eсx = 0x1024910. А по этому адресу располагается буфер, который учавствует в сравнении строк(смотри выше). Сделаем смелое предположение, что это и есть сгенериный отзыв. Проверим и мы окажемся абсолютно правы.

Замечательно скажете вы, а как же увязывается "отзыв" с паролем. На самом деле несколько вариантов:
0.Из "пароля" генерится "отзыв".
1. Наоборот.
2. Всё генерится из общего хеша по разным ОБРАТИМЫМ алгоритмам.
А теперь давайте думать. Ведь "пароль" каждый раз получается разный, а значит используется некоторого рода генератор случайных чисел. Это значит, что если мы и найдём алгоритм генерации пароля(отзыва) из этого хеша, то будем не в состоянии предугадать этот хеш. Отсюда вывод: надо опираться либо на "пароль" либо на "отзыв" и найти точку сопряжения в генерации двух этих величин.
Ладно, давайте добивать генерацию отзыва.
Рассмотрим фрагмент:

.text:004175BC                 push    0
 .text:004175BE                 push    eax
 .text:004175BF                 call    dword ptr [edx+4]
 .text:004175C2                 mov     ecx, esi
 .text:004175C4                 call    sub_4174E0		
 .text:004175C9                 push    eax		
 .text:004175CA                 mov     [esi+0EEh], ax	
 .text:004175D1                 call    sub_410B70		
 .text:004175D6                 movsx   ecx, ax		>>1
 .text:004175D9                 push    ecx		>>0
 .text:004175DA                 lea     edx, [esi+102h]	
 .text:004175E0                 push    offset aU_2     ; "%u"
 .text:004175E5                 push    edx
 .text:004175E6                 call    CString::Format(char const *,...)

>>0 Здесь отправляем хеш на преобразование в строку "отзыв".
>>1 Возможно хеш является результатом работы функции sub_410B70, а может быть и нет. Посмотрим в отладчике, изменяется ли регистр eax. Да изменяется. В таком случае давайте резберём логику работы sub_410B70.
И так функция принимает один аргумент, а результат работы как обычно в eax.

.text:00410B70 sub_410B70      proc near               ; CODE XREF: sub_417520+B1p
 .text:00410B70 
 .text:00410B70 var_18          = dword ptr -18h
 .text:00410B70 var_14          = dword ptr -14h
 .text:00410B70 var_10          = word ptr -10h
 .text:00410B70 var_C           = byte ptr -0Ch
 .text:00410B70 arg_0           = word ptr  4
 .text:00410B70 
 .text:00410B70                 sub     esp, 18h
 .text:00410B73                 movsx   eax, [esp+18h+arg_0]	>>0
 .text:00410B78                 push    edi
 .text:00410B79                 push    eax
 .text:00410B7A                 lea     ecx, [esp+20h+var_C]		>>1
 .text:00410B7E                 push    offset aD_1     ; "%d"		>>2
 .text:00410B83                 push    ecx
 .text:00410B84                 call    ds:sprintf			>>3
 .text:00410B8A                 lea     edi, [esp+28h+var_C]
 .text:00410B8E                 or      ecx, 0FFFFFFFFh
 .text:00410B91                 xor     eax, eax
 .text:00410B93                 add     esp, 0Ch			>>4
 .text:00410B96                 xor     edx, edx
 .text:00410B98                 repne scasb			>>5
 .text:00410B9A                 not     ecx				
 .text:00410B9C                 mov     [esp+1Ch+var_18], edx
 .text:00410BA0                 add     ecx, 0FFFFFFFEh		>>6
 .text:00410BA3                 mov     [esp+1Ch+var_14], edx
 .text:00410BA7                 pop     edi
 .text:00410BA8                 test    cx, cx
 .text:00410BAB                 mov     [esp+18h+var_10], dx
 .text:00410BB0                 jl      short loc_410BCE
 .text:00410BB2                 movsx   ecx, cx
 .text:00410BB5                 push    ebx
 .text:00410BB6                 push    esi
 .text:00410BB7                 lea     eax, [esp+ecx+20h+var_C]	>>7
 .text:00410BBB                 lea     esi, [ecx+1]		>>8
 .text:00410BBE 
 .text:00410BBE loc_410BBE:                             ; CODE XREF: sub_410B70+5Aj	>>9
 .text:00410BBE                 mov     bl, [eax]
 .text:00410BC0                 movsx   ecx, dx
 .text:00410BC3                 inc     edx
 .text:00410BC4                 dec     eax
 .text:00410BC5                 dec     esi
 .text:00410BC6                 mov     byte ptr [esp+ecx+20h+var_18], bl		>>A
 .text:00410BCA                 jnz     short loc_410BBE				>>9
 .text:00410BCC                 pop     esi
 .text:00410BCD                 pop     ebx
 .text:00410BCE 
 .text:00410BCE loc_410BCE:                             ; CODE XREF: sub_410B70+40j
 .text:00410BCE                 lea     edx, [esp+18h+var_18]
 .text:00410BD2                 push    edx
 .text:00410BD3                 call    ds:atol					>>B
 .text:00410BD9                 imul    eax, eax					>>C
 .text:00410BDC                 mov     ecx, eax	
 .text:00410BDE                 shl     ecx, 6
 .text:00410BE1                 add     ecx, eax
 .text:00410BE3                 shl     ecx, 4
 .text:00410BE6                 add     ecx, eax
 .text:00410BE8                 mov     eax, 0FFFFFF69h
 .text:00410BED                 shl     ecx, 1
 .text:00410BEF                 sub     eax, ecx
 .text:00410BF1                 xor     eax, 0FFFFE14Eh
 .text:00410BF6                 not     eax
 .text:00410BF8                 and     eax, 7FFFh				>>C
 .text:00410BFD                 add     esp, 1Ch					>>D
 .text:00410C00 
 .text:00410C00 unknown_libname_21:
 .text:00410C00                 retn
 .text:00410C00 sub_410B70      endp

>>0 В еах загружаем значение аргумента функции.Это целочисленное значение, Однако может содержать знак.
>>1 В ecx указатель на буфер результата функции.
>>2 Указываем, что результирующая строка должна представлять целочисленный аргумент функции sprinf в десятичном виде.(было 0xF стало "16" например, кстати строка может содержать знак "-").
>>3 Собственно сам вызов функции sprintf. Для справки по этой функции (как и для любой другой) рекомендую пользоваться MSDN(MicrоSoft Developers Net www.msdn.com).
>>4 Корректировка стека после вызова функции.
>>5 Сканирование строки с целью узнать её длину.
>>6 В ecx длина строки. Кстати очень распространённая конструкция. Строка сканируется сама с собой. Выход из сканирования приосходит когда достигается 0 в строке, т.е. конца строки.
>>7 В еах указатель на послебний байт строки.
>>8 lea esi, [ecx+1] очень интересная конструкция. Вы знаете зачем нужна комманда lea? Почитайте описание. Там указано что она используется для вычисления адреса переменной или что-то в этом духе. Рассмотрим пример: lea eax,[ebp+esi+40h]. Обычно в ebp адрес начала массива данных, в еsi смешение в байтах относительно начала массива, а 40h это просто константа, интерпретируемая тоже как смещение. Что будет в eax? eax = ebp+esi+40h. А откуда процессор знает, что в ebp именно адрес массива? Правильно он не знает. А почему бы не использовать lea для сложения чисел?
Причём lea может складывать 2 регистра(один из которых может быть умножен на число кратное 2), и в добавок ко всему константу. И всё это одной командой. А add этого не умеет. Если перенести всё это к нашей ситуации, то esi будет содержать ecx+1, то есть длина строки + 1.
>>9 Сдесь в цикле делается строка-перевёртыш,т.е. было "-1567" стало "7651-".
>>A Строка перевёртыш будет в локальной переменной var_18.
>>B Вызываем atol (Ansi TO Long) и получаем на выходе в регистре eax целое беззнаковое число.
>>C Рассмотрим подробно.

.text:00410BD9                 imul    eax, eax		>> Возведение в квадрат eax = eax*eax	
 .text:00410BDC                 mov     ecx, eax		>> ecx = eax 
 .text:00410BDE                 shl     ecx, 6		>> ecx = ecx << 6
 .text:00410BE1                 add     ecx, eax		>> ecx  = ecx +eax
 .text:00410BE3                 shl     ecx, 4		>> ecx = ecx <<4
 .text:00410BE6                 add     ecx, eax		>> ecx = ecx + eax
 .text:00410BE8                 mov     eax, 0FFFFFF69h             >> eax = 0xFFFFFF69
 .text:00410BED                 shl     ecx, 1		>> ecx = ecx <<1
 .text:00410BEF                 sub     eax, ecx		>> eax = eax - ecx
 .text:00410BF1                 xor     eax, 0FFFFE14Eh	>> eax = eax ^ 0xFFFFE14E
 .text:00410BF6                 not     eax			>> eax  = !eax (побитовое инвертирование)
 .text:00410BF8                 and     eax, 7FFFh		>> eax = eax & 0x7FFF

Чтобы не заморачиваться с интерпретацией достаточно просто завести две переменные для eax и ecx соответственно и повторить алгоритм.
>>D Корректировка вершины стека после вызова atol.
Я думаю воплотить алгоритм работы функции в код Вам не составит труда.

Так функцию мы разобрали. Вернёмся к фрагменту:

.text:004175BC                 push    0
 .text:004175BE                 push    eax
 .text:004175BF                 call    dword ptr [edx+4]
 .text:004175C2                 mov     ecx, esi
 .text:004175C4                 call    sub_4174E0		
 .text:004175C9                 push    eax		
 .text:004175CA                 mov     [esi+0EEh], ax	>>0	
 .text:004175D1                 call    sub_410B70

>>0 Смотрите аргумент для только-что разобранной нами функции сохраняется в глобальную переменную. Зачем? Вероятно не просто для того чтобы использовать два байта памяти. Поскролим листинг в надежде узнать, какое применение находит эта переменная.
Есть!

.text:00417612                 xor     edx, edx
 .text:00417614                 mov     dx, [esi+0EEh]
 .text:0041761B                 push    edx
 .text:0041761C                 push    offset aXxU     ; "XX%u"
 .text:00417621                 push    ebx
 .text:00417622                 call    CString::Format(char const *,...)

Из переменной берётся хеш и преобразовывается в строку, причём первые два байта строки равны "XX". После выполнения функции ebx содежит указатель на сформированную строку.
Интересно, зачем это хеш преобразовывать в строку? Может быть формируется "пароль"? Давайте проверим. Смотрим дальше.

.text:00417627                 mov     eax, [ebx]		>>0
 .text:00417629                 mov     cl, [eax+2]		>>1
 .text:0041762C                 mov     byte ptr [eax+2], 2Dh	>>2
 .text:00417630                 mov     [eax], cl		                >>3
 .text:00417632                 mov     edx, [ebx]		>>4
 .text:00417634                 mov     ecx, [edx-8]		>>5
 .text:00417637                 mov     dl, [ecx+eax-1]	                >>6
 .text:0041763B                 add     dl, 11h		                >>7
 .text:0041763E                 mov     [eax+1], dl		>>8

>>0 В eax указатель на строку.
>>1 В cl 2-ой символ строки.(Учитесь читать с нуля).
>>2 Записываем вместо 2-ого символа "-". 0x2D в ASCII есть "-".
>>3 Записываем 2-ой символ(который сохранили в регистре cl) на место нулевого символа.
>>4 В edx указатель на строку.
>>5 Странно как-то. Кажется в ecx длина строки.
>>6 В dl последний(учитесь читать с нуля) символ строки.
>>7 dl = dl + 0x11
>>8 Записываем значение dl поверх первого(с нуля считаем) символа строки.
А дальше вызываются малоинересные API функции.
Посмотрите, очень напоминает "пароль". Проверте и вы в этом убедитесь.
Для наглядности приведу пример формирования "пароля". Пусть изначально имеем строку "XX12345",где начальный хеш в десятичной системе счисления равен 12345. Имеем:
>>2 "XX-2345"
>>3 "1X-2345"
>>8 "1F-2345"
То есть получается такая ситуация: имеется какой-то хеш, из него получаются "отзыв" и "пароль". Вспомним, что "пароль" нам известен, а необходимо найти "отзыв". Это значит, что если мы реверсируем алгоритм генерации "пароля" и узнаем хеш, то можем повторить алгоритм генерации "отзыва" и получить собственно отзыв. Реверсировать алгоритм генерации "пароля" и найти хеш элементарно. С генерацией отзыва по хешу тоже проблем возникнуть не должно, т.к. по-моему всё разжёвано так , что дальше некуда. Вот собственно и всё. Единственное, что тревожит мой ум, так это процесс генерации генерации хеша. Однако для создания кейгена это не требуется, а посему оставлю этот вопрос открытым.

3. General Protection Fault.
Вот мы и вскрыли выродка :) Кстати если посмотреть дистрибутив, то можно обнаружить папку со страшным названием HASPDRV. И почему это не использовано в защите? В справке написано, что можно выбирать способ авторизации: hasp, ключевая дискета и "пароль-отзыв". Три способа взлома программы. Лучше бы сделали один комбинированный метод. Похоже оставили только один. Сыро товарищи...сыро.

Коминг сун
XOR всему голова или что делают на www.security.ru


Обсуждение статьи: "Гарант" или вскрытие выродка >>>


Комментарии к статье: "Гарант" или вскрытие выродка

HikeR 10.07.2005 23:56:04
а сейчас там все в молебоксах...
---
Devastator 12.07.2005 08:57:38
екзешник
http://www.cracklab.ru/f/files/7e32_BREEDR32.rar
---
HackSoft 14.07.2005 09:39:13
Стоило столько писать, прога то ломается в пять скунд. В ней есть административная дыра. Кого интересует поищите SoftIceом после распаковки IVIP или Free - это режимы устаноки без ввода номера :)
---
junk 15.07.2005 07:06:10
Был бы молодцом если б поновее заценил.
версии с 5.4.x breedera использовали генератор по числу с конвертом его в 2 числа для строки отзыва, также, если ген не нужен, есть там sete/или setne - занопить и ок. Удачи всем
---
K@@ 26.08.2005 16:12:05
Верно, что сыро. Но щас в платформе F1 малость покруче. Хотя тоже ломаемо. :)
---
Andrey 06.09.2005 17:32:10
>Верно, что сыро. Но щас в платформе F1 малость покруче. Хотя тоже ломаемо. :)

А можно поподробнее пожалуйста...
---

Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS