Enterprise Just Builder

Solution for Enterprise Software Architecture

kernel

探索 Windows 10 的控制流保护机制

得益于系统漏洞补强技术的持续改进,微软在其 Windows10 和 Windows 8.1 Update 3 中默认启用了一个新机制。其技术被称为控制流保护(Control Flow Guard 简称CFG)。1

与其它漏洞补强机制如地址空间布局随机化(ASLR)或者数据执行保护(DEP)一样,该技术提高了漏洞渗透的难度。无疑,它将引发攻击者渗透技术的改变。正如ALSR 催生了堆喷射技术,DEP 催生了return-oriented-programming 技术。

本文的测试程序是在 Windows 10 RS2 (build 15063) 以及 Visual Studio 2017 上编译的。要完全支持 CFG ,编译器和操作系统都必须被正确设置。由于漏洞渗透机制发生在系统层面,CFG 的实现要求编译器、操作系统用户态类库以及内核模块共同协作。MSDN 上有数篇文章介绍如何一步步启用 CFG. 2

微软 CFG 实现主要集中在对间接调用保护上。

请看下面测试代码:

>>> 阅读全文

 

, ,

Windows CSRSS 详解 (1) 基础概念

微软之所以引入 “Windows 环境子系统” 这一概念,其理念是为了用户程序提供一个严格定义的原生函数子集。这是因为 Windows 操作系统被设计成同时支持原生 Windows 和 POSIX 类型的可执行程序(在 Windows 10 Red Stone 中还新添加了 Windows Subsystem for Linux 子系统),因此对于每一类应用,开发者必须区隔其所调用的 API。每个环境子系统通常包含两个主要部分:1

  • 子系统进程:一个正常的 ring-3 级应用程序,负责处理某些子系统特定的功能。
  • 子系统 DLL: 特定子系统专用的系统类库集,是额外添加在用户程序与原生系统调用间的调用层。

事实上 Windows 子系统 — — 也称为 CSRSS (客户端/服务器运行时子系统 Client/Server Runtime Subsystem) 是系统强制的执行环境,换句话说,如果没有没有 CSRSS.exe 在后台运行, Windows 不能正确工作。由于 CSRSS 所绑定的职责,该子系统对每个 Windows 都是必须的,包括服务器版本 (它可以不处理交互式用户会话)。另一方面,POSIX (psxss.exe) 和 Windows Subsystem for Linux 属于可选子系统 — — 因此,只在需要才启动相关进程。

子系统的启动信息被保存在注册表键 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems 下面。默认的启动信息配置如下:

  • 名称: Debug 类型: REG_EXPAND_SZ 数据:
  • 名称: Kmode 类型: REG_EXPAND_SZ 数据: \SystemRoot\System32\win32k.sys
  • 名称: Optional 类型: REG_MULTI_SZ 数据: Posix
  • 名称: Posix 类型: REG_EXPAND_SZ 数据: %SystemRoot%\system32\psxss.exe
  • 名称: Required 类型: REG_MULTI_SZ 数据: Debug Windows
  • 名称: Windows 类型: REG_EXPAND_SZ 数据: %SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ServerDll=sxssrv,4 ProfileC

大多数人对支持 Windows API 的子系统 DLL 都有所耳闻 — 即 kernel32.dll、 user32.dll、 gdi32.dll、 advapi32.dll 等等,无数的 Windows 软件开发者在日常工作中都会用到它们。而对于 POSIX 子系统来说,只需用到 psxdll.dll 这一个模块就可以实现所需的 Unix API。

此外,每一个进程都会与某个特定的子系统关联,该属性是由链接器在编译过程中设置的,并保存在 PE 结构的相关字段中:

>>> 阅读全文

 

, ,

用 IDA Windbg 调试器实现 VMWare 上的 Windows 内核调试

文章评价:
IDA 是交互式反汇编器(Interactive Disassembler)的简称,它由总部位于比利时列日市(Liège)的 Hex-Rays 公司的一款产品。IDA是一种递归下降反汇编器。其主要优势在于呈现尽可能接近源代码的代码。它不仅使用数据类型信息,而且通过派生的变量和函数名称来尽其所能地注释生成的反汇编代码。这些注释将原始十六进制代码的数量减到最少,并显著增加了向用户提供的符号化信息的数量。

和 Windows 原生的调试器 windbg 相比, IDA 可以将程序结构和算法的核心以类似 C 语言的形式呈现,因此更容易通过阅读代码来定位问题的所在。

本文将介绍如何透过 IDA 的 Windbg 插件来实现 Windows 内核调试。

使用 WinDbg 进行 Windows 内核调试通常需要两台计算机,一台称为Debuggee,即目标机,另一台作为 Debugger,也称为主机 HOST。

目标机可以是物理机或虚拟机。如果是物理机,那么该机器至少需要一个串口、1394口或者支持 USB 调试的 USB 端口,另外你还需要与对应的调试连接线,如串口线、1394总线、USB 2.0 或 3.0 调试线等等。 Windows 8 之后的操作系统,普遍使用 USB 3.0 端口进行调试,通过 USBView 工具查看目标机,可以确定机器上哪一个 USB 端口支持调试。

>>> 阅读全文

 

, , , ,

Windows LPC 通信机制剖析

LPC (Local procedure calls) 是 NT 内核实现一种基于消息的高速通信机制,可用于用户进程之间、用户进程与内核驱动之间或者内核驱动之间的通信。举个用户态进程间通信的例子,比如在创建登录会话时,CSRSS.exe 就通过 SmssWinStationApiPort 和 SMSS.exe 沟通,或者进程可以通过 LsaAuthenticationPort 就安全问题与 LSASS.exe 沟通。又比如在读写加密文件时, KSecDD.sys 会因为 EFS 秘钥的加密和解密与 LSASS.exe 通信, 这是用户进程与内核驱动通信的例子。1

LPC 采用两种不同机制以便在服务端与客户端之间传递数据:如数据长度小于 304 字节,则使用 LPC 消息缓冲;反之,数据长度超过 304 字节,则使用共享内存 (通过 NtCreateSection ),该内存段会同时映射到服务端和客户端的地址空间中。

除了当作远程过程调用(Remote Procedure Calls)的备选协议外,LPC 还被广泛运用到整个系统中,比如 Win32 应用与 CSRSS.EXE 间的通信,SRM 和 LSASS间的通信以及 WinLogon 与 LSASS 的通信等等。

LPC 强制在客户端与服务端间采用同步通信模型。从 Vista开始, LPC 开始被废弃转而采用称为异步本地进程间通信(ALPC)的新机制。 和 LPC 相比,ALPC 固有的优势是:从客户端到服务端的所有调用都是异步的,即客户端不需要阻塞或等待服务器对消息作出响应。从 Vista 之后,如果某些老应用调用了 LPC API,则会被自动重定向到新的 ALPC API。

LPC API
LPC API 是原生 API,换句话说 API 分别通过 NTDLL.dll 和 NTOSKRNL.exe 导出到用户模式和内核模式。LPC API 并未被导出到 Win32 层,因此 Win32 应用不能直接使用 LPC。不过如果进行 RPC 通信时使用 “ncalrpc” 协议序列(协议序列代表 RPC 通信使用的网络协议。2,就可以指定 LPC 作为下层传输协议,从而让 Win32 应用间接使用 LPC。

>>> 阅读全文

 

, , ,

IRP

在 Windows NT内核的 IO 模型中,有两类 IRP:threaded irp和non-threaded irp,顾名思义,前者跟thread绑定,后者跟thread无关。当一个threaded irp被创建时,创建线程会有一个队列保存该irp,直到irp完成之后才释放。当你试图让这条线程退出时,系统会检测队列看里面是否还有irp没完成,如果有,线程会一直等待,直到所有的irp全部完成。而non-thread irp则正好相反,如果该irp已经返回到了创建它的地方你还继续complete它,BSOD将会发生。1

Threaded IRP
如前面所讲,threaded irp和线程绑定在一起。当user mode程序发起IO操作(比如WriteFile函数),或者一个驱动向另一个驱动发起IO操作(比如ZwWriteFile),IOManager首先找到文件handle对应的驱动栈,获取栈顶的PDEVICE_OBJECT,生成一个irp,将IO操作的各种信息放入irp中,并将此irp放入线程的irp队列中,然后调用IoCallDriver将irp转给驱动栈一层层处理,每往下一层,IO_STACK_LOCATION中的指针就会往下移一个位置(实际情况比这稍微复杂一点点,以后再说,现在先把它想成是一个栈,每往下一层就是push一次),直到最后跟硬件打完交道返回。返回的过程正好与上述相反。最末一层处理完irp后,会调用IoCompleteRequest,此函数会将IO_STACK_LOCATION指针往上移(pop),并最后调用上层驱动设置的CompletRoutine。每层的CompleteRoutine做完相应工作后都会调用IoCompleteRequest讲irp继续往上转,直到栈顶PDEVICE_OBJECT。此时IO_STACK_LOCATION已经在顶部,所谓再上层的CompleteRoutine也不存在,所以本层CompleteRoutine中操作将irp归还给IOManager,IOManager负责回收资源,并从线程irp队列中去除相应的项。

这个过程和函数调用很像,像到你都会发问:为什么搞成这种模式,函数调用和返回不也有压栈出栈的操作吗,干嘛不借用这套机制反而要另起炉灶单独搞一套。答案很简单,就是效率。nt内核的IO操作全是异步的,调用IoCallDriver的线程和CompleteRoutine响应到的线程,不一定是同一条。关于这点我们以后再谈,但它却引出了一个目前我们就需要关心的问题:既然CompleteRotine的线程环境可能已经切换了,那么IOManager如何知道从哪个线程的irp队列里去除相应项?答案也很简单,就是保存在irp中。每一个threaded irp的PIRP->Thread域都保存它的创建线程,IOManager就是用它来寻找线程。注意:有人会想从PIRP->Thread里获得当前线程的信息,这是不对的。

Non-threaded irp
non-threaded irp一般都由驱动创建而不是IOManager,线程不在irp队列里保存它的实例,并且它的PIRP->Thread为NULL。当CompleteRoutine一路返回调到该irp的创建者时,千万不要返还给IOManager,直接free掉就可以了。

irp创建函数
从user mode一路下来的irp一定是threaded irp这很明显。那么内核态的函数中,哪些是创建threaded irp,哪些是创建non-threaed irp的?以下表格列出了各函数:

>>> 阅读全文

 

, , ,

为 Windows NT 内核扩展 API

与 Unix 基于中断的系统调用接口类似, Windows NT 提供了大量未经文档化的基础系统服务,即原生 API (Native API)。这些内核模式的系统服务被 Win32、 Posix 和 OS2 等环境子系统用于在 NT 微内核(Windows NT 是个修改过的微内核体系结构的操作系统)之上实现其运行环境。在 Win32 下,原生 API 通过 NTDLL.DLL 的导出函数提供给其他 API 和程序使用,如 KERNEL32.DLL 中的许多函数只是简单地重定向到 NTDLL.DLL 中。

事实上 NTDLL.DLL 那些调用系统服务的函数不过是个存根函数,它们只是对 SysEnter/SysCall(早些版本使用 INT 2EH)1等系统调用接口的简单包装。以 NtCreateFile 函数为例:(Windows 7 SP1 64位):2 ,它的具体实现如下:

mov     r10,rcx ;保存 rcx 的值,rcx 将被置为 rip(返回值)
mov     eax,52h ;系统调用号
syscall
ret
nop     dword ptr [rax+rax]

存根函数负责在 EAX 中填入系统调用号,随后执行本机调用接口实现从用户态向内核态的跳转。EAX 中的调用号将用以查找并调用系统服务以完成 API 的最终功能。每个原生 API 的调用号各不同,例子中 NTCreateFile 的调用号是 0x52h,且随着 Windows 的版本和架构不尽相同,有兴趣的话可以查询 Windows X86 系统调用表Windows X86-64 系统调用表

执行 SYSCALL 或 SYSENTER 时,CPU 并不会象执行 CALL一样保存返回地址和状态信息,所以需要存根函数来保存返回信息。3

当从原生 API 进入内核态以后,后续则交由 NCI 派发器 (NCI Dispatcher)处理。 NCI 派发器在微内核中(NTOSKRNL.EXE) 实现的, NCI 派发器( _KiSystemService ) 本质上就是 INT 2EH 的处理函数(handler)。NCI 派发器通过系统服务描述符表 (System Service Descriptor Table — SSDT) 来查找并调用某个服务的相应处理函数,函数参数则通过 NCI 传递。(一些内核调试器比如 IMHO 或者 NuMega Soft-ICE 可以通过 NTCALL 命令来显示 SSDT)。 微软并未提供文档来说明如何扩展原生 API,并通过 NCI 来调用自己的内核服务。所以本文试图通过变通的办法来解决这一问题。不过在此之前,我们还是大致看一下来如何通过 NCI 来调用原生 API 服务,或者其他 NCI 服务。

>>> 阅读全文

 

, , , ,

Windows 内部探秘:对象和对象管理器

内核需要维护有关众多资源的大量数据,如进程、线程、文件等等,为此内核使用了“内核数据结构”,即对象。从物理上看:每个内核对象只是内核分配的一个内存块,并只能给内核访问。从逻辑上看:该内存块是个数据结构,其数据成员维持有关该对象的信息。

在计算机术语中,“对象” 就是一个(或一组)数据结构以及定义于其上的若干操作,换句话说,想要对该数据结构有所动作,就必须通过定义的操作,别无它途。计算机历史上,“对象”这个概念出现于 Unix 之后,所以在 Unix 以及 Linux 中并没有明确的“对象”概念,Unix 把所有设备当成特殊文件,这已经是超越以往操作系统的一次飞跃,但是微软在设计 Windows 的时候,“对象”这个概念已经相当成熟,因此在设计中运用“对象”这个概念就成为顺理成章的事,所以这两种操作系统在设计理念上的显著不同在于:Unix 把设备看成是特殊文件, 而 Windows 把设备和文件都看成是对象。

对象和普通数据结构之间最根本的区别在于:对象的内部结构是被隐藏的,除了通过对象服务,否则无法直接读取或者改变对象的内部数据。请注意:为了兼顾可移植性,大部分操作系统代码都是用 C 语言编写的,因此在 Windows 内核中对象的“操作”与其数据结构的结合是松散的,并不具有封装、继承、多态等这些基本要素。

如果你编写 Windows 应用程序,你可能会遇到进程、线程、文件或者事件,这些都是对象的一些例子。反映到 Windows 内核中,任何一个进程都是进程对象类型的一个实例,文件是文件对象类型的一个实例,如此等等。这些对象又是以内核创建并管理的一些底层对象为基础的。这些顶层对象被称为“管理层对象 Executive Object”,底层的对象为“核心层对象 Kernel Object”。

核心层对象
核心层对象是在内核中实现的一些更加基础的对象,它实现了操作系统底层原语与机制。核心层对象对用户态代码是不可见的,只能在内核代码被创建与使用,核心层对象提供了操作系统的基础功能与机制比如同步、中断等等,在核心层对象之上再构建出管理层对象。每个管理层对象都包含一个或者多个核心层对象。

>>> 阅读全文

 

,

了解Linux操作系统 (一)进程管理

文章评分:

前言
Linux 是一种由全世界开发者共同开发的开源操作系统。其源代码可以自由获取并可以在 GNU GPL 授权下使用。有很多公司提供不同的系统发行版供用户使用,如Redhat和Novell SUSE。大部分桌面发行版都可以从网站上免费下载,但服务器版一般是需要购买的。

在过去的几年里,Linux 被世界上许多公司的数据中心所使用。如今 Linux 操作系统为科学领域和企业用户所认可。它已经成为一种多种用途的操作系统。你能在多种嵌入式设备中发现它,如:防火墙、手机或电脑主机。所以 Linux 的性能对于科学领域和企业用户来说已经成为一个热门议题。然而一个操作系统可能被用来计算全球的天气预报或者被用来运行数据库等多种用途,Linux 必须能够为各种可能的使用情境提供优良性能。大多数 Linux 发行版含有常规的调校参数来满足所有用户。

IBM 意识到作为一种操作系统,Linux 非常适合在 IBM 系统之上运行企业级应用。大多数企业应用现在都可以运行在 Linux 上,包括文件服务器、打印服务器、数据库服务器、Web服务器、以及沟通和邮件服务器。

在企业级服务器运行 Linux 时需要对其性能进行监控,在必要时需要对服务器进行调优以消除影响用户的性能瓶颈。本红皮书将介绍一些调优 Linux 的方法、监控分析服务器性能的工具、以及对于特定应用的关键性能参数。本文目的是说明怎样分析和调校 Linux 操作系统,从而为在系统上运行的各种不同应用提供优良的性能。

>>> 阅读全文

 

, , , , ,