• 我们在哪一颗星上见过 ,以至如此相互思念 ;我们在哪一颗星上相互思念过,以至如此相互深爱
  • 我们在哪一颗星上分别 ,以至如此相互辉映 ;我们在哪一颗星上入睡 ,以至如此唤醒黎明
  • 认识世界 克服困难 洞悉所有 贴近生活 寻找珍爱 感受彼此

恶意代码分析实战:第七章 分析恶意Windows程序

恶意代码分析实战 云涯 4年前 (2020-07-22) 2096次浏览

多数恶意代码以Windows平台为目标,并且与操作系统进行紧密交互。

1 Windows API

恶意代码调用适当windows api即可完成恶意功能。

1.1 类型和匈牙利表达法

Windows总体上使用匈牙利表达法,作为API的函数标识符。这个表达式使用前缀命名模式。比如virtualAllocEx函数的第三个参数是dwSize,这是一个DWORD类型。

images

1.2 句柄

句柄是在操作系统中被打开或者被创建的项,比如窗口,进程,模块,菜单,文件等等。

1.3 文件系统函数

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

微软提供的创建文件函数:

CreateFile 创建文件

ReadFile | WriteFile 读和写

CreateFileMapping | MapViewOfFile 文件映射;CreateFileMapping从磁盘加载一个文件到内存中,MapViewOfFile函数则返回一个指向映射的基地址指针,可以用来访问内存中的文件。

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

1.4 特殊文件

Windows系统中有一些特殊的文件类型,它们的访问方式与普通文件不太一样,但是它们不能使用盘符与文件夹格式进行访问如c:\docs,恶意程序经常使用特殊文件。

有些特殊文件比普通文件更隐蔽,因为它们在列目录的时候不会显示出来,某些特殊文件可以提供对系统硬件和内部数据更强的访问能力。

特殊文件可以作为字符串参数传递到任何文件操作函数中,并像普通文件一样进行操作。

14.1 共享文件

共享文件可以通过名字命名空间访问的文件和备用数据流

共享文件是以\\serverName\share或者\\?serverName\Share开头命名的特殊文件。他们用来访问保存在共享目录中的目录或者文件。

2 windows注册表

用来保存操作系统与程序的配置信息,比如设置和选项。恶意代码经常使用注册表来实现持久驻留和保存配置数据。

根键:注册表被划分为5个根键。

两个最常用的键是HKLM和HKCU。一些键实际上是虚拟键值,提供一种引用底层注册表信息的方式。比如HKEY_CURRENT_USER键实际上存储在HEKY_USERS\SID 中,这里的SID是当前登录用户的安全描述符。

子键:就是一个文件夹中的子文件夹。

:一个键是注册表文件中的文件夹。根键和子键都是键。

值项:是一个配对的名字和值。

值或数据:存储在注册表中的数据

2.1 常用注册表函数

RegOpenkeyEx 打开一个注册表进行编辑和查询

RegSetValueEx 添加一个新值到注册表中,并设置它的数值

RegGetValue 返回注册表中的一个值项或数值。

2.2 使用.reg注册表脚本

使用.reg后缀的文件,即可自动合并文件包含的信息到注册表中,类似于修改注册表脚本。

3 网络API

3.1 伯克利兼容套接字

伯克利兼容套接字的网络功能在windows上是通过winsock库实现的,主要在ws2_32.dll中。

 

 

3.2 网络的服务器和客户端

在客户端调用中,会看到socket,然后紧跟着一个connect调用,后面还可能跟着send和recv调用。一个服务应用就是socket,bind,listen,accept函数陆续被调用。

3.3 WinINet API

更高一级的windows API,它被保存在wininet.dll中。

winine它实现了应用层协议,列入HTTP和FTP。

4 跟踪恶意代码的运行

4.1 DLL 动态链接库

恶意代码如何使用DLL:

保存恶意代码

将恶意代码保存在DLL中

通过使用Windows DLL

使用系统DLL导出函数完成恶意功能

通过使用第三方DLL

可以使用火狐DLL链接一个服务器,而不是使用系统DLL。

4.2 进程

同一个内存地址,如00400000,但是物理地址是不同的。例如一个进程在00400000位置保存了一些东西,另一个进程也在那个地址保存了一些东西,但是这些进程不会冲突,这些内存地址是相同的,但是物理地址是不同的。

内存地址只有上下文环境才有意义,例如寄信,知道主大街202号是无法确定一个具体地址,除非有邮政编码等。

地址00400000也没有告诉数据是保存在哪的,除非告诉是哪个进程。

创建一个进程

恶意代码通常是用CreateProcess来创建一个简单的远程shell。CreateProcess函数的一个参数,STARTUPINFO包含一个进程的标准输入,标准输出以及标准错误流句柄。恶意代码可以设置这些值为套接字,这样当这个程序写入标准输出时,它实际上会写入到套接字上。因而允许一个攻击者执行远程shell。

 

代码第一行,SocketHandle被保存在EAX中,这个进程的lpstartupinfo保存标准输入①,标准输出②以及标准错误③,它们会被新进程使用。④处保存这个要执行的命令,被在⑤处作为参数压入栈。

CreateProcess会创建一个新进程,以便所有输入和输出都被重定向到一个套接字。

 

4.3 线程

线程上下文

当一个线程运行时,它对CPU或CPU核有着完全控制,并且其他线程不能影响CPU或核的状态。

当一个线程改变CPU中某个寄存器值时,它不会影响其他线程。

一个操作系统在线程间切换时,在CPU中所有值会被保存到一个线程上下文的结构体中。然后这个操作系统会加载这个线程上下文到一个新的线程中,并使这个新线程在CPU中执行。

 

在①处的代码访问一个局部变量,并且在EDX中保存,然后将EDX压入栈。如果另一个线程刚好要在这两条代码之间运行一些代码,并且修改EDX寄存器,EDX值就会是错误的,代码也不会执行。

当线程上下文被切换时,如果另外一个线程在这两条指令之间执行,EDX值会被保存到线程上下文中。当这个线程再次执行push时,线程上下文就会被恢复,EDX将再次保存正确的值。通过这种方式,没有线程能够干扰其他线程的寄存器标志。

创建一个线程

CreateThread函数创建一个新线程。还可以通过多种方式来使用CreateThread。

恶意代码可以使用CreateThread来加载一个新的恶意库到进程中。通过调用CreateThread时将起始地址设置为LoadLibrary地址。传递给CreateThread参数是要被加载库的名字,新的DLL被加载到这个进程内存中中,然后DLLMain调用。

恶意代码可以为输入输出创建两个线程,一个用来在套接字或管道上监听,并输出到一个进程的标准输入里。另一个从标准输出读取数据,并发送到套接字或者管道上。恶意代码的目的是发送所有信息到单一套接字或者管道,来和运行的程序进行无缝通信。

 

创建两个线程,第一个线程函数ThreadFunction1,创建一个循环调用ReadFile,从一个管道中读取数据,然后它将数据通过send转发到另一个套接字。第二个函数执行一个循环调用Recv函数,读取网络中发送的任何数据,然后将这些数据通过writeFile函数转发到一个管道,这样就可以被应用程序读取了。

4.4 使用互斥量的进程间协作

互斥量在内核中叫互斥门。互斥量是全局对象,用于协调多个进程和线程。

互斥量主要用于控制共享资源的访问。例如两个线程必须访问同一个内存结构,但是一次只能有一个线程访问,互斥量就是用来控制这种共享访问。

同一时刻,一个线程拥有一个互斥量。互斥量通过CreateMutex函数创建。

进程通过OpenMutex调用来获取另一个进程中互斥量的句柄。

4.5 服务

OpenSCManger 返回一个服务控制管理器的句柄,它被用来进行所有与服务相关的函数调用。所有要和服务交互的代码都会调用这个函数。

CreateService 添加一个新服务到服务控制管理器,并且允许调用者指定服务是否在引导时自动启动,或者必须手动启动。

StartService 启动一个服务,并且仅在服务手动启动时使用。

Windows支持多种服务类型。恶意代码最常用的是WIN32_SHARE_PROCESS 类型,这种类型将这个服务的代码保存在一个DLL中,并且在一个共享的进程中组合多个不同的服务。在任务管理器中能找到svchost.exe,他们在运行WIN32_SHARE_PROCESS 类型的服务。

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

4.6 组件对象模型

基本定义

微软组件对象模型(COM)定义了用于创建在运行时交互的可重用的软件库的二进制互操作性标准。您可以使用COM库,而不汇编成应用程序的需求。COM是许多微软产品和技术,如Windows媒体播放器和Windows Server的基础。

在COM规范下将能够以高度灵活的编程手段来开发、维护应用程序。可以将一个单独的复杂程序划分为多个独立的模块进行开发,这里的每一个独立模块都是一个自给自足的组件,可以采取不同的开发语言去设计每一个组件。在运行时将这些组件通过接口组装起来以形成所需要的应用程序。构成应用程序的每一个组件都可以在不影响其他组件的前提下被升级。这里所说的组件是特指在二进制级别上进行集成和重用而能够被独立生产获得和配置的软件单元

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

 

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

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

CoCreateInstance函数用来获取对OCM功能的访问。

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

一个恶意代码例子,它使用了被Internet Explorer实现的IWebBrowser2接口的Navigate函数。恶意代码首先调用CoCreateInstance函数,这个函数接收恶意代码正在请求对象的CLSID和IID,然后操作系统查找这个类信息,并在这个程序没有在运行时,加载执行这个功能的程序。CoCreateInstance函数返回一个接口指针,执行包含函数指针的结构体。要使用这个COM服务器功能,恶意代码需要调用一个函数,这个函数的指针被保存在从CoCreateInstance返回的结构体中。

①处保存IID为D30C1661-CDAF-11D0-8A3E-00C04FC9E26E,表示IWebBrowser2接口,②处保存CLSID为0002DF01-0000-0000-C000-000000000046表示Internet Explorer。

当一个程序调用CoCreateInstance时,操作系统使用注册表中的信息,来判断哪个文件包含被请求的COM代码

一旦结构体从CoCreateInstance函数返回调用,COM客户端调用位于这个结构体中某个偏移处的一个函数。

这个COM对象保存在栈上,然后被移到EAX中。然后这个结构体的第一个值指向一个函数的指针表。这个表中偏移0x2c处是Navigate函数。

COM服务器恶意代码

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

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

 

4.7 异常

异常机制允许一个程序在普通执行流程之外处理时间。

结构化异常处理(SEH)是windows的一个处理机制。SEH信息被保存在栈上。

一个异常处理帧在①处被放到栈上。这个特殊位置fs:0指向栈上保存着异常信息的一个地址。在栈上的一个异常处理帧的位置,同时也包括被调用者在②处使用的异常处理器,它在函数的末尾被恢复。

当一个异常发生时,Windows查看fs:0来寻找保存异常信息的栈位置,然后这个异常处理器被调用。这个异常被处理后,执行返回到主线程。

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

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

5 内核与用户模式

操作系统和硬件驱动运行在内核模式。

在用户模式,每一个进程都有它自己的内存,安全权限,以及资源。通常,用户模式不能直接访问硬件,并且它被限制 只能访问CPU所有寄存器和可用指令的一个子集。为了在用户模式中操作硬件或改变内核中的状态,必须依赖Windows API。

当调用API函数操作内核结构体时,它会通过一个调用进入内核。在反汇编中,SYSENTER、SYSCALL或者INT 02E存在,指明一个调用被使用进入到内核。直接通过跳转从用户模式到内核模式是不可能的。

6 原生API

原生API是用来和windows进行交互的底层API,他们很少被非恶意程序使用。

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

ntdll函数像内核中的函数一样,使用API和结构体。这些函数组成原始API。直接调用原始API很隐蔽。

例如,这个恶意代码调用NtReadFile和NtWriteFile函数,而不是windows的ReadFile和WriteFile函数。这些函数在ntdll.dll中。

7 实验

Lab 7-1

首先要检查导入表,将精力集中在最有意思的函数上

从main进入到401040

检查/创建互斥体”HGL345“

打开服务控制管理器,创建服务”Malservice“

创建systemTime结构体,年份定位为2100年,随后调用WaitForSingleObject等待直到2100年。

20次循环,创建20个线程

每个线程死循环,使用”Internet Explorer 8.0″访问”http://www.malwareanalysisbook.com“进行DDOS攻击

总结:通常,一个服务必须实现用来停止或暂停的函数,并且它必须能够改变它的状态,来使用户和操作系统知道这个服务已经启动,但是这个恶意代码并没有做这些,它的服务状态总是为start_pending,并且这个服务无法停止。恶意代码经常实现足够的功能,来达到作者目标,而不会去考虑规范要求的整个功能。

 

Lab 7-2

检查导入函数发现

SysFreeString

SysAllocString

VariantInit

CoCreateInstance

OleInitialize

OleUninitialize

这些函数都是与COM有关的。

查看riid, 和rclsid, 分别是D30C1661-CDAF-11D0-8A3E-00C04FC9E26E和0002DF01-0000-0000-C000-000000000046

riid对应是IwebBrowser,clsid对应Internet Exploer, 通过CoCreateInstance返回COM对象.

 

之后被偏移量使用, Insert键添加一个结构,导入IWebBrowser2Vtbl结构体,即可解析

在IDA中无法识别的函数,一个策略是检查头文件以寻找在调用CoCreateInstance时的指定接口。这些头文件在微软VS和SDK中。例如Navigate函数实在.h文件中第十二个函数,对应偏移为0x2C处。

Navigate会允许程序启动浏览器.

Lab 7-3

有两个程序,分别是DLL和exe

分析EXE

alloca_probe 分配一个较大的栈空间

send函数发送”hello”

必须要判断程序会在回复中做什么!  recv接收C2指令

调用strncmp,判断前五个字符是不是sleep

这段代码检查缓冲区是不是以exec开始的,如果是,strncmp将返回0.然后调用createprocess函数

CreateProcess函数需要关注commandLine函数

CommandLine早些时候被保存在栈上,我们需要判断是在哪里,

我们并没有在任何地方写入commandline

DLLmain开头告诉我们,我们接收的缓冲区是在0x1000开始的,并且这个值使用lea指令来设置的,如lea edx, [esp+1208h+buf],数据本身是保存在栈上的,并且不仅仅是一个指向数据的指针。同样,0x0FFB使我们的接收缓冲区5个字节的事实告诉我们这个要被执行的命令是我们接收缓冲区中保存的任意5字节的东西。

因此接收的数据是 exec 指令

为什么0xFFB是buf的一部分?

buf是1000,而且是个栈,栈是从大地址到小地址递增,就会递增到FFB。因此buf包含0xFFB。

分析exe

kernel32.exe在不同系统上是不同的,因此这个程序是量身定制的。

检查是否是两个参数

打开两个文件,分别是C:\Windows\System32\Kernel32.dll和Lab07-03.dll,然后映射到内存中

通过将kernel32.dll的导出表复制到lab07-03.dll中,作为重定向到处函数。

 

然后修改名字为kerne1.exe,再然后遍历C盘,把调用kernel.dll的exe修改,修改为调用kerne1.exe,这样就实现了永久驻留!


云涯历险记 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:恶意代码分析实战:第七章 分析恶意Windows程序
喜欢 (0)