本文主要讲解应用层hook种类、技术以及利用Syscall进行绕过。
正常调用流程:
HOOK后调用流程:
创建一个代理对象,然后把原始对象替换为我们的代理对象,这样就可以在这个代理对象为所欲为,例如修改参数或替换返回值。
示例代码为编译时将shellcode存放于.edata段内,使用时对pe文件解析获取.edata段的内存起始地址,因为该段内存无可执行属性,使用virtualprotect修改内存属性为可执行,然后跳转到shellcode执行 shellcode为一段弹框代码。
通过对进程注入dll 并在dll入口处对VirtualProtect进行Inline hook。
修改函数流程:
未完成hook时,点击按钮正常运行shellcode代码内容:
完成HOOK函数virtualprotect后,再次点击捕捉到virtualprotect函数调用:
2.VirtualProtect内部调用ntdll ZwProtectVirtualMemory
3.ntdll ZwProtectVirtualMemory 实现
VirtualProtect用户层至内核层调用流程:
VirtualProtect(Kernel32)--->ZwProtectVirtualMemory(ntdll)--->sysenter
4.1 如何绕过三环的系统APIhook
通过上图可知ntdll 进入0环调用函数实体 是利用syscall进入的,那么模仿一个ntdll的syscall进入过程就可以绕开所有用户层的hook。
-
hell's Gate 项目地址: https://github.com/am0nsec/HellsGate
-
SysWhispers2 项目地址: https://github.com/jthuraisamy/SysWhispers2
-
SysWhispers3 项目地址:https://github.com/klezVirus/SysWhispers3
4.2 主要思路
-
使用syscall时必须知道函数调用号;
-
32位程序与64位程序在64位系统下调用Syscall的区别。
本文主要以SysWhispers2项目代码为代码讲解。
代码讲解:
PSW2_PEB Peb = (PSW2_PEB)__readfsdword(0x30);
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;
}
PEB TEB结构图:
PEB结构体 Ldr存放着模块链表:
_PEB_LDR_DATA结构体,链表内存放着进程的模块信息:
_LIST_ENTRY 双向链表:
_LDR_DATA_TABLE_ENTRY 存放着dll信息,如名字、基址、大小等信息:
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);
PE文件结构图:
CFF查看导出表函数:
hell's Gate代码段:
// 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) {
BYTE high = *((PBYTE)pFunctionAddress + 5 + cw);
BYTE low = *((PBYTE)pFunctionAddress + 4 + cw);
pVxTableEntry->wSystemCall = (high << 8) | low;
break;
}
存在问题:当函数入口被hook,
MOV R10, RCX
MOV RCX, <syscall>
发生变化,无法判断获取。
SysWhispers2代码段:
// Sort the list by address in ascending order.
for (DWORD 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;
}
}
}
NtProtectVirtualMemory PROC
push ebp
mov ebp, esp
push 001911B13h ; Load function hash into ECX.
call SW2_GetSyscallNumber ;Number -> eax
lea esp, [esp+4]
mov ecx, 5h ;参数个数
push_argument:
dec ecx
push [ebp + 08h + ecx * 4]
jnz push_argument
push ret_address_epilog ;ret address
call do_sysenter_interupt
lea esp, [esp+4]
ret_address_epilog:
mov esp, ebp
pop ebp
ret
do_sysenter_interupt:
mov edx, esp
sysenter
ret
NtProtectVirtualMemory ENDP
32位程序在64位系统下:
NtProtectVirtualMemory PROC
push ebp
mov ebp, esp
push 001911B13h ; Load function hash into ECX.
call SW2_GetSyscallNumber ;Number -> eax
lea esp, [esp+4]
mov ecx, 5h ;参数个数
push_argument:
dec ecx
push [ebp + 08h + ecx * 4] ;参数入栈
jnz push_argument
push ret_address_epilog ;ret address
call dword ptr internal_cleancall_wow64_gate ; call KiFastSystemCall
lea esp, [esp+4]
ret_address_epilog:
mov esp, ebp
pop ebp
ret
wow64_gate地址:fs:[0xC0]
internal_cleancall_wow64_gate = (void*)__readfsdword(0xC0);
示例通过使用syscall调用内存属性修改:
成功绕过hook:
实战绕过部分杀毒:
https://github.com/microsoft/Detours
https://github.com/am0nsec/HellsGate
https://github.com/jthuraisamy/SysWhispers2
https://github.com/klezVirus/SysWhispers3
https://github.com/mai1zhi2/ShellCodeFramework
索隐实验室是长沙火线云网络科技有限公司的安全研究团队之一,索隐寓意发现隐秘的攻击。索隐实验室聚焦威胁检测与分析技术,包括入侵检测、溯源分析、自动化应急响应等方向。
原文始发于微信公众号(火线云安全研究团队):Hook示例及Syscall绕过
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论