
Инструмент для выполнения атаки «человек посередине» в памяти
Инструмент MemITM (Mem In The Middle) был разработан для удобного перехвата «сообщений» в памяти процессов Windows. Мы разработали множество пользовательских инструментов перехвата памяти для захвата сетевых сообщений до шифрования или IPC-сообщений, а также для их проверки или изменения с целью фазинга. Каждый инструмент был очень специфичным, не универсальным, реализованным на C/ASM, и его было нелегко использовать/поддерживать/адаптировать.
Инструмент MemITM был разработан для решения этих проблем и состоит из:
Вы можете скачать его (исходники + скомпилированная версия) здесь: https://github.com/Amossys/MemITM
Допустим, вы хотите перехватывать записи в файл (есть более простые способы сделать это, но это для примера) в конкретном процессе Windows. Запись в файл выполняется функцией WriteFile, которая экспортируется из kernelbase.dll и соответствует схеме BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, ...). WriteFile — это функция __fastcall: lpBuffer указывается регистром RDX, а NumberOfBytesToWrite — R8. Давайте перехватим эти буферы.
generate.idapython.py и просто выполните getHook(LocByName("WriteFile"), "rdx","r8"), а затем updateConfig("config.bin").python memitm.py notepad.exe config.bin.Вот и всё! Хук установлен на функцию WriteFile, а функции «logger» и «fuzzer» из memitm.py будут получать копию буфера.
Функция getHook позволяет указывать регистры для буфера и его размера, но в некоторых случаях это может быть не так. Допустим, вы хотите перехватывать системные вызовы NtCreateFile и получать имя файла. NtCreateFile соответствует схеме NTSTATUS NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, ...), и имя файла можно получить через ObjectAttributes->ObjectName->Buffer (размер: ObjectAttributes->ObjectName->Length * sizeof(WCHAR)).
getHook также позволяет указывать shellcode в параметре t1customOpcodes вместо регистров. Этот shellcode должен:
RCX;RDX;В случае с NtCreateFile мы можем просто выполнить этот код ASM для выполнения работы:
mov rax, [r8+0x10] ; rax is now ObjectAttributes->ObjectName
mov rcx, [rax + 0x10] ; rcx is now ObjectAttributes->ObjectName->Buffer
mov rdx, [rax] ; rdx is now ObjectAttributes->ObjectName->Length
and rdx, 0xFFFF ; Length is a USHORT
shl rdx, 1 ; and must be *2
Соберем это (используя, например, онлайн-дизассемблер): 498B4010488B4810488B104881E2FFFF000048D1E2. Теперь наш вызов getHook должен быть таким: getHook(LocByName("NtCreateFile", t1customOpcodes="498B…E2").
Да! Файл конфигурации может содержать определения нескольких точек перехвата, и функция getHook добавляет их в файл. Вы можете устанавливать точки перехвата в нескольких модулях. Например, вы можете одновременно перехватывать сообщения ALPC и IOCTL.
Вы сможете различать их по «идентификатору сообщения» в колбэках python.
Не волнуйтесь, также есть простой HTTP-сервер (logserver.py) и функция httpNetSend, которые позволяют отправлять ваши тестовые примеры (и изменения) на удаленный хост в реальном времени.
Что ж, вы можете начать с использования функции bufferBitFlip, чтобы... переворачивать несколько битов. Логирование сырых сообщений позволит вам написать свой собственный диссектор и начать вручную реализовывать лучший фаззер :). Помните: вы не можете изменить размер буфера!
Например, в нашем примере с WriteFile следующая функция fuzzer заменит «hello» на «world»:
def fuzzer(data, msgID=None, pid = 0):
return data.replace("hello","world")
Например, для сообщений ALPC может быть следующая настройка memitm.py:
def logger(data, msgID=None, pid=0):
totalLen = 0
dataLen = 0
typ = 0
dataInfoOffset = 0
cid = 0
tid = 0
messageID = 0
clientViewSize = 0
# skip the PORT_MESSAGE header (0x18 bytes)
if len(data) > 0x18:
totalLen = struct.unpack("<H",data[:2])[0]
dataLen = struct.unpack("<H",data[2:4])[0]
typ = struct.unpack("<H",data[4:6])[0]
dataInfoOffset = struct.unpack("<H",data[6:8])[0]
zeroInit = struct.unpack("<L",data[4:8])[0]
cid = struct.unpack("<L",data[8:0xC])[0]
tid = struct.unpack("<L",data[0xC:0x10])[0]
messageID = struct.unpack("<L",data[0x10:0x14])[0]
clientViewSize = struct.unpack("<L",data[0x14:0x18])[0]
logMessage = "ALPC message : %x:%x - %x:%x - %d:%d - %x - %x" % (totalLen, dataLen, typ, dataInfoOffset, cid, tid, messageID, clientViewSize)
print logMessage
return
Скрипт IDA Pro содержит простой движок хукинга, который позволяет перемещать несколько инструкций в определенную область (производный от нашего инструмента DIMCT). Он должен обрабатывать множество крайних случаев, таких как инструкции, относительные по памяти, перекрестные ссылки и т.д., и, вероятно, будет выдавать много сообщений «не могу установить хук здесь», если вы попытаетесь перехватывать очень маленькие базовые блоки (<5 байт для x86-двоичных файлов, <12 байт для x64), содержащие несколько перекрестных ссылок. Для получения дополнительной информации просто прочитайте исходный код :).
Он генерирует следующую информацию в файле конфигурации:
RCX/RDX и вызывает процедуру логирования DLL;Инжектор DLL — это простой инжектор DLL (использующий CreateRemoteThread/LoadLibraryA).
Сама DLL инициализирует область разделяемой памяти и ожидает данные конфигурации (область разделяемой памяти имеет 3 общих поля: идентификатор сообщения разделяемой памяти, размер сообщения и буфер сообщения). После получения она анализирует и устанавливает хуки в память, и после этого обновление конфигурации не допускается (вы должны завершить процесс, если хотите установить новые хуки). Любой установленный хук попадет в функцию DLL genericHookFunction. Эта функция просто заполняет область памяти (защищенную критическими секциями) данными буфера, его размером и идентификатором сообщения (установленным в старших битах поля идентификатора сообщения разделяемой памяти). Для уведомления процесса python используются 2 события, чтобы сигнализировать о новых сообщениях и ожидать, пока процесс их исправит (тайм-аут составляет 2 секунды). Если буфер был обновлен, исходный буфер также изменяется.
Скрипт memitm.py запускает процесс инжектора и ожидает создания разделяемой памяти. Затем он отправляет конфигурацию и ожидает события сообщения. Когда оно получено, разделяемая память читается, и функции logger и fuzzer вызываются с буфером, идентификатором процесса и идентификатором сообщения. Если функция logger возвращает буфер, отличный от исходного, и его размер равен, он записывается в область разделяемой памяти. Затем сигнализируется событие «ACK», и скрипт ожидает новое сообщение.
Надеемся, этот инструмент будет полезен, любая помощь/отзывы будут оценены! Кроме того, если вы француз и любите Ренн, мы нанимаем :)