один
Предисловие
Одним из способов уклонения от обнаружения AV является шифрование памяти. Поскольку антивирусное программное обеспечение не сканирует память постоянно, а периодически сканирует конфиденциальную память, вы можете вызвать сон в шелл-коде cs, чтобы зашифровать область памяти исполняемого файла, а затем расшифровать память в конце сна, чтобы избежать антипрограммной памяти. сканирование. Цель убийства.
Основываясь на вышеизложенных принципах и в сочетании с собственными идеями, я успешно реализовал динамическую антиубийственность Tinder, 360, Defender, Kabbah и т. д. Среди них Kabbah невозможно запустить с помощью cmd, иначе он будет немедленно убит. cmd слишком строгий. В результате шеллкод не работает. Он убивается перед выполнением сна для шифрования памяти. Его можно запустить обычным способом с помощью powershell или других методов, но он убивается после часа работы. Даже если cs использует файл конфигурации профиля, характеристики трафика все равно слишком очевидны. рекомендуется попробовать другие методы, более непопулярный c2.
Это будет объяснено ниже, включая следующие четыре аспекта:
Среди них 32-битная реализация антивируса с шифрованием памяти относительно проста, тогда как 64-битная более сложна и не может быть реализована с помощью простого хука. Здесь используется код ShellcodeFluctuation.
Наконец, в процессе реализации шифрования памяти также были обнаружены недостатки и предложены усовершенствованные методы.
Примечание. Эта статья предназначена для новичков, поэтому она довольно длинная.
два
визуализации
Ниже приведено 32-битное Шифрование памяти, свободной от убийственных визуализаций, 64-битная версия почти такая же и не будет выпущена:
три
hook
Windows API Hook — это механизм, аналогичный прерываниям на платформе Windows. Это позволяет приложениям перехватывать и обрабатывать сообщения Windows или указанные события. Когда указанное сообщение отправляется, программа-перехватчик может перехватывать сообщение до того, как оно достигнет целевого окна, тем самым получая контроль над сообщением, а затем обрабатывая или изменяя и добавляя функции. нам нужно.
Технология перехвата широко используется во многих областях безопасности. Например, функция активной защиты антивирусного программного обеспечения включает в себя мониторинг некоторых конфиденциальных API, которые требуют перехвата этих API; троянским вирусам, которые крадут пароли, необходимо перехватывать сообщения с клавиатуры; даже системы Windows и некоторые приложения должны использовать технологию перехвата при обновлении.
Перехватчики API обычно включают в себя следующие методы:
Следует отметить, что поскольку шелл-код CS получает адрес Windows API путем обхода структуры PEB и таблицы экспорта PE-файла и поиска необходимых модулей и функций API на основе хэш-значения экспортируемой функции, метод перехвата IAT недействителен для Здесь в основном используются встроенные хуки CS.
Здесь используются две функции ReadProcessMemory и WriteProcessMemory:
// Перемещает данные в указанном диапазоне адресов из адресного пространства копировать указанного процесса в указанный буфер текущего процесса.
BOOL ReadProcessMemory(
[in] HANDLE hProcess, // Чтение дескриптора процесса для Память.
[in] LPCVOID lpBaseAddress, // Указатель на базовый адрес в указанном процессе для чтения.
[out] LPVOID lpBuffer, // Указатель на буфер, который получает содержимое из адресного пространства указанного процесса.
[in] SIZE_T nSize, // Количество байтов, которые необходимо прочитать из указанного процесса.
[out] SIZE_T *lpNumberOfBytesRead // Указатель на переменную, которая получает количество байтов, переданных указанному процессу.
);
// Записывает данные в область Память указанного процесса.
BOOL WriteProcessMemory(
[in] HANDLE hProcess, // Дескриптор процесса Память будет изменен.
[in] LPVOID lpBaseAddress, // Указатель на базовый адрес данных, записанных в указанном процессе.
[in] LPCVOID lpBuffer, // Указатель буфера, в который должны быть записаны записанные данные.
[in] SIZE_T nSize, // Количество байтов, указанное процессом для записи.
[out] SIZE_T *lpNumberOfBytesWritten
);
Чтение и запись памяти требуют соответствующих разрешений, иначе их нельзя изменить. При использовании функций ReadProcessMemory и WriteProcessMemory для чтения и записи памяти соответствующие разрешения будут получены автоматически, поэтому вы можете изменить разрешения без использования VirtualProtect.
Измените код функции, чтобы перейти к нескольким методам перехода к сборке, которые могут использоваться нашей функцией. За комментариями следует машинный код:
// Метод первый: используйте переход по относительному адресу jmp.
jmp <относительный адрес> ; E9 <относительный адрес>
// Способ два: использовать регистр jmp для перехода по абсолютному адресу.
mov eax, <абсолютный адрес> ; B8 <абсолютный адрес>
jmp eax ; FF E0
// Путь три, используйте push ret переход по абсолютному адресу
push <абсолютный адрес> ; 68 <абсолютный адрес>
ret ; C3
Среди них относительный адрес рассчитывается следующим образом:
относительный адрес = Адрес функции для перенаправления - Адрес инструкции jmp - Общая длина инструкций перехода jmp.
В 32-битной системе адрес функции имеет длину 4 байта. Если вы хотите изменить функцию MessageBox для перехода к функции HookedMessageBox, адрес функции MessageBox — 12340000h, адрес HookedMessageBox — 12345678h, а длина инструкции — 1 байт. инструкции jmp + 4 байта адреса функции = 5, тогда относительный адрес=12345678h-12340000h-5=00005678h
Итак, инструкция перехода jmp:
jmp 00005678h ; E9 78 56 00 00
Поскольку за jmp может следовать только адрес длиной не более 4 байтов, jmp может перейти на любой адрес в 32 битах. В 64-битном формате длина адреса равна 8 байтам, если длина адреса команды jmp такая же, как и адрес. перепрыгнуть. Если разница адресов превышает 4 байта, относительный переход по адресу jmp не может быть использован.
32-битный встроенный метод перехвата относительно прост в реализации. Процесс реализации выглядит следующим образом:
Ниже в качестве примера будет использована функция MessageBox с использованием встроенного метода перехвата для перехвата MessageBox и перехода к функции HookedMessageBox.
Сначала введите функцию setHook, которая используется для установки ловушки oldAddress, сохраняющей адрес функции MessageBox:
Используйте функцию ReadProcessMemory, чтобы прочитать первые 6 байтов исходной функции MessageBox из памяти, которые необходимо восстановить при отмене привязки.
Как использовать здесьтриpush ret перехода по абсолютному адресу, используйте memcpy_s для записи адреса функции HookedMessageBox, который будет переброшен, в массив машинного кода ловушки. Если вы используете метод 1 для выполнения перехода по относительному адресу jmp, измените его следующим образом:
void setHook() {
SIZE_T bytesRead = 0;
// Сохраните первые 6 байт исходной функции MessageBoxA и их необходимо восстановить при отвязке.
ReadProcessMemory(GetCurrentProcess(), oldAddress, messageBoxOriginalBytes, 6, &bytesRead);
// Рассчитать относительный адрес
DWORD_PTR offsetAddress = (DWORD_PTR)HookedMessageBox - (DWORD_PTR)oldAddress - 5;
char patch[6] = { 0xE9, 0, 0, 0, 0 };
memcpy_s(patch + 1, 4, &offsetAddress, 4);
// Напишите хук в MessageBoxAPамять
WriteProcessMemory(GetCurrentProcess(), (LPVOID)oldAddress, patch, sizeof(patch), &bytesWritten);
}
Наконец, используйте WriteProcessMemory для записи массива подключенного машинного кода в память.
Затем посмотрите на функцию HookedMessageBox, к которой нужно перейти. За исключением другого имени, другие параметры, возвращаемое значение, тип вызова и т. д. функции HookedMessageBox должны быть такими же, как и исходная функция MessageBox:
При переходе от MessageBox к функции HookedMessageBox будут напечатаны параметры выполнения функции, затем отцепите MessageBoxA, затем вызовите исходный MessageBoxA и сохраните результат, а затем сбросьте хук.
Входя в основную функцию, мы сначала вызываем исходную функцию MessageBox, затем динамически получаем адрес функции MessageBox через GetProcAddress, затем вызываем функцию setHook для установки хука, затем отображаем всплывающее окно после хука и ставим точку останова. в setHook:
Запустите программу и появится всплывающее окно:
Нажмите OK до точки останова, чтобы прервать выполнение, затем введите oldAddress в окне дизассемблирования рядом с ней и нажмите Enter, чтобы просмотреть ассемблерный код функции MessageBoxA. Это код функции до отсутствия перехвата. Обратите внимание на первую 6-ю машину. коды (8B FF 55 8B EC 83):
Затем выполните функцию setHook() и перейдите к MessageBoxA после перехватчика:
Перепроверив адрес функции oldAddress, вы можете увидеть, что первые 6 машинных кодов были изменены для перехода к функции, которую мы установили сами:
Продолжите выполнение, и после подключения появится всплывающее окно:
Затем вы можете увидеть в консоли перехваченные параметры вызова функции, что указывает на успешный перехват:
Полный код выглядит следующим образом:
#include <iostream>
#include <Windows.h>
using namespace std;
FARPROC oldAddress = NULL;
SIZE_T bytesWritten = 0;
char messageBoxOriginalBytes[6] = {};
int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
void setHook() {
SIZE_T bytesRead = 0;
// Сохраните первые 6 байт исходной функции MessageBoxA и их необходимо восстановить при отвязке.
ReadProcessMemory(GetCurrentProcess(), oldAddress, messageBoxOriginalBytes, 6, &bytesRead);
void* hookedAddress = HookedMessageBox;
char patch[6] = { 0x68, 0, 0, 0, 0, 0xC3 };
memcpy_s(patch + 1, 4, &hookedAddress, 4);
// Напишите хук в MessageBoxAPамять
WriteProcessMemory(GetCurrentProcess(), (LPVOID)oldAddress, patch, sizeof(patch), &bytesWritten);
}
int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
// Распечатать значение, перехваченное из MessageBoxAфункция
cout << "Ohai from the hooked function" << endl;
cout << "Text: " << lpText << endl;
cout << "Caption: " << lpCaption << endl;
// Отсоединить MessageBoxA
WriteProcessMemory(GetCurrentProcess(), (LPVOID)oldAddress, messageBoxOriginalBytes, sizeof(messageBoxOriginalBytes), &bytesWritten);
// Вызов исходного MessageBoxA
int r = MessageBoxA(NULL, lpText, lpCaption, uType);
// Сбросьте хук, чтобы он мог прослушать в следующий раз
setHook();
return r;
}
int main()
{
// Показывать окно сообщений перед перехватом
MessageBoxA(NULL, "hi", "hi", MB_OK);
HINSTANCE library = LoadLibraryA("user32.dll");
// Получить адрес функции MessageBox в Память
oldAddress = GetProcAddress(library, "MessageBoxA");
/*
* Создать крючок:
* push <address of new MessageBoxA)
* ret
*/
setHook();
// Показать окно сообщения после подключения
MessageBoxA(NULL, "hi", "hi", MB_OK);
MessageBoxA(NULL, "hi", "hi", MB_OK);
return 0;
}
3. 64-битный встроенный перехватчик
Так как за jmp и push может идти только 4-байтовый адрес, то этого далеко недостаточно для обращения к 64-битному 8-байтовому адресу. Поэтому абсолютные переходы по адресу могут выполняться только с помощью регистров:
// Способ два: использовать регистр jmp для перехода по абсолютному адресу.
mov r12, <абсолютный адрес> ; 49 BC <абсолютный адрес>
jmp r12 ; 41 FF E4
// Путь три, используйте push ret переход по абсолютному адресу
mov r12, <абсолютный адрес> ; 49 BC <абсолютный адрес>
push r12 ; 41 54 <абсолютный адрес>
ret ; C3
Регистры, такие как eax и ebx в 32-битных системах, соответственно изменяются на регистры, такие как rax и rbx в 64-битных системах, и есть еще 8 регистров, таких как r8-r15.
Затем внесите соответствующие изменения в функцию setHook:
char messageBoxOriginalBytes[13] = {};
void setHook() {
SIZE_T bytesRead = 0;
// Сохраните первые 12 байт исходной функции MessageBoxA и их необходимо восстановить при отвязке.
ReadProcessMemory(GetCurrentProcess(), oldAddress, messageBoxOriginalBytes, 13, &bytesRead);
void* hookedAddress = HookedMessageBox;
char patch[13] = { 0x49, 0xBC, 0, 0, 0, 0, 0, 0, 0, 0, 0x41, 0x54, 0xC3 };
memcpy_s(patch + 2, 8, &hookedAddress, 8);
// Напишите хук в MessageBoxAPамять
WriteProcessMemory(GetCurrentProcess(), (LPVOID)oldAddress, patch, sizeof(patch), &bytesWritten);
}
Затем запустите его, и вы сможете нормально подключить функцию MessageBoxA в 64-битной версии:
Стоит отметить, что не все функции можно перехватить с помощью встроенных перехватчиков под 64-битной версией, поэтому реализация 32-битного шифрования памяти и 64-битного шифрования памяти немного различаются.
Четыре
Шифрование памяти
Используйте слова, начинающиеся на «Шифрование» технология памяти - поскольку антивирусное программное обеспечение не сканирует Память напрямую,Но прерывистая чувствительность сканирования Память,Поэтому сон можно вызвать в шеллкоде cs, чтобы поместить исполняемый файл Память области шифрования.,По окончании спящего режима расшифруйте Память, чтобы избежать сканирования Память антивирусным программным обеспечением и его уничтожения. При выполнении шифрования Память,На что следует обратить внимание,То, что требует шифрования, это не тот Память, на который мы подали заявку для шелл-кода.,Вместо этого сам шеллкод использует функцию VirtualAlloc для подачи заявки на Память:
Нам нужно зашифровать память 2, запрошенную самим шеллкодом, с помощью функции VirtualAlloc, для чего необходимо подключить функцию сна к нашей специальной функции HookSleep:
Итак, вопрос в том, чтобы зашифровать память 2, как получить адрес памяти 2?
В 32-битной версии мы можем напрямую подключить функцию VirtualAlloc для перехвата адреса возврата. В 64-битной версии это не будет работать, если вы по-прежнему используете 32-битный метод для перехвата функции VirtualAlloc. Причина также указана выше. В 64-битной версии не все функции можно перехватить с помощью встроенного перехватчика.
Сравните функциональную память VirtualAlloc в 32-разрядной версии и функциональную память VirtualAlloc в 64-разрядной версии:
Можно обнаружить, что VirtualAllocфункция Память в 64-битной версии имеет только одно предложение команды перехода jmp.,Ошибки могут возникнуть при перехвате функции, которая содержит только однопредложение инструкций перехода jmp.,Такая ошибка может не произойти,При подключении VirtualAlloc под 64-битной версией,Нет проблем, если мы позвоним сами,Можно подключить нормально,Но возникает ошибка при вызове шеллкода cs.,Поэтому функцию VirtualAlloc нельзя подключить под 64-битной версией.,Так как же получить адрес Память2 под 64-битной версией? Подробности см. ниже в разделе «64-битное Шифрование памяти».
После того, как память 2 зашифрована, память 1 также должна пройти некоторую обработку. Вы можете использовать VirtualFree для освобождения памяти 1 или зашифровать ее, как память 2.
Сначала подключите функцию VirtualAlloc:
Сохраните адрес и размер запрошенной памяти 2 в функции HookedVirtualAlloc, а HookVirtualAlloc используется для установки перехватчика VirtualAlloc.
Затем подключите функцию Sleep:
В функции HookedSleep сначала освобождается память 1, затем VirtualProtect изменяет адрес памяти 2, полученный на предыдущем шаге, чтобы он был доступен для чтения и записи, а затем шифрует память 2 и изменяет адрес памяти 2, делая его недоступным. Затем вызовите исходную функцию Sleep и расшифруйте память после завершения функции Sleep.
Затем установите хуки Sleep и VirtualAlloc в основной функции, а затем выделите память для выполнения шеллкода:
Здесь нет дорогостоящей загрузки обратного вызова, используется только простейшая загрузка указателя.
После выполнения вы можете видеть, что функция VirtualAlloc вызывается три раза:
Первый раз вызывается, когда мы выделяем память для шелл-кода, а следующие два раза вызываются самим шелл-кодом. Проверьте память, запрошенную шелл-кодом, вызывающим VirtualAlloc в первый раз, и вы обнаружите, что она была освобождена. cs шеллкод освобождает часть памяти после выполнения фрагмента кода:
Давайте посмотрим на Память, запрошенную шеллкодом, вызывающим VirtualAlloc в два-й раз. Это Память, фактически выполняемая cs:
На этом этапе 32-битное Шифрование памяти завершено.
64-битная реализация памятихотетькопироватьодиннекоторый,Не могу подключить VirtualAlloc,Вместо этого используйте функцию VirtualQueryEx:
// Получить информацию о диапазоне страниц в виртуальном адресном пространстве указанного процесса.
SIZE_T VirtualQueryEx(
[in] HANDLE hProcess, // Запросить дескриптор процесса, информация Память которого получена.
[in, optional] LPCVOID lpAddress, // Указатель на базовый адрес запрашиваемой области страницы.
[out] PMEMORY_BASIC_INFORMATION lpBuffer, // Точки для возврата указанной информации о диапазоне страниц. MEMORY_BASIC_INFORMATION Указатель на структуру.
[in] SIZE_T dwLength // lpBuffer Размер буфера, на который указывает параметр
);
Постоянно увеличивая размер lpAddress,Вы можете просматривать все диапазоны страниц в виртуальном адресном пространстве.,MEMORY_BASIC_INFORMATION Указатель структуры, который возвращает информацию всей страницы через параметр trith:
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress; // Указатель на базовый адрес области страницы
PVOID AllocationBase;
DWORD AllocationProtect;
WORD PartitionId;
SIZE_T RegionSize; // Размер площади, т.е. размер Память
DWORD State; // Статус страниц в зоне
DWORD Protect; // Разрешения страницы
DWORD Type; // Статус страниц в зоне
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
Среди них, если Тип структуры MEMORY_BASIC_INFORMATION равен MEM_PRIVATE, это означает, что эта память применяется динамически, поэтому либо мы подаем на нее заявку, либо к ней применяется шеллкод.
Вызовите встроенную функцию _ReturnAddress() в функции HookedSleep, чтобы получить адрес вызова функции callerAddress, затем просмотрите всю информацию о страницах памяти через VirtualQueryEx, а затем сравните ее со всеми диапазонами страниц памяти, полученными ранее, если адрес вызова функции находится внутри. эта страница памяти. Диапазон указывает, что это адрес, запрошенный шелл-кодом, то есть память 2. Таким образом, адрес памяти 2 успешно получен:
void initVirtualAllocSet(PVOID callerAddress) {
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD PageSize = si.dwPageSize;
DWORD_PTR dwMin = (DWORD_PTR)si.lpMinimumApplicationAddress;
DWORD_PTR dwMax = (DWORD_PTR)si.lpMaximumApplicationAddress;
MEMORY_BASIC_INFORMATION mbi = { 0 };
while (dwMin < dwMax)
{
VirtualQueryEx(GetCurrentProcess(), (LPCVOID)dwMin, &mbi, sizeof(mbi));
if (mbi.Type == MEM_PRIVATE && (mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_EXECUTE_READ || mbi.Protect == PAGE_READWRITE))
{
if (callerAddress >= mbi.BaseAddress && (DWORD_PTR)callerAddress <= (DWORD_PTR)mbi.BaseAddress + mbi.RegionSize) {
currentMbi = mbi;
cout << "Address: " << mbi.BaseAddress << "\tSize: " << mbi.RegionSize << "\tstate: " << hex << mbi.State << "\ttype: " << hex << mbi.Type << endl;
}
}
dwMin += mbi.RegionSize;
}
}
Заходим в функцию HookedSleep:
Раньше адрес памяти 2 получался через функцию initVirtualAllocSet, а затем память шифровалась. Здесь память 1 и память 2 шифруются вместе, но кода для расшифровки памяти в дальнейшем нет. функция выполняется, возникнет тревога, так как память не расшифрована. Возникает ошибка 0xc0000005, то есть нет разрешения на доступ. Здесь для расшифровки используется механизм VEH.
Основные механизмы обработки исключений в Windows включают VEH (векторная обработка исключений), SEH (структурированная обработка исключений), C++EH и т. д. SEH — это __try, __finally, __try, __Exception, а C++EH — это обработка исключений, предоставляемая C++, их последовательность обработки исключений выглядит следующим образом:
Вы можете видеть, что VEH находится ближе к нижнему уровню и, следовательно, может обрабатывать больше ошибок.
Мы определяем функцию обработки ошибок PvectoredExceptionHandler и используем VEH для обработки ошибки 0xc0000005, о которой сообщалось ранее:
проходитьExceptionInfo->ContextRecord->RipВы можете получить адрес, по которому произошла ошибка,Затем сравните, находится ли адрес в диапазоне Память2.,Если да, то расшифруйте его. Затем нам нужно зарегистрировать эту функцию в основном адресе функции обработки VEH:
AddVectoredExceptionHandler(1, &PvectoredExceptionHandler);
Используйте 64-битный встроенный метод перехвата для перехвата Sleep:
Прочие аспекты и 32-битное Шифрование То же, что и память, пока 64-битное Шифрование память завершена, эффект выполнения:
пять
Недостатки и улучшения
Недостатки использования такого Шифования памяти:
улучшать:
Шеллкод может быть разделен на несколько фрагментов. Только исполняемый фрагмент находится в расшифрованном состоянии, а остальные части находятся в зашифрованном фрагменте. При выполнении зашифрованного фрагмента для расшифровки фрагмента используется механизм VEH. Чем меньше шеллкод. разделен, тем лучше эффект предотвращения уничтожения. Трудность в том, как разделить шеллкод так, чтобы он разделялся точно в конце ассемблерного предложения.
Исходный код доступен для скачивания только в небольшом секретном кругу [Место сезонного сбора].
шесть
Справочная ссылка
https://github.com/mgeeky/ShellcodeFluctuation
https://www.ired.team/offensive-security/code-injection-process-injection/how-to-hook-windows-api-using-c++
https://blog.csdn.net/qq_22642239/article/details/89509151
https://mp.weixin.qq.com/s/w_DuPYc-QsGtb9S4QSCpTw
Безопасность кои
Платформа для обучения технологиям безопасности и обмена инструментами
Нажмите, чтобы поделиться
Нажмите, чтобы добавить в избранное
Нравится
Нажмите, чтобы увидеть