分析恶意代码需要熟知的Windows概念

分析恶意Windows程序

Winodws API

类型和匈牙利表达法

​ 多数Windows API使用它自己的名字,来表示C语言类型。例如,DWORD和WORD类型分别表示32位和16位无符号整数。标准C类型如int、short,以及unsigneed int通常并不使用。

​ Windows总体上使用匈牙利表达法,作为API函数标识符。这个表达式使用一个前缀命名模式,来使识别一个变量的类型更为容易。包含一个32位无符号整数的变量,或DWORD,会以dw开头。

Windows API中的常见类型

类型和前缀描述
WORD(w)一个16位的无符号数值
DWORD(dw)一个双字节、32位的无符号数值
Handles(H)一个对象索引,句柄中存储的信息并没有文档化,而且这一句柄应该只被Windows API来操作,例子如HModule、HInstance、HKey
Long Pointer(L)一个指向另一个类型的指针,例如,LPByte是指向字节的指针,LPCSTR是一个指向字符串的指针,字符串通常是由LP作为前缀的,因为他们实际上是指针。偶尔你也会看到Pointer(P)而不是LP作为另一类型的前缀,在32位系统里,他们和LP是一样的,在16位系统中它们之间的区别才是有意义的。
Callback表示一个将会被Windows API调用的函数,例如,InternetSetStatusCallback函数传递一个函数指针,当系统有Internet连接状态的更新时将会调用这个函数

句柄

​ **句柄是操作系统中被打开或创建的项,比如一个窗口、进程、模块、菜单、文件等等。**句柄在它们引用一个对象或其他某个内存位置这点上很像指针。然而,和指针不同的是,句柄不能被用来进行数学操作(说白了句柄不是一个常量),并且它们不总是表示对象地址。你能对句柄做的唯一的事情,是保存它,并在后续函数调用中使用它来引用同一个对象。

​ CreateWindowEx函数是一个句柄的简单例子。它返回一个HWND,这是一个窗口句柄。任何时候当你想要对那个窗口做些什么,比如调用DestoryWindow函数时,你都需要使用这个句柄。

文件系统函数

​ 恶意代码与系统交互的一个最常用的方式就是创建或修改文件

​ 微软提供了多个函数来访问文件系统:

CreateFile

​ 这个函数用来创建和打开文件。它可以打开已经存在的文件,管道,流,以及I/O设备,还能创建新的文件。参数dwCreationDisposition控制CreateFile函数是否创建一个新的文件,或是打开一个已经存在的文件。

ReadFile和WriteFile

​ 这两个函数被用来对文件进行读和写。两个都将文件作为流来操作。当你第一次调用ReadFile时,你从一个文件中读取后续一些字节;下次调用它,你读取它后面的一些字节。例如,若你打开一个文件,并用40字节大小调用ReadFile,下次你再调用它,它将会40字节之后开始读。

CreateFileMapping和MapViewOfFile

​ 文件映射经常被恶意代码作者使用,因为它们允许将一个文件加载到内存中,以便更加容易地进行操作。CreateFileMapping函数负责从磁盘上加载一个文件到内存中。MapViewOfFile函数则返回一个指向映射的基地址指针,它可以被用来访问内存中的文件。程序调用两个函数,能够使用从MapViewOfFile函数返回的指针,从文件中的任意位置进行读取和写入。

注意:文件映射被普遍用来复制Windows加载器的功能。在获得一个文件的映射以后,恶意代码可以解析PE头,并对内存中的文件进行所有需要的修改,因此使PE文件就像被操作系统加载器加载一样执行起来。

特殊文件

​ Windows系统中有一些特殊的文件类,它们的访问方式与普通文件不太一样,但是它们不能通过它们的盘符与文件夹进行访问。恶意代码经常使用特殊文件。

拓展:盘符:通俗来讲电脑磁盘就是盘符,目录指计算机里面的文件、程序、盘符;而打开盘符后又很多的子目录。简而言之,C盘、D盘、E盘等这些字母开始的盘就是通常所说的盘符。

​ 有些特殊文件要比普通文件更隐蔽,因为它们在列目录时不会显示出来。某些特殊文件可以提供对系统硬件和内部数据更强的访问能力。特殊文件可以作为字符串参数被传递给任何文件操作函数中,并像普通文件一样进行操作。

共享文件

​ 共享文件是以\\severName\share或\?\severName\share开头命名的特殊文件。它们用来访问保存在共享目录中的目录或文件。\?\前缀告诉操作系统禁用所有的字符串解析,并允许访问长文件名。

通过名字空间访问的文件

​ 在操作系统中,还有一些文件可以通过名字空间进行访问。名字空间可以被认为是固定数目的文件夹,每一个文件夹中保存不同类型的对象。底层的名字空间是NT名字空间,以前缀\开始。NT名字空间可以访问所有设备,以及所有在NT名字空间中存放的其他名字空间。

​ 以前缀\\.\开始的Win32设备名字空间,经常被恶意代码用来直接访问物理设备,并且像一个文件一样进行读写操作。例如,一个程序可能使用\\.\PhysicalDisk1来直接访问PhysicalDisk1,而忽略它的文件系统,因此这允许程序通过普通API不可能做到的方式来修改磁盘。使用这个方法,恶意代码可以读写数据到一个未分配的扇区,而无须创建或访问文件,这允许它避开病毒与安全程序的检测。

备用数据流

​ 备用数据流(ADS)特性允许附加数据被添加到一个已存在的NTFS文件中,相当于添加一个文件到另外一文件中。额外数据在列一个目录时不会被显示出来,并且当显示文件内容时也不显示;而只有在你访问流时,它才是可见的。

拓展:NTFS是为电脑提高长文件名、数据保护和恢复,并通过目录和文件许可实现安全性。NTFS支持大硬盘和在多个硬盘上存储文件(称为卷)。它就像一个储存器和翻译器。

​ ADS数据流根据约定normalFile.txt:Stream:$DATA来命名,这允许一个程序去读写一个流。恶意代码作者喜欢ADS,因为它能被用来隐藏数据。

Windows注册表

​ Windows注册表被用来保存操作系统与程序的配置信息,比如设置和选项。和文件系统一样,它是基于主机的感染迹象的很好来源,并且能够揭示出关于恶意代码功能的有用信息。

​ 恶意代码经常使用注册表来完成持久驻留或者存储配置数据。恶意代码添加到注册表中,这使它在计算机引导时能够自动运行。

下列是几个重要的注册表术语;

  • 根键 注册表被划分为称为根键的5个顶层节。有时,术语HKEY和储巢也被使用。每一个根键又一个特定的目的。
  • 子键 一个子键就像是一个文件夹的子文件夹
  • 一个键是一个注册表的文件夹,它可以包含额外的文件夹或键值。根键和子键都是键。
  • 值项 一个值项是一个配对的名字和值
  • 值或数据 值或数据是存储在注册表项中的数据

注册表根键

​ 注册表被划分为下面5个根键:

  • ​ HKEY_LOCAL_MACHINE(HKLM) 保存对本地机器全局设置。
  • ​ HKEY_CURRENT_USER(HKCU) 保存当前用户特定的设置。
  • ​ HKEY_CLASSES_ROOT 保存定义的类型信息。
  • ​ HKEY_CURRENT_CONFIG 保存关于当前硬件配置的设备,特别是与当前和标准配置之间不同的部分。
  • HKEY_USERS 定义默认用户、新用户和当前用户的配置

​ 两个最常用的根键是HKLM和HKCU。一些键实际是虚拟键值,提供一种引用底层注册表信息的方式。例如,HKCU键实际上存储在HKEY_USERS\SID中,这里SID是当前登录用户的安全描述符。在例如,一个常用的子键,HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run,包含一系列值,这些值列举了当一个用户登录时被自动启动的可执行程序。根键是HKEY_LOCAL_MACHINE,它保存了SOFTWARE、Microsoft、Windows、CurrentVersion以及RUN子键。

Regedit

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AaG1Q0IH-1685522818325)(C:\Users\86153\AppData\Roaming\Typora\typora-user-images\image-20230525160008659.png)]

​ 注册表编辑器(Regedit),如图所示,是Windows内建的用来查看和编辑注册表的工具。在左侧的窗口中显示了被打开的子键。右侧的窗口则显示了这子键中的值项。每一个值项都有它的名字、类型和值。当前被查看子键的完整路径在窗口底部显示。

自启动程序

​ 向Run子键中写入项,是设置程序自启动的方法。

​ Autoruns工具列举了在操作系统启动时会自动启动运行的代码。它列举出了自启动的可执行文件,加载到Internet Explorer和其他程序中的DLL程序,以及加载到内核中的驱动。Autoruns会在注册表中检查约25~30个位置,来找到被设计成自动运行的代码,但是它不需要列举出所有的项

常用注册表函数

​ 恶意代码经常使用作为Windows API一部分的注册表函数,来修改注册表,以使它们自身能够在系统引导时自动启动运行。下面是最常用的注册表函数:

  • RegOpenKeyEx 打开一个注册表进行编辑和查询。有些函数允许你查询和编辑一个注册表键,而不用先打开它,但是大多数程序还是会先使用RegOpenKeyEx
  • RegSetValueEx 添加一个新值到注册表,并设置它的数值。
  • RegGetValue 返回注册表中一个值项的数值

​ 当在恶意代码中看到这些函数时,你应该标识出它们访问的注册表键值。

使用.reg文件的注册表脚本

​ 用.reg作为扩展的文件包含一个人类可读的注册表数据。当一个用户双击一个.reg文件时,它会自动地通过合并文件包含的信息到注册表中,来修改注册表——几乎像一个修改注册表的脚本一样。

网络API

伯克利兼容套接字(所谓套接字,就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端)

​ 伯克利兼容套接字的网络功能在Windows系统中是由Winsock库实现的,主要在ws2_32.dll中。在所有库函数中,socket connect bind listen accept send recv函数最常见。

​ 伯克利兼容套接字的网络函数

函数描述
socket创建一个套接字
connect将一个套接字绑定到特定端口,应该在accept之前调用
bind预示着一个套接字将进入监听,等待入栈连接
listen向一个远程套接字打开一个连接,并接受连接
accept向一个远程套接字打开一个连接,远程套接字必须在等待连接
send从远程套接字接受数据
recv发送数据到远程套接字

网络的服务器和客户端

​ 一个网络程序通常有两个端点:服务器端,它维护一个打开套接字并等待入站连接;客户端,它连接到一个正在等待的套接字。

​ WSAStartup函数初始化Win32 socket系统,然后一个套接字通过socket函数被创建。bind函数将这个套接字附加到一个端口。listen调用将这个套接字设置为监听状态,然后accept调用挂起,等待一个来自远程套接字的连接。

WinINet API

​ 除了Winsock API以外,还有一个称为WinINet API的更高一级Windows API。WinINet API函数被保存在Wininet.dll中。如果一个程序从这个DLL中导入函数,它就是在使用更高以及的网络API。

​ WinINet API实现了应用层协议,如HTTP和FTP。可以根据恶意代码打开的是何种连接,来理解它在做什么事情。

  • InternetOpen被用来初始化一个到互联网的连接
  • InternetOpenUrl被用来访问一个URL(它可以是一个HTTP页面和一个FTP资源)
  • InternetReadFile和ReadFile函数工作原理相似,允许程序从一个来自互联网的下载文件中读取数据。

跟踪恶意代码的运行

​ 除了在IDP Pro中可见的跳转和指令调用之外,恶意代码还有很多方式可以用来转移执行。访问单一文件以外的代码,首先也是最常见的方式就是通过使用DLL程序。

DLL

​ 动态链接库(DLL)是使用库来在多个应用程序之间共享代码的Windows特有方式。一个DLL程序是不能独自运行的可执行文件,但它可以导出一些被其他应用程序使用的函数。

​ 静态库是在DLL程序引入之前的标准方案,并且仍然在使用,但是它们更加少见一些。现在普遍使用DLL程序,**因为被DLL程序使用的内存可以在正运行的进程之间共享。**再有就是当DLL程序在发布一个可执行文件时,你可以使用Windows系统上已知的DLL程序,而无须去重新发布它们。这帮软件开发者和恶意代码作者最小化发布软件的大小规模。

​ DLL程序也是一种有用的代码复用方式。

拓展:恶意代码作者如何使用DLL

  • 保存恶意代码

​ 有时,恶意代码作者发现将恶意代码保存到一个DLL文件比.exe文件更加有利。有些恶意代码会附加到其他进程,但是每一个进程只能包含一个.exe文件。因此恶意代码有时使用DLL程序,来将它自己加载到另外一个进程中。

  • 通过使用Windows DLL

​ 几乎所有的恶意代码都使用在每个系统上都可以发现的Windows基础DLL程序。Windows DLL程序包含了需要与操作系统交互的功能。而一个恶意程序使用Windows DLL的方式,经常为恶意代码分析师提供巨大的洞察力。导入函数、导出函数等,全部都是从Windows DLL程序导入的。

  • 通过使用第三方DLL

​ 基本上,DLL文件看起来几乎和.exe文件一样。DLL使用PE文件格式,并且只有一个单一标志,指示这个文件是一个DLL,而不是一个.exe。DLL经常有更多导出后函数,并且通常导入函数较少。除了那个标志之外,一个DLL和一个.exe之间没有实质的区别。

​ DLL的主函数是DLLMain。它没有标记,而且并不是一个DLL中的导出函数,但是它在PE头中被指定为文件的入口点。任何时候一个进程加载或卸载库,会创建一个新线程,或一个已存在的线程结束时,这个函数都会被调用来通知DLL。这个通知允许DLL来管理每个进程或每个线程的资源。

​ 多数DLL没有线程粒度的资源,并且它们忽略由线程活动引起对DLLMain的调用。如果DLL有必须在线程粒度进行管理的资源,那么这些资源可以为分析师提供一些提示,来了解这个DLL的目的。

进程

恶意代码也可以通过创建一个新进程,或修改一个已存在的进行,来执行当前程序之外的代码。一个进程是Windows正在执行的程序。每一个进程管理他自己的资源,诸如打开的句柄与内存。一个进程包含一个或多个由CPU执行的进程。传统上,恶意代码包括它自己的独立进行,但更新型的恶意代码则普遍将自身代码作为其他程序的一部分执行。

Windows使用进程作为管理资源的容器,并保持程序隔离且不相互干扰。一个Windows系统上任何时间通常由20~30个进程在运行,它们共享相同的系统资源。

​ 操作系统中在多个进程间共享的一个尤其重要的资源是系统内存。为了达到这个目的,每一个进程被给予一块与所有其他进程隔离的内存空间,而这是进程可以使用的内存地址范围。

当进程需要内存时,操作系统会分配内存,并为这个进程提供一个地址来访问这块内存。

创建一个新进程

​ 恶意代码最常使用的创建进程函数时CreateProcess.这个函数有许多参数,并且调用者由很多控制它如何被创建的方法。例如,恶意代码能够调用这个函数,来创建一个进程来执行它的恶意代码,这样便可以绕过基于主机的防火墙以及其他安全机制。或者它可以创建一个Internet Explorer的实例,然后使用那个程序来访问而已内容。

​ 恶意代码通常使用CreteProcess,来创建一个简单的远程shell。CreateProcess函数的一个参数,STARTUPINFO结构,包含一个进程的标准输入、标准输出以及标准错误流的句柄。一个恶意程序可以设置这值为套接字,这样当这个程序写入标准输出时,它实际上会写道套接字上,因而允许一个攻击者执行远程shell,而不需要运行出CreateProcess之外的任何函数。

​ 对CreateProcess的调用会创建一个新进程,以便所有的输入和输出被重定向到一个套接字。要找到这台远程主机,我们需要判断这个套接字在哪里被初始化。

​ 恶意代码经常在一个程序的资源节存储另一个程序,并创建一个新进程。

线程

​ 进程是执行代码的容器,线程才是Windows操作系统真正要执行的内容。线程是被CPU执行的独立指令序列,而不需要等待其他进程。一个进程包含一个或多个线程,它们执行进程中的一部分代码。一个进程中的所有线程共享同样的内存空间,但是每一个由他自己的处理器、寄存器和栈。

线程上下文

​ 当一个线程运行时,它对CPU或CPU核有着完全的控制,并且其他线程不能影响CPU或核的状态。当一个线程改变CPU中某个寄存器的值时,它不会影响任何其他线程。一个操作系统在线程间切换之前,在CPU中的所有值会被保存到一个称为线程上下文的结构体中。然后操作系统加载这个线程上下文到一个新的线程中,并使这个新线程在CPU中执行。

简单叙述一下线程上下文的作用,如果现在访问一个变量并将其压入栈中,此过程用两条汇编指令可以实现,现在,如果另一个线程要在这两条指令之间运行一些代码并改变了变量的值此时再将变量的值压入栈中,变量的值是错误的,而且代码也不正确执行。当线程上下文切换被使用时,如果另外一个线程在这两条指令之间运行,变量的值就会保存到线程上下文中。当这个线程再次开始并执行入栈操作时,线程上下文就会被恢复,变量的值再次保存正确的值。

创建一个线程

​ CreateThread函数被用来创建一个新线程。函数的调用者指定一个起始地址,它经常被叫做start函数。执行从这个地址开始直到这个函数返回,尽管这个函数不需要返回,这个线程可以在进程结束前一直运行。当分析调用CreateThread的代码时,除了分析这个start函数外,你还需要分析调用CreateThread的剩下代码。

​ CreateThread的调用者可以指定线程开始的函数位置,并且一个单一参数可以被传递给这个start函数。这个参数可以是任意值,依赖于这个线程要开始执行的函数。

​ 恶意代码使用CreateThread函数的方式

  • 恶意代码可以使用CreateThread,来加载一个新的恶意库文件到进程中,通过在调用CreateThread时将起始地址设为LoadLibrary的地址。(传递给CreateThread的参数是要被加载库的名字。新的DLL被加载到这个进程的内存中,然后DLLMain被调用)
  • 恶意代码可以为输入和输出创建两个线程:一个用来在套接字或管道上监听,并输出到一个进程的标准输入里,另一个用来从标准输出读取数据,并发送到套接字或管道上。恶意代码的目标就是发送所有信息到单一的套接字或管道,来和运行的应用程序进行无缝通信。

使用互斥量的进程间协作

​ 一个和线程与进程相关的话题是互斥量(mutex),在内核中也称为互斥门(mutant)。互斥量是全局对象,用于协调多个进程和线程。

​ 互斥量主要用于控制共享资源的访问,并且经常被恶意代码所使用。例如,若两个线程必须访问一个内存结构,但是一次只能有一个线程可以安全访问,互斥量就可以被用来控制这种共享访问。

​ 同一时刻,只有一个线程拥有一个互斥量。互斥量对恶意代码分析很重要,因为它们经常使用硬编码的名字,将它们作为基于主机的感染迹象是很好的选择。互斥量名字硬编码十分常见,因为如果一个互斥量被两个不适用其他方式通信的进程使用时,它的名字必须是互相一致的。

​ 线程通过一个对WaitForSingleObject的调用,获取对互斥量的访问,并且任何后续线程试图获取对它的访问时,都必须等待。当一个线程完成对互斥量的使用后,需要使用ReleaseMutex(ReleaseMutex是一种线性指令,具有释放线程拥有的互斥体的控制权)函数。

服务

​ 恶意代码执行附加代码的另一种方式是将它作为服务安装。Windows允许通过使用服务,来使任务作为后台应用程序运行,而不需要它们自己的进程或线程;代码被Windows服务管理器调度和运行,但没有用户输入。在Windows操作系统上的任何指定时间,都会有多个服务在运行。

​ 使用服务对恶意代码作者来说有很多优势。其中一个使服务通常作为SYSTEM(SYSTEM的中文意思是系统,在Windows中拥有比管理员更大的权限,在Windows中主要作为系统服务或进程的运行账户。)或其他特权账户运行。这并不是一个漏洞,因为你需要管理员访问权限,才能安装一个服务,但是对恶意代码作者来说很方便,因为SYSTEM账户有比管理员或用户账户更多的权限。

​ 服务也提供另一种在系统上维护持久化驻留的方式,因为它们可以被设置成当操作系统启动时自动运行,并且可能甚至不在任务管理器中作为一个进程显示出来。一个用户查找所有运行的应用程序,也不会找到任何可疑的东西,因为恶意代码不是运行在一个独立进程中。

拓展:在命令行使用net start可以列举出正在运行的服务,但是这样做只会显示出运行服务的名字。有些程序,比如前面提到过的Autoruns工具,可以被用来搜集关于运行服务的更多信息。

  • ​ OpenSCManager 返回一个服务控制管理的句柄,它被用来进行所有后续与服务相关的函数调用。所有要和服务交互的代码会调用这个函数。
  • ​ CreateService 添加一个新服务到服务控制管理器,并且允许调用者指定服务是否在引导时自动启动,或者必须手动启动。
  • ​ StartService 启动一个服务,并且仅在服务被设置成手动启动时使用。或者必须手动启动。

​ Windows操作系统支持多种服务类型,它们以独特的方式执行。恶意代码最常使用的是WIN32_SHARE_PROCESS类型,这种类型将这个服务的代码保存在一个DLL中,并且在一个共享的进程种组成多个不同的服务。在任务管理器中,你可以找到一个名为svchost.exe进程的多个实例,它们在运行WIN32_SHARE_PROCESS类型的服务。

​ WIN32_OWN_PROCESS类型有时也被使用,因为它在一个.exe文件中保存代码,而且作为一个独立进程进行。

​ 最后一个常见的服务类型是KERNEL_DRIVER,它被用来加载代码到内核中执行。

​ SC程序是Windows包含的一个命令行工具,你可以使用它来调查和操作服务。它包含了添加、删除、启动、停止,以及查询服务的命令。

组件对象模型

​ 微软组件对象模型(COM)是一个接口标准,它使得不同软件组件在不知道其他组件代码的结构规范时,相互之间可以进行调用。分析使用COM的恶意代码时,你需要能够判断那段代码会被作为一个COM函数进行调用运行。

​ COM可以支持任何编程语言,并且被设计一种可复用的软件组件,并可以被所有程序所利用。COM使用了一个对象结构,在与面向对象的编程语言中可以很好配合使用,COM也并不排斥非面向对象的编程语言。

​ COM被实现成一个客服-服务器框架。客户端是那些使用COM对象的程序,服务器是那些可复用的软件组件——也就是COM对象本身。微软提供了很多COM对象给程序使用。

​ 每一个使用COM的线程,必须在调用任何其他COM库函数之前,至少调用一次OleInitialize或CoInitializeEx函数。

CLSID、IID,以及COM对象的使用

​ COM对象通过它们的全局唯一标识符(GUID),分为类型标识符(CLSID)以及接口标识符(IID),来进行访问。

​ CoCreateInstance函数被用来获取对COM功能的访问。恶意代码使用的一个常用函数是Navigate,它允许一个程序启动Internet Exploer,并访问一个Web地址。Navigate函数是IWebBrowser2组件接口的一部分,这个接口指定了一个必须被实现的函数列表,但是它没有指定那个程序会提供这个功能。提供这个功能的程序就是实现了IWebBrowser2接口的COM类。在多数例子中,IWebBrowser2接口被Internet Explorer实现。接口通过一个叫IID的GUID来标识,而COM类通过一个叫做CLSID的GUID来标识。

COM服务器恶意代码

​ 有些恶意代码实现了一个恶意COM服务器,继而被其他应用使用。对恶意代码来说,常用的COM服务器功能是通过浏览器帮助对象(BHO),这是Internet Explorer的第三方插件。BHO没有限制,所以恶意代码作者使用它们在Internet Explorer进程中运行代码,这允许他们监控互联网流量、跟踪浏览器的使用,以及与互联网通信,而且并不使用它们自己的进程。

​ 实现一个COM服务器的恶意代码通常很容易检测,因为它导出了几个函数,包括DllCanUnloadNow、DIIGetClassObject、DllInstall、DIIRegisterServer,以及DIIUnregisterServer,它们都必须有COM服务器软件导出。

异常:当事情出错时

​ 异常机制允许一个程序在普通执行流程之外处理时间。多数时间里,异常是由错误引起的,诸如除零错误。当一个异常发生时,执行转移到处理这个异常的特殊例程。有些异常,比如除零异常,是由硬件抛出的;其他的,比如无效内存访问,是由操作系统抛出的。你也可以在代码中使用RaiseException调用,显式地抛出一个异常。

​ 结构化异常处理(SEH)是Windows的异常处理机制。在一个32为系统中,SEH信息被保存在栈上。

​ 下面给出显示了一个有异常处理的函数前几行的反汇编代码

01006170pushoffset loc_10061C0
01006175moveax,large fs:0
01006178pusheax
0100617Cmovlarge fs:0,esp

​ 在这个函数开始,一个异常处理帧在第一条语句被放入栈中。这个特殊位置fs:0指向栈上保存着异常信息的一个地址。在栈上的是一个异常处理帧的位置,同时也包括被调用者在第三条语句使用的异常处理器,他在函数的末尾被恢复。当一个异常发生时,Windows查看fs:0来寻找保存异常信息的栈位置,然后这个异常处理器被调用。在这个异常被处理后,执行返回到主线程。

​ 异常处理器是可嵌套的,并且不是所有的处理器都会对应着所有异常。如果当前帧的异常处理器不处理这个异常,这个异常会被传递给调用者帧的异常处理器。最终,如果这些异常处理器中没有一个上映这个异常,那么顶层的异常处理器将使应用程序崩溃。

内核与用户模式

​ Windows使用两种处理器特权级别:内核模式与用户模式。

​ 几乎所有代码都运行在用户模式,除了操作系统和硬件驱动,它们运行在内核模式。在用户模式,每一个进程都有它自己的内存、安全权限,以及资源。如果一个用户模式程序执行一个无效指令并崩溃,Windows可以回收所有资源,并终止这个程序。

​ 通常,用户模式不能直接访问软件,并且它被限制只能访问CPU上所有寄存器和可用指令的一个子集。为了在用户模式中操作硬件或改变内核的状态,你必须以来Windows API。

​ 当你调用了一个Windows API函数操作内核结构体时,它会通过一个调用进入内核。在反汇编中SYSENTER、SYSCALL或者INT 0X2E的存在,指明一个调用被使用进入到内核。直接通过跳转从用户模式到内核模式是不可能的,这些指令使用查看表来定位一个预定义函数,从而在内核中执行代码。

​ 所有运行在内核的进程共享资源和内存地址。内核模式代码有更少的安全检查。如果在内核运行的代码执行并且包括无效指令,操作系统就不能继续进行,产生的结果就是著名的Windows蓝屏。

​ 运行在内核中的代码可以操纵运行在用户空间的代码,但是运行在用户空间的代码只能通过定义好的接口来影响内核。即使所有运行在内核的代码共享内存和资源,处于活跃状态的进程上下文也总是只有一个。

​ 内核代码对恶意代码作者来说非常重要,因为在内核模式下可以做比用户模式更多的事情。大多数安全程序,比如反病毒软件和防火墙,都运行在内核模式,这样它们能够访问和监控运行在系统上的所有应用程序的活动。运行在内核模式的恶意代码可以更容易地干扰安全程序或绕过防火墙。

​ 明显地,运行在内核模式的恶意代码比运行在用户空间的恶意代码更加强大。在内核空间中以特权用户运行和非特权用户运行进程间的任何区别都被移除了。除此之外,操作系统的审计特性也不应用于内核。处于这些原因,几乎所有的rootkit都会利用运行在内核的代码。

原生API

在这里插入图片描述

​ 原生API是用来和Windows进行交互的的底层API,它们很少被非恶意程序使用,但是在恶意代码作者之间却很受欢迎。调用原生API函数可以绕过普通的Windows API。

​ 当调用Windows API中的一个函数时,这个函数通常不会直接执行请求的动作,因为大多数重要数据结构都被保存在内核中,在内核外面的代码(用户模式代码)是无法访问它们的。上图说明了对于大多数API调用来说,是如何工作的。

​ 用户应用程序被给予对用户API(比如kernel32.dll和其他DLL)的访问,这些DLL会调用Ntdll.dll,这是一个特殊的DLL程序,它管理用户空间与内核的交互。然后处理器切换到内核模式,并执行一个内核中的函数,通常它位于ntoskrnl.exe中。

​ ntdll函数像内核中的函数一样,使用API和结构体。这些函数组成了原生API。应用程序不建议调用原生API,但是操作系统中没有任何东西能阻止它们这样做。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值