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

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

hint: Maybe this program doesn't do more than it seems, our special agent have told us that when the program was executed in a different country, it behaved differently

Когда мы запускаем программу:

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

Поэтому мы ищем все, что может иметь отношение к нашей стране и происхождению:

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

И… Открывая в IDA в start(), так же, как и в части 2, мы видим два вызова, в которых первый должен снова инициализировать __security_cookie. Второй вызов интересен, но чтобы сэкономить время, давайте откроем procmon с IDA и отладим каждый вызов, мы пытаемся отследить вызов, который обращается к реестру локали:

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

Выглядит знакомо? Так и должно быть, мы сталкиваемся с этим вызовом [ebp+var_8] раньше в части 2. Итак, я отлаживаю и что я вижу во втором вызове? Этот:

Выглядит подозрительно, верно? Давайте посмотрим, что делает этот API:

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

LCType [in] - The locale information to retrieve.lpLCData [out, optional] - Pointer to a buffer in which this function retrieves the requested locale information. This pointer is not used if cchData is set to 0cchData [in] - Size, in characters, of the data buffer indicated by lpLCData.
  • Мы игнорируем lpLocaleName (NULL)
  • LCType — 20000005h
  • lpLCData ebp+var_4
  • cchData равно 4

Итак, мы знаем, что ebp+var_4 содержит данные о наших локалях, но какие данные? Давайте посмотрим, какие данные 20000005h представляют…:

Источник: http://www.pinvoke.net/default.aspx/kernel32.GetLocaleInfoEx

0x20000005 в двоичном формате — это 00100000000000000000000000000101, что соответствует:

  • 0x5 -> LOCALE_ICOUNTRY, код страны
  • 0x20000000 -> LOCALE_RETURN_NUMBER, вернуть число вместо строки

Кстати, это было тяжело… документация msdn по этой функции — отстой.

Итак, мы знаем, что он возвращает код страны (у меня это 1, ISD-код страны — США) в виде числа и сохраняет его в:

mov word ptr unk_DCAFC9, ax

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

Обратите внимание, что размер равен 2, важно, чтобы точка останова работала для всего диапазона памяти, который нам нужен (в нашем случае это поле размером 2 байта). И мы прерываемся на:

Обратите внимание, что он сравнивает второй байт результата с 1. Давайте посмотрим, какой страной он хочет, чтобы мы были:

  • Функция GetLocaleInfo возвращает код страны по ISD-кодам:
  • Коды стран, которые пройдут эту проверку: 0x100–0x1FF -> 256–511.

В моем случае это false, я сейчас исправлю это условие (сделаю его истинным), но обратите внимание, что делает эта функция, она копирует содержимое (87 байт) sub_1C16D0 по адресу sub_1C10E0. Если вы увидите, что находится по смещению sub_1C10E0, вы увидите:

Обратите внимание, это сообщение, которое мы видели? Так что это заменяет функцию, это хорошая новость! но к чему? Что ж:

Мы вернемся к этому позже, а пока давайте продолжим и посмотрим, что происходит:

И здесь он терпит неудачу, когда вы ищете, что eax содержит «Kernel32», кажется, что он пытается загрузить kernel32, но действительно ли это происходит?

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

Через некоторое время я искал, что может происходить, и в конце концов возвращаюсь к нашему:

вызов [ebp+var_8] происходит 5 раз, и мы знаем цель второго раза (GetLocaleInfoEx()) и третьего раза (исправленное условие). Но как насчет четвертого и пятого раз? Посмотрим:

Выглядит знакомо? оба вызова создают разные потоки. Давайте посмотрим, что они делают:

Таким образом, он вызывает некоторую функцию (sub_1C2610) со смещением main() в качестве параметра, но, что более важно, он на самом деле повторяет эту функцию снова и снова до бесконечности. sub_1C2610 выглядит следующим образом:

Таким образом, функция sub_1C2610 довольно большая, поэтому я показываю только соответствующую часть, и если мы посмотрим дальше на функцию, мы легко увидим, что она изменяет основную (что может быть плохо), но два условия не должны быть истинными:

  • cmp ecx, 20h — всегда будет ложным, поскольку мы знаем, что оно равно arg_8, что равно 3000h.
  • cmp ecx, 80h — То же, что и раньше.
  • bt dword_41B7E0, 1 — Эта проверка покажет, действительно ли функция изменит наш файл main. И все зависит от того, что находится в адресе dword_41B7E0. Давайте посмотрим, меняет ли какая-то процедура значение в этом адресе, и если да, то почему:

Ставим аппаратную точку останова записи по адресу и…:

Этот вызов меняет значение, почему и что он говорит? Мы можем видеть использование инструкции cpuid, которая используется в некоторых методах защиты от отладки против виртуализации. Сейчас я собираюсь предположить, что эта функция определяет, что программа находится в стадии отладки, и изменяет это значение, что приводит к тому, что наши новые потоки уничтожают наш основной () функция. давайте попробуем пропустить эти темы из когда-либо созданных:

И мы возвращаемся к функции, которую мы переопределяем, которая была вызвана из основной (первый вызов):

Так что же делает эта функция?

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

  • [eax+0ch] указывает на Ldr.
  • eax + 14h указывает на InMemoryOrderModuleList.
typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
typedef struct _LDR_DATA_TABLE_ENTRY { PVOID Reserved1[2]; LIST_ENTRY InMemoryOrderLinks; PVOID Reserved2[2]; PVOID DllBase; PVOID EntryPoint; PVOID Reserved3; UNICODE_STRING FullDllName; BYTE Reserved4[8]; PVOID Reserved5[3]; union { ULONG CheckSum; PVOID Reserved6; }; ULONG TimeDateStamp; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

Весь код ищет KERNEL32.DLL в загруженной библиотеке:

Как только он нашел это:

  • mov ecx, dword_FCC1E0 — Перемещение адреса 810000h в ecx.
  • mov eax, [edx+18h] — Перемещение адреса kernel32.dll в eax.
  • mov [edx+18h], ecx — заменить адрес kernel32.dll на 810000h.

Итак, мы знаем, что вся цель этой процедуры — изменить базовый адрес Kernel32.dll во время выполнения на адрес по своему выбору (810000h). На этот раз LoadLibrary будет работать (мы остановили потоки, которые вызывали у нас проблемы), но помните, что на самом деле она загрузит dll по адресу 810000h.

Мы видим, что вызываем API GetComputerNameW из «нашего» ядра32 (обратите внимание, что шестнадцатеричное представление показывает имя процедуры, которое было отправлено в GetProcAddress):

Итак, мы входим в функцию GetComputerName и видим следующее:

Я хотел узнать, что здесь происходит, но это бесполезно, просто бегите, пока не дойдете до этого раздела:

Здесь мы видим, что он сравнивает содержимое адреса ecx с содержимым адреса edx. содержимое edx генерируется тем, что у нас есть в командной строке (читайте ранее), но сравнивается с некоторым фиксированным адресом (loc_252000)

  • Это цикл, в котором esi (значение в начале цикла равно 12, что означает, что вам нужно скопировать 16 байт, а не 12, из-за условия).
  • Только если значения одинаковы, он продолжается.

Итак, давайте изменим наши значения, чтобы они соответствовали ожидаемым значениям:

Как только вы это сделаете, надеюсь, вы увидите это:

До следующего раза 🙂

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