Введение в реверсинг с нуля, используя IDA PRO. Часть 19.

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

1.png

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

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

2.png

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

3.png

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

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

4.png

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

5.png

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

6.png

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

7.png

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

8.png

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

9.png

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

10.png

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

11.png
12.png

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

13.png

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

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

14.png

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

15.png

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

16.png

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

17.png

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

18.png

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

19.png

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

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

20.png


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

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

21.png

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

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

22.png

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

23.png

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

24.png

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

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

25.png

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

26.png

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

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

27.png
28.png

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

29.png

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

30.png

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

31.png

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

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

32.png

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

33.png

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

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

34.png

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

Код (C++):
  1. user=raw_input()
  2. largo=len(user)
  3. if (largo> 0xB):
  4. exit()
  5.  
  6. USERMAY=""
  7.  
  8. for i in range(largo):
  9. if (ord(user)<0x41):
  10. print "CARACTER INVALIDO"
  11. exit()
  12. if (ord(user) >= 0x5A):
  13. USERMAY+= chr(ord(user)-0x20)
  14. else:
  15. USERMAY+= chr(ord(user))
  16.  
  17.  
  18. print "USER",USERMAY

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

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

35.png

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

36.png

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

37.png

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

38.png

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

39.png

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

40.png

41.png

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

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

42.png

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

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

43.png

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

44.png

45.png

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

46.png

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

47.png

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

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

48.png

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

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

49.png

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

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

50.png

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

51.png

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

52.png

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

53.png

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

54.png

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

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

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

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

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

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

55.png

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

56.png

57.png

58.png

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

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

Код (C++):
  1. sum=0
  2. user=raw_input()
  3. largo=len(user)
  4. if (largo> 0xB):
  5. exit()
  6.  
  7. USERMAY=""
  8.  
  9. for i in range(largo):
  10. if (ord(user)<0x41):
  11. print "CARACTER INVALIDO"
  12. exit()
  13. if (ord(user) >= 0x5A):
  14. userMAY+= chr(ord(user)-0x20)
  15. else:
  16. USERMAY+= chr(ord(user))
  17.  
  18. print "USER",USERMAY
  19.  
  20. for i in range(len(userMAY)):
  21. sum+=ord (userMAY)
  22.  
  23. print "SUMATORIA", hex(sum)
  24.  
  25. xoreado= sum ^ 0x5678
  26. print "XOREADO", hex(xoreado)
  27.  
  28. TOTAL= xoreado ^ 0x1234
  29.  
  30. print "PASSWORD", TOTAL

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

59.png

60.png

61.png

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

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


Автор текста: Рикардо Нарваха - Ricardo Narvaja (@ricnar456)
Перевод на английский: IvinsonCLS (@IvinsonCLS)
Перевод на русский с испанского+английского: Яша_Добрый_Хакер(Ростовский фанат Нарвахи).
Перевод специально для форума системного и низкоуровневого программирования — WASM.IN
22.10.2017
Версия 1.0

10 16.099
yashechka

yashechka
Ростовский фанат Нарвахи

Регистрация:
2 янв 2012
Публикаций:
90

Комментарии


      1. dreamseller 21 окт 2018
        Питоновский код надо запускать не из иды
        В иде raw_input не работает на ввод с консоли
        yashechka нравится это.
      2. yashechka 23 окт 2017
        :thank_you::thank_you::thank_you:
        DzenGURU нравится это.
      3. yashechka 21 окт 2018
        Спасибо.
      4. yashechka 13 окт 2018
        Питон ещё та штука.
      5. yashechka 13 окт 2018
        Питон ещё та штука.
      6. Leonid228 12 окт 2018
        Странно питон ругается на raw_input() и если задать переменную самому то все работает.
      7. Leonid228 12 окт 2018
        Пытаюсь запустить скрипт, в лог питона пишет:

        error: Traceback (most recent call last):
        File "<string>", line 2, in <module>
        EOFError: EOF when reading a line
      8. yashechka 28 фев 2018
        :preved::preved::preved:
      9. yashechka 28 окт 2017
        Больше 3-х тысяч просмотров это статьи. Это как вообще?:swoon:
      10. yashechka 22 окт 2017
        Долго ли умеючи?:acute::acute::acute: