Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Всем привет. Сегодня у нас на разборе crackme, суть которого - это найти пароль, но он отличается от обычных crackme,т.к в нем присутствует симуляция виртуальной машины(не знаю как это правильно называется.). Что же, приступим.
Картинок здесь уж очень много, но мне кажется так легче объяснить)
Я пытался объяснить максимально понятно и не растягивая, по этому объяснять некоторые функции по типу mov from register я не стал, да и незачем. Там все довольно просто(4 ассемблерные строки)
1) Как найти main функцию.
Наша главная задача заключается в поиске main функции. Когда я только начинал, я не мог ее так просто найти. Мне приходилось залазить в строки и искать в строках что-то типа"Enter the pass:", без него же я не мог его найти. Всего есть 2 варианта(возможно еще какие нибудь, но я пользуюсь только этими).
Вариант 1: Если вы уже реверсили довольно немного программ, написанные например на С++, вы можете найти main по памяти.
Вариант 2: Если же вы новичок или не встречали программ, например написанных на Pascal и вы не понимаете где может быть main функция, а строк,которые могли бы подсказать нет, тогда вы просто жмете Trace Over(F8) пока вы не увидите, что черная строка пропала,а в статусе написано Running. Например как тут:
2) Анализ main функции.
Вот мы и находимся в main функции. Здесь мы видим довольно несложные действия
*Адресу. Я ошибся)
Содержатся*
Вкратце про init_vm. Она просто инициализирует адрес(D09090) значениями, и также переносит наш байт код в "регистр"(помечено 1). Инициализирует просто выделением памяти и записи туда этих значений
Все эти значения - это регистры. Красным отмечена область,которая не входит в регистры. Я сделал кривой скриншот, а переделывать было лень(((. В основном, они хранят разные EIP, чтобы при помощи этого сделать "цикл". Это понадобится нам при чтения пароля и его проверки и при расшифровки нашего сообщения о введении пароля.
3) Разбор байтов.
Здесь очень много байт кодов и разбор каждого занял бы довольно много времени, да и вы бы наверное устали читать).
По этому я разберу только самые важные.
1) 0x1С байт код.
Далее идет запись "p" в регистр. Вы этого не видите, т.к в скрин не влез, но там есть такая же функция как и на скрине выше.
2) 0x3С байт код.
У нас идет вывод этой 1 буквы, в нашем случае "p". Функция mov from register, перемещает значение с нашего виртуального регистра в регистр дебаггера сбоку. Вы можете посмотреть на него на скрине выше. Далее идет печать с помощью putchar функции.
3) 0x13 байт код. Тут идет уменьшение числа. Это число отвечает за длину пароля. Функция длинная, но там ее основная цель это уменьшение числа в виртуальном регистре
Кстати, забыл упомянуть в начале, что вот этот участок в нашем виртуальном регистре это ZF(zero flag). Он будет использоваться при сравнении пароля, длинны пароля, длинны текста, где нас просят ввести пароль. Этим самым действием, автор реализовал CMP инструкцию .Если кто-то не понял вот пример.
У нас есть 2 числа. 0x80 и 0x79. Нам нужно их сравнить, но как? Это делается при помощи отнимания от 0x80 - 0x79. Если результат не равен 0, тогда ZF = 0, иначе ZF = 1.
4) 0x3A байт код. Этот байт код вызывает функцию,которая перемещает некое значение в виртуальный EIP. В нашем случае, это
То есть,мы имеем некое число записанное в r14. Это число - это указатель на то, какую инструкцию нам нужно выполнить следующую. Судя по числу ,которое хранит r14 регистр, мы можем сделать вывод, что он хранит указатель назад. То есть,представим такую картину.
Я сейчас напишу рандомные байты, это никак не относится к нашему крякми, просто для понимания.
Мы имеем вот такую последовательность байт кода.
Адрес Байт код
0 0xA 0xB 0xC 0xD
4 0xE 0xF 0x10 0x11
Наш регистр r14 содержит адрес 0, и это равно 0xA. А наш виртуальный регистр EIP имеет адрес 0x4 ,что соответствует 0xE.
Наша функция ,которая изображена выше, делает такие вещи. Она перемещает регистр r14 в регистр eip. Следуя примеру,
до перемещения наш виртуальный регистр EIP имел адрес 0x4 ,что соответствует 0xE, после применения перемещения, регистру eip присвоено АДРЕС r14, что соответствует адресу 0 и соответствует значению 0xA. То есть, теперь наш EIP имеет адрес регистра R14(0 адрес). Это используется для создания цикла. С помощью 0x13 байт кода, мы сокращаем количество повторений в цикле. Думаю,суть вы уловили.
Далее мы повторяем расшифровку и вывод по символу 10 раз.
В итоге мы получаем password->
Финальная часть
Все основные байт коды мы разобрали. Теперь осталось просто понять генерацию пароля. Скажу сразу, что она несложная. Сама суть,я думаю, заключалась в выяснении за что отвечает каждый байт код.
Все начинается с того, что у нас считывают пароль с помощью байт кода 0x3D.
Далее мы переходим в самую главную функцию, мы ее уже встречали, и я говорил вам,что она разделена на несколько частей, для разных нужд, но выполняет похожие задачи.
Ее байт код 0x1C
Выглядит она так:
Константа всегда статическая, то есть она не меняется и она равна 0xAC.
Результат XORING записывается в то место, где мы хранили наш символ , в моем случае в 16 виде 0x31 в ascii представлении это "1".
Мой результат 0x9D(что неправильно,смотрите дальше)
Идем дальше.
Далее мы попадаем в функцию с байт кодом 0x2E. Она нам уже известна. Тут просто сверяются результаты xoring путем отнимания.
Если мой результат xoring равен константе 0xC5 , тогда ZF = 1 и программа продолжает сверять,иначе программа завершается.
Вот это место с объяснениями
Тут я думаю,все понятно. Первая mov функция присваивает eax ту самую константу 0xC5 ,а вторая дает нам результат xoring,в моем случае 0x9D
Внизу ошибка. Если при отнимании результат получается 0, тогда ZF = 1, иначе ZF = 0.
Вот эта наша функция. Мы имеем константу,которая должна получиться 0xC5 и которая получилась 0x9D. По логике, мы должны xor 0xC5 на 0xAC(это статическая константа, смотри выше,мы уже говорил про нее). Что и равно "i" или "69" в hex.
Все,первый символ у нас "i".
Следующая константу, мы получаем в функции с байт кодом 0x0A. Это она выглядит внутри
Там будет 2 или 3 функции, входим в нее и находим
У вас не будет подписано, т.к я ее подписал сам. Заходим внутрь.
Видим такое дело
В конечном итоге, мы получаем свою константу. В моем случае это 0xC1. Опять xor ее с 0xAC. Это равно 0x6D hex и в ascii "_"
Делаем так с каждым символом и получаем.
Конечный результат:
i_4m_t0p_cr4ck3r!
Всем спасибо. Это конец.
Я старался объяснить как можно понятнее и самые важные места, чтобы это заняло не так много времени, но в итоге это все равно заняло 3 часа.
Если бы я все разбирал, боюсь представить сколько бы времени я потратил)))
p.s извините за ошибки в словах на скринах.
Картинок здесь уж очень много, но мне кажется так легче объяснить)
Я пытался объяснить максимально понятно и не растягивая, по этому объяснять некоторые функции по типу mov from register я не стал, да и незачем. Там все довольно просто(4 ассемблерные строки)
1) Как найти main функцию.
Наша главная задача заключается в поиске main функции. Когда я только начинал, я не мог ее так просто найти. Мне приходилось залазить в строки и искать в строках что-то типа"Enter the pass:", без него же я не мог его найти. Всего есть 2 варианта(возможно еще какие нибудь, но я пользуюсь только этими).
Вариант 1: Если вы уже реверсили довольно немного программ, написанные например на С++, вы можете найти main по памяти.
Вариант 2: Если же вы новичок или не встречали программ, например написанных на Pascal и вы не понимаете где может быть main функция, а строк,которые могли бы подсказать нет, тогда вы просто жмете Trace Over(F8) пока вы не увидите, что черная строка пропала,а в статусе написано Running. Например как тут:
Вот мы и находимся в main функции. Здесь мы видим довольно несложные действия
*Адресу. Я ошибся)
Содержатся*
Вкратце про init_vm. Она просто инициализирует адрес(D09090) значениями, и также переносит наш байт код в "регистр"(помечено 1). Инициализирует просто выделением памяти и записи туда этих значений
Все эти значения - это регистры. Красным отмечена область,которая не входит в регистры. Я сделал кривой скриншот, а переделывать было лень(((. В основном, они хранят разные EIP, чтобы при помощи этого сделать "цикл". Это понадобится нам при чтения пароля и его проверки и при расшифровки нашего сообщения о введении пароля.
3) Разбор байтов.
Здесь очень много байт кодов и разбор каждого занял бы довольно много времени, да и вы бы наверное устали читать).
По этому я разберу только самые важные.
1) 0x1С байт код.
Далее идет запись "p" в регистр. Вы этого не видите, т.к в скрин не влез, но там есть такая же функция как и на скрине выше.
2) 0x3С байт код.
У нас идет вывод этой 1 буквы, в нашем случае "p". Функция mov from register, перемещает значение с нашего виртуального регистра в регистр дебаггера сбоку. Вы можете посмотреть на него на скрине выше. Далее идет печать с помощью putchar функции.
3) 0x13 байт код. Тут идет уменьшение числа. Это число отвечает за длину пароля. Функция длинная, но там ее основная цель это уменьшение числа в виртуальном регистре
Кстати, забыл упомянуть в начале, что вот этот участок в нашем виртуальном регистре это ZF(zero flag). Он будет использоваться при сравнении пароля, длинны пароля, длинны текста, где нас просят ввести пароль. Этим самым действием, автор реализовал CMP инструкцию .Если кто-то не понял вот пример.
У нас есть 2 числа. 0x80 и 0x79. Нам нужно их сравнить, но как? Это делается при помощи отнимания от 0x80 - 0x79. Если результат не равен 0, тогда ZF = 0, иначе ZF = 1.
4) 0x3A байт код. Этот байт код вызывает функцию,которая перемещает некое значение в виртуальный EIP. В нашем случае, это
То есть,мы имеем некое число записанное в r14. Это число - это указатель на то, какую инструкцию нам нужно выполнить следующую. Судя по числу ,которое хранит r14 регистр, мы можем сделать вывод, что он хранит указатель назад. То есть,представим такую картину.
Я сейчас напишу рандомные байты, это никак не относится к нашему крякми, просто для понимания.
Мы имеем вот такую последовательность байт кода.
Адрес Байт код
0 0xA 0xB 0xC 0xD
4 0xE 0xF 0x10 0x11
Наш регистр r14 содержит адрес 0, и это равно 0xA. А наш виртуальный регистр EIP имеет адрес 0x4 ,что соответствует 0xE.
Наша функция ,которая изображена выше, делает такие вещи. Она перемещает регистр r14 в регистр eip. Следуя примеру,
до перемещения наш виртуальный регистр EIP имел адрес 0x4 ,что соответствует 0xE, после применения перемещения, регистру eip присвоено АДРЕС r14, что соответствует адресу 0 и соответствует значению 0xA. То есть, теперь наш EIP имеет адрес регистра R14(0 адрес). Это используется для создания цикла. С помощью 0x13 байт кода, мы сокращаем количество повторений в цикле. Думаю,суть вы уловили.
Далее мы повторяем расшифровку и вывод по символу 10 раз.
В итоге мы получаем password->
Финальная часть
Все основные байт коды мы разобрали. Теперь осталось просто понять генерацию пароля. Скажу сразу, что она несложная. Сама суть,я думаю, заключалась в выяснении за что отвечает каждый байт код.
Все начинается с того, что у нас считывают пароль с помощью байт кода 0x3D.
Далее мы переходим в самую главную функцию, мы ее уже встречали, и я говорил вам,что она разделена на несколько частей, для разных нужд, но выполняет похожие задачи.
Ее байт код 0x1C
Выглядит она так:
Константа всегда статическая, то есть она не меняется и она равна 0xAC.
Результат XORING записывается в то место, где мы хранили наш символ , в моем случае в 16 виде 0x31 в ascii представлении это "1".
Мой результат 0x9D(что неправильно,смотрите дальше)
Идем дальше.
Далее мы попадаем в функцию с байт кодом 0x2E. Она нам уже известна. Тут просто сверяются результаты xoring путем отнимания.
Если мой результат xoring равен константе 0xC5 , тогда ZF = 1 и программа продолжает сверять,иначе программа завершается.
Вот это место с объяснениями
Тут я думаю,все понятно. Первая mov функция присваивает eax ту самую константу 0xC5 ,а вторая дает нам результат xoring,в моем случае 0x9D
Внизу ошибка. Если при отнимании результат получается 0, тогда ZF = 1, иначе ZF = 0.
Вот эта наша функция. Мы имеем константу,которая должна получиться 0xC5 и которая получилась 0x9D. По логике, мы должны xor 0xC5 на 0xAC(это статическая константа, смотри выше,мы уже говорил про нее). Что и равно "i" или "69" в hex.
Все,первый символ у нас "i".
Следующая константу, мы получаем в функции с байт кодом 0x0A. Это она выглядит внутри
Там будет 2 или 3 функции, входим в нее и находим
У вас не будет подписано, т.к я ее подписал сам. Заходим внутрь.
Видим такое дело
В конечном итоге, мы получаем свою константу. В моем случае это 0xC1. Опять xor ее с 0xAC. Это равно 0x6D hex и в ascii "_"
Делаем так с каждым символом и получаем.
Конечный результат:
i_4m_t0p_cr4ck3r!
Всем спасибо. Это конец.
Я старался объяснить как можно понятнее и самые важные места, чтобы это заняло не так много времени, но в итоге это все равно заняло 3 часа.
Если бы я все разбирал, боюсь представить сколько бы времени я потратил)))
p.s извините за ошибки в словах на скринах.
Вложения
Последнее редактирование: