D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Всем привет друзья!
Снова конкурс, а значит снова появился повод поделиться чем-то интересным и полезным с комьюнити.
Хочу выразить огромный респект организаторам, за то что нашли крутой способ для форума собрать классный контент, повышать репутацию форума и притянуть молодых.
В моей прошлой конкурсной статье вами были заданы вопросы про загрузчик, и я тогда ответил:
Сказано - сделано.
Загрузчик это действительно отдельная, интересная тема, к которой нужно подойти основательно и расставить все точки над i.
Проект криптора, описанный в прошлой статье, содержит уже откомпилированные версии шеллкодов загрузчика в память, без исходного кода.
В этой статье я поделюсь с вами его полным исходным кодом, опишу что он делает и как происходит обработка и мэппинг файла в память в нюансах.
Мы поговорим о том, чем это решение отличается от системного загрузчика, посмотрим относительно кода системного загрузчика в XP.
Порассуждаем о пользе прямого мэппинга в памяти перед инжектами, такими как RunPE и иже с ними.
Немного предыстории:
Появившийся как концепт, способ загрузки RunPE был практически идеальным решением загрузки бинарика в память на рубеже 08-12гг.
Тогда антивирусы еще относительно плохо умели хучить вызовы и перехватывать управление вновь созданного процесса, проверяя его контекст.
По сути проактивные системы проходили своё становление как самостоятельные технологии и во главе угла стояли сигнатурный и эвристические
алгоритмы обнаружения крипторов.
Принцип работы RunPE достаточно прост:
Стаб (контейнер криптованного файла) пораждает новый приостановленный процесс через CreateProcess с флагом CREATE_SUSPENDED,
получает контекст главного потока через GetThreadContext, после чего приостановленный процесс анмепится с помощью NtUnmapViewOfSection
и по адресу ImageBase выделяется память через VirtualAllocEx с флагом PAGE_EXECUTE_READWRITE.
Далее в выделенную память через WriteProcessMemory записываются хидер криптованного файла и секции.
Финальные штрихи это установка нового контекста на точку входа для главного потока через SetThreadContext и восстановление
приостановленного потока через ResumeThread.
Для поддержки x64 файлов в RunPE требовалось совершить минимум изменений с учетом смещений и регистров (eax\rax, ebx\rbx и т.д.) и вот уже готова полноценная поддержка х64 файлов.
В целом история с RunPE довольно простая, очень стабильная и хорошо отрабатывающая практически любые типы нативных PE файлов, будь то хитрые
заглушки в TLS, аномальные заголовоки, упакованные файлы и т.д.
Одна беда - технологию с того времени так затерли до дыр, что любой уважающий себя антивирус научился определять этот способ загрузки в память,
что напрочь отменяло бы саму идею сокрытия криптуемого файла от антивирусов. С появлением механизмов сканирования памяти эта технология окончательно умерла.
Есть еще промежуточные способы загрузки в память, я отнесу их к RunPE-подобным и назову их инжектами. В данной статье их рассматривать я не буду.
LoadPE:
Первые попытки нативной (почему нативной - опишу ниже) загрузки бинарного файла в память (в паблике) выкладывал покойный Great, как раз на дамаге и на васме.
Те наработки хоть и отражали суть концепции, хотя и работали кое как, но умели обрабатывать только ограниченное количество самых стандартных PE файлов.
Любые отклонения от эталонного PE формата - и загрузка такого файла в память фейлилась.
Позже свои наработки отрывками выкладывал el-, тоже на дамаге.
Многие "мастера" не стали заморачиваться со своим кодом и рипнули загрузчик с криптованных семплов, тупо вставив в свой криптор, мол ведь и так работает.
Ну а некоторым, как мне, захотелось разобраться в сути технологии и написать своё решение, которое было бы способно обрабатывать доминирующее большинство PE файлов,
с учетом разных хитрых, нестандартных техник, поддерживающее как 86 так и 64 архитектуры PE формата.
И вот на базе этих наработок и путем долгих реверсов оригинального загрузчика винды, технология нативной загрузки в память шлифовалась, пока не выкатилось готовое решение.
Почему я называю этот способ нативным? Да потому, что в отличие от незамысловатого RunPE, в LoadPE нам нужно руками воспроизводить действия, которые обычно выполняет
сам загрузчик винды.
Рассмотрим механизм, который используется в нативном загрузчике винды:
В основе загрузки любого модуля лежит функция LoadLibrary, точнее её более низкоуровневые функции из ntdll, такие как LdrLoadDll и LdrpLoadDll.
C++: Скопировать в буфер обмена
Настоятельно советую ознакомиться с полным кодом этих функций. Дабы вы не искали в моменте, я также выложу его далее,
чтобы не засорять основной посыл данной статьи.
о событиях.
Моя реализация выглядит гораздо проще, но от этого не менее эффективная и минимальный необходимый набор действий она выполняет.
А еще она после компляции весит чуть более 2 кб
Ок, давайте взглянем на мой загрузчик:
Он поддерживает x86\x64 PE файлы, как EXE так и DLL, а еще гибридные, которые одновеменно и EXE и DLL.
Умеет хэндлить ActCtx, SEH, стартап код, анпакинг с помощью aplib, восстанавливать Tls колбеки и многое другое.
Участки, отвечающие за обработку x64 PE обрамлены в соотвествующие дефайны #ifdef _WIN64.
C++: Скопировать в буфер обмена
pe2mem - основная функция загрузчика, принимает единственный параметр PPE_LOADER_PARAMS params.
В pNt читаем NtHeaders криптованного файла
В Base у нас адрес, по которому нужно загрузить файл.
В IsImageDll мы определяем, читая Characteristics какой файл перед нами EXE или DLL. (Это позволяет обрабатывать файлы тансформеры).
В SizeOfBase соотвественно размер образа.
Вот эта структура:
C++: Скопировать в буфер обмена
Думаю и так ясно что она делает, передаёт ImageBase, буфер файла, его размер и дополнительные параметры.
C++: Скопировать в буфер обмена
Данный участок кода это такой лайфхак.
Если ваш стаб имеет код инициализации (т.н. startup код), у вас практически гарантированно возникнут проблемы на этом этапе.
Данный код позволяет проскипать пролог и получить его итоговый размер.
Нужно учитывать, что для каждой версии стартап кода сигнатура может отличаться. Данная реализация покрывает версии студий VC2008, VC2010.
C++: Скопировать в буфер обмена
В этом участке кода мы получаем указатель на Peb, узнаем адреса модулей NTDLL и KERNEL32.
Далее мы находим указатели на необходимые WinAPI функции по их хэшам, и заполняем структуру KernelProcs.
hash_find_proc позволяет по хэшу от строки найти функцию.
C++: Скопировать в буфер обмена
Данная функция позволяет хэшировать строку, в том числе юникодную, на выходе получить DWORD от этой строки.
C++: Скопировать в буфер обмена
В SavedFile выделям память в размере file_size.
Проверяем флаги, если PE_LDR_FLAG_USE_APLIB, то используем распаковку буфера с помощью алгоритма aPlib_depack.
Если в флаги не передан PE_LDR_FLAG_NOT_STUB и Peb->OSMajorVersion > 5, то пересчитываем Fls колбеки соответствующим образом.
Код: Скопировать в буфер обмена
Алгоритм распаковки буфера aPlib_depack.
C++: Скопировать в буфер обмена
В случае, если мы собраны под x64, находим Rsp.
C++: Скопировать в буфер обмена
В данном участке кода мы получаем указатели на DOS и Nt заголовки.
Меняем аттрибуты выделенной памяти на PAGE_EXECUTE_READWRITE.
Копируем заголовки, секции.
Обрабатываем Peb.
Обрабатываем ресурсы.
Обрабатываем импорт.
Обрабатываем релоки.
Обрабатываем TLS.
C++: Скопировать в буфер обмена
Обрабатываем Peb.
C++: Скопировать в буфер обмена
Обрабатываем ресурсы тушки.
Тут важный момент - activation context . Начиная с 7ки в винде в ресурсах появилось понятие manifest или RESID=24. Это нужно в том числе для правильной работы UAC.
Эта функция правильным образом хэндлит такие ресурсы. Читать подробнее тут.
C++: Скопировать в буфер обмена
Обрабатываем таблицу импорта тушки.
В том числе и по ординалу для всяких модулей вроде comctl32 и прочих.
Многие делают это не совсем правильно. Вот правильный вариант.
C++: Скопировать в буфер обмена
Обрабатываем таблицу смещений.
C++: Скопировать в буфер обмена
Обрабатываем TLS колбеки.
На неумении обрабатывать TLS сыпятся очень многие крипторы.
Эта функция обрабатывает основные типы TLS колбеков и таким образом многократно увеличивает покрытие файлов с TLS.
C++: Скопировать в буфер обмена
Тут снова важный момент. Подробнее тут.
C++: Скопировать в буфер обмена
Правильная обработка таблицы исключений для Peb->OSMajorVersion==6 && Peb->OSMinorVersion > 1 // 8++.
C++: Скопировать в буфер обмена
Правильно обрабатываем SEH.
Код: Скопировать в буфер обмена
restore_regs - Восстанавливаем пролог, корректируем стек и переходим по точке входа в замепленномый образ.
C++: Скопировать в буфер обмена
Для x64 PE находим Rsp.
C++: Скопировать в буфер обмена
Дизассемлер длин.
Что касается преимуществ данного способа меппинга - это отсутствие вызовов, таких как порождение нового процесса или получение и смена контекста, работа с потоками, на которые могли бы триггерить проактивки.
Для более скрытного использования загрузчика можно подружить его в syscallы и будет вообще малина.
Скачать
Снова конкурс, а значит снова появился повод поделиться чем-то интересным и полезным с комьюнити.
Хочу выразить огромный респект организаторам, за то что нашли крутой способ для форума собрать классный контент, повышать репутацию форума и притянуть молодых.
В моей прошлой конкурсной статье вами были заданы вопросы про загрузчик, и я тогда ответил:
Загрузчик это отдельная интересная тема, на которую можно будет статью написать и разобрать все нюансы, такие как правильный хэндлинг TLS, ресурсов криптуемого приложения, обработку импорта, экспорта и т.д..
Еще на отдельную тему тянет статья по антиэмуляции (генератору уникальных антиэмуляторов, привет инде). В крипторе есть задел в завязывании генерируемого кода на магические переменные, результат которых отдают антиэмуляторы.
Об этом тоже есть что рассказать, может как нибудь в другой раз.
Нажмите, чтобы раскрыть...
Сказано - сделано.

Загрузчик это действительно отдельная, интересная тема, к которой нужно подойти основательно и расставить все точки над i.
Проект криптора, описанный в прошлой статье, содержит уже откомпилированные версии шеллкодов загрузчика в память, без исходного кода.
В этой статье я поделюсь с вами его полным исходным кодом, опишу что он делает и как происходит обработка и мэппинг файла в память в нюансах.
Мы поговорим о том, чем это решение отличается от системного загрузчика, посмотрим относительно кода системного загрузчика в XP.
Порассуждаем о пользе прямого мэппинга в памяти перед инжектами, такими как RunPE и иже с ними.
Немного предыстории:
Появившийся как концепт, способ загрузки RunPE был практически идеальным решением загрузки бинарика в память на рубеже 08-12гг.
Тогда антивирусы еще относительно плохо умели хучить вызовы и перехватывать управление вновь созданного процесса, проверяя его контекст.
По сути проактивные системы проходили своё становление как самостоятельные технологии и во главе угла стояли сигнатурный и эвристические
алгоритмы обнаружения крипторов.
Принцип работы RunPE достаточно прост:
Стаб (контейнер криптованного файла) пораждает новый приостановленный процесс через CreateProcess с флагом CREATE_SUSPENDED,
получает контекст главного потока через GetThreadContext, после чего приостановленный процесс анмепится с помощью NtUnmapViewOfSection
и по адресу ImageBase выделяется память через VirtualAllocEx с флагом PAGE_EXECUTE_READWRITE.
Далее в выделенную память через WriteProcessMemory записываются хидер криптованного файла и секции.
Финальные штрихи это установка нового контекста на точку входа для главного потока через SetThreadContext и восстановление
приостановленного потока через ResumeThread.
Для поддержки x64 файлов в RunPE требовалось совершить минимум изменений с учетом смещений и регистров (eax\rax, ebx\rbx и т.д.) и вот уже готова полноценная поддержка х64 файлов.
В целом история с RunPE довольно простая, очень стабильная и хорошо отрабатывающая практически любые типы нативных PE файлов, будь то хитрые
заглушки в TLS, аномальные заголовоки, упакованные файлы и т.д.
Одна беда - технологию с того времени так затерли до дыр, что любой уважающий себя антивирус научился определять этот способ загрузки в память,
что напрочь отменяло бы саму идею сокрытия криптуемого файла от антивирусов. С появлением механизмов сканирования памяти эта технология окончательно умерла.
Есть еще промежуточные способы загрузки в память, я отнесу их к RunPE-подобным и назову их инжектами. В данной статье их рассматривать я не буду.
LoadPE:
Первые попытки нативной (почему нативной - опишу ниже) загрузки бинарного файла в память (в паблике) выкладывал покойный Great, как раз на дамаге и на васме.
Те наработки хоть и отражали суть концепции, хотя и работали кое как, но умели обрабатывать только ограниченное количество самых стандартных PE файлов.
Любые отклонения от эталонного PE формата - и загрузка такого файла в память фейлилась.
Позже свои наработки отрывками выкладывал el-, тоже на дамаге.
Многие "мастера" не стали заморачиваться со своим кодом и рипнули загрузчик с криптованных семплов, тупо вставив в свой криптор, мол ведь и так работает.
Ну а некоторым, как мне, захотелось разобраться в сути технологии и написать своё решение, которое было бы способно обрабатывать доминирующее большинство PE файлов,
с учетом разных хитрых, нестандартных техник, поддерживающее как 86 так и 64 архитектуры PE формата.
И вот на базе этих наработок и путем долгих реверсов оригинального загрузчика винды, технология нативной загрузки в память шлифовалась, пока не выкатилось готовое решение.
Почему я называю этот способ нативным? Да потому, что в отличие от незамысловатого RunPE, в LoadPE нам нужно руками воспроизводить действия, которые обычно выполняет
сам загрузчик винды.
Рассмотрим механизм, который используется в нативном загрузчике винды:
В основе загрузки любого модуля лежит функция LoadLibrary, точнее её более низкоуровневые функции из ntdll, такие как LdrLoadDll и LdrpLoadDll.
C++: Скопировать в буфер обмена
Код:
NTSTATUS
LdrpInitializeProcess (
IN PCONTEXT Context OPTIONAL,
IN PVOID SystemDllBase,
IN PUNICODE_STRING UnicodeImageName,
IN BOOLEAN UseCOR,
IN BOOLEAN ImageFileOptionsPresent
)
/*++
Routine Description:
This function initializes the loader for the process.
This includes:
- Initializing the loader data table
- Connecting to the loader subsystem
- Initializing all staticly linked DLLs
Arguments:
Context - Supplies an optional context buffer that will be restore
after all DLL initialization has been completed. If this
parameter is NULL then this is a dynamic snap of this module.
Otherwise this is a static snap prior to the user process
gaining control.
SystemDllBase - Supplies the base address of the system dll.
UnicodeImageName - Base name + extension of the image
UseCOR - TRUE if the image is a COM+ runtime image, FALSE otherwise
ImageFileOptionsPresent - Hint about existing any ImageFileExecutionOption key.
If the key is missing the ApplicationCompatibilityGoo and
DebugProcessHeapOnly entries won't be checked again.
Return Value:
Status value
--*/
NTSTATUS
NTAPI
LdrpLoadDll(
ULONG Flags OPTIONAL,
IN PWSTR DllPath OPTIONAL,
IN PULONG DllCharacteristics OPTIONAL,
IN PUNICODE_STRING DllName,
OUT PVOID *DllHandle,
IN BOOLEAN RunInitRoutines
)
/*++
Routine Description:
This function loads a DLL into the calling process address space.
Arguments:
DllPath - Supplies the search path to be used to locate the DLL.
DllCharacteristics - Supplies an optional DLL characteristics flag,
that if specified is used to match against the dll being loaded.
DllName - Supplies the name of the DLL to load.
DllHandle - Returns a handle to the loaded DLL.
Return Value:
TBD
--*/
NTSTATUS
LdrpMapDll(
IN PWSTR DllPath OPTIONAL,
IN PWSTR DllName,
IN PULONG DllCharacteristics OPTIONAL,
IN BOOLEAN StaticLink,
IN BOOLEAN Redirected,
OUT PLDR_DATA_TABLE_ENTRY *LdrDataTableEntry
)
/*++
Routine Description:
This routine maps the DLL into the users address space.
Arguments:
DllPath - Supplies an optional search path to be used to locate the DLL.
DllName - Supplies the name of the DLL to load.
StaticLink - TRUE if this DLL has a static link to it.
LdrDataTableEntry - Supplies the address of the data table entry.
Return Value:
Status value.
--*/
Настоятельно советую ознакомиться с полным кодом этих функций. Дабы вы не искали в моменте, я также выложу его далее,
чтобы не засорять основной посыл данной статьи.
Как можно увидеть, винда делает просто кучу дополнительных манипуляций при загрузке файла, дополнительные проверки, обработку всего и вся и в том числе нотификациюДля тех, кто всё же хочет в подробностях изучить этот механизм в оригинале, можете найти код в файлах ...\base\ntdll\ldrinit.c, ...\base\ntdll\ldrapi.c и ...\base\ntdll\ldrsnap.c
Нажмите, чтобы раскрыть...
о событиях.
Моя реализация выглядит гораздо проще, но от этого не менее эффективная и минимальный необходимый набор действий она выполняет.
А еще она после компляции весит чуть более 2 кб
Ок, давайте взглянем на мой загрузчик:
Он поддерживает x86\x64 PE файлы, как EXE так и DLL, а еще гибридные, которые одновеменно и EXE и DLL.
Умеет хэндлить ActCtx, SEH, стартап код, анпакинг с помощью aplib, восстанавливать Tls колбеки и многое другое.
Участки, отвечающие за обработку x64 PE обрамлены в соотвествующие дефайны #ifdef _WIN64.
C++: Скопировать в буфер обмена
Код:
VOID WINAPI pe2mem(PPE_LOADER_PARAMS params)
{
DWORD_PTR Base = params->base;
PIMAGE_NT_HEADERS pNt = ((PIMAGE_NT_HEADERS)((DWORD_PTR)Base + ((PIMAGE_DOS_HEADER)Base)->e_lfanew));
BOOL IsImageDll = (pNt->FileHeader.Characteristics & IMAGE_FILE_DLL) ? TRUE : FALSE; // for hybrids, our exe may be dll, we must restore how exe, but fix peb how dll
DWORD SizeOfBase = pNt->OptionalHeader.SizeOfImage;
В pNt читаем NtHeaders криптованного файла
В Base у нас адрес, по которому нужно загрузить файл.
В IsImageDll мы определяем, читая Characteristics какой файл перед нами EXE или DLL. (Это позволяет обрабатывать файлы тансформеры).
В SizeOfBase соотвественно размер образа.
Вот эта структура:
C++: Скопировать в буфер обмена
Код:
typedef struct _PE_LOADER_PARAMS
{
DWORD_PTR base; // ImageBase
PVOID file; // Pointer to file buffer
DWORD file_size; // File size
DWORD flags; // Additional flags
}PE_LOADER_PARAMS, *PPE_LOADER_PARAMS;
C++: Скопировать в буфер обмена
Код:
#ifndef _WIN64
DWORD PrologLocalVarsSize = 0;
if( !(params->flags & PE_LDR_FLAG_NOT_STUB) )
{
//find c++ seh_prolog4
PBYTE pScan = (PBYTE)((DWORD_PTR)Base + pNt->OptionalHeader.AddressOfEntryPoint);
for(int i = 0; i < 30; i++ )
{
if( *pScan==0xE9 && *(PWORD)(pScan + 3)==0xFFFF ) // E9 9F FD FF FF jmp __DllMainCRTStartup (exe)
break;
if( *pScan==0xE8 && *(PWORD)(pScan + 3)==0xFFFF ) // E8 9F FD FF FF call __DllMainCRTStartup (dll)
break;
pScan++;
}
if( *pScan!=0xE9 && *pScan!=0xE8 )
return;
pScan = (PBYTE)((INT_PTR)pScan + 5 + *(PINT_PTR)(pScan + 1)); // __DllMainCRTStartup
if( *pScan==0x6A )
{
PrologLocalVarsSize = *(pScan + 1);
}else if( *pScan==0x68 )
{
PrologLocalVarsSize = *(PDWORD)(pScan + 1);
}else{
return ;
}
}
#endif
Если ваш стаб имеет код инициализации (т.н. startup код), у вас практически гарантированно возникнут проблемы на этом этапе.
Данный код позволяет проскипать пролог и получить его итоговый размер.
Нужно учитывать, что для каждой версии стартап кода сигнатура может отличаться. Данная реализация покрывает версии студий VC2008, VC2010.
C++: Скопировать в буфер обмена
Код:
PEB* Peb = GET_PEB();
PLDR_DATA_TABLE_ENTRY Entry = (PLDR_DATA_TABLE_ENTRY)Peb->Ldr->InInitializationOrderModuleList.Flink;
Entry = CONTAINING_RECORD(Entry,LDR_DATA_TABLE_ENTRY,InInitializationOrderLinks);
DWORD_PTR Ntdll = (DWORD_PTR)Entry->DllBase;
DWORD_PTR NtdllEnds = Ntdll + Entry->SizeOfImage;
// kernelbase ?
while( hash_stringW(Entry->BaseDllName.Buffer)!=HASH_KERNEL32_DLL )
{
Entry = (PLDR_DATA_TABLE_ENTRY)Entry->InInitializationOrderLinks.Flink;
Entry = CONTAINING_RECORD(Entry,LDR_DATA_TABLE_ENTRY,InInitializationOrderLinks);
}
DWORD_PTR Kernel32 = (DWORD_PTR)Entry->DllBase;
DWORD_PTR Kernel32Ends = Kernel32 + Entry->SizeOfImage;
KernelProcs kprocs = {
(TD_GetProcAddress)HASH_GetProcAddress,
(TD_VirtualAlloc)HASH_VirtualAlloc,
(TD_LoadLibraryA)HASH_LoadLibraryA,
(TD_VirtualProtect)HASH_VirtualProtect,
(TD_VirtualFree)HASH_VirtualFree,
(TD_ActivateActCtx)HASH_ActivateActCtx,
(TD_CreateActCtxA)HASH_CreateActCtxA,
(TD_TlsAlloc)HASH_TlsAlloc,
(TD_TlsSetValue)HASH_TlsSetValue,
(TD_FlsFree)HASH_FlsFree,
(TD_RtlAddFunctionTable)HASH_RtlAddFunctionTable,
(TD_RtlDeleteFunctionTable)HASH_RtlDeleteFunctionTable,
(TD_AcquireSRWLockShared)HASH_AcquireSRWLockShared,
NULL
};
for(int i = 0; i < sizeof(KernelProcs)/sizeof(DWORD_PTR); i++)
{
*((PDWORD_PTR)&kprocs + i) = (DWORD_PTR)hash_find_proc((PVOID)Kernel32,*((PDWORD_PTR)&kprocs + i));
}
Далее мы находим указатели на необходимые WinAPI функции по их хэшам, и заполняем структуру KernelProcs.
hash_find_proc позволяет по хэшу от строки найти функцию.
C++: Скопировать в буфер обмена
Код:
DWORD API_CALL hash_string(PCHAR String,BOOL IsUnicode)
{
DWORD i;
DWORD Result = 0;
if( String )
{
for(i=0; *String ;i++)
{
Result = _rotl(Result,3);
Result ^= ( *String>='A' && *String<='Z' ? *String | 0x20 : *String );
String++;
if( IsUnicode )
String++;
}
Result &= ~IMAGE_ORDINAL_FLAG32;
}
return Result;
}
C++: Скопировать в буфер обмена
Код:
__debugbreak();
PVOID SavedFile = kprocs.VirtualAlloc(NULL,params->file_size,MEM_COMMIT,PAGE_READWRITE);
if( SavedFile )
{
if( params->flags & PE_LDR_FLAG_USE_APLIB )
{
aplib_depack_fast(params->file, SavedFile);
}else{
mem_copy(SavedFile,params->file,params->file_size);
}
PIMAGE_DOS_HEADER pDos;
PIMAGE_NT_HEADERS pNt;
// free fls data
if( !(params->flags & PE_LDR_FLAG_NOT_STUB) && Peb->OSMajorVersion > 5 )
{
DWORD Count = Peb->FlsCount;
for(int i = 0; i <= Count; i++)
{
__debugbreak();
if( IN_DLL(Peb->FlsCallbacks[i].Base,Base,Base + SizeOfBase) )
{
kprocs.FlsFree(i);
}
}
}
Проверяем флаги, если PE_LDR_FLAG_USE_APLIB, то используем распаковку буфера с помощью алгоритма aPlib_depack.
Если в флаги не передан PE_LDR_FLAG_NOT_STUB и Peb->OSMajorVersion > 5, то пересчитываем Fls колбеки соответствующим образом.
Код: Скопировать в буфер обмена
Код:
aplib_depack_fast proc a:DWORD, b:DWORD
; aP_depack_asm_fast(const void *source, void *destination)
mov [rsp + 8], rsi
mov [rsp + 16], rdx
push rdi
mov rsi, rcx
mov rdi, rdx
cld
mov dl, 80h
literal:
mov al, [rsi]
add rsi, 1
mov [rdi], al
add rdi, 1
mov r9, 2
nexttag:
getbitM
jnc literal
getbitM
jnc codepair
xor rax, rax
getbitM
jnc shortmatch
getbitM
adc rax, rax
getbitM
adc rax, rax
getbitM
adc rax, rax
getbitM
adc rax, rax
jz thewrite
mov r9, rdi
sub r9, rax
mov al, [r9]
thewrite:
mov [rdi], al
add rdi, 1
mov r9, 2
jmp short nexttag
codepair:
getgammaM rax
sub rax, r9
mov r9, 1
jnz normalcodepair
getgammaM rcx
domatchM r8
jmp nexttag
normalcodepair:
add rax, -1
shl rax, 8
mov al, [rsi]
add rsi, 1
mov r8, rax
getgammaM rcx
cmp rax, 32000
sbb rcx, -1
cmp rax, 1280
sbb rcx, -1
cmp rax, 128
adc rcx, 0
cmp rax, 128
adc rcx, 0
domatchM rax
jmp nexttag
shortmatch:
mov al, [rsi]
add rsi, 1
xor rcx, rcx
db 0c0h, 0e8h, 001h
jz donedepacking
adc rcx, 2
mov r8, rax
domatchM rax
mov r9, 1
jmp nexttag
donedepacking:
mov rax, rdi
sub rax, [rsp + 24]
mov rsi, [rsp + 16]
pop rdi
ret
aplib_depack_fast endp
C++: Скопировать в буфер обмена
Код:
#ifdef _WIN64
pDos = (PIMAGE_DOS_HEADER)Base;
pNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
DWORD_PTR SaveRegs[r_all];
mem_zero(SaveRegs,sizeof(DWORD_PTR)*r_all);
__debugbreak();
PDWORD_PTR Rsp = (PDWORD_PTR)get_return();
while( !IN_DLL(*Rsp,Kernel32,Kernel32Ends) && !IN_DLL(*Rsp,Ntdll,NtdllEnds) )
{
Rsp = find_next_rsp(SaveRegs,Rsp);
}
#endif
C++: Скопировать в буфер обмена
Код:
pDos = (PIMAGE_DOS_HEADER)SavedFile;
pNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
DWORD OldProtection;
__debugbreak();
if( kprocs.VirtualProtect((PVOID)Base,pNt->OptionalHeader.SizeOfImage,PAGE_EXECUTE_READWRITE,&OldProtection) )
{
PVOID NewImageBase = (PVOID)Base;
mem_set(NewImageBase,0,pNt->OptionalHeader.SizeOfImage); // bugfix: must be NULL where not writed something
mem_copy(NewImageBase, pDos, pNt->OptionalHeader.SizeOfHeaders);
PIMAGE_SECTION_HEADER Sections = IMAGE_FIRST_SECTION(pNt);
for (INT i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
mem_copy((PVOID)((DWORD_PTR)NewImageBase + Sections->VirtualAddress), (PVOID)((DWORD_PTR)pDos + Sections->PointerToRawData), Sections->SizeOfRawData);
Sections++;
}
PIMAGE_DOS_HEADER pNewDos = (PIMAGE_DOS_HEADER)NewImageBase;
PIMAGE_NT_HEADERS pNewNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pNewDos + pNewDos->e_lfanew);
process_peb(NewImageBase,IsImageDll,(PVOID)Base);
process_resource(&kprocs,NewImageBase); //bugfix: manifest must be processed first, and other dll may be after that, or import may fail call [NULL]
process_import(&kprocs,NewImageBase);
process_relocs(NewImageBase); //
process_tls(&kprocs,NewImageBase); // tls must be after relocs, because they fix StartDataAddress and other info
Меняем аттрибуты выделенной памяти на PAGE_EXECUTE_READWRITE.
Копируем заголовки, секции.
Обрабатываем Peb.
Обрабатываем ресурсы.
Обрабатываем импорт.
Обрабатываем релоки.
Обрабатываем TLS.
C++: Скопировать в буфер обмена
Код:
VOID __fastcall process_peb(PVOID ImageData,BOOL IsImageDll,PVOID OldImageBase)
{
PEB* Peb = GET_PEB();
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ImageData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
if( !IsImageDll )
{
Peb->ImageBaseAddress = (DWORD_PTR)ImageData;
}
PLDR_DATA_TABLE_ENTRY Entry = (PLDR_DATA_TABLE_ENTRY)Peb->Ldr->InMemoryOrderModuleList.Flink;
while( Entry!= (PLDR_DATA_TABLE_ENTRY)&Peb->Ldr->InMemoryOrderModuleList )
{
Entry = CONTAINING_RECORD(Entry,LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks);
if( Entry->DllBase==OldImageBase )
{
Entry->DllBase = ImageData;
Entry->EntryPoint = (PVOID)((DWORD_PTR)pDos + pNt->OptionalHeader.AddressOfEntryPoint);
Entry->SizeOfImage = pNt->OptionalHeader.SizeOfImage;
break;
}
Entry = (PLDR_DATA_TABLE_ENTRY)Entry->InMemoryOrderLinks.Flink;
}
}
C++: Скопировать в буфер обмена
Код:
VOID __fastcall process_resource(KernelProcs* Procs,PVOID ImageData)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ImageData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
PIMAGE_RESOURCE_DIRECTORY Resource = (PIMAGE_RESOURCE_DIRECTORY)((DWORD_PTR)pDos + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
if (Resource!=(PIMAGE_RESOURCE_DIRECTORY)pDos)
{
CHAR Name[8];
ULONG_PTR Cookie;
*(PDWORD)Name = 'a';
ACTCTXA Ctx;
Ctx.cbSize = sizeof(ACTCTXA);
Ctx.dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID;
Ctx.lpResourceName = MAKEINTRESOURCEA(1);
Ctx.lpSource = Name;
Ctx.wProcessorArchitecture = 0;
Ctx.lpAssemblyDirectory = NULL;
Ctx.lpApplicationName = NULL;
Ctx.hModule = (HMODULE)ImageData;
HANDLE hCtx = Procs->CreateActCtxA(&Ctx);
if( hCtx!=INVALID_HANDLE_VALUE )
{
Procs->ActivateActCtx(hCtx,&Cookie);
}
}
}
Тут важный момент - activation context . Начиная с 7ки в винде в ресурсах появилось понятие manifest или RESID=24. Это нужно в том числе для правильной работы UAC.
Эта функция правильным образом хэндлит такие ресурсы. Читать подробнее тут.
Microsoft.Windows.ActCtx object
The Microsoft.Windows.ActCtx object references manifests and provides a way for scripting engines to access side-by-side assemblies. The Microsoft.Windows.ActCtx object can be used to create an instance of a side-by-side assembly with COM components.
The Microsoft.Windows.ActCtx object comes as an assembly in Windows Server 2003. It can also be installed by applications that use the Windows Installer for setup and include it as a merge module in their installation package.
Нажмите, чтобы раскрыть...
C++: Скопировать в буфер обмена
Код:
VOID __fastcall process_import(KernelProcs *Procs,PVOID ImageData)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ImageData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR Import = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)pDos + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
if (Import!=(PIMAGE_IMPORT_DESCRIPTOR)pDos)
{
while (Import->Name != 0)
{
PCHAR DllName = (PCHAR)((DWORD_PTR)pDos + Import->Name);
HMODULE Dll = Procs->LoadLibraryA(DllName);
if (!Dll)
return ;
PDWORD_PTR pImport = (Import->OriginalFirstThunk ? (PDWORD_PTR)((DWORD_PTR)pDos + Import->OriginalFirstThunk) : (PDWORD_PTR)((DWORD_PTR)pDos + Import->FirstThunk));
PDWORD_PTR pAddress = (PDWORD_PTR)((DWORD_PTR)pDos + Import->FirstThunk);
while (*pImport)
{
DWORD_PTR FuncAddress = NULL;
#ifdef _WIN64
if ((*pImport & IMAGE_ORDINAL_FLAG64)) // ordinal
#else
if ((*pImport & IMAGE_ORDINAL_FLAG32)) // ordinal
#endif
{
DWORD_PTR Ordinal = (*pImport & 0xFFFF);
FuncAddress = (DWORD_PTR)Procs->GetProcAddress(Dll, (PCHAR)Ordinal);
}else{
PCHAR FuncName = (PCHAR)((DWORD_PTR)pDos + *pImport + 2);
FuncAddress = (DWORD_PTR)Procs->GetProcAddress(Dll, FuncName);
}
*pAddress++ = FuncAddress;
pImport++;
}
Import++;
}
}
}
В том числе и по ординалу для всяких модулей вроде comctl32 и прочих.
Многие делают это не совсем правильно. Вот правильный вариант.
C++: Скопировать в буфер обмена
Код:
VOID __fastcall process_relocs(PVOID ImageData)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ImageData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
PIMAGE_BASE_RELOCATION BaseRelocs = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)pDos + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
INT_PTR Delta;
if (BaseRelocs!=(PIMAGE_BASE_RELOCATION)pDos)
{
PIMAGE_BASE_RELOCATION Reloc = BaseRelocs;
Delta = (INT_PTR)ImageData - pNt->OptionalHeader.ImageBase;
do
{
PIMAGE_FIXUP_ENTRY Fixup = (PIMAGE_FIXUP_ENTRY)((DWORD_PTR)Reloc + sizeof(IMAGE_BASE_RELOCATION));
for (int r = 0, sz = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) >> 1; r < sz; r++)
{
if (Fixup->Type == IMAGE_REL_BASED_HIGHLOW)
{
*(PINT)((DWORD_PTR)pDos + Reloc->VirtualAddress + Fixup->Offset) += Delta;
}else if (Fixup->Type==IMAGE_REL_BASED_DIR64)
{
if(Reloc->VirtualAddress + Fixup->Offset==0x63D88 )
{
Delta = (INT)ImageData - pNt->OptionalHeader.ImageBase;
}
*(PINT_PTR)((DWORD_PTR)pDos + Reloc->VirtualAddress + Fixup->Offset) += Delta;
}
Fixup++;
}
Reloc = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)Reloc + Reloc->SizeOfBlock);
} while (Reloc->VirtualAddress);
}
}
C++: Скопировать в буфер обмена
Код:
VOID __fastcall process_tls(KernelProcs* Procs,PVOID ImageData)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ImageData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
PIMAGE_TLS_DIRECTORY Tls = (PIMAGE_TLS_DIRECTORY)((DWORD_PTR)pDos + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress);
if( Tls!=(PIMAGE_TLS_DIRECTORY)pDos )
{
PBYTE pTlsData = (PBYTE)Procs->VirtualAlloc(NULL,Tls->EndAddressOfRawData - Tls->StartAddressOfRawData,MEM_COMMIT,PAGE_READWRITE);
if( pTlsData )
{
mem_copy(pTlsData,Tls->StartAddressOfRawData,Tls->EndAddressOfRawData - Tls->StartAddressOfRawData);
Procs->TlsSetValue(Procs->TlsAlloc(),pTlsData);
PDWORD_PTR Ptrs = (PDWORD_PTR)Procs->VirtualAlloc(NULL,1088*sizeof(DWORD_PTR),MEM_COMMIT,PAGE_READWRITE);
if( Ptrs )
{
if( *(PDWORD_PTR)Tls->AddressOfIndex==-1 )
{
Ptrs[0] = (DWORD_PTR)pTlsData;
}else{
Ptrs[ *(PDWORD_PTR)Tls->AddressOfIndex ] = (DWORD_PTR)pTlsData;
}
// http://svn.netlabs.org/repos/libc/trunk/libc/include/klibc/nt/fib.h
#ifdef _WIN64
__writegsqword(0x58,(DWORD_PTR)Ptrs);
#else
__writefsdword(0x2C,(DWORD_PTR)Ptrs);
#endif
}
}
}
}
На неумении обрабатывать TLS сыпятся очень многие крипторы.
Эта функция обрабатывает основные типы TLS колбеков и таким образом многократно увеличивает покрытие файлов с TLS.
C++: Скопировать в буфер обмена
Код:
#ifdef _WIN64
PIMAGE_DOS_HEADER pNtdllDos = (PIMAGE_DOS_HEADER)Ntdll;
PIMAGE_NT_HEADERS pNtdllNt = (PIMAGE_NT_HEADERS)((DWORD_PTR)pNtdllDos + pNtdllDos->e_lfanew);
PIMAGE_SECTION_HEADER pTextSection = IMAGE_FIRST_SECTION(pNtdllNt);
PVOID LdrpInvertedFunctionTable = NULL;
PBYTE stext = (PBYTE)((DWORD_PTR)pNtdllDos + pTextSection->VirtualAddress);
for(int i = 0; i < pTextSection->Misc.VirtualSize; i++)
{
if( *(stext + i)==0xE8 )
{
DWORD_PTR Address = (DWORD_PTR)((INT_PTR)stext + i + 5 + *(PINT32)(stext + i + 1));
if( Address==(DWORD_PTR)kprocs.AcquireSRWLockShared )
{
PBYTE pMov = stext + i + 5;
PBYTE pStart = pMov;
if( *pMov==0x44 ) pMov++; // mov r9d
if( *pMov==0x8B )
{
pMov++;
if( *pMov==0x0D || *pMov==0x15 )
{
pMov++;
LdrpInvertedFunctionTable = (PVOID)((INT_PTR)pStart + (pMov - (stext + i + 5)) + 4 + *(PINT32)pMov);
break;
}
}
}
}
}
Обработка секции кода с помощью AcquireSRWLockShared, чтобы правильно готовить синхронизацию на чтение и запись.Цитата с хабра: RWLock — это такой примитив синхронизации, позволяющий одновременное чтение и эксклюзивную запись. Т.е. чтение блокирует запись, но не блокирует чтение других тредов, а запись блокирует все.
Нажмите, чтобы раскрыть...
C++: Скопировать в буфер обмена
Код:
__debugbreak();
if( LdrpInvertedFunctionTable )
{
__debugbreak();
if( Peb->OSMajorVersion==6 && Peb->OSMinorVersion > 1 ) // 8++
{
PRTL_INVERTED_FUNCTION_TABLE8 table = (PRTL_INVERTED_FUNCTION_TABLE8)LdrpInvertedFunctionTable;
for(int i = 0; i < table->Count; i++)
{
if( table->Entries[i].ImageBase==(PVOID)Base )
{
table->Entries[i].ImageBase = NewImageBase;
table->Entries[i].ImageSize = pNewNt->OptionalHeader.SizeOfImage;
table->Entries[i].ExceptionDirectory = (PIMAGE_RUNTIME_FUNCTION_ENTRY)((DWORD_PTR)NewImageBase + pNewNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress);
table->Entries[i].ExceptionDirectorySize = pNewNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
break;
}
}
}else{ // < 7
PRTL_INVERTED_FUNCTION_TABLE7 table = (PRTL_INVERTED_FUNCTION_TABLE7)LdrpInvertedFunctionTable;
for(int i = 0; i < table->Count; i++)
{
if( table->Entries[i].ImageBase==(PVOID)Base )
{
table->Entries[i].ImageBase = NewImageBase;
table->Entries[i].ImageSize = pNewNt->OptionalHeader.SizeOfImage;
table->Entries[i].ExceptionDirectory = (PIMAGE_RUNTIME_FUNCTION_ENTRY)((DWORD_PTR)NewImageBase + pNewNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress);
table->Entries[i].ExceptionDirectorySize = pNewNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
break;
}
}
}
}
#endif
C++: Скопировать в буфер обмена
Код:
#ifndef _WIN64
// remove SAFESEH if exists
if( pNewNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress )
{
pNewNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress = NULL;
pNewNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size = NULL;
}
// restore seh
PSEH_FRAME Seh = (PSEH_FRAME)__readfsdword(0);
while( IN_DLL(Seh->Callback,Base,Base + SizeOfBase) )
{
Seh = Seh->Next;
}
__writefsdword(0,(DWORD_PTR)Seh);
// find ebp end
PSTACK_FRAME Ebp = (PSTACK_FRAME)read_ebp();
while( !IN_DLL(Ebp->ReturnAddress,Kernel32,Kernel32Ends) && !IN_DLL(Ebp->ReturnAddress,Ntdll,NtdllEnds) )
{
Ebp = Ebp->Next;
}
restore_regs(Ebp,(pNt->FileHeader.Characteristics & IMAGE_FILE_DLL),PrologLocalVarsSize,(DWORD_PTR)NewImageBase + pNt->OptionalHeader.AddressOfEntryPoint);
#else
restore_regs(
SaveRegs,(pNt->FileHeader.Characteristics & IMAGE_FILE_DLL),(DWORD_PTR)NewImageBase + pNt->OptionalHeader.AddressOfEntryPoint);
#endif
}
kprocs.VirtualFree(SavedFile,NULL,MEM_RELEASE);
}
}
Код: Скопировать в буфер обмена
Код:
restore_regs proc EbpFrame,IsDll,PrologSize,Ep
; ecx - ebp
; edx - is dll
; stack - Ep
;int 3
mov ecx, EbpFrame
.if IsDll
; skip mov esi, esp, after all regs will restored
add dword ptr [ecx + 4], 2
.else
; msvc2008
mov edx, ecx
sub edx, PrologSize
mov ebx , dword ptr [edx - 014h]
mov esi , dword ptr [edx - 018h]
mov edi , dword ptr [edx - 01Ch]
.endif
mov edx, Ep
; correct stack
mov ebp, dword ptr [ecx]
lea esp, [ecx + 4]
;int 3
jmp edx
restore_regs endp
C++: Скопировать в буфер обмена
Код:
#ifdef _WIN64
PDWORD_PTR __fastcall find_next_rsp(PDWORD_PTR regs,PDWORD_PTR Rsp)
{
ldasm_data dasm;
dasm.lde_flags_table = lde_get_table();
dasm.lde_flags_table_ex = lde_get_table_ex();
PBYTE pCode = (PBYTE)*Rsp; // return address
DWORD len = 0;
DWORD r11;
Rsp = Rsp + 1;
do
{
pCode = pCode + len;
len = lde_length(&dasm,LDE_CPU_TYPE_X64,pCode);
if( !len )
return NULL;
DWORD Step = 0;
DWORD creg,offset;
if( len==8 )
{
/*
.text:0000000140001DB1 48 8B 84 24 98 01 00 00 mov rax, [rsp+198h]
.text:0000000140001DB9 48 8B 8C 24 98 01 00 00 mov rcx, [rsp+198h]
.text:0000000140001DC1 48 8B 94 24 98 01 00 00 mov rdx, [rsp+198h]
.text:0000000140001DC9 48 8B 9C 24 98 01 00 00 mov rbx, [rsp+198h]
.text:0000000140001DD1 48 8B A4 24 98 01 00 00 mov rsp, [rsp+198h]
.text:0000000140001DD9 48 8B AC 24 98 01 00 00 mov rbp, [rsp+198h]
.text:0000000140001DE1 48 8B B4 24 98 01 00 00 mov rsi, [rsp+198h]
.text:0000000140001DE9 48 8B BC 24 98 01 00 00 mov rdi, [rsp+198h]
.text:0000000140001DF1 4C 8B 84 24 98 01 00 00 mov r8, [rsp+198h]
.text:0000000140001DF9 4C 8B 8C 24 98 01 00 00 mov r9, [rsp+198h]
.text:0000000140001E01 4C 8B 94 24 98 01 00 00 mov r10, [rsp+198h]
.text:0000000140001E09 4C 8B 9C 24 98 01 00 00 mov r11, [rsp+198h]
.text:0000000140001E11 4C 8B A4 24 98 01 00 00 mov r12, [rsp+198h]
.text:0000000140001E19 4C 8B AC 24 98 01 00 00 mov r13, [rsp+198h]
.text:0000000140001E21 4C 8B B4 24 98 01 00 00 mov r14, [rsp+198h]
.text:0000000140001E29 4C 8B BC 24 98 01 00 00 mov r15, [rsp+198h]
*/
if( (*(PWORD)pCode==0x8B48 || *(PWORD)pCode==0x8B4C) && pCode[3]==0x24 )
{
if( *pCode==0x4C ) Step += r_step;
creg = (*(pCode + 2) - 0x84)/8;
offset = *(PDWORD)(pCode + 4);
regs[Step + creg] = *(PDWORD_PTR)((PBYTE)Rsp + offset);
}
/*
.text:0000000140001D01 48 8D 84 24 98 01 00 00 lea rax, [rsp+198h]
.text:0000000140001D09 48 8D 8C 24 98 01 00 00 lea rcx, [rsp+198h]
.text:0000000140001D11 48 8D 94 24 98 01 00 00 lea rdx, [rsp+198h]
.text:0000000140001D19 48 8D 9C 24 98 01 00 00 lea rbx, [rsp+198h]
.text:0000000140001D21 48 8D A4 24 98 01 00 00 lea rsp, [rsp+198h]
.text:0000000140001D29 48 8D AC 24 98 01 00 00 lea rbp, [rsp+198h]
.text:0000000140001D31 48 8D B4 24 98 01 00 00 lea rsi, [rsp+198h]
.text:0000000140001D39 48 8D BC 24 98 01 00 00 lea rdi, [rsp+198h]
.text:0000000140001D41 4C 8D 84 24 98 01 00 00 lea r8, [rsp+198h]
.text:0000000140001D49 4C 8D 8C 24 98 01 00 00 lea r9, [rsp+198h]
.text:0000000140001D51 4C 8D 94 24 98 01 00 00 lea r10, [rsp+198h]
.text:0000000140001D59 4C 8D 9C 24 98 01 00 00 lea r11, [rsp+198h]
.text:0000000140001D61 4C 8D A4 24 98 01 00 00 lea r12, [rsp+198h]
.text:0000000140001D69 4C 8D AC 24 98 01 00 00 lea r13, [rsp+198h]
.text:0000000140001D71 4C 8D B4 24 98 01 00 00 lea r14, [rsp+198h]
.text:0000000140001D79 4C 8D BC 24 98 01 00 00 lea r15, [rsp+198h]
*/
if( (*(PWORD)pCode==0x8D48 || *(PWORD)pCode==0x8D4C) && pCode[3]==0x24 )
{
if( *pCode==0x4C ) Step += r_step;
creg = (*(pCode + 2) - 0x84)/8;
offset = *(PDWORD)(pCode + 4);
regs[Step + creg] = (DWORD_PTR)Rsp + offset;
}
}else if( len==5 )
{
/*
.text:0000000140001D61 48 8B 44 24 11 mov rax, [rsp+11h]
.text:0000000140001D66 48 8B 4C 24 11 mov rcx, [rsp+11h]
.text:0000000140001D6B 48 8B 54 24 11 mov rdx, [rsp+11h]
.text:0000000140001D70 48 8B 5C 24 11 mov rbx, [rsp+11h]
.text:0000000140001D75 48 8B 64 24 11 mov rsp, [rsp+11h]
.text:0000000140001D7A 48 8B 6C 24 11 mov rbp, [rsp+11h]
.text:0000000140001D7F 48 8B 74 24 11 mov rsi, [rsp+11h]
.text:0000000140001D84 48 8B 7C 24 11 mov rdi, [rsp+11h]
.text:0000000140001D89 4C 8B 44 24 11 mov r8, [rsp+11h]
.text:0000000140001D8E 4C 8B 4C 24 11 mov r9, [rsp+11h]
.text:0000000140001D93 4C 8B 54 24 11 mov r10, [rsp+11h]
.text:0000000140001D98 4C 8B 5C 24 11 mov r11, [rsp+11h]
.text:0000000140001D9D 4C 8B 64 24 11 mov r12, [rsp+11h]
.text:0000000140001DA2 4C 8B 6C 24 11 mov r13, [rsp+11h]
.text:0000000140001DA7 4C 8B 74 24 11 mov r14, [rsp+11h]
.text:0000000140001DAC 4C 8B 7C 24 11 mov r15, [rsp+11h]
*/
if( (*(PWORD)pCode==0x8B48 || *(PWORD)pCode==0x8B4C) && pCode[3]==0x24 )
{
if( *pCode==0x4C ) Step += r_step;
creg = (*(pCode + 2) - 0x44)/8;
offset = *(pCode + 4);
regs[Step + creg] = *(PDWORD_PTR)((PBYTE)Rsp + offset);
}
/*
.text:0000000140001CB1 48 8D 44 24 11 lea rax, [rsp+11h]
.text:0000000140001CB6 48 8D 4C 24 11 lea rcx, [rsp+11h]
.text:0000000140001CBB 48 8D 54 24 11 lea rdx, [rsp+11h]
.text:0000000140001CC0 48 8D 5C 24 11 lea rbx, [rsp+11h]
.text:0000000140001CC5 48 8D 64 24 11 lea rsp, [rsp+11h]
.text:0000000140001CCA 48 8D 6C 24 11 lea rbp, [rsp+11h]
.text:0000000140001CCF 48 8D 74 24 11 lea rsi, [rsp+11h]
.text:0000000140001CD4 48 8D 7C 24 11 lea rdi, [rsp+11h]
.text:0000000140001CD9 4C 8D 44 24 11 lea r8, [rsp+11h]
.text:0000000140001CDE 4C 8D 4C 24 11 lea r9, [rsp+11h]
.text:0000000140001CE3 4C 8D 54 24 11 lea r10, [rsp+11h]
.text:0000000140001CE8 4C 8D 5C 24 11 lea r11, [rsp+11h]
.text:0000000140001CED 4C 8D 64 24 11 lea r12, [rsp+11h]
.text:0000000140001CF2 4C 8D 6C 24 11 lea r13, [rsp+11h]
.text:0000000140001CF7 4C 8D 74 24 11 lea r14, [rsp+11h]
.text:0000000140001CFC 4C 8D 7C 24 11 lea r15, [rsp+11h]
*/
if( (*(PWORD)pCode==0x8D48 || *(PWORD)pCode==0x8D4C) && pCode[3]==0x24 )
{
if( *pCode==0x4C ) Step += r_step;
creg = (*(pCode + 2) - 0x44)/8;
offset = *(pCode + 4);
regs[Step + creg] = (DWORD_PTR)Rsp + offset;
}
}else if( len==1 || len==2 )
{
/*
.text:0000000140001D61 58 pop rax
.text:0000000140001D62 59 pop rcx
.text:0000000140001D63 5A pop rdx
.text:0000000140001D64 5B pop rbx
.text:0000000140001D65 5C pop rsp
.text:0000000140001D66 5D pop rbp
.text:0000000140001D67 5E pop rsi
.text:0000000140001D68 5F pop rdi
.text:0000000140001D69 41 58 pop r8
.text:0000000140001D6B 41 59 pop r9
.text:0000000140001D6D 41 5A pop r10
.text:0000000140001D6F 41 5B pop r11
.text:0000000140001D71 41 5C pop r12
.text:0000000140001D73 41 5D pop r13
.text:0000000140001D75 41 5E pop r14
.text:0000000140001D77 41 5F pop r15
*/
BOOL next = FALSE;
if( *pCode>=0x5B && *pCode<=0x5F ) // only rbp - rdi
{
next = true;
creg = *pCode - 0x58;
}else if( *pCode==0x41 && *(pCode + 1)>=0x5C && *(pCode + 1)<=0x5F) // only r12-r15
{
next = true;
creg = *(pCode + 1) - 0x58;
Step += r_step;
}
if( next )
{
regs[Step + creg] = *(PDWORD_PTR)Rsp;
Rsp = Rsp + 1;
}
}else if (len==4 && *pCode==0x48 && *(PWORD)(pCode + 1)==0xC483 )
{
// .text:0000000140001C51 48 83 C4 28 add rsp, 28h
Rsp = (PDWORD_PTR)((PBYTE)Rsp + pCode[3]);
}else if( len==7 && *pCode==0x48 && *(PWORD)(pCode + 1)==0xC481 )
{
// .text:0000000140001BE6 48 81 C4 88 00 00 00 add rsp, 88h
Rsp = (PDWORD_PTR)((PBYTE)Rsp + *(PDWORD)(pCode + 3) );
}else if( len==3 && *pCode==0x49 && *(PWORD)(pCode + 1)==0xE38B )
{
//.text:0000000140002680 49 8B E3 mov rsp, r11
Rsp = (PDWORD_PTR)regs[r_r11];
}
} while (*pCode!=0xC3);
regs[r_rsp] = (DWORD_PTR)Rsp;
return Rsp;
}
C++: Скопировать в буфер обмена
Код:
DWORD __fastcall lde_length(ldasm_data *ld,LDE_CPU_TYPE CpuType, PVOID Memory)
{
BYTE *p = (PBYTE)Memory;
BYTE s, op, f;
BYTE rexw, pr_66, pr_67;
s = rexw = pr_66 = pr_67 = 0;
/* dummy check */
if (!Memory)
return 0;
/* init output data */
mem_zero(ld, sizeof(ldasm_data) - sizeof(PBYTE) - sizeof(PBYTE));
/* phase 1: parse prefixies */
while (ld->lde_flags_table[*p] & OP_PREFIX)
{
if (*p == 0x66)
pr_66 = 1;
if (*p == 0x67)
pr_67 = 1;
p++; s++;
ld->flags |= F_PREFIX;
if (s == 15) {
ld->flags |= F_INVALID;
return s;
}
}
/* parse REX prefix */
if (CpuType == LDE_CPU_TYPE_X64 && *p >> 4 == 4)
{
ld->rex = *p;
rexw = (ld->rex >> 3) & 1;
ld->flags |= F_REX;
p++; s++;
}
/* can be only one REX prefix */
if (CpuType == LDE_CPU_TYPE_X64 && *p >> 4 == 4)
{
ld->flags |= F_INVALID;
s++;
return s;
}
/* phase 2: parse op Memory */
ld->opcd_offset = (BYTE)(p - (BYTE*)Memory);
ld->opcd_size = 1;
op = *p++; s++;
/* is 2 byte opcede? */
if (op == 0x0F)
{
op = *p++; s++;
ld->opcd_size++;
f = ld->lde_flags_table_ex[op];
if (f & OP_INVALID){
ld->flags |= F_INVALID;
return s;
}
/* for SSE instructions */
if (f & OP_EXTENDED) {
op = *p++; s++;
ld->opcd_size++;
}
}
else {
f = ld->lde_flags_table[op];
/* pr_66 = pr_67 for opMemorys A0-A3 */
if (op >= 0xA0 && op <= 0xA3)
pr_66 = pr_67;
}
/* phase 3: parse ModR/M, SIB and DISP */
if (f & OP_MODRM) {
BYTE mod = (*p >> 6);
BYTE ro = (*p & 0x38) >> 3;
BYTE rm = (*p & 7);
ld->modrm = *p++; s++;
ld->flags |= F_MODRM;
/* in F6,F7 opMemorys immediate data present if R/O == 0 */
if (op == 0xF6 && (ro == 0 || ro == 1))
f |= OP_DATA_I8;
if (op == 0xF7 && (ro == 0 || ro == 1))
f |= OP_DATA_I16_I32_I64;
/* is SIB byte exist? */
if (mod != 3 && rm == 4 && !(!CpuType == LDE_CPU_TYPE_X64 && pr_67)) {
ld->sib = *p++; s++;
ld->flags |= F_SIB;
/* if base == 5 and mod == 0 */
if ((ld->sib & 7) == 5 && mod == 0) {
ld->disp_size = 4;
}
}
if (!mod)
{
if (CpuType == LDE_CPU_TYPE_X64) {
if (rm == 5) {
ld->disp_size = 4;
if (CpuType == LDE_CPU_TYPE_X64)
ld->flags |= F_RELATIVE;
}
}
else if (pr_67) {
if (rm == 6)
ld->disp_size = 2;
}
else {
if (rm == 5)
ld->disp_size = 4;
}
}
else if (mod == 1)
{
ld->disp_size = 1;
}
else if (mod == 2)
{
if (CpuType == LDE_CPU_TYPE_X64)
ld->disp_size = 4;
else if (pr_67)
ld->disp_size = 2;
else
ld->disp_size = 4;
}
if (ld->disp_size) {
ld->disp_offset = (BYTE)(p - (BYTE *)Memory);
p += ld->disp_size;
s += ld->disp_size;
ld->flags |= F_DISP;
}
}
/* phase 4: parse immediate data */
if (rexw && f & OP_DATA_I16_I32_I64)
ld->imm_size = 8;
else if (f & OP_DATA_I16_I32 || f & OP_DATA_I16_I32_I64)
ld->imm_size = 4 - (pr_66 << 1);
/* if exist, add OP_DATA_I16 and OP_DATA_I8 size */
ld->imm_size += f & 3;
if (ld->imm_size) {
s += ld->imm_size;
ld->imm_offset = (BYTE)(p - (BYTE *)Memory);
ld->flags |= F_IMM;
if (f & OP_RELATIVE)
ld->flags |= F_RELATIVE;
}
/* instruction is too long */
if (s > 15)
{
ld->flags |= F_INVALID;
s = 0;
}
return s;
}
Что касается преимуществ данного способа меппинга - это отсутствие вызовов, таких как порождение нового процесса или получение и смена контекста, работа с потоками, на которые могли бы триггерить проактивки.
Для более скрытного использования загрузчика можно подружить его в syscallы и будет вообще малина.
Скачать
View hidden content is available for registered users!
View hidden content is available for registered users!