> For the complete documentation index, see [llms.txt](https://yutewiyof.gitbook.io/intro-rev-ida-pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yutewiyof.gitbook.io/intro-rev-ida-pro/chast-19.md).

# Часть 19

[\[Используемые материалы\]](https://github.com/yutewiyof/intro-rev-ida-pro/tree/e1367e11cc661f3d69c02ae5f733b7dd168bc5ab/.gitbook/assets/files/19.zip)

В этой главе, у нас уже достаточно знаний и умений, чтобы реверсить оригинальный крекми **CRUEHEAD**а. Поэтому открываем его в **ЗАГРУЗЧИКЕ**, выключая опцию **MANUAL LOAD**. Исходный файл не упакован, так что нет необходимости загружать его вручную.

![](/files/-Ljtu1LwWFlu6J9v6Xjp)

Загрузчик остановился здесь. В нашем случае, поскольку это не консольное приложение, то нужно не только анализировать функцию **MAIN**. Мы знаем, что в оконных приложениях существует **ЦИКЛ**сообщений, который обрабатывает взаимодействие пользователя с окном, его выполненные щелчки, нажатия клавиш, движения мыши и т.д. и согласно каждому действию пользователя, цикл запрограммирован для выполнения различных действий с помощью этого кода.

Уже знаем, что первую вещь, которую мы должны сделать - это попробовать найти строки. Если из этого ничего не выйдет, мы должны искать **API** функции или функции, которые использует программа. В нашем случае, строки хорошо видны. Так что будем следовать этому пути.

![](/files/-LjqtGWC3KuEIIzBNKqg)

Переходя в строку **NO LUCK**, мы попадаем в область, где программа принимает какое-то решение. Для этого делаем двойной щелчок по этой строке и попадаем в это место.

![](/files/-LjqtGWEYE-EYplSWA_O)

Я могу видеть перекрёстные ссылки с помощью нажатия на клавишу **X** или **CTRL + X**

После нажатия на эту клавишу, видно, что существует две перекрестные ссылки на эту строку.

![](/files/-LjqtGWGQ9YhHupSyZWi)

Давайте посмотрим первую из них.

![](/files/-LjqtGWIGC_8Q0iRP9Wz)

Я закрашиваю этот блок в красный цвет, так как из-за этих инструкций происходит ошибка или плохое сообщение.

![](/files/-Ljtu1NDrX-ij5mBRtSX)

Давайте посмотрим, как в него можно попасть из программы.

![](/files/-Ljtu1NRvaaWgRhl6JpC)

Из этой картинки видно, что соседний блок должен вести в хорошее сообщение. Давайте посмотрим, что внутри этой функции по адресу **0x40134D**.

![](/files/-Ljtu1N_2r6xMozF-zGc)

После закрашивания, листинг будет выглядеть так.

![](/files/-Ljtu1NiAOlZu4JSp9ea)

Другая перекрёстная ссылка на строку **NO LUCK** указывает сюда.

![](/files/-Ljtu1NtylWpiPms1GC-)

В другое плохое сообщение можно попасть отсюда.

![](/files/-Ljtu1O4vKLzS1-eKptQ) ![](/files/-LjqtGWWgCcxbYZpZ0TG)

Видим, что аргумент, который передаётся в эту функцию, это адрес (**OFFSET, СМЕЩЕНИЕ, прим. Яши**) глобальной переменной, которую мы будем называть **STRING**. Если сделаем щелчок по этой переменной, то перейдём к адресу где она размещена в программе.

![](/files/-LjqtGWYhbuIYFoc44ld)

Видно, что это буфер длиной **3698** байт расположенный по адресу **0x40218E** находящийся в секции **DATA**.

Он имеет две ссылки. Чтобы увидеть, где в коде идёт работа с этой переменной, нажимаем **X**.

![](/files/-LjqtGW_TrIw0-GPQ-l6)

Видим, что существует перекрёстная ссылка, которая ведёт сюда.

![](/files/-Ljtu1OpDFz_uN7VRYVN)

**API** Функция **GetDlgItemTextA** используется для ввода каких либо данных в программу. Давайте посмотрим информацию про неё в **MSDN**.

![](/files/-Ljtu1P-ontcmQJR5u_0)

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

![](/files/-LjqtGWfrvay4VlxqUWC)

Видно, что существует две записи с тем же дескриптором **HWND**. Следовательно, я предполагаю, что они должны быть полями для ввода имени пользователя и пароля, которые поступают в крэкми, когда мы нажимаем кнопку в окне **REGISTER**.

![](/files/-Ljtu1PL8-tJfGQL8G9C)

Также, видно, что они имеют такие номера контрола **nIDDlgItem** : **0x3E8** и **0x3E9**.

![](/files/-Ljtu1PSfATdkHNlDY5X)

Используя программу **GREATIS WINDOWSE**, можно получить информацию о тех окнах, над которыми находится курсор. Взять её можно здесь.

<http://www.greatis.com/wdsetup.exe>

![](/files/-Lmttt-I-8EvSC47toHg)

Я вижу, что верхний **EDIT BOX CONTROL** равен **0x3E8**, а нижний - **0x3E9**.

Также, я могу переименовать буферы, в которые попадают введённые строки. Первый будет называться **STRING\_USER**, а второй **STRING\_PASSWORD**. Оба допускают только максимум **0x0B**символов, несмотря на то, что имеют буферы намного больше.

![](/files/-Ljtu1Pau3u1CVE9BMwm)

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

Мы уже видели, что программа обрабатывает буфер **STRING\_USER** здесь.

![](/files/-Ljtu1PmcdT5LnWIx52l)

Давайте анализировать, что программа делает здесь с этим буфером, но прежде, мы можем изменить имена функций, так как по-видимому первая обрабатывает буфер **STRING\_USER**, а вторая функция обрабатывает буфер **STRING\_PASSWORD**.

![](/files/-Ljtu1PyemUChxsPsjms)

Сейчас давайте анализировать функцию **PROCESA\_USER**.

![](/files/-Ljtu1Q9kMa8H5XbwfHC)

При переименовании переменной, я использую то же самое имя, которое выбрала **IDA**, хотя я мог бы написать **P**\_**STRING**\_**USER**, так как это также указатель на буфер.

С помощью **SET TYPE** я меняю тип функции и её аргументы и обращаю внимание, чтобы аргументы распространились в комментарии.

![](/files/-Ljtu1QKr6va3PuW19G4)

Видим, что пояснение, которое я добавил, совпадает с именем аргумента.

![](/files/-Ljtu1QVx5BS6aNTUDrB)

Видно, что существует **ЦИКЛ**, который будет читать **БАЙТЫ** буфера **STRING\_USER**. **ЦИКЛ** будет повторяться, пока он не будет равен нулю, т.е. пока не закончится строка и тогда программа сможет перейти на **ЗЕЛЁНУЮ** стрелку.

Здесь Вы видите **ЦИКЛ**. Он увеличивает **ESI**, чтобы читать побайтно каждый символ буфера **STRING\_USER**, и сравнивает каждый из них с числом **0x41**.

![](/files/-LjqtGWxqN_9hh5IcWj6)

![](/files/-Ljtu1QsXOJ5RQrOnrF_)

Мы можем сделать правый щелчок по числу **0x41** и изменить его на символ **A**, который является символом **ASCII** для этого значения.

![](/files/-LjqtGX0IExBemMJszIN)

Если значение в **AL** ниже, чем символ **A**, программа перебросит нас в зону **NO LUCK**. Если мы посмотрим в таблицу **ASCII**, увидим, что программа не принимает числа в имени **ПОЛЬЗОВАТЕЛЯ**, а только буквы, так как они больше или равны **A**.

![](/files/-LjqtGX26W1PLvTIN4L7)

Так что, программа проверяет, чтобы все символы буфера **STRING\_USER** были больше чем **0x41**, т.е. больше или равны символу **A**.

![](/files/-LjqtGX4K3ZVeTDxH16Y)

Программа, также, с помощью инструкции **JNB** проверяет, чтобы символ был не ниже символа **Z** и если это так передаёт управление на **БЛОК** по адресу **0x401394**. А если иначе, берет следующий символ и повторяет цикл.

Таким образом, программа обрабатывает все заглавные символы за исключением **Z**. Если символ больше или равен **Z**, то программа переходят в блок по адресу **0x401394**. Давайте посмотрим, что там делает программа.

![](/files/-LjqtGX6Yk4wZVmteb87)

Я назвал эту функцию **RESTA\_20**, потому что это то, что она делает. Если символ больше символа **Z**, программа вычитает из него число, сохраняет результат и выходит из функции.

![](/files/-Ljtu1RpzkYMIicw-NJP)

Другими словами, если вы введете символ с кодом **0x61**, что является маленькой буквой "**a**", программа вычтет из значения **0x20**, и получится значение **0x41**, что является большой буквой "**A**". Программа делает то же самое со всеми символами, которые больше или равны **Z**.

Если символ равен **Z**, то вычитая из него значение **0x20**, получим результат **0x3A**, что является символом двух точек "**:**"

![](/files/-Ljtu1Rz3Z_5wxFrq1zl)

Вопрос таков. Можем ли мы уже создать скрипт для **PYTHON**, чтобы получить кейген?

```python
user=raw_input()
largo=len(user)

if (largo > 0xB):
    exit()

USERMAY=""
for i in range(largo):
    if (ord(user[i]) < 0x41):
        print "CARACTER INVALIDO"
        exit()
    if (ord(user[i]) >= 0x5A):
        USERMAY+= chr(ord(user[i])-0x20)
    else:
        USERMAY+= chr(ord(user[i]))

print "USER",USERMAY
```

Мы видим, что скрипт делает то же самое, что и программа. Он берет по одному символы строки **ПОЛЬЗОВАТЕЛЯ** и сравниваем их с кодом **0x41**. Если символ меньше, он говорит нам, что это недопустимый символ и переносит нас на **ВЫХОД**. Но если он больше или равен **0x5A**, то программа вычитает из него **0x20** и добавляет его к строке **USERMAY**.

Мы видим, что если я введу имя **pePP**, программа транслирует его в имя **PEPP**.

![](/files/-Ljtu1S9GniK49OWijls)

А если я ввожу символ **Z**, скрипт преобразовывает его в символ **":"** как мы и говорили выше.

![](/files/-LjqtGXEbssw9LeVDFZf)

До этого момента, скрипт делает то же самое, что и программа. Посмотрим, что делает программа после выхода из **ЦИКЛА**. Она продолжает выполняться здесь.

![](/files/-LjqtGXG1ak26LiXeFpm)

Когда крекми найдёт символ, который равен нулю, он покинет **ЦИКЛ** и перейдёт к блоку по адресу **0x40139C**.

![](/files/-Ljtu1Sb6j2aovwyEnFJ)

Видим, что перед увеличением **ESI**, крекми **КЛАДЕТ** его в стек, чтобы сохранить исходное значение, которое указывает на начало строки, и затем с помощью инструкции **POP ESI** программа восстанавливает регистр **ESI**, перед тем как войти в функцию по адресу **0x4013С2**.

![](/files/-Ljtu1Sm_kmZW_bIvFjY)

Мы видим, что это **ЦИКЛ**, который складывает все байты, поэтому я буду называть его **SUMATORIA**(**СУММА**), так что мы можем добавить этот блок в наш скрипт.

![](/files/-LjqtGXM7MqfHuoCzu-r)

![](/files/-LjqtGXOKjGHhD49Jbv0)

Скрипт суммирует все байты и печатает сумму.

Чтобы проверить правилен ли скрипт, я помещаю **BP** на следующей строке после вызова функции **SUMATORIA** и ввожу **pepe** в поле **user** и **989898** в поле **password**.

![](/files/-LjqtGXQGHKu_MkKUMm3)

И вижу, что получается сумма равная **0x12A**, так что всё работает правильно.

В этой строке, сумма **XOR**ится с помощью ключа **0x5678**, поэтому я добавляю это выражение также в скрипт.

![](/files/-Ljtu1TQLFiRe66_khB5)

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

![](/files/-Ljtu1TbmB8GFTragbRK)

![](/files/-Ljtu1Tl-51fnTuomxUb)

Затем крекми переносит результат из **EDI** в регистр **EAX** и выходит из этого блока.

![](/files/-Ljtu1Twao-DBrAYro23)

Затем программа **ПОМЕЩАЕТ** регистр **EAX** в стек и восстанавливает его с помощью инструкции **POP EAX** перед окончательным сравнением. Другими словами в инструкции **CMP EAX**, **EBX**, первым членом будет это значение, которое поступает из функции **PROCESA\_USER**.

![](/files/-LjqtGX_EvEMFhYjEbum)

Сейчас давайте посмотрим, что программа будет делать с паролем в функции **PROCESA\_PASS**.

Войдя в неё, мы увидим такой код.

![](/files/-LjqtGXbdvP57QTmh9xD)

Здесь программа считывает каждый байт и помещает его в регистр **BL** и вычитает из этого значения число **0x30**, которое остается в регистре в **EBX**, затем умножает **EDI** на **0x0A** и суммирует полученное значение с **EBX**.

Я дополняю следующую часть скрипта с помощью этого кода.

![](/files/-Ljtu1US909zPBHmlAY_)

Я не ввожу пароль с клавиатуры. Я просто так его тестирую, потому что в кейгене, мы просто вводим пользователя, но я вижу, что если мой пароль равен например числу **989898**.

**SUM2**, это переменная, в которой хранится сумма умноженная на **0xA** и затем к ней прибавляется очередной байт, из которого вычитается значение **0x30**.

![](/files/-Ljtu1UbFypRTwr9IKRi)

Выполнив скрипт, я вижу, что для пароля **989898** у меня получается результат **f1aca**, что является **HEX** значением строки **989898**.

![](/files/-Ljtu1UmTDsvMfiV1pgA)

Всё это в нашем скрипте может сводиться в конвертирование строки в **HEX** с помощью функции **HEX()**.

![](/files/-LjqtGXjrbn5nzJCnDI0)

Скрипт даёт мне точно такой же результат.

![](/files/-LjqtGXlwxJ9uNqK3F-Y)

Наконец, скрипт **XOR**ит этот результат с помощью ключа **0x1234** и выходит из блока, чтобы сравнить результат со значением, которое возвратила функция **PROCESA\_USER** в **EAX**.

![](/files/-LjqtGXn3Fim95PrbLVS)

Так что общая формула будет такой:

**HEX(пароль) ^ 0x1234 = XOR**

Где **XOR** - это результат, который вернула функция **PROCESA\_USER**.

Немного изменим уравнение:

**HEX(пароль) = XOR ^ 0x1234**

Другими словами, если я **XOR**ю результат ключом **0x1234**, я уже почти получаю ответ.

![](/files/-Ljtu1VUZ6X7LvB2tcC6)

Если запустим скрипт со строкой **PEPE**.

![](/files/-LjqtGXrKuzOesH0oY5O)

![](/files/-LjqtGXtNOs9Orp9jEi1)

![](/files/-MX3mIu8jLgfBKLqeFo7)

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

Здесь я копирую код в кейген.

```python
sum = 0
user = raw_input()
largo = len(user)
if (largo > 0xB):
    exit()
USERMAY = ""
for i in range(largo):
    if (ord(user) < 0x41):
        print "CARACTER INVALIDO"
        exit()
    if (ord(user) >= 0x5A):
        userMAY += chr(ord(user) - 0x20)
    else:
        USERMAY += chr(ord(user))

print "USER",USERMAY

for i in range(len(userMAY)):
    sum += ord (userMAY)

print "SUMATORIA", hex(sum)

xoreado= sum ^ 0x5678

print "XOREADO", hex(xoreado)

TOTAL= xoreado ^ 0x1234

print "PASSWORD", TOTAL
```

Даже в редких случаях с символом **Z**, получаем такой результат:

![](/files/-Ljtu1W3eHqt3QXNkcjb)

![](/files/-LjqtGXzb40oxexwZglh)

![](/files/-LjqtGY0B2EZ6umb1R13)

Таким образом, мы отреверсили и сделали кейген для крекми **CRUEHEAD**. Теперь мы увидимся с Вами в **20**-й главе.

До следующей главы, друзья.

Автор оригинального текста — Рикардо Нарваха.

Перевод и адаптация на английский язык — IvinsonCLS.

Перевод и адаптация на русский язык — Яша Яшечкин.

Перевод специально для форума системного и низкоуровневого программирования - WASM.IN

22.10.2017

[**Источник: ricardonarvaja.info**](http://ricardonarvaja.info/WEB/IDA%20DESDE%20CERO/CURSO%20DE%20IDA%20TUTES/19-INTRODUCCION%20AL%20REVERSING%20CON%20IDA%20PRO%20DESDE%20CERO.docx)
