搜索
查看: 12934|回复: 22
收起左侧

[新手上路] 安软入门指南(三):Windows的“绝对领域”

  [复制链接]
白露为霜
发表于 2017-7-21 19:35:22 | 显示全部楼层 |阅读模式
本帖最后由 root1605 于 2017-8-26 13:43 编辑
   安软入门指南(三):
Windows的“绝对领域”


                                                               目录
                                                         Ⅰ 预备
                                                         ①指针&Windows句柄
                                                         ②陷阱分发
                                                         ③SSDT/IDT
                                                         Ⅱ Windows API
                                                         ①为什么要有“接口”
                                                         ②内核对象
                                                         ③从用户层到内核层的调用过程
                                                         Ⅲ Windows内核的部分防御措施
                                                         ①微软内核模式代码签名
                                                         ②内核补丁保护
                                                         Ⅳ Wow64的基本原理
上一篇:安软入门指南(二):UAC&DLL注入
下一篇:安软入门指南(四):常见的误解

共四篇,此贴为第三篇
By root1605
来自bbs.kafan.cn



评分

参与人数 1原创 +1 收起 理由
Tarchia + 1 版区有你更精彩: )

查看全部评分

白露为霜
 楼主| 发表于 2017-7-21 19:36:10 | 显示全部楼层
本帖最后由 root1605 于 2017-8-26 13:47 编辑
Ⅰ预备
        阅读本篇之前请先阅读前两篇。本篇是相对来说最复杂的一篇,涉及到的概念比较多,需要结合起来理解。
①指针&Windows句柄
       本文所说的(也是大部分时候提到的)指针都是指一个存储着内存地址的“变量”,这个地址可以是物理上的内存地址,也可以是虚拟地址空间里的地址,而在Windows系统下“指针”都是包含指向虚拟内存空间中的地址,而在一些嵌入式设备可能直接对应物理地址。地址也是一个“数字”,Windows实现了一个线性地址空间的虚拟内存系统。
       例如:
using System;
class test
{
     unsafe static void Main()
      {
      int* fib = stackalloc int[100];   
      *fib=520;                                    
      *(fib+3)=1314;                           
      }
}

int* fib = stackalloc int[100];:分配100个“单位”(每个“单位”能够容纳一个32位的int型数据),并把第一个“单位”的地址存储到fib中(fib是一个“指针”),这些32位的int型数据也是线性的,知道第一个的数据的地址只要向后偏移就可以找到其他数据。
     *fib=520:根据fib所包含的地址,给这个地方的内存填上520。由于地址空间是线性的,所以*(fib+3)可以根据fib的地址向后偏移3个“单位”(指的是前文所述的“单位”),然后再将这块内存填上数据1314。
     这种线性关系是逻辑上的,而实际上物理内存可能不连续(内存分页机制),请参见第一篇。

     指针也可以指向函数,通过这种指针可以调用函数。
#include <stdio.h>
int subtract(int x, int y) { return x - y;}
int main() {
  int (*subtractPtr)(int, int) = subtract;//声明函数指针,指向subtract函数
  int y = (*subtractPtr)(10, 2);
  int z = subtractPtr(10, 2);
  return 0;
}

稍后还会提到一个概念“函数指针数组”,“数组”是把一系列(一般是相同类型的数据)在虚拟地址空间中连续排布的简单数据组织方式(即同类型元素的集合,不同于数学概念中的集合),“数组”中的具体内容叫“元素”,能够访问到元素的标号叫做“索引”。数组在内存中排布是连续的,(实际上还是直接的地址操作,fib[3]可以理解为根据数组的头元素偏移3个单位,访问到第4个元素)所以,本文第一个例子可以改写为:
using System;
class test
{
     unsafe static void Main()
      {
      int* fib = stackalloc int[100];   
      fib[0]=520;                                    
      fib[3]=1314;                           
      }
}



     Windows句柄是对“对象”的一种“标识符”,在现实生活中“对象”指一个个存在的实物,在Windows中指的也是“实物”,比如一个文件,一个进程等。通过句柄可以访问到句柄所代表的“对象”,虽然句柄也是一个整数值,但它不是标识符,不能直接从内存中取出相应的“对象”,需要将此“标识符”传给系统,有系统来把对象“返回”给你。
#include <windows.h>
#include <iostream>
int main()
{
        DWORD pid;
        std::cin >> pid;
        HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,
                FALSE,
                pid);
        if (hProcess == NULL)
        {
                std::cout << "Unable to get the handle!\n";
                system("pause");
                return -1;
        }
        BOOL bret = ::TerminateProcess(hProcess,
                0);
        if (!bret)
        {
                std::cout << " Failed!\n";
                system("pause");
        }
            ::CloseHandle(hProcess);
                std::cout << "Succeed!";
                system("pause");
                return 0;
        }


    此程序在读入要终止进程的ID号后,通过OpenProcess函数获得目标进程的“句柄”,再将句柄传递给TerminateProcess函数,Windows操作系统会终止目标进程。使用句柄访问,不需要(且不能)直接操作内存,相对更安全,获取句柄如果权限不足等原因,可能无法获取,当然是就没法结束进程了。


②陷阱分发
        先介绍两个概念:“中断”,“异常”。不要总把它们和操作系统或者软件崩溃联系到一起。“中断”和“异常”指的是让处理器跳转到正常指令流以外的的代码的操作系统条件,“中断”和“异常”可以由软件或硬件捕捉到。而“陷阱”,是一种机制,当发生“中断”或者“异常”时,处理器会把当前执行任务的线程转到操作系统的某一位置去,这时候线程控制权在“陷阱处理器”里面,“陷阱处理器”是一些函数,不是具体的硬件。硬件和软件都可能导致“中断”,“异常”   。  
       当有些设备需要响应系统时,就会触发一个“中断”,处理器会捕捉到这个信息。中断驱动的硬件可以让系统有序高效地进行I/O操作,而不必轮番查询各个硬件的状态。系统软件也会触发“中断”,Windows在需要时会制造一个“软中断”触发线程的分发,可以调度线程。
       下面一段的内容可以暂时不看:
       外部设备的I/O中断传入中断控制器的一根线上(实际存在的),这个控制器连接着中断处理器。处理器在中断后,就会请求控制器获得一个IRQ(中断请求),控制器将此翻译成一个中断号,在IDT(中断分发表)中找到中断分发例程(相当于函数)。IDT包含函数指针数组。(一个数组,数组中的每个元素都是一个指针,这些指针指向函数)

       IDT可以被挂钩(Hook掉)。中断处理例程可以被替换成恶意的函数。
       异常是程序运行时产生的状况,Windows提供的“结构化异常处理”的设计,可以允许在程序异常时获取控制而不是直接崩溃掉,在“捕获”异常后,可以修正指令流,返回到异常发生处(展开栈),或者不处理此异常,让系统处理,可能系统会有相关的异常处理措施。比如Microsoft Visual C++中可以用__try  __except __finally这样的语法来访问到这种处理机制。

③SSDT/IDT
       SSDT是系统服务描述表,它把R3的Windows API和R0的内核API联系起来,相当于是调用Windows API时,通向内核的指路牌。SSDT不只包含一个地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址,服务函数个数之类。SSDT也可以Hook。具体关于SSDT的内容,下文介绍。

ⅡWindows API
①为什么要有“接口”
      我们假设有一一供水厂,一用水的用户,连接水厂与用户的管道。按照一般人的思维,取水都是用水龙头,这个水龙头可以视作是水厂提供给用户的接口,用户不需要知道水是怎么过来的,从哪儿来的,只要根据约定按规定使用就行。
      如果思维稍微“活跃一点”,某一天在发现了路上的输水管,于是在那里你违规取到了水,这样的确可以,但是水厂公司不会认为有人会这么做,当它考虑到升级水管并使水管改道时,这个违规使用的接口就没有任何用处了。
      使用接口的意义之一在于只用关心怎么使用而不必关心内部构造,即使接口背后的原理完全改变了,接口的提供者一般也会保证接口的有效性。这也就是Windows API的意义,总不能在Windows 8上运行的好好的程序到Windows 8.1就没法使用了,内核可以改,但修改外部接口,是一项需要严格考虑的事情。

      而使用Microsoft未公开的函数需要对系统有深刻的理解,否则你无法保证你调用的函数就是正确的,并且你也很难保证在新版本的系统中这个函数还存不存在,因为未公开,这个函数也许是“有坑的”,人家就没打算让你去用。使用了这类函数的Windows应用程序,不能保证其在各个Windows版本中的兼容性。并不是在用户模式下能调用的函数就一定有文档,在内核模式下调用的函数就没有文档,即使是设备驱动程序,也会借助系统提供的函数完成任务。

②内核对象
      为了控制应用程序,Windows维护了一些不允许被用户应用程序直接访问的数据。内核对象是操作系统分配的一个内存块,这个内存块是一个数据结构,维护着与对象相关的信息,它可以被在内核模式下的代码访问。内核对象的记录数据,叫做“系统资源”。内核对象是一种抽象地表示。
      应用程序和内核交互要使用内核对象。每一个内核对象,都有API函数来操作,使用这些API可以在系统的监视下完成对内核对象的操作。内核对象本身也有状态信息。内核对象有一些公共的属性,比如安全属性,使用计数之类的,但有些信息并不是共有的,比如进程内核对象有“优先级类”,而文件内核对象有“共享模式”。

      内核对象不同于一般的数据结构,其内部是不透明的,不能直接读写对象但可以通过提供的对象服务。有了内核对象,系统就可以为系统资源提供可以标识的名称,进程之间可以共享资源和数据,当未经授权的代码访问时可以阻止,跟踪对象引用等。
      举一个线程内核对象的例子:
      线程内核对象是一个包含了线程状态信息的数据结构,可以通过调用CreateThread函数,系统就会为这个新的线程分配一个内核对象。系统提供了很多操作线程的函数,也是靠访问内核对象来管理。这个数据结构的基本成员有“CONTEXT”,“使用计数“,“暂停次数”,“退出代码”,“是否受信”等等。
      操作系统内核通过计数器统计有多少进程在使用这个内核对象。进程终止不是摧毁内核对象的条件,只有当计数器归0时操作系统内核才会这样。
      因为内核对象仅能从内核模式访问,所以一般不会直接定位它在内存中的位置。正当方法是使用相关的API函数。调用函数创建内核对象后,函数会把标识此内核对象的句柄返回给调用者。句柄一般是一个可以由一个进程中所有线程能够访问的一个不透明的值。许多API函数需要句柄做参数,目的就是为了清楚要操作的是哪一个内核对象。句柄一般是进程相关的,仅对当前进程有效。如果非得把进程A的一个句柄传递给进程B,进程B使用这个句柄向函数传递参数,可能会失败。除非通过某种机制,比如调用DuplicateHandle函数,可以允许一个进程将句柄分享给其他进程,但这样做相对来说更复杂。

      内核对象一般是进程内部的资源,“使用计数”属性是各种内核对象之间共有的属性。它指明了进程内对某一个内核对象的引用次数。如果引用次数为0,该资源会被系统关闭掉。当进程内的线程创建一个内核对象后,系统会为它分配内核对象资源,此时计数器为1,每次打开这个内核对象,计数器会加1,如果关闭它,就会减1,到计数器为0时,说明该内核对象已经被废弃,可以被回收了,系统会释放此内核对象资源。
③从用户层到内核层的调用过程
       本文不会牵扯MinWin工程相关的内容,否则会使文章十分复杂冗长。
       以Windows API CreateFile为例:
       创建文件,本质上还是让控制电流让指定区域的小磁块磁化,代表“1”和“0”,但这一工作不可能由程序员来完成,每个机器的型号都不一样,而且控制电流这种过于底层的事情是很困难的。所以并不会有人这样去读写文件。比如有个程序要读写文件,正常情况下会调用CreateFile,调用之后会被解析为调用Native API,经过内核调用KiSystemService处理器,在SSDT里找到NtWriteFile函数的位置,然后继续传导,进一步被解析成驱动程序指令,传递给文件系统驱动,如果有过滤驱动,必须通过过滤驱动,再由过滤驱动传导给下一层,最后达到FSD层,最终一定会解释成控制硬盘读写的电流,不然这个调用没有效果。每个接口要做的都只是把上一层命令传导给下一层接口,本来根本没法直接控制电流,但因为一层层的传递,读写文件只需要调用一个函数就行,这样才使得应用软件编写没那么复杂,不然,程序员不仅得是程序员,还要是物理学家。
       Windows API的功能主要位于kernel32.dll,advapi32.dll,user32.dll,gdi32.dll动态链接库文件中。Native API被封装在ntdll.dll中,ntdll.dll不是真正实现函数的dll,它的作用是“传导器”,它是R3到R0间的入口。ntdll.dll中的功能受到调用(NtCreateFile等等),于是对应的功能编号存入eax寄存器,然后使用sysenter指令,导致一个"自陷"(以前的机器上可能是 int 0x2e,64位机器上可能是syscall指令,总之效果一样)。
       KiSystemService从eax寄存器里取出函数代号,在SDT(一种结构)中查找对应的Native API函数,这种结构在系统中一共有4个,一个是SSDT,位于ntoskrnl.exe,记录了系统关键功能的位置,涉及文件操作,注册表操作,进程操作等。还有一个是Shadow SSDT,位于win32k.sys中,记录了和用户与图形界面有关的功能,不是很核心的功能。另外两个SDT的位置预留。找到函数后还会一步步调用(真正实现了功能的函数)。

       写文件时,调用文件系统驱动,如果有文件系统过滤驱动,那么会先通过文件系统过滤驱动,并层层传给底层文件系统驱动。任何一个环节出了问题都没办法调用成功。而通过"挂钩"这些关键函数,就可以实现对这些函数的控制。
       挂钩方法很多,也很复杂。但它的目的无非是比原函数更早获得调用消息,决定要不要继续传递函数调用,或者篡改了调用信息再传递。比如之前有些木马,喜欢挂钩SSDT中文件操作的函数,调用这类函数首先是指向木马,再是正常传递。比如通过挂钩ntdll.dll中的NtShutdownSystem,拦截掉所有关机请求,就没法关机了,当然这不保险,钩子在R3,如果某个驱动程序调用硬件抽象层中的HalReturnToFirmware函数,根本不经过ntdll这一层,钩子是无用的。所以有些人会选择在SSDT/Shadow SSDT下挂钩,或者安装文件过滤驱动等等方式达到目的。

ⅢWindows内核的部分防御措施
①微软内核模式代码签名

       在64位版本中的Windows中,内核模式代码签名策略是无法规避的(Kernel Mode Code Signing),64位设备驱动程序必须是经过某一个主码认证权威机构发放的密钥来签名。按照Microsoft的想法,没有经过签名的驱动程序是不能安装的(除非启动时“禁用驱动程序强制签名,这是临时性的)。

       但内核模式代码签名策略也不是100%保险的。一个简单的方法就是把它捆绑到一个已有的驱动程序上面。具体细节不再描述。这些被利用的驱动程序可能被吊销签名。
       内核模式代码签名策略在64位系统上强制执行,32位操作系统中也有,但此策略默认不被开启。在64位操作系统上,这一策略优先于即插即用驱动策略。


内核补丁保护
       一些32位设备驱动程序可能会修改系统调用表,截取系统调用,甚至给内存中的内核镜像“打补丁”,这样可以给某些内核函数添加特性。64位的Windows有了内核补丁保护技术(Kernel Patch Protection),即“PatchGuard”,它的目的是尽量防止那些不被Microsoft官方认可的修补系统或者钩子技术。
      像(Windows 7上)
Ntoskrnl.exe
Hal.dll
Ci.dll
Kdcom.dll
Pshed.dll
Clcs.sys
Ndis.sys
Tcpip.sys

是内核,硬件抽象层,网络栈的低层部分等部件。对内核或硬件抽象层的代码打补丁,可以干扰常规的操作。如果要在打开的端口上加后门,可以给Ndis.sys打补丁。   
      全局描述表符(GDT)的目的是实现基于环的特权级,即R0,R3。恶意利用它可以建立一个“调用门”,使得R3可以以R0的特权执行恶意操作。
      IDT(中断服务描述符表),CPU读取它并指向正确的中断处理函数,恶意的驱动程序可以直接在“中断”这一层截取I/O,屏蔽页面错误,隐藏内存中的内容。
      SSDT(系统服务描述表),包含由每个系统调用处理函数的函数指针数组)。
      MSR,用于实现前文提到的系统调用,恶意程序可以改写LSTAR,钩住所有系统调用。
      还有其他组件,本文不在讲述。上述的组件都是KPP的保护对象。当KPP检测到上述的对象发生变化,会让系统崩溃掉。


Ⅳ Wow64的基本原理
      纠结安软不是所谓的“原生64位”?先来看看32位的应用程序是怎么在64位操作系统上运行的吧。
      Wow64的全名是“Windows-on-Windows 64-bit”,允许在64位Windows上执行32位x86应用程序。实际上它是一组用户模式的动态链接库文件外加一点来自于内核的支持,这些支持主要是模拟32位版本的数据结构,因为像TEB(线程环境块)一般只有64位版本,不能直接由32位程序使用。
      介绍3个重要的Wow64的DLL:
Wow64.dll
Wow64Cpu.dll
Wow64Win.dll


       Wow64.dll,管理进程以及线程的创建,钩住了异常分发,Ntoskrnl.exe导出的系统调用,文件,注册表的重定向(类似于第二篇讲的UAC文件注册表虚拟化)。
       Wow64Cpu.dll,在Wow64中运行的程序管理32位CPU模式(环境),提供与硬件相关的CPU模式切换的支持。
       Wow64Win.dll,截住Win32k.sys的系统调用(GUI系统调用)。

       在没有干扰的情况下,所有32位转变为原生64位系统的代码执行路径都被Wow64钩住了。
       进程管理器将64位Ntdll.dll和32位Ntdll.dll加载到进程的虚拟地址空间,当初始化过程调用时,Wow64.dll内部初始化代码被执行。然后才是Wow64建立起32位Ntdll.dll所需要的运行环境,CPU模式会切换到32位下。从此之后,该进程就相当于运行在原生32位操作系统上了。
       Wow64钩住了异常分发,当64位内核发送异常给Wow64进程,原生的异常和和Context Record会被记录,准备相关环境后把它像32位操作系统一样分发出去。
       所有内核模式到用户模式的回调也被钩住了。输入参数(64位->32位),输出参数(32位->64位),不知道“回调”是什么,可参考【新手指南】样本区常用词汇简单解析:http://bbs.kafan.cn/thread-2076005-1-1.html
       和UAC的虚拟化机制类似,Wow64也实现了文件系统的重定向。
       为保持应用程序尽量兼容64位操作系统(很多程序甚至不需要重新编译,32位程序直接可以在64位系统上运行)。因此读写某些目录会被重定向到专有的目录中,比如将“\Windows\System32”的路径名替换为“\Windows\Syswow64”,将“\Windows\Regedit.exe”重定向到“\Windows\Syswow64\syswow64\Regedit.exe”等等,在原来的32位程序中指定访问这些文件夹,但其实是被64位操作系统重定向了的。注册表重定向类似,不在介绍。
       Wow64无法直接加载16位应用程序,而在32为操作系统上由于Wow32的存在,可以加载。64位操作系统上没有Wow32模块,但有些应用十分广泛的安装器是16位的,对于这些安装器,操作系统有专门的支持,可以保证运行。
       Wow64有一定限制,32位内核模式驱动程序不能被Wow64加载,必须是原生64位驱动才能运行。安全软件如果没有针对64位的驱动,那么驱动不能加载,但它的UI之类的东西可以被Wow64加载,原生64位,没有什么必要。Wow64进程可以加载32的DLL,但不能加载64位的,当然64位的也不能加载32位的DLL(一般来说是如此)。
       所以纠结于原生64位的安软,没有任何必要,与安全性没有什么关系。

本篇到此结束。
引领五基生活
发表于 2017-7-21 19:51:17 | 显示全部楼层
姐姐辛苦啦~
PlayBoy.C
发表于 2017-7-21 19:55:10 | 显示全部楼层
学习了   写这么多必须支持一下
149713172
发表于 2017-7-21 20:14:13 | 显示全部楼层
不错,学习看看!
liuuym
发表于 2017-7-21 20:37:29 | 显示全部楼层

不错,继续学习看看
绯色鎏金
发表于 2017-7-21 21:58:08 | 显示全部楼层
灵异事件。。。。。。

说起来确实在模块有数据,首页就是不显示来着。。。。。。。

评分

参与人数 1人气 +1 收起 理由
白露为霜 + 1 可能是被我的标题吓坏了。。。

查看全部评分

lai001lai007
发表于 2017-7-22 14:02:48 | 显示全部楼层
感谢楼主,学习到了不得了的东西啊
xiazaiba
发表于 2017-7-22 15:57:01 | 显示全部楼层
直接是看天书呀
紫仟陌尘
发表于 2017-7-24 10:42:24 | 显示全部楼层
看不懂,学过C语言,感觉跟着差不多,但是C语言也还给老师了
您需要登录后才可以回帖 登录 | 快速注册

本版积分规则

手机版|杀毒软件|软件论坛| 卡饭论坛

Copyright © KaFan  KaFan.cn All Rights Reserved.

Powered by Discuz! X3.4( 湘ICP备2021004765号-1 ) GMT+8, 2021-4-11 03:44 , Processed in 0.125180 second(s), 18 queries .

卡饭网所发布的一切软件、样本、工具、文章等仅限用于学习和研究,不得将上述内容用于商业或者其他非法用途,否则产生的一切后果自负,本站信息来自网络,版权争议问题与本站无关,您必须在下载后的24小时之内从您的电脑中彻底删除上述信息,如有问题请通过邮件与我们联系。

快速回复 客服 返回顶部 返回列表