-
-
[原创]默默无闻·恶意代码分析第七章
-
2021-4-7 20:34 7065
-
系列往期:
工具说明
参照书籍:《恶意代码分析实战》;
系统环境:Windows7 x86
使用工具:WinHex、CFF、StudyPE+、Exeinfo PE、Resource Hacker、Depends Walker、OD、IDA、MSDN;
摘要
这一章节还是比较重要的,总结了在逆向Windows恶意代码中可能遇到的形式;
所以此贴以知识点总结为主;
知识点总结
1、Windows API常见类型
类型和前缀 | 描述 |
---|---|
WORD(w) | 一个16位的无符号数值 |
DWORD(dw) | 一个双字节、32位的无符号数值 |
Handles(H) | 一个对象索引,只能被Windows API操作 |
Long Pointer(L) | 指向另一类型的指针,如LPCSTR是一个指向字符串的指针 |
Callback | 一个将会被Windows API调用的回调函数; |
2、句柄
多说无益,首先要了解的是:
1、每个进程内核有句柄表;
2、系统有一个全局句柄表;
3、而句柄就是指向这些表的指针,所以句柄是有两种情况的;
参考书籍《Windows 核心编程》
3、文件系统
根据《恶意代码分析实战》中列出的函数,更多的在之前和以后的帖子中,或者查询MSDN(最有效,,最详细)
函数 | 描述 |
---|---|
CreateFile | 用于创建和打开文件;可以打开已经存在的文件,管道,流,以及I/O设备,还能创建新的文件; |
ReadFile和WriteFile | 用来对文件的读和写,都将文件作为流来操作; |
CreateFileMapping和MapViewOfFile | 允许一个文件加载到内存中,以便更容易的进行操作;<br />CreteFileMapping函数负责从磁盘上加载一个文件到内存中;<br />MapViewOfFile函数则返回一个指向映射的基地址指针; |
这些函数都是常被恶意代码用到的函数,比如可以模拟系统加载PE,写成一个加载器;
4、特殊文件
Windows系统中的特殊文件类型,这些文件在目录中不会显示出来,某些特殊文件可以提供对系统硬件和内部数据更强的访问能力;
书中介绍了三种类型,分别是
共享文件
、通过名字空间访问的文件
、备用数据流
;
4.1、共享文件
共享文件以\\serverName\share
或\\?\serverName\share
开头命名的特殊文件;
- 它们用于访问保存在共享目录中的目录或文件;
\\?\
前缀告诉操作系统禁用所有的字符串解析,并允许访问文件名;
4.2、通过名字空间访问的文件
名字空间可以被认为是固定数目的文件夹,每一个文件夹中保存不同类型的对象;
底层的名字空间是NT名字空间,以前缀\
开始。
NT名字空间可以访问所有设备,以及所有在NT名字空间中存在的其它名字空间;
使用WinObj对象管理器名字空间查看器
查看名字空间;
\\.\
开始的Win32设备名字空间,经常被Malware用来直接访问物理设备;- 例如,使用
\\.\PhysicalDisk1
来直接访问PhysicalDisk1,而忽略它的文件系统,直接读写数据到一个未分配的扇区,以避开病毒和安全程序检查; - 也可以使用命名空间
\Device\PhysicalMemory
直接访问物理内存,允许用户空间程序写到内核空间;
- 例如,使用
从Windows 2003 SP1开始,Device\PhysicalMemory
从用户空间已经无法访问。但是可以从内核空间访问。由此可见,反病毒要把内核学好才行。
4.3、备用数据流
备用数据流(ADS)特性允许附加数据被添加到一个已存在 的MTFS文件;
就是添加一个文件到另外一个文件中,额外数据在列一个目录时不会被显示出来,只有在访问流时,它才可见;
- ADS根据约定
normalFile.txt::Stream:$DATA
来命名,它允许要给程序去读写一个流;
Malware作者喜欢ADS,因为它能被用来隐藏数据;
5、Windows 注册表
注册表用来保存操作系统与程序的配置信息,例如设置和选项;
恶意代码经常使用注册表来完成持久驻留或者存储配置数据。
注册表术语:
名称 | 描述 |
---|---|
根键 | 注册表被划分为称为根键的5个顶层节。<br />每一个根键有一个特定的目的。 |
子键 | 就像是一个文件夹中的子文件夹 |
键 | 一个键是一个注册表中的文件夹,它可以包含额外的文件夹或键值。<br />根键和子键都是键; |
值项 | 一个值项是一个配对的名字和值; |
值或数据 | 值或数据是存储在注册表项中的数据; |
5.1、注册表根键
注册表根键 | 描述 |
---|---|
HKEY_LOCAL_MACHINE(HKLM) | 保存对本地机器全局设置; |
HKEY_CURRENT_USER(HKCU) | 保存当前用户特定的设置; |
HKEY_CLASSES_ROOT | 保存定义的类型信息; |
HKEY_CURRENT_CONTIG | 保存关于当前硬件配置的设置,特别是与当前和标准配置直接不同的部分; |
HKEY_USERS | 定义默认用户、新用户和当前用户的配置; |
两个最常用的根键是HKLM
和HKCU
(这些键通常通过它们的缩写来引用)。
一些键实际是虚拟键值,提供一种引用底层注册表信息的方式:
例如,HKEY_CURRENT_USER
键实际上存储在HKEY_USERS\SID
中,这里的SID
是当前登陆用户的安全描述符;
例如,一个常用的子健,HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
,包含一系列值,这些值列举了当一个用户登陆时被自动启动的可执行程序。
根键是HKEY_LOCAL_MACHINE
,它保存了SOFTWARE
、Microsoft
、Windows
、CurrentVersion
以及Run
子键;
5.2、Regedit
- 注册表编辑器(Regedit);
快捷键win+r
,然后在运行窗口输入regedit
,打开注册表编辑器;
5.2.1、自启动程序查找
- 使用
regedit.exe
打开;
根据选框中的路径查找(这是用windows10本机查找的);
5.2.2、常用注册表函数
函数名 | 描述 |
---|---|
RegOpenKeyEx | 打开一个注册表进行编辑和查询;<br />有些函数允许直接查询或编辑,但是大部分还是要用到这个函数; |
RegSetValueEx | 添加一个新值到注册表,并设置数值; |
RegGetValue | 返回注册表中的一个值项的数值; |
如果在实战中遇到Malware访问一些键值时,可以通过搜索引擎寻找相关信息;
5.2.4、使用.reg文件的注册表脚本
用.reg
作为扩展的文件包含一个人类可读的注册表数据;
运行.reg
文件,它会自动通过合并文件包含的信息到注册表中,来修改注册表;(几乎像一个脚本)
实例:
1 2 3 4 5 | Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run] "MaliciousValue" = "\"C:\\Windows\\evil.exe\"" |
这是Microsoft官网的教程:如何使用 .reg 文件添加、修改或删除注册表子项和值
6、网络API
这是Malware经常用到的东西了;
6.1、伯克利兼容套接字
最常使用的套接字,因为其功能在Windows和UNIX系统上是几乎一样的;
Windows系统中是由Winsock
库实现的,主要在ws2_32.dll
中,下表给出了对一些常用函数的描述;
函数 | 描述 |
---|---|
socket | 创建一个套接字; |
bind | 将一个套接字绑定到特定端口,应该在accept之前调用; |
listen | 预示着一个套接字将进入监听,等待入站连接; |
accept | 向一个远程套接字打开一个连接,并接受连接; |
connect | 向一个远程套接字打开要给连接,远程套接字必须在等待连接; |
recv | 从远程套接字接受数据; |
send | 发送数据到远程套接字; |
WSAStartup
函数必须要在其它网络函数之前被调用,以便为这些网络库分配资源;
当调试代码查找网络连接入口时,在WSAStartup
函数中设置一个断点,是非常有效的方式;
6.2、网络的服务器和客户端
一个网络程序通常有两个端点:
服务端:它维护一个打开套接字并等待入站连接;
客户端:它连接到一个正在等待的套接字;
恶意代码可以是任意一个!!!
6.3、WinINet API
除了Winsock API
以外,还有一个叫WinInetAPI
的更高级的Windows API
(这里的高级是指ISO/OSI网络体系结构和TCP/IP协议模型中的相对位置);
WinInet API
实现了应用层协议,如HTTP和FTP,通过判断打开的是何种连接,来理解它的功能;
函数 | 描述 |
---|---|
InternetOpen | 用来初始化一个到互联网的连接; |
InternetOpenUrl | 用来访问一个url(它可以是一个HTTP页面或是一个FTP资源); |
InternetReadFile | 和ReadFile函数工作原理相似,允许程序从一个互联网下载的文件中读取数据; |
7、跟踪恶意代码的运行
7.1、DLL
DLL本身是不能运行的,但是它可以导出函数,给其它应用程序调用;
DLL的基本结构:
1、dll和exe文件都使用PE文件格式,有一个专门字段(File_Header.Characteristics)描述具体是哪个文件;
2、通常DLL导出函数,导出函数多,导入函数少;
3、DLL的主函数是DllMain,它并不是导出函数,只是在PE中被描述为入口点。
4、任何时候一个进程加载或卸载库,会创建一个新进程,或是一个已存在的线程结束时,DllMain函数会被调用来通知DLL,这个通知允许DLL来管理每个进程或线程的资源;
5、多数DLL没有线程粒度的资源,并且它们忽略由线程活动引起DllMain的调用;
5.1、如果DLL必须在线程粒度进行管理的资源,那么这些资源可以为分析师提供一些提示,来了解这个DLL目的;
恶意代码是如何使用DLL的呢?
- 保存恶意代码
将Malware保存到一个DLL文件,然后附加到其它进程;
- 通过使用Windows DLL
任何程序都能使用系统上的Windows基础DLL程序,通过查看Malware的导入表中的Windows API,可以猜测实现了什么功能;
- 通过使用第三方DLL
除了系统的基础DLL,还可以使用第三方的DLL;
例如:
使用Mozilla Firefox DLL来连接服务器;
自定义的DLL和Malware(exe文件)一同发布;
7.2、进程
恶意代码可以通过创建一个新进程,或修改一个已存在的进程,来执行当前程序之外的代码;
传统上,恶意代码包括它自己的独立进程(独立恶意行为程序);
现在更普遍的是将自身代码作为其它进程的一部分执行(导入表注入之类的操作);
这就好比,前者是一匹狼,后者是个披着羊皮的狼;
创建进程函数CreateProcess
,MSDN中描述:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 函数的作用是:创建一个新进程及其主线程。新进程执行指定的可执行文件。 BOOL CreateProcess( LPCTSTR lpApplicationName, / / pointer to name of executable module LPTSTR lpCommandLine, / / pointer to command line string LPSECURITY_ATTRIBUTES lpProcessAttributes, / / process security attributes LPSECURITY_ATTRIBUTES lpThreadAttributes, / / thread security attributes BOOL bInheritHandles, / / handle inheritance flag DWORD dwCreationFlags, / / creation flags LPVOID lpEnvironment, / / pointer to new environment block LPCTSTR lpCurrentDirectory, / / pointer to current directory name LPSTARTUPINFO lpStartupInfo, / / pointer to STARTUPINFO LPPROCESS_INFORMATION lpProcessInformation / / pointer to PROCESS_INFORMATION ); |
7.3、线程
这里我们学习操作系统的时候描述的更清楚,这里就简单描述;
一个进程中有多个线程;
进程是执行代码的容器,线程才是Windows操作系统真正要执行的内容;
相同进程中的不同线程共享内存空间,但是每个线程有独立的寄存器和栈,独享处理器;
线程上下文跳转
当一个线程运行时,它独享CPU,并且其它线程不能影响CPU或核的状态;
当一个线程改变CPU中某个寄存器值时,它不会影响其它线程;
操作系统在线程间切换之前,CPU中所有值都被保存在一个叫做线程上下文
的结构体中;
操作系统加载新线程的线程上下文
结构体,使这个线程在CPU中执行;
创建进程函数CreateThread
,MSDN中描述:
1 2 3 4 5 6 7 8 9 10 | CreateThread函数创建一个线程,在调用进程的地址空间中执行。 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, / / pointer to security attributes DWORD dwStackSize, / / initial thread stack size LPTHREAD_START_ROUTINE lpStartAddress, / / pointer to thread function LPVOID lpParameter, / / argument for new thread DWORD dwCreationFlags, / / creation flags LPDWORD lpThreadId / / pointer to receive thread ID ); |
微软还使用了纤程。纤程是被线程管理的;
可以简单的理解成:进程包含线程,线程包含纤程;
7.4、使用互斥量的进程间协作
一个和线程与进程相关的话题是互斥量
(mutex);
内核中称为互斥门
(mutant);
互斥量是全局对象,用于协调多个进程和线程;
同一时刻只有一个线程拥有一个互斥量,通过互斥量来控制共享资源的访问;
更多细节查看多线程编程相关书籍;
7.5、服务
恶意代码执行附加代码的另一种方式是将它作为服务安装。
Windows允许通过使用服务,来使任务作为后台应用程序运行,而不需要它们自己的进程或线程,代码被Windows服务管理器 调度和运行,但没有用户输入;
在Windows操作系统上的任何指定实践,都会有多个服务在运行;
服务是提供系统上持久化驻留的另外一种方式,因为它可以被设置成当操作系统启动时自动运行,甚至不会在任务管理器中作为进程显示出来;net start
命令可以列举处正在运行的服务。还可以通过Autoruns
等工具查看更多细节
- 操作服务的Windows API:
API | 描述 |
---|---|
OpenSCManager | 返回一个服务控制管理器的句柄,它被用来进行所有后续与服务相关的函数调用。所有要和服务交互的代码都会调用这个函数; |
CreateService | 添加一个新服务到服务控制管理器,并且允许调用者指定服务是否在引导时自动启动,或者必须手动启动; |
StartService | 启动一个服务,并且仅在服务被设置成手动启动时使用; |
Windows操作系统支持多种服务类型,它们以独特的方式执行;
恶意代码最常用的是
WIN32_SHARE_PROCESS
类型,这种类型将这个服务的代码保存在一个DLL中,并且在一个共享的进程中组合多个不同的服务。
在任务管理器中,一个名叫svchost.exe
进程的多个实例,它们在运行WIN32_SHARE_PROCESS
类型的服务;WIN32_OWN_PROCESS
类型有时也被使用,因为它在一个.exe
文件中保存代码,而且作为一个独立进程运行;- 还有一个常见的服务类型是
KERNEL_DRIVER
,它被用来加载代码到内核中执行;
本地系统上服务的信息被保存在注册表中,每个服务在HKLM\SYSTEM\CurrentControset\Service
下面有一个子键;
这里要说明的我是用Win10找的,用Win7效果一样;
- 利用
SC
程序是Windows命令行工具,可以用来查询和操作服务,包含了添加
、删除
、启动
、停止
以及查询
服务的命令;
7.6、组件对象模型
微软组建对象模型(COM)是一个接口标准;
它使不同软件组件在不知道其它组件代码的接口规范时,互相之间可以进行调用;
分析使用COM的代码时,需要判断哪段代码会被作为一个COM函数进行调用运行;
更多细节请看《COM原理与应用》,这里只做一些总结;
- COM被实现成一个C/S框架,客户端是那些使用COM对象的程序,服务器是那些可复用的软件组件–也就是COM对象本身。
- 每一个使用COM的线程,必须在调用任何其它COM库函数之前,至少调用一次
OleInitialize
函数或CoInitializeEx
函数;所以可以搜索这些调用判断是否使用了COM功能;
CLSID、IID以及COM对象的使用
COM对象通过它们的全局唯一标识符(GUID),分为类型标识符(CLSID)以及接口标识符(IID),来进行访问。
CoCreateInstance
函数被用来获取对COM功能的访问;Navigate
函数允许一个程序启动Internet Explorer,并访问一个Web地址;Navigate
函数是IWebBrowser2
组件接口的一部分,这个接口指定一个必须被实现的函数列表,但是它没有指定哪个程序会提供这个功能;- 提供这个功能的程序就是实现了
IWebBrowser2
接口的COM类;
- 提供这个功能的程序就是实现了
- 例如
IWebBrowser2
接口被Internet Explorer
实现。接口通过一个叫做IID
的GUID
来标识,而COM类通过一个叫做CLSID
的GUID
来标识;
当一个程序调用
CoCreateInstance
函数时,操作系统使用注册表中的信息,来判断哪个文件包含被请求的COM代码;HKLM\SOFTWARE\Classes\CLSID
和HKCU\SOFTWARE\Classes\CLSID
注册表键存储了关于那些代码执行这个COM服务器的信息;
对于在实战逆向中IDA Pro
无法识别的COM组件函数,一个策略是检查头文件,以寻找在调用CoCreateInstance
时指定的接口;这些文件包含在微软Visual Studio和平台SDK中,并且都能在互联网上找到;
例如:Navigate
函数是在.h
文件中的第12个函数,它对应的偏移0x2C
处,所以在调用时就是+0x2C
的偏移;
有些COM对象会被作为DLL实现,它们被加载到到COM客户端可执行文件的进程空间中;
当这个COM对象被安装成DLL加载时,CLSID的注册表项会包含子键InprocServer32
,而不是LocalServer32
;
COM服务器恶意代码
Malware实现了一个恶意COM服务器,使其被其它应用使用;
对于Malware来说,常用的COM服务器功能是通过浏览器帮助对象(BHO),这是Internet Explorer
的第三方插件。
BHO没有限制,所以恶意代码作者使用它们在Internet Explorer
进程中运行代码,这允许它们监控互联网流量、跟踪浏览器的使用,以及与互联网通信,而且并不使用它们自己的进程;
一个实现COM服务器的Malware通常很容易检测,因为导出了几个函数,包括DllCanUnloadNow
、DllGetClassObject
、DllInstall
、DllRegisterServer
,以及DllUnregisterServer
,它们都必须由COM服务器软件导出;
7.6、异常:当事情出错时
异常机制允许一个程序在普通执行流程之外处理事件;
结构化异常处理(SEH)是Windows的异常处理机制。SEH是一个重要知识点,后续单独开一帖再补充吧
7.7、内核模式与用户模式
这个也是老生常谈的一个点了,内核模式(R0),用户模式(R3);
当一个Windows API函数操作内核结构体时,它会通过一个调用进入内核。在反汇编中SYSENTER
、SYSCALL
或者INT 0x2E
的存在,指明一个调用被使用进入到内核;
直接通过跳转从用户模式到内核模式是不可能的,这些指令使用查找表来定位一个预定义函数,从而在内核中执行代码;
所有运行在内核的进程共享资源和内存地址。内核模式代码有更少的安全检查。如果内核运行的代码执行并且包含无效指令,操作系统就不能继续运行,产生结果就是Windows蓝屏;
- 运行在内核中的代码可以操纵运行在用户空间的代码,但是运行在用户空间的代码只能通过定义好的接口来影响内核。
- 即使所有运行在内核的代码共享内存和资源,处于活跃状态的进程上下文也总是只有一个;
更多的内容,将后续单独开帖再做补充;
7.8、原生API
原生API是用来和Windows进行交互的底层API,调用原生API函数可以绕过普通的Windows API;
这里的原始API指的是最接近内核的API接口,书中指的是Ntdll.dll
;
在ntdll.dll
的导出表中,同样的函数会同时有Nt
前缀或Zw
前缀;
关于Ntdll.dll
的更多信息,查询在线资源
总结
在分析Windows恶意程序时,Windows病毒的各种形式就显得尤为重要了;
光看书有点乏,就干脆写起了读书笔记,效果是好,就是进度有点慢了;
以上内容,欢迎大佬指点!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课