Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >浅谈 Windows Syscall

浅谈 Windows Syscall

作者头像
意大利的猫
发布于 2021-12-20 13:27:07
发布于 2021-12-20 13:27:07
6K11
代码可运行
举报
文章被收录于专栏:漫流砂漫流砂
运行总次数:1
代码可运行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
本文由团队成员 ryze-t 投稿给安全客,链接如下:
https://www.anquanke.com/post/id/261582
点击阅读原文可跳转至作者博客

0x00 syscall 基础概念

Windows下有两种处理器访问模式:用户模式(user mode)和内核模式(kernel mode)。用户模式下运行应用程序时,Windows 会为该程序创建一个新进程,提供一个私有虚拟地址空间和一个私有句柄表,因为私有,一个应用程序无法修改另一个应用程序的私有虚拟地址空间的数据;内核模式下,所有运行的代码都共享一个虚拟地址空间, 因此内核中驱动程序可能还会因为写入错误的地址空间导致其他驱动程序甚至系统出现错误。

内核中包含了大部分操作系统的内部数据结构,所以用户模式下的应用程序在访问这些数据结构或调用内部Windows例程以执行特权操作的时候,必须先从用户模式切换到内核模式,这里就涉及到系统调用。

x86 windows 使用 sysenter 实现系统调用。

x64 windows 使用 syscall 实现系统调用。

0x01 syscall 运行机制

以创建线程的函数 CreateThread() 举例,函数结构如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  HANDLE WINAPI CreateThread(    _In_opt_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,      _In_      SIZE_T                 dwStackSize,    _In_      LPTHREAD_START_ROUTINE lpStartAddress,    _In_opt_  LPVOID                 lpParameter,    _In_      DWORD                  dwCreationFlags,    _Out_opt_ LPDWORD                lpThreadId  );

示例代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<Windows.h>DWORD WINAPI Thread(LPVOID p){  MessageBox(0, 0, 0, 0);  return 0;}void main(){  //DebugBreak();  CreateThread(NULL, 0, Thread, 0, 0, 0); // 创建线程  Sleep(1000);}

使用 Procmon 查看创建线程的堆栈:

可以看到在用户层 CreateThread的调用栈为:

kernel32.dll!CreateThread → KernelBase.dll!CreateRemoteThreadEx → ntdll.dll!ZwCreateThreadEx

其本质是 ntdll.dll中保存着执行功能的函数以及系统服务调用存根,ntdll.dll导出了Windows Native API,其具体实现其实在 ntoskrnl.exe 中。

IDA 查看 ntdll.dll!ZwCreateThreadEx:

可以看到,调用 ZwCreateThreadEx,实际上调用的是 NtCreateThreadEx,然后通过判断机器是否支持 syscall 后,会执行 syscall 或 int 2E。

如果熟悉 ntdll.dll 的话会知道,ntdll.dll 中的一部分导出函数都是采用这种形式,如 NtCreateProcess:

代码几乎一样,区别在于 mov eax 0B4h,也就是在执行syscall 前,传入 eax 的值不同。即 eax 中存储的是系统调用号,基于 eax 所存储的值的不同,syscall 进入内核调用的内核函数也不同。

0x02 NtCreateThreadEx

2.1 CreateThread 调用流程

跟随调用栈来解析一下 CreateThread() 真实运行流程。

首先是 Kernel32.dll!CreateThread,直接在 IDA function 窗口并不能搜到这个函数,查看导出表:

进入后知道,CreateThread 实际上是 CreateThreadStub:

CreateThreadStub 会调用 Kernelbase!CreateRemoteThreadEx

CreateRemoteThread 结构如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
HANDLE CreateRemoteThreadEx(  [in]            HANDLE                       hProcess,  [in, optional]  LPSECURITY_ATTRIBUTES        lpThreadAttributes,  [in]            SIZE_T                       dwStackSize,  [in]            LPTHREAD_START_ROUTINE       lpStartAddress,  [in, optional]  LPVOID                       lpParameter,  [in]            DWORD                        dwCreationFlags,  [in, optional]  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,  [out, optional] LPDWORD                      lpThreadId);

IDA 查看该函数找到 NtCreateThreadEx:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
v13 = NtCreateThreadEx(          &ThreadHandle,          0x1FFFFFi64,          v39,          hProcess,          v37,          v38,          v14,          0i64,          v34 & -(__int64)((dwCreationFlags & 0x10000) == 0),          v15,          v46);

2.2 NtCreateThread 参数结构

解析一下 NtCreateThread 参数结构,先看一下定义过的变量:

  • 第一个参数是 &ThreadHandle,ThreadHandle = 0
  • 第二个参数是0x1FFFFF
  • 第三个参数是 v39

跟进该函数

由于原程序中代码为 CreateThread(NULL, 0, Thread, 0, 0, 0),因此 lpThreadAttributes = NULL,所以传到 BaseFormatObjectAttributes 中的参数 (int)a2 = 0,a3 = 0。

因此根据程序逻辑 v39 = *a4 = 0

  • 第四个参数是 hProcess
  • 第五个参数是 lpStartAddress
  • 第六个参数是 lpParameter
  • 第七个参数是 v14,代码逻辑如下:

因此要找到程序运行逻辑,是进入哪一个 LABEL。

结合 Windbg 查看,在 ntdll!NtCreateThreadEx 处下断点,根据 fastcall 调用约定,前四个参数由寄存器传递( RCX、RDX、R8、R9),其他参数由 RSP+0x20 开始压栈,因此第七个参数位置应是 RSP+0x30,由于此时已经执行 CALL 指令,由于返回地址入栈,RSP-8,因此参数应该由 RSP+0x28 开始:

可以看到第五个参数为 0007ff6`2b8e4383,这也就是创建的线程的起始地址,第六个参数为0,第七个参数也为0

  • 第八个参数为 0
  • 第九个参数为 v34 & -(__int64)((dwCreationFlags & 0x10000) == 0),根据 windbg 调试结果也为0
  • 第十个参数为 v15,根据 windbg 调试结果也为0
  • 第十一个参数为 v46,是一个数组

这里附上其完整结构: typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx) ( OUT PHANDLE hThread, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID lpStartAddress, IN PVOID lpParameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, OUT PVOID lpBytesBuffer);

2.3 直接调用 NtCreateThreadEx

根据分析参数得到的结果来看,如果想直接调用 NtCreateThreadEx,代码应为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<Windows.h>#include <stdio.h>typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx)(  OUT PHANDLE hThread,  IN ACCESS_MASK DesiredAccess,  IN PVOID ObjectAttributes,  IN HANDLE ProcessHandle,  IN PVOID lpStartAddress,  IN PVOID lpParameter,  IN ULONG Flags,  IN SIZE_T StackZeroBits,  IN SIZE_T SizeOfStackCommit,  IN SIZE_T SizeOfStackReserve,  OUT PVOID lpBytesBuffer);DWORD WINAPI Thread(LPVOID p){  return 0;}pfnNtCreateThreadEx NtCreateThreadExFunc(){  HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");  if (hNtdll == NULL)  {    printf("Load Ntdll.dll error:%d\n", GetLastError());    return FALSE;  }  pfnNtCreateThreadEx NtCreateThreadEx = (pfnNtCreateThreadEx)GetProcAddress(hNtdll, "NtCreateThreadEx");  if (NtCreateThreadEx == NULL)  {    printf("Load NtCreateThreadEx error:%d \n", GetLastError());    return FALSE;  }  return NtCreateThreadEx;}int main(){  //DebugBreak();  HANDLE ProcessHandle = NULL;  HANDLE ThreadHandle = NULL;  ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 6396); // 6396是计算器的pid  if (ProcessHandle == NULL)  {    printf("OpenProcess error:%d\n",GetLastError());    return FALSE;  }  pfnNtCreateThreadEx NtCreateThreadEx = NtCreateThreadExFunc();  NtCreateThreadEx(&ThreadHandle, 0x1FFFFF, NULL, ProcessHandle, Thread, NULL, FALSE, NULL, NULL, NULL, NULL);  if (ThreadHandle != NULL)  {    printf("Success! ThreadHandle=%d\n", GetThreadId(ThreadHandle));  }  Sleep(10000);  CloseHandle(ThreadHandle);  CloseHandle(ProcessHandle);  return TRUE;}

运行结果如下:

0x03 syscall 调用

在 VS2019 中新建一个文件为 syscall.asm,右键解决方案生成自定义依赖性:

选择 masm:

右键 syscall.asm → 属性,选择 Microsoft Macro Assembler

在 syscall.asm 中写入如下(win10 1511):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.codeNtCreateThreadEx procmov r10,rcxmov eax,0B4hsyscallretNtCreateThreadEx endpend

保存后新建 main.c

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<Windows.h>#include <stdio.h>EXTERN_C NTSTATUS NtCreateThreadEx(  OUT PHANDLE hThread,  IN ACCESS_MASK DesiredAccess,  IN PVOID ObjectAttributes,  IN HANDLE ProcessHandle,  IN PVOID lpStartAddress,  IN PVOID lpParameter,  IN ULONG Flags,  IN SIZE_T StackZeroBits,  IN SIZE_T SizeOfStackCommit,  IN SIZE_T SizeOfStackReserve,  OUT PVOID lpBytesBuffer);DWORD WINAPI Thread(LPVOID p){  return 0;}int main(){  //DebugBreak();  HANDLE ProcessHandle = NULL;  ProcessHandle = GetCurrentProcess(); // 获取当前进程句柄  if (ProcessHandle == NULL)  {    printf("OpenProcess error:%d\n", GetLastError());    return FALSE;  }  HANDLE ThreadHandle = NULL;  NtCreateThreadEx(&ThreadHandle, 0x1FFFFF, NULL, ProcessHandle, Thread, NULL, FALSE, NULL, NULL, NULL, NULL);  if (ThreadHandle != NULL)  {    printf("Success! ThreadId=%d\n", GetThreadId(ThreadHandle));  }  else  {    printf("Fail!");  }  if (WaitForSingleObject(ThreadHandle, INFINITE) == WAIT_FAILED)  {    printf("[!]WaitForSingleObject error\n");    return FALSE;  }  CloseHandle(ThreadHandle);  CloseHandle(ProcessHandle);  return TRUE;}

这里注入的是自身进程,所以看起来更加直观,在用户层调用栈中并未出现 CreateThread 相关API:

0x04 Syscall 项目

由上述可知, syscall 这种方法主要可以应对 EDR 对 Ring3 API 的 HOOK,主要的问题是不同版本的 Windows Ntxxx 函数的系统调用号不同,且调用时需要逆向各 API 的结构方便调用。于是 github 上陆续出现了一些项目,持续更新 syscall table,如 syscalls 或 system Call tables,同时对于未公开 API 的结构就需要通过查找或自己逆向了。

4.1 Syswhispers

基于 Syscall 出现了一个非常方便的项目—Syswhispers,Syswhispers 的原理与上述大致相同,它更加方便的生成 asm 文件以及一个头文件,通过包含头文件就可以进行 syscall。

用法如下:

可以看到生成了两个文件,在解决方案资源管理器中的头文件中导入这两个文件:

与之前对 asm 的操作一样,生成 masm 依赖项,然后更改属性→项类型选择 Microsoft Macro Assembler。

在C文件中加上 #include "syscall.h"

示例代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<Windows.h>#include <stdio.h>#include "syscall.h"DWORD WINAPI Thread(LPVOID p){  return 0;}int main(){  //DebugBreak();  HANDLE ProcessHandle = NULL;  ProcessHandle = GetCurrentProcess();  if (ProcessHandle == NULL)  {    printf("OpenProcess error:%d\n", GetLastError());    return FALSE;  }  HANDLE ThreadHandle = NULL;  NtCreateThreadEx(&ThreadHandle, 0x1FFFFF, NULL, ProcessHandle, Thread, NULL, FALSE, NULL, NULL, NULL, NULL);  if (ThreadHandle != NULL)  {    printf("Success! ThreadId=%d\n", GetThreadId(ThreadHandle));  }  else  {    printf("Fail!");  }  if (WaitForSingleObject(ThreadHandle, INFINITE) == WAIT_FAILED)  {    printf("[!]WaitForSingleObject error\n");    return FALSE;  }  CloseHandle(ThreadHandle);  CloseHandle(ProcessHandle);  return TRUE;}

编译通过后执行:

查看 asm 文件:

在不指定版本的情况下,Syswhispers 会导出指定函数的所有已知版本的系统调用号,根据版本的不同再来指定调用。

4.2 Syswhispers2

在今年原作者对 Syswhispers 进行了些许改进,更新成 Syswhispers2。

Syswhispers2 与 Syswhispers 最大的不同在于 Syswhispers2 不再需要指定 Windows 版本,也不再依赖于以往的系统调用表,而是采用了系统调用地址排序的方法,也就是这篇 Bypassing User-Mode Hooks and Direct Invocation of System Calls for Red Teams。其具体含义是先解析 Ntdll.dll 的 导出地址表 EAT,定位所有以 “Zw” 开头的函数,将开头替换成 “Nt”,将 Code stub 的 hash 和地址存储在 SYSCALL_ENTRY 结构的表中,存储在表中的系统调用的索引是SSN(System Service Numbers,系统服务编号)。

用法与 Syswhispers 大致相同,不同的点在于,在使用时会生成三个文件:

在导入时要将 syscall.c 也导入到源代码中

syscall.c 中存储着系统调用地址排序和哈希比较的功能。

编译运行后:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 NOP Team 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
好文我顶
好文我顶
回复回复点赞举报
推荐阅读
syscall的检测与绕过
通过汇编直接NtCreateThreadEx在函数种通过syscall进入ring0
红队蓝军
2023/02/25
1.3K0
syscall的检测与绕过
高级远程线程注入NtCreateThreadEx
在Windows下NtCreateThreadEx是CreateRemoteThread的底层函数。RtlCreateUserThread 也是对 NtCreateThreadEx的一层包装
IBinary
2022/05/10
1.5K0
突破SESSION0隔离的远程线程注入
传统的远程线程技术一般是向普通用户进程注入线程。而要是想隐藏的更深,则需要突破SESSION0隔离机制,将自身进程注入到系统进程中,使得自己更加隐蔽。 突破SESSION0隔离的远程线程注入与传统的CreateRemoteThread实现DLL远程线程注入相比区别在与是用更为底层的ZwCreateEx函数来创建的。
YanXia
2023/04/07
4270
突破SESSION0隔离的远程线程注入
3.3 DLL注入:突破会话0强力注入
Session是Windows系统的一个安全特性,该特性引入了针对用户体验提高的安全机制,即拆分Session 0和用户会话,这种拆分Session 0和Session 1的机制对于提高安全性非常有用,这是因为将桌面服务进程,驱动程序以及其他系统级服务取消了与用户会话的关联,从而限制了攻击者可用的攻击面。
微软技术分享
2023/09/13
3770
远程线程注入Dll,突破Session 0
其实今天写这个的主要原因就是看到倾旋大佬有篇文章提到:有些反病毒引擎限制从lsass中dump出缓存,可以通过注入lsass,就想试试注入lsass
HACK学习
2021/05/14
1.1K0
64位 & Windows 内核6
继续学习《逆向工程核心原理》,本篇笔记是第五部分:64位 & Windows 内核6
红客突击队
2022/09/29
6890
64位 & Windows 内核6
bypass Bitdefender
渗透时,可能会遇到各种各样的的杀软,但每个杀软特性不同,在绕过前,往往都需要分析,本文就Bitdefender进行分析
红队蓝军
2022/05/17
2790
bypass Bitdefender
恶意软件开发——突破SESSION 0 隔离的远线程注入
在Windows XP,Windows Server 2003以及更早的版本中,第一个登录的用户以及Windows的所有服务都运行在Session 0上,这样的做法导致用户使用的应用程序可能会利用Windows的服务程序提升自身的权限,为此,在后续的Windows版本中,引入了一种隔离机制,普通应用程序已经不再session 0中运行。
玖柒的小窝
2021/09/15
7150
恶意软件开发——突破SESSION 0 隔离的远线程注入
实战|使用Windows API绕过进程保护
这个自我保护实际上是加载360SelfProtection.sys驱动(看这名字应该还有360SelfProtection_win10.sys文件),在0环通过hook等手段保护注册表项,重要进程进程等。
HACK学习
2021/11/19
2.6K0
浅析syscall
最近在面试一些人的免杀问题时总会谈到syscall,但对于一些检测、细节、绕过检测反而没有说的很清楚,本文简单总结一些syscall的方式,来帮你唬过面试官。
鸿鹄实验室
2022/02/17
3.7K0
浅析syscall
免杀必会- 规避杀软的库
在编写恶意软件时,我们时常会用到系统的一些库,库的使用是非常简单,好用的,只需要导入头文件,那么就可以使用相应的api或函数,但是如果用于免杀或者c2,但是在EDR和终端软件横行的现在,不太“好”,下面将是我们在做免杀时或自己开发c2时常用的一些库,有现成调用代码,复制粘贴即可使用。
Gamma实验室
2022/12/01
1.4K0
bypass Bitdefender
渗透时,可能会遇到各种各样的的杀软,但每个杀软特性不同,在绕过前,往往都需要分析,本文就Bitdefender进行分析
红队蓝军
2022/04/18
7380
bypass Bitdefender
C2基石--syscall模块及amsi bypass
最近读了一些C2的源码,其中shad0w的syscall模块具有很高的移植性,分享给有需要的朋友。
鸿鹄实验室
2021/08/25
6990
SysWhispers:如何通过直接系统调用实现AVEDR绕过
SysWhispers能够生成Header文件和ASM文件,并通过发送直接系统调用来绕过反病毒以及终端防护响应工具。该工具支持Windows XP至Windows 10的所有系统核心调用,生成的样本文件可以直接从“example-output/”目录获取。
FB客服
2020/02/20
1.7K0
SysWhispers:如何通过直接系统调用实现AVEDR绕过
26种对付反调试的方法
本文针对的是Windows操作系统中常用的防破0解及防逆向工程保护技术,即反调试方法,各种防逆向工程技术的主要目标是尽可能多的使逆变工具尽可能失效。
战神伽罗
2019/07/24
5.8K0
红队免杀培训-手把手教使用系统调用(上)
为了应对,AV/EDR对一些常规windows的api的监控,使用的github项目为Syswhispers,其实CS官方有个付费工具包叫 CobaltStrike Artifact,可以定制化生成有效负载,当然其中也包括了使用系统调用,替换掉beacon里面的api函数,当然付费真的用不起,对于穷人来说,只能靠手动冲!
Gamma实验室
2022/03/29
1.2K0
红队免杀培训-手把手教使用系统调用(上)
模拟隐蔽操作 - 动态调用(避免 PInvoke 和 API 挂钩)
TLDR:介绍 DInvoke,这是 SharpSploit 中的一个新 API,可作为 PInvoke 的动态替代品。使用它,我们展示了如何从内存或磁盘动态调用非托管代码,同时避免 API 挂钩和可疑导入。
Khan安全团队
2022/01/17
2.1K0
总结到目前为止发现的所有EDR绕过方法
所有关注攻击性安全社区的人都会在过去两年中一次又一次地遇到Userland hooking, Syscalls, P/Invoke/D-Invoke等术语。我自己也遇到了一些我不完全理解的博客文章和工具。我有时觉得我需要从头开始积累知识。由于我在很多情况下不需要这些“新”技术,我把这些课题的研究推迟了几个月。
黑伞安全
2021/02/26
9.4K0
总结到目前为止发现的所有EDR绕过方法
C/C++ 实现常用的线程注入
各种API远程线程注入的方法,分别是 远程线程注入,普通消息钩子注入,全局消息钩子注入,APC应用层异步注入,ZwCreateThreadEx强力注入,纯汇编实现的线程注入等。
微软技术分享
2022/12/28
7150
驱动开发:内核远程线程实现DLL注入
在笔者上一篇文章《内核RIP劫持实现DLL注入》介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的,本章将继续探索全新的注入方式,通过NtCreateThreadEx这个内核函数实现注入DLL的目的,需要注意的是该函数在微软系统中未被导出使用时需要首先得到该函数的入口地址,NtCreateThreadEx函数最终会调用ZwCreateThread,本章在寻找函数的方式上有所不同,前一章通过内存定位的方法得到所需地址,本章则是通过解析导出表实现。
微软技术分享
2023/06/25
4660
驱动开发:内核远程线程实现DLL注入
相关推荐
syscall的检测与绕过
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验