Задача шабака этого года для исследователей — самолет. Я решил эту проблему некоторое время назад и решил опубликовать свое решение здесь. Задача состоит из трех частей, и я расскажу о второй в этом посте.

Файл ReadMe.txt содержит:

hint: Same as before, this executable has a password that you need to extract, but unlike the last time, you will face against a simple Anti debugging Technique and many more fun stuff

Во-первых, как и в первой части, давайте просто запустим Second.exe и посмотрим, что произойдет:

Откроем файл в IDA и посмотрим, о какой «Технике защиты от отладки» идет речь:

В IDA автоматически откроется функция _main(), так как с точки зрения программиста она является точкой входа, но настоящей точкой входа является start(). Посмотрим, что у нас получилось с запуском программы:

Мы видим два вызова, после некоторого сканирования я могу сказать вам, что первый предназначен для инициализации __security_cookie, который используется для обнаружения переполнения стека, давайте перейдем к следующему jmp sub_8E3A52:

Мы видим, что main вызывается из этой функции:

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

Вызов [ebp+var_8] интересен, он повторяется три раза, и при дублировании я заметил, что третий вызов был таким:

Он создал поток для следующей функции:

Первая подсказка: «Обнаружена попытка отладки… прерывание!» 😉 второй - ExitProcess API, который заставит нашу программу выйти в любое время, когда она захочет. Но как он обнаружит нашу отладку? На самом деле это известный метод анти-отладки, когда вы выполняете отладку и прерывание по определенному адресу из-за точки останова, отладчик фактически замораживает все потоки одного и того же процесса, что также приводит к зависанию этой функции. Цикл внутри этой функции просто выполняет API GetTickCount64(), чтобы получить:

Источник: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724411(v=vs.85).aspx

Retrieves the number of milliseconds that have elapsed since the system was started.

И сравните время между каждой итерацией. Как только разница между этими временами превышает 100 (64 часа) мс, предполагается, что программа находится в стадии отладки, и запускается ExitProcess().

Так как это обойти? Легко, пропустите этот поток из когда-либо созданного в первую очередь, пропустив функцию:

Здорово! Теперь мы можем отлаживать по своему усмотрению. Если мы будем отлаживать программу при вводе случайного пароля, то увидим следующую ошибку:

Так что же это за файл GewttingSchwifty.bat? Когда я посмотрел на папку, она действительно существует, но это точно не файл .bat. После открытия его с помощью notepad++ я вижу это:

Для чего используется этот файл и почему он был создан? Если вы просмотрите код, вы увидите, что ошибка происходит прямо здесь:

Содержимое файла обрабатывается некоторым ключом, затем оно записывается в файл и пытается загрузить его как библиотеку с помощью API LoadLibraryA(). Это говорит нам о нескольких вещах:

  • Файл должен быть допустимой библиотекой для успешной работы программы.
  • Ключ к содержимому нашего файла должен быть изменен, но нам нужно знать, с чем он связан (выгрузите содержимое зашифрованного файла, прежде чем оно будет подвергнуто xoring).

Код xored в следующем вызове:

Давайте сломаем и посмотрим, против чего xored:

edx — это ключ, а [edi+eax] — содержимое, в моем примере edx равно 6F665DA2, но что более интересно, так это расшифрованное содержимое файла. Мы знаем, что он находится в edi-адресе, поэтому давайте проследим за ним в дампе и увидим следующее:

Таким образом, мы знаем содержимое файла (по крайней мере, его начало) до того, как он был расшифрован, и мы знаем, что это должна быть действующая библиотека. Мы также знаем, что ключ имеет длину 4 байта, поэтому все, что нам действительно нужно знать, — это правильное значение, которое должно быть в каких-то 4 байтах (для восстановления ключа). Посмотрим структуру в формате PE:

IT начинается с заголовка DOS, который начинается с подписи «MZ» (0x5A4D), а затем:

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

Таким образом, ключ должен быть (не забывайте о прямом порядке байтов): 00905A4D ^ E80285B5 = E892DFF8.

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

Мы получаем пароль от пользователя при вызове sub_8E63D4, но это не самый интересный вызов, вызов sub_8D1150 — это тот, который берет наш пароль и генерирует ключ (помещая его в eax, который после перехода в [ebp+var_8]). Давайте посмотрим его содержимое, помните, что наш пароль передается этой функции с помощью регистра ecx:

Хорошо, хорошо, если это не хеш Фаулера-Нолл-Во, так что это известная хэш-функция, которая означает, что попытка сделать вывод, какое значение будет генерировать наш желаемый E892DFF8, будет невозможно, если только грубая сила не на нашей стороне или какая-то радужная таблица содержит наш хеш.

Источник: https://gist.github.com/underscorediscovery/81308642d0325fd386237cfa3b44785c

//fnv1a 32 and 64 bit hash functions // key is the data to hash, len is the size of the data (or how much of it to hash against) // code license: public domain or equivalent // post: https://notes.underscorediscovery.com/constexpr-fnv1a/ inline const uint32_t hash_32_fnv1a(const void* key, const uint32_t len) { const char* data = (char*)key; uint32_t hash = 0x811c9dc5; uint32_t prime = 0x1000193; for(int i = 0; i < len; ++i) { uint8_t value = data[i]; hash = hash ^ value; hash *= prime; } return hash; } //hash_32_fnv1a

К сожалению, мне не удалось найти строку, генерирующую это значение, поэтому давайте продолжим, как обычно, в ленивом режиме:

Изменяем регистр eax на правильный ключ и смотрим, что происходит после LoadLibraryA():

Вызываем какую-то (порядковый номер = 0x10) функцию из той библиотеки, начало функции выглядит так:

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

Довольно просто написать «Что круче, чем быть крутым?» в файл по адресу «\\\\.\\pipe\\flumbus_channel», важно то, что он переопределяет содержимое этого файла, а затем считывает 8 символов из этого файла в буфер. Например, скажем, у меня есть файл со следующим содержимым:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12345678

Тогда после этих строк будет:

What is cooler than being cool?12345678

И он прочитает в буфер «12345678». Но какого хрена этот путь «\\.\pipe\flumbus_channel»? Именно так мы используем именованные каналы в Windows:

Источник: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365783(v=vs.85).aspx

Для удобства я изменю это на «C:\spipe\flumbus_channel» во время отладки, чтобы контролировать 8 символов, которые программа будет читать в реальном файле, но это именованный канал, и вы также можете создать именно этот именованный канал. в другом приложении и заставить приложение работать так же хорошо.

Следующий фрагмент кода:

Этот просто преобразует 8 символов, которые мы прочитали ранее, в формат верхнего регистра. В последней части есть подвох:

Итак, есть два вызова (sub_10001290 и sub_10001350), первый изменит 965 байт в сегменте данных dll в соответствии (xored) с 8 байтами, которые он прочитал из файла. Но более важно то, что второй вызов проверит эти 965 байт по нашей любимой хеш-функции (Fowler-Noll-Vo Hash), а затем решит, удалось это или нет (ожидаемый хеш: 55B8B000). На этот раз, если вы попытаетесь пойти ленивым путем, вы потерпите неудачу, изменение хеша так, чтобы он указывал на то, что вы хотите, не поможет, потому что вам действительно нужно изменить эти 965 байтов, чтобы пройти этот вызов. Просто чтобы прояснить это, я покажу, что произойдет, если вы просто исправите условие:

Обратите внимание, что я изменил путь на «C:\\spipe\flumbus_channel».

И…

Мы печатаем мусор, который мы создали, введя неправильный пароль в C:\\spipe\flumbus_channel. Так какой правильный пароль? Что ж, если вы ищете «Что круче, чем быть крутым?» в гугле вы получите ответ: ICE COLD. Итак, давайте изменим наш файл flumbus_channel на это:

И в этот раз…:

Вот и все, часть 2, увидимся в части 3.

Первоначально опубликовано на http://unravelit.net 19 августа 2017 г.