avatar

目录
第7章 分析恶意Windows程序

第七章 分析恶意Windows程序

7x1 Windows API

1. 类型和匈牙利表达法

Windows总体上使用匈牙利表达法作为API函数标识符,表达式使用前缀命名模式

Windows API常见类型
mark

2. 句柄

句柄是在操作系统中被打开或被创建的项(一个窗口,进程,模块,菜单,文件等)
句柄不能用来做数学操作
我们所能做的只有保存它,并在后续函数调用中使用它来引用同一个对象

3. 文件系统函数

恶意代码与系统交互的一个最常用的方式就是创建或修改文件
独特文件名或修改为既有文件名是明显的基于主机的感染迹象

CreateFile

用来创建和打开文件,可打开已存在的文件,管道,流,及I/O设备,能创建新文件

ReadFile和WriteFile

用来对文件进行读和写操作

CreatFileMapping和MapViewOfFile

从磁盘加载一个文件到内存和返回一个指向映射的基地址指针(可用来访问内存中的文件)

mark

4. 特殊文件

共享文件

以\serverName\share或\?\serverName\share开头命名的特殊文件,用来访问在共享目录中的目录或文件

通过名字空间访问的文件

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

以前缀\.\开始的Win32设备名字空间,经常被恶意代码用来直接访问物理设备,并且像一个文件一样进行读写操作

使用\Device\PhysicalMemory 来直接访问物理内存,这允许用户空间程序写到内核空间中。这个技术已经被恶意代码用来修改内核,并隐藏用户空间的程序。

备用数据流

备用数据流(ADS)特性允许附加数据被添加到一个已存在的NTFS文件中,相当于添加一 个文件到另外一 文件中。额外数据在列一 个目录时不会被显示出来,并且当显示文件内容时也不显示;而只有在你访问流时,它才是可见的。
ADS数据流根据约定normalFile.txt:Stream:$DATA来命名,这允许一个程序去读写一个流。恶意代码作者喜欢ADS,因为它能被用来隐藏数据。

7x2 Windows注册表

Windows注册表被用来保存操作系统与程序的配置信息
mark

1. 注册表根键

mark

2. Regedit

注册表编辑器,Windows内建的用来查看和编辑注册表的工具

3. 自启动程序

向Run子键中写入项,可设置程序自启动
Autoruns工具列举在操作系统启动时会自动启动运行的代码

4. 常用注册表函数

mark

5. 练习分析注册表操作代码

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

7x3 网络API

1. 伯克利兼容套接字

网络功能在Windows系统中由Winsock库实现,主要在ws2_32.dll中
mark
WSAStartup函数必须在其他网络函数之前被调用
调试代码查找网络接口时,可在WSAStartup函数中设置断点

2. 网络的服务器和客户端

一个网络程序通常有两个端点:服务器端,它维护一个打开套接字并等待入站连接:客户端,它连接到一个正在等待的套接字。而恶意代码可以是这两端中的任意一个。

3. WinINet API

mark

7x4 跟踪恶意代码的运行

1. DLL

动态链接库(DLL)是使用库来在多个应用程序之间共享代码的Windows特有方式。一个DLL程序是不能独自运行的可执行文件,但它可以导出一些被其他应用程序使用的函数。
被DLL程序使用的内存可以在正运行的进程之间共享。
在发布一个可执行文件时,你可以使用Windows系统上已 知的DLL程序,而无须去重新发布它们。这帮助软件开发者和恶意代码作者最小化发布软件的大小规模。
DLL程序也是一种有用的代码复用方式

恶意代码作者如何使用DLL

保存恶意代码
通过使用Windows DLL
通过使用第三方DLL

基本DLL结构

DLL使用PE文件格式,并且只有一个单一标志,指示这个文件是一个
DLL,而不是一个.exeDLL经常有更多导出函数,并且通常导入函数较少。
DLL的主函数是DllMain。它没有标记,而且并不是一个DLL中的导出函数,但是它在PE头中被指定为文件的入口点。任何时候一个进程加载或卸载库,会创建一个新线程,或一个已程结束时,这个函数都会被调用来通知DLL。这个通知允许DLL来管理每个进程或每个线程的资源存在的线
程的资源。

2. 进程

创建一个新进程

恶意代码最常使用的创建新进程函数是CreateProcess

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

3. 线程

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

线程上下文

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

创建一个线程

CreateThread函数被用来创建一个新线程。函数的调用者指定一个起始地址,它经常被叫做sta rt函数。执行从这个起始地址开始直到这个函数返回,尽管这个函数不需要返回,这个线程可以在进程结束前一直运行。
CreateThread的调用者可以指定线程开始的函数位置,并且一个单一参数可以被传递给这个start函数。这个参数可以是任意值,依赖于这个线程要开始执行的函数。

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

4. 使用互斥量的进程间协作

互斥量(mutex), 在内核中也称为互斥门(mutant)是全局对象,用于协调多个进程和线程。
互斥量主要用于控制共享资源的访问,并且经常被恶意代码所使用。
同一时刻,只有一个线程拥有一个互斥量。

线程通过一个对WaitForSingleObject的调用,获取对互斥量的访问,井且任何后续线程试图获取对它的访问时,都必须等待。当一个线程完成对互斥量的使用后,需要使用ReleaseMutex函数。

一个互斥量可以通过CreateMutex函数进行创建。而进程可以通过OpenMutex调用来获取另一个进程中互斥量的句柄。恶意代码通常创建一个互斥量,并试图使用同一个名字来打开一个已存在的互斥量,通过这种方式,可以确定恶意代码一次只有一个唯一实例在运行。

5. 服务

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

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

服务可以通过一些Windows API函数来进行安装和操作

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

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

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

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

关于本地系统上服务的信息被保存在注册表中。每个服务在HKLM\SYSTEM\CurrentControlSet\Services下面有一个子键。

6. 组件对象模型

微软组件对象模型(COM)是一个接口标准,它使得不同软件组件在不知道其他组件代码的接口规范时,相互之间可以进行调用。

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

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

每一个使用COM的线程,必须在调用任何其他COM库函数之前,至少调用一次Olelnitialize或CoInitializeEx函数。所以,一个恶意代码分析师可以搜索这些调用,来判断一个程序是否使用了COM功能,然而,知道恶意代码片段作为客户端程序使用COM对象并没有提供很多信息,因为COM对象是繁杂且广泛的。一旦你判断程序在使用COM,你就需要找到一些正在被使用对象的标识符来继续分析。

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

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

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

COM服务器恶意代码

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

实现一个COM服务器的恶意代码通常很容易检测,因为它导出了几个函数,包括DllCanUnloadNow、DllGetClassObject、Dlllnstall、DI 1 RegisterServer, 以及DllUnregisterServer,它们都必须由COM服务器软件导出。

7. 异常:当事情出错时

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

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

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

异常处理器可以让恶意代码获得执行机会。一个指向异常处理信息的指针被保存在栈上,在栈溢出时,一个攻击者可以覆盖这个指针。通过指定一个新的异常处理器,攻击者可以在一个异常发生时获得执行机会。

7x5 内核与用户模式

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

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

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

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

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

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

7x6 原生API

原生API是用来和Windows进行交互的底层API。
调用原生API函数可以绕过普通的Windows API。

当调用Windows API中的一个函数时,这个函数通常不会直接执行请求的动作,因为大多数重要数据结构都被保存在内核中,在内核外面的代码 (用户模式代码)是无法访问它们的。微软为了使用户应用程序能够达到必需的功能,创建了一个多步骤的调用过程。
mark

用户应用程序被给予对用户API (比如kernel32.dll和其他DLL)的访问,这些DLL会调用ntdlLdll,这是一个特殊的DLL程序,它管理用户空间与内核的交互。然后处理器切换到内核模式,并执行一个内核中的函数,通常它位于ntoskrnl.exe中。这个过程是令人费解的,但是内核和用户API之间的分离,允许微软修改内核而不会影响应用程序。

ntdll函数像内核中的函数一样,使用API和结构体。这些函数组成了原生API。

尽管微软不提供关于原生API的完整文档,还是有网站和书来文档化这些函数。最好的参考书是由GaryNebbett (Sams, 2000)撰写的Windows NT/2000 Native API Reference,尽管它已经很旧了。在线资源如 http://undocumented.ntinternals.net 以提供最近的信息。

有一系列的原生API调用可以被用来获取关于系统的信息、进程、线程、句柄,以及其他项目。这 些 包 括 NtQuerySystemlnformation , NtQuerylnformationProcess > NtQuerylnformationThread > NtQuerylnformationFile, 以及NtQuerylnformationKey。这些调用提供比任何可用Win32调用更详细的信息,并且其中一些函数允许你给文件、进程、线程等设置细粒度的属性。

另一个恶意代码普遍使用的原生API函数是NtContinue。这个函数被用来从一个异常处理返回,并且它的意图是在一个异常被处理后转移执行回到一个程序的主线程。然而,要返回的位置在异常上下文中被指定,并且它可以被修改。恶意代码经常使用这个函数来以复杂的方式转移执行,从而使一个分析师感到困惑,并且使一个程序更加难调试。

原生应用程序是那些不使用Win32子系统而只调用原生API的应用程序。这样的应用程序对恶意代码来说是罕见的,对非恶意代码来说几乎是不存在的,所以一个原生应用程序很可能就是恶意的。在PE头中的子系统指明了一个程序是不是原生应用程序。

文章作者: kabeor
文章链接: https://kabeor.github.io/%E7%AC%AC7%E7%AB%A0%20%E5%88%86%E6%9E%90%E6%81%B6%E6%84%8FWindows%E7%A8%8B%E5%BA%8F/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 K's House

评论