Проще говоря, мне было скучно однажды, и мне попросил моего друга, Максимуса Хакермана, дать мне что -то делать. Сразу же он отправил простой исполняемый файл под названием «reverseme.exe» (вирустотальная ссылка) с простым комментарием «Печать скрытое сообщение в своем собственном приложении». Естественно, файлы заголовка CPP не были предоставлены. После запуска исполняемого файла он просто открывается в качестве приложения на основе консоли, печатает «отправил все волшебные пакеты, скоро выйдет, Beep Boop!» а затем почти сразу же выходит; Я думаю, что «скоро» станет субъективным.
К счастью, нет, кажется, не является анти-дебаггированием, так что я думаю, Хакерман чувствовал себя хорошо в день.
После прикрепления WindBG и разборщика мы видим, что приложение бросает, что изначально кажется, что нездоровый divide by zero исключениям.
Тем не менее, при ближайшем рассмотрении мы обнаруживаем, что это исключение брошено непосредственно перед печати приложений его единственного видимого сообщения. Важно отметить, что два автомобиля (обработчики исключений с вектором) зарегистрированы немного до этой точки, поэтому вполне вероятно, что это исключение является намеренным и используется для управления потоком управления. Дешевый трюк действительно, чтобы попытаться отбросить нас на самом деле!
Забыв автомобилей на данный момент, можно с уверенностью предположить, что если приложение «отправляет» пакеты, то он использует функцию Winsock Send для этого (хотя в приложении ясно говорится, что они «волшебные» пакеты, так что мы увидим).
Как и ожидалось, выясняется, что вы не можете отправлять пакеты по магии, поэтому функция send Winsock действительно импортируется.
Поместив точку останова на функции отправки, мы можем посмотреть, есть ли какие -либо соответствующие данные, которые мы можем абстрагировать во время отправки. К сожалению, к тому времени, когда буфер загружается в функцию, данные уже зашифрованы. Тем не менее, мы можем определить, что буфер всегда имеет длину 3 байта, гнездо всегда связано с жестким кодированным значением 0x69 ( NICE ), и что точка перерыва функции отправки достигается в общей сложности в 14 раз.
Есть несколько способов обойти указанное шифрование. Одним из них является полностью отменить его, что может оказаться большими усилиями, другой должен найти желаемые данные до шифрования и абстрагировать их, прежде чем они станут шифрованы. Последнее значительно проще, чем первое, поэтому мы идем с этим; Не стесняйтесь обратить вспять шифрование, хотя у меня был краткий взгляд, и это не ужасно. В качестве альтернативы, если вы чувствуете, что вам нравится, вы всегда можете перезаписать ценность ручки сокета и заключить с ней приемную прикладную сделку.
К сожалению, в сегменте данных, только для чтения, нет полезных строк, кроме первоначального сообщения, поэтому нет полезных указателей из этого аспекта!
Возвращаясь к автомобилям, мы видим, что оба зарегистрированных обработчика используются для копирования нескольких неизвестных объектов в выделенный буфер памяти. Похоже, что это сделано с помощью Memcpy, используя функцию в качестве второго параметра, который, в свою очередь, использует целое число жестко -кодируемого в качестве второго параметра. Стоит отметить, что эти жесткие значения не превышают 14 в любой момент. Я включил только один из автомобилей на снимке экрана, так как они в основном идентичные кодовые, за исключением жестких значений.
Намечая на одну из функций MEMCPY и осматривая подфункцию во втором параметре, мы можем увидеть, что целое число жестко -кодированного (13 / 0DH в примере ниже) установлено на первый байт параметра A1, а A1+1 содержит символ, сразу после того, как указатель был выполнен.
Если мы проверим некоторые из других 14 вызовов этой функции, мы сможем найти то же поведение, повторяющееся; Распожав символы от 1 до 14, используя соответствующий номер в качестве индикатора порядка, мы видим, что они начинают издавать некоторые разборчивые слова. Теперь мы можем быть очень ленивыми и просто сделать приложение для консоли, чтобы распечатать сообщение, как только мы узнаем, что это такое, но это действительно похоже на мошенничество. Кроме того, сообщение может измениться в какой -то момент. Итак, давайте напишем пещеру кода, чтобы перехватить значения, прежде чем они зашифруют.
Извините, но мы используем C ++ для этого! Если вы предпочитаете использовать C#, не стесняйтесь пройти через всю платформу процесс вызова 9,7 миллиона функций и возвращайтесь сюда, когда закончите. В любом случае, сначала нам нужно решить, где кодировать пещеру. К счастью, мы уже знаем! Анализируя приведенную выше функцию, однако кратко мы знаем, что с помощью выделенной точки RDX содержит индекс, а RDX+1 содержит соответствующий символ. Ниже приведен код сборки для обсуждаемой функции.
Теперь, логически говоря, лучшее место, где можно прыгнуть, было бы в mov [rsp+arg_8], rdx , поскольку нас не волнует третий параметр, но хотим перехватить регистр RDX и RDX+1 . Чтобы сделать эти дети, нам понадобится несколько байтов: 10 байтов для инструкции MOV , чтобы перенести адрес нашей пещеры кода в регистр (мы будем использовать RAX , подробнее об этом позже в течение 10 часов в новостях) и 2 байта для инструкции JMP , чтобы перейти к реестру. Для тех из вас, кто имеет ПТСР от дальнейшей математики, это составляет 12 байтов. Теперь, прежде чем мы пойдем всю тетю Бесси и спагрифицируем этот код с помощью WriteProcessMemory, мы должны учитывать, что нет идеального места для замены 12 байтов в вышеуказанной сборке. Если мы хотим прыгнуть из смещения 0x2905 ( mov [rsp+arg_8], rdx ), и нам нужно 12 байтов для этого, то это заставит нас к смещению 0x2917 , который является ударом между двумя инструкциями MOV . К сожалению, если бы мы просто писали там наши байты, это полностью запустило бы сборку и, вероятно, вызвало бы некоторые «интересные» побочные эффекты. В результате, это будет проще (возможно, немного более хакерское, извините, не извините), чтобы добавить несколько одно-байтовых инструкций для заполнения и завершить до конца инструкции. Добро пожаловать на борт, 0x90.
В любом случае, теперь, когда мы знаем, каков наш план, так что давайте напишем какой -нибудь код: Cue Intense Hacker Man Music !
Ниже приведено, как будет выглядеть первоначальный прыжок в нашу кодовую пещеру, когда байты будут записаны в сборку приложения, в комплекте с собственным слайдом NOP.
Однако, прежде чем мы фактически напишу и заменим какую -либо сборку, нам нужно запустить приложение в приостановленном состоянии; Это остановит время выполнения приложения на ранней стадии, так что изменения памяти могут быть внесены до того, как приложение попадет в инструкцию, которая нас интересует. Приведенный ниже кодовый извлечение показывает этот процесс, и я не буду слишком много переходить на него, как вы сможете просматривать его в исходных файлах этого репо, и он в основном говорит сама за себя.
Теперь, когда процесс порожден, нам нужно приобрести базовый адрес процесса. Обычно вы можете использовать EnumProcessModules для этого, но, поскольку мы сразу же приостановили основной процесс потока, PEB не содержит полностью заполненную структуру PEB_LDR_DATA, в частности, InMemoryOrderModuleList , поэтому мы не можем в настоящее время получить базовый адрес. Кстати, это нигде не задокументировано на MSDN. К счастью, это относительно легко обойти; Очень быстро возобновив процесс, запрашивая модули, а затем запасение процесса, мы можем получить необходимую нам информацию, без слишком много процесса. Как и в большинстве вещей в Windows, Microsoft любит повторять, что ее операционная система является превосходной, снова не документируя необходимые нам функции: NtSuspendProcess и NtResumeProcess . Удобно, что мой папа дружит с Биллом Гейтсом, и он говорит мне, что эти функции живут в сочетании в ntdll.dll , поэтому мы можем получить их, используя приведенный ниже класс, который я сделал ранее:
Теперь, когда у нас есть две функции, которые нам нужны, мы можем возобновить процесс, запросить модули и ресторировать процесс:
Вы можете задаться вопросом, почему петля while ждет двух открытий модулей, а не одного? Что ж, Microsoft надеется набрать Hattrick с фрикаделькой в задней части незарегистрированной Spaghetti Net, также не упомянув, что первый модуль, найденный EnumProcessModules будет NTDLL.DLL, а второй будет исполняемым. Хотя это звучит разумно, после того, как исполняемый файл будет найден, он будет обменять индексы на ntdll.dll. Вот пример:
Результат после запроса только первого модуля:
Результат после запроса двух модулей:
Прежде чем мы пойдем дальше, нам нужно написать код сборки, который делает начальный прыжок в пещеру кода, и саму кодовую пещеру. По сути, мы перезаписываем некоторую память, начиная с смещения 0x2905 , сделаем прыжок, сделаем шпионю нашего кода, а затем перепрыгните на 0x2911 , чтобы продолжить обычный поток программы. Первоначально мы объявляем перемещение адреса в регистр RAX как MOV RAX, 0x0 , так как адрес, на который мы подбегаем, динамичен, и мы еще не знаем, что это такое. RAX является безопасным регистром для использования, так как он нестабильный регистр и в любом случае перезаписывается вскоре. Забавный факт, именно так Nintendo запрограммировала оригинальный платформер Mario Bros, с большим количеством прыжков (скажите мне, что я смешно)! Ниже приведено, как смотрится код в компиляторе; Это может быть создано другими способами, но я решил разобрать необходимые инструкции по сборке в байт -код. Если вы хотите сделать это дома, используйте этот сайт.
Код для фактической кодовой пещеры немного сложнее, и логика для него также прокомментирована в этом файле, но вот грубый процесс:
R10RDX , которая содержит индекс пакета, в R11BRDX , который содержит символ, в R11B+12 ) в R10 (что 0x2911 )R10 нам также необходимо переписать код сборки, который мы перезаписываем (со слайдом NOP) в нашу кодовую пещеру, чтобы сохранить стек и т. Д.; Этот код ссылается в области « Predetermined Assembly », за исключением NOPS. Ниже приведена пещера кода в его уродливой славе:В основном тяжелая часть.
На данный момент, по сути, у нас есть все, что нам нужно, чтобы просеять скрытое сообщение из памяти, нам просто нужно реализовать его. Чтобы резюме, у нас есть ручка к приостановленному процессу, который мы породили, 2 байтовые массивы, которые представляют логику сборки, базовый адрес приостановленного процесса, хранящий в modules[0] и смещение того, где нам нужно написать логику прыжков. Приведенный ниже фрагмент кода создает адрес для перехода, адрес кодовой пещеры (чтобы перейти к), адрес нашего 3-байтового хранения памяти, записывает адреса в код сборки, а затем записывает код сборки приостановленного процесса, прежде чем возобновить его:
Волшебный стиль Гарри Поттера для codeCaveStorageAddr - это преобразование адреса в байты, а значения жестко -кодированных в циклах предназначены для динамических размещений адресов в массивах сборки. Конечно, это можно сделать гораздо более чистым образом (не пишите магические цифры, дети), я просто ленив после того, как пришлось вручную выписывать все эти байты. Последние несколько битов для написания - это циклы для чтения, используя ReadProcessMemory, память, хранить символы и индексы в упорядоченный байтовый массив, сигнализируйте байт, используя WriteProcessMemory , когда чтение текущей памяти закончено, и печатайте скрытое сообщение. Мы знаем, что функция отправки происходит только 14 раз, поэтому мы выходим из петли while, как только массив байтов заполнен; Это может быть изменено с большим количеством изменений памяти, чтобы сигнализировать о нашем приложении, что процесс «выходит», и наш цикл может быть остановлен, а не использование жестко -кодированного значения 14, но это работает для этого примера.
Результат? Наше скрытое сообщение напечатано в нашем собственном приложении консоли! Если кто-то любопытен, NPT является ссылкой на другую часть программного обеспечения Maximums Hacker-Whatever-I-Called-Him.