恶意代码分析实战笔记-1

 诸葛建伟老师翻译的书,泽华大佬推荐的,看里面有好多作业,感觉不错,拿来读读。

0x00 恶意代码分析技术入门

 被感染的主机可以用特征码来辨别是否被感染。特征码分为基于主机的特征码、基于网络的特征码。基于主机的特征码关注的是恶意代码对系统做了什么(例如创建或修改的文件,对注册表的特定修改),而不是恶意代码本身的特性(类似于病毒特征码)。晚会罗特征码是通过检测网络流量来检测恶意代码。

 恶意代码可以分为:

(1)后门:攻击者无需认证即可连接到远程计算机上,并可以在本地系统执行命令。

(2)僵尸网络:被同一个僵尸网络感染的计算机会听从一台服务器的控制。

(3)下载器:用于下载其它恶意代码。

(4)启动器:用于启动其他恶意程序的恶意代码。

(5)内核套件:用来隐藏其他恶意代码,使得代码很难被受害者发现。

(6)蠕虫:用于自我复制和感染其他计算机的恶意代码。

(7)勒索软件、间谍软件、发送垃圾邮件等。

 分析恶意代码的通用规则(感觉很有用):

(1)不要过于陷入细节,大多数恶意程序是复杂的,不可能了解每一个细节,要关注最关键的功能。

(2)不同的任务有不同的方法,没有通用的方法。

(3)不断学习新技术。

0x01 静态分析基础技术

 拿到一个恶意程序,步骤为:

(1)上传VirusTotal看看。

(2)计算哈希值,之后在网上搜一搜是否有这个恶意样本。

(3)使用Strings程序查看文件中的可打印字符串(ASCII与Unicode)。

 很多恶意程序都会加壳,这样的话其可打印字符串就会很少。加壳代码通常会包含LoadLibraryGetProcAddress函数,用于加载和使用其他函数。可以使用PEiD检测加壳。但是注意,很多PEiD会在没有警告的情况下运行恶意代码。

 一些知识点复习:

(1)导入函数:程序使用的但存储在另一程序中的函数。代码库通过链接方式被链接到主程序中,然后程序员将一些导入函数链接到他们编写的程序中。链接分为静态链接、动态链接、运行时链接,运行时链接在恶意代码中经常使用,此时,只有当需要使用函数时,才链接到库,而不是像动态链接那样在程序启动时链接。一些windows API允许程序员导入并没有在程序文件头中列出的链接函数,这些API有:LoadLibraryGetProcAddressLdrGetProcAddressLdrLoadDll。可以使用Dependency Walker观察动态链接函数,PE view可以查看PE头字段,Resource Hacker可以查看PE文件的.rsrc字段

 常见的dll都有:

DLL 描述
kernel32.dll 核心系统功能,比如访问与操作内存、文件与硬件。
advapi32.dll 对核心windows组件的访问,比如服务管理器与注册表。其中Software\Microsoft\Windows\CurrentVersion\Run注册表项表示windows启动时会自动装载哪些程序。
user32.dll 包含用户界面组件,如按钮、滚动条以及响应用户操作的组件。
gdi32.dll 图形显示与操作的函数。
ntdll.dll windows内核的接口,可执行文件一般不直接导入这个dll,而是由kernel32.dll间接导入。如果可执行文件导入了ntdll.dll,那么意味着程序员企图使用那些不是正常提供给windows使用的函数,例如隐藏功能与操作进程。
wsock32.dll和ws2_32.dll 联网dll。
wininet.dll 更高层次的网络函数,例如ftp、http等。

Ex后缀的函数名,例如CreateWindowEx等,这意味着:此函数曾经被更新过。

(2)导出函数:exe文件也可以有导出函数。

 PE文件格式:

节名 描述
.text 可执行的代码
.rdata 全局可访问的只读数据,可能包含导入导出函数信息
.data 程序的全局数据
.idata 可能包含导入函数信息
.edata 可能包含导出函数信息
.pdata 只在64位可执行文件中存在,存储异常处理信息
.rsrc 使用的资源,例如图标、图片、字符串等
.reloc 重定位库文件的信息

 windows不会关心实际的分节名称,因为使用了PE头中的其他信息来确定如何使用一个分节。因此,分节名称可以被更改。如果PE中节的虚拟大小大于五粒大小,那么就要怀疑是否为加壳代码。

实验1

 1. Lab01-01.exe报警:55/71,Lab01-01.dll报警:44/70。

 2. 编译时间右键查看属性即可。

 3. 查看PE节都正常,Lab01-01.exe/dll未有混淆迹象。

 4. 导入函数:CreateFileA|CopyFileA|FindNextFileA|_stricmp

 5. 有CopyFileA复制文件的导入函数。答案:检查C:\windows\system32\kerne132.dll用数字1代替了字母l,此文件可以用来在主机作为恶意代码感染的迹象进行搜索

 6. 没有网络迹象。答案:在dll中有一个私网ip地址127.26.152.13字符串。

 7. 可能是查找所有文件,并匹配字符串。答案:dll文件可能是一个后门,而exe文件是用来安装与运行此dll文件的。

实验2

 1. Lab01-02.exe报警:56/71。

 2. UPX节,有加壳。

 3. 去壳后,有导入函数CreateThread|WaiteForSingleObject|CreateServiceA|InternetOpenUrlA,猜测是在互联网下载程序并运行。

 4. 可能是在http://www.malwareanalysisbook.com中下载的程序。答案:通过名为Malservice的服务,并通过到http://www.malwareanalysisbook.com的网络流量,来检查被恶意代码感染的主机。

实验3

 1. Lab01-03.exe报警:61/71。

 2. 无节名,且虚拟大小远大于存储大小,猜测有加壳或混淆。使用FSG脱壳,也就是x64dbg手脱一下。

 3. 去壳后,有导入函数OleInitialize|CoCreateInstance|VariantInit等,这些有的函数是来自于ole32.dll,此dll可以实现基于 RPC的远程对象通信机制,用于在网络上进行对象的创建和调用。

 4. 有一些导入库,例如ole32.dlloleaut32.dll,这能够创建COM组件,实现RPC远程对象通信等。

实验4

 1. Lab01-04.exe报警:59/71。

 2. 没有加壳迹象。

 3. 2019-08-31 06:26:59编译的。

 4. 有导入函数LoadLibraryA|WinExec等,猜测此运行了某些命令或程序。答案:此程序会写文件到磁盘上,然后执行它。

 5. 无通信迹象。答案:字符串\system32\wupdmgr.exe表示,此程序会在这个位置创建或修改文件,而另一个URL字符串表示会在此URL上下载文件。

 6. 发现有一个GUI32程序,猜测 Lab01-04.exe会调用这个GUI32程序。

0x02 在虚拟机中分析恶意代码

 主机模式逻辑结构:

image-20230924233603889

 桥接模式:允许虚拟机和物理机器一样连接到相同的物理网卡上。

0x03 动态分析基础技术

 沙箱:Norman沙箱、GFI沙箱。

 沙箱的缺点:只能简单的运行程序,不能带有命令行选项;可能无法记录所有事件,因为有的恶意代码等待一天后才执行;等等。

如何运行一个dll文件?

方法1:rundll32.exe

1
2
rundll32.exe dll_name, func_name
rundll32.exe dll_name, 函数序号(例如 #5)

方法2:修改PE头

 在IMAGE_FILE_HEADER的特征域中擦除IMAGE_FILE_DLL(0x200)标记,这样的话点击就会调用DLLMain函数。

方法3:安装为服务的dll

 有的dll会被安装为服务,例如它导出了InstallService函数,那么就可以运行:

1
2
3
rundll32.exe dll_name, InstallService service_name
// windows 中启动服务
net start service_name

 如果没有InstallService,而是有ServiceMain函数,那么就要修改注册表为一个未使用服务进行手动安装,注册表为HKLM\SYSTEM\CurrentControlSet\Services,之后使用net start启动此服务。

进程监视器 Process Monitor

 windows中的进程监视器可以进行文件监视与注册表监视。但是由于它能监视几乎所有的系统调用,且会保存在内存中,因此可能耗尽内存而使得机器崩溃,因此需要限定捕获时间。监视器的过滤操作详见P41。Process Monitor默认排除procmon.exe与pagefile的日志记录,因为他们经常被访问且没有提供有用的信息。

 如果恶意代码是开机启动的,那么需要将进程监视器安装为自启动驱动,从而捕获恶意代码的启动事件。

进程浏览器 Process Explorer

 列出所有进程,进程载入的dll,各种进程属性等。默认服务为粉色,进程为蓝色,新进程为绿色,被终止进程为红色。

 进程浏览器可以验证磁盘上的镜像文件是否有微软的签名,具体在双击进程->Image->Verify。此过程验证的是磁盘上的镜像文件,而不是内存中的,所以攻击者如果使用进程替换技术,此功能就检查不出来了(类似于内存马)。

 还可以查看进程中的字符串来识别是否有进程替换(即磁盘文件与内存中文件是否相同),具体在双击进程->Strings->Image/Memory

 还可以Find->Find Handle or DLL找到句柄或dll,当在磁盘中发现恶意dll并想知道谁用了它时,就可以用此方法找到恶意进程。

 还可以分析而已文档,先打开进程浏览器,之后打开此文档,查看文档是否启动了任意进程,并通过双击进程->Image->path查看进程位置。

比较注册表的工具 RegShot

 可比较运行恶意代码前后两次的注册表快照。

模拟网络的工具

 恶意代码会经常连接到网络,所以可以模拟网络,而不需要实际连接互联网。首先,必须防止恶意代码探测到自己处于虚拟环境中。

(1)ApateDNS:可以用来查看恶意代码发出的DNS请求,其通过在本地上监听UDP的53端口,使用用户指定的IP地址去响应DNS查询请求(例如程序请求www.baidu.com,我返回192.168.1.1,也可以选择NXDOMAIN,表示不返回任何ip)。

(2)Netcat:可以被用在支持端口扫描、隧道等工具的对内/对外链接上。监听模式下其可以当作服务器,在连接模式下可以充当客户端。netcat可以从标准输入得到数据并进行网络传输,通过标准输出显示到远程主机中

(3)Wireshark:截获并记录网络数据包。

(4)INetSim:可以模拟HTTP、HTTPS、FTP、DNS等,它会努力伪装的像一台真正的服务器。它会相应恶意代码发来的请求,例如恶意代码请求图片,那么INetSim就会返回一张图片。

动态工具组合分析的例子

 准备工作:(1)启动进程监视器,过滤器设置为保留可执行恶意代码文件名,并清空所有事件。(2)启动进程浏览器。(3)使用RegShot对注册表拍摄快照。(4)使用INetSim和ApateDNS设置虚拟网络。(5)使用Wireshark记录网络行为。

 虚拟网络的配置架构如下所示:

image-20230927190703285

 分析过程见P55。

实验1

 1. 此程序或许被加壳,仅有ExitProcess导入函数,字符串列表如下:

image-20230928123000193

 2-3. 感染迹象特征与网络特征码,程序运行报错,所以无法动态调试。已看答案,在此不再描述。

实验2

 1. 查看导出表,有Install函数,故运行:

1
rundll32.exe Lab03-02.dll, Install aaa

 就会将dll安装为aaa服务,但是答案使用的rundll32.exe Lab03-02.dll, installA aaa

 2. net start aaa。真正运行时1,2都在ntdll.exe中出错误,不知道为啥。

 3. 在进程浏览器中查找Lab03-02.dll被使用的程序。

 4. 使用进程pid过滤。

 注:启动任何服务,60s后才能看到网络通信。5-6的感染迹象与网络特征码在此略,主要是运行不起来。

实验3-实验4

 此文件也运行不起来,直接看看答案。实验3中的程序是击键记录器,写入到某个log中。实验4中的程序则会删除自身。

 注:svchost.exe作为孤儿进程是不寻常的,它通常是services.exe的子进程。

0x04 x86反汇编速成班

 快速过一遍。

 计算机系统的6个抽象层次,从低到高列举,层次越低,可移植性越差:

(1)硬件:唯一的物理层,由电子电路组成,这些电路实现了XOR、AND、NOT门的复杂组合,称为数字逻辑。

(2)微指令:又叫固件,微指令只能在为它设计的特定电路上执行,它们通常是为特定的计算机硬件设计的。

(3)机器码:机器码由多条微指令实现,机器码由操作码opcode组成,高级语言编写的程序可以编译成机器码。

(4)低级语言:汇编语言。

(5)高级语言:C/C++,可编译成机器码。

(6)解释型语言:C#、Perl、.NET、java等,翻译为字节码,在解释器中运行。解释器独立于操作系统,可以将字节码翻译成机器码,可以自己处理错误与管理内存。

 汇编语言分为x86/x64/SPARC/PowerPC/MIPS/ARM,其中x86是大部分PC使用的体系结构,因此大部分恶意代码是x86编译的。

 x86体系结构如下:

image-20231010215224592

  • 控制单元:使用指令指针寄存器IP从内存中取指令。
  • 算术逻辑单元ALU:执行从内存中取来的质量,并将结果放到寄存器或内存中。

 程序内存分布与增长方向:

image-20231010215649324

 表示127.0.0.1,大端序(高位在前):0x7F000001,小端序(低位在前):0x0100007F。

 寄存器要注意的点:

(1)SF(sign):当运算结果为负数,SF置为1;TF:置位时,x86每次处理一条指令。

(2)pusha:按顺序AX、CX、DX、BX、SP、BP、SI、DI压入栈中。

(3)pushad:按顺序EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI压入栈中。

 函数调用要注意的点:push 参数;call A(相当于push eip;jmp A);push ebp;mov ebp, esp。函数结束时,mov esp, ebp;pop ebp;ret(相当于pop eip;jmp eip)

 条件指令要注意的点:

(1)ja与jg相比,ja使用无符号比较,jg使用有符号比较。

(2)jb与jl相比,jb使用无符号比较,jl使用有符号比较。

(3)jo:溢出标志OF=1;js:符号位SF=1;jecxz:ECX=0。

 重复指令要注意的点:

(1)repe cmpsb:逐字节比较EDI与ESI两块缓冲区,当ECX=0或缓冲区不一致时,则停止比较。

(2)rep stosb:给定初始值AL,将AL赋给EDI缓冲区地址的所有字节,直到ECX=0。

(3)rep movsb:ESI到EDI复制,直到ECX=0。

(4)repne scasb:在一段缓冲区中搜索一个字节,EDI为缓冲区地址,AL为要找的字节,直到ECX=0或找到该字节。

0x05 IDA pro

 快速过一遍。

 默认情况下,图形模式中,红色箭头代表条件跳转未被采用,绿色表示条件跳转被采用,蓝色表示无条件跳转。文本模式种,实线箭头表示无条件跳转,虚线表示条件跳转。对于二进制线性地址空间条而言,浅蓝色是库代码,红色是编译器生成的代码,深蓝色是用户代码,导入数据为粉红色,已经定义的数据为灰色,未定义的数据为棕色。

 有时候Ida无法识别出基于EBP的栈帧,修正方法:Alt+P -> BP based frame -> 4 bytes for Saved Reg

IDA可以画出函数间的调用图

 各种细节的操作更新到了tool-use中。

 IDC脚本:

(1)IDC脚本学习在ida安装目录下的idc文件夹下,有空学习(挖个坑)。

(2)IDC脚本是由函数组成的程序,所有函数都是静态的,参数无需指定类型,auto被用来定义局部变量,其中事先定义好了很多内建函数。

(3)举例:PEiD是一个检测加壳的工具,它也可以识别加密算法,其对应的插件Krypto ANALyzer可以导出idc脚本如下,此脚本负责在文件的ida数据库中设置书签和注释:

1
2
3
4
5
6
7
8
9
10
11
#include <idc.idc>
static main(void){
auto slotidx;
slotidx = 1;
// 在 0x00403108 添加标记,内容为 "RIGNDAEL [S] [char]"
// 0,0,0 代表标记类型、颜色与边界样式
// slotidx+0 代表创建标记
MarkPosition(0x00403108, 0, 0, 0, slotidx+0, "RIGNDAEL [S] [char]");
// 在 0x00403109 的前一个非尾指令处添加注释
MakeComm(PrevNotTail(0x00403109), "RINDAEL [S] [char]\nRIJNDAEL(AES):SBOX(also used in other ciphers).");
}

 IDAPython:

(1)感觉比idc更方便,比idc强大很多。idapython可以访问idaapi,idc与idautils 3个模块

(2)例子,对idb中的所有call指令上色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from idautils import *
from idc import *

# ScreenEA 获取光标位置
# SegStart(ScreenEA()) 与 SegEnd(ScreenEA()) :获取段开头地址与段结尾地址
# Heads(SegStart(ScreenEA()), SegEnd(ScreenEA())) :获取指定范围内所有指令的地址
heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA()))
functionCalls = []
for i in heads:
# 获取指定地址处的助记符
if GetMnem(i) == "call":
functionCalls.append(i)
for i in functionCalls:
# CIC_ITEM 表示设置的类型为 ITEM 类型
# 0xc7fdff 表示颜色值
SetColor(i, CIC_ITEM, 0xc7fdff)

实验1

 1. 0x1000D02E

 2. 0x100163CC

 3. 9

 4. pics.praticalmalwareanalysis.com

 5. 23

 6. 1

 7. 0x10095B34

 8. 启动计算器。答案:启动远程shell会话。

 9. 运行完sub_10003695,将返回值给dword_1008E5C4。sub_10003695函数的作用是检测操作系统版本的platformID是否为2,即是否为win7之类的。

 10. 会执行sub_100052A2函数,其内容是发送字符串+工作时间到远端。答案:查询注册表WorkTime与WorkTimes,并通过shell发送出去。

 11. 在系统中找寻是否有某个名字的程序,若有则发送到远端。

 12. 获取systemid并发送给远端。答案:改为GetSystemLanguage。

image-20231011120149863

 13. 图好复杂,看不出来。答案:

(1)View->Graph->User xrefs chart,选择递归深度为1。

image-20231011133749190

image-20231011133758687

(2)同理,选择递归深度为2。

 14. 30s。

 15. 2,1,6。

 16. 分别定义为AF_INET、Sock_stream、IPPROTO_TCP。但是IDA没有这个符号。答案:选择,右击,use standard symbolic constant。

 17. 被使用了。如下所示:

image-20231011131417839

 字符串表示:找到虚拟机,安装中止。

image-20231011131454159

 18. 发现了一段字符。

 19. 脚本有些过时,对于ida7.7而言,更改如下:

1
2
3
4
5
6
7
8
9
from idautils import *
import idaapi
import ida_bytes
sea = get_screen_ea()

for i in range(0x00,0x50):
b = idaapi.get_bytes(sea+i, 1)
decoded_byte = int.from_bytes(b, byteorder='little') ^ 0x55
ida_bytes.patch_byte(sea+i, decoded_byte)

 20. A键,变为:xdoor is this backdoor, string decoded for Practical Malware Analysis Lab :)1234

 21. 脚本工作原理不必赘述。

0x06 识别汇编中的C代码结构

 目的是理解程序的总体功能,而不是分析每一条指令。其中,if/for/while循环的代码分析在此省略。在此,再重新说明一下函数的调用约定。

(1)cdecl。函数完成时由caller清理栈,返回值保存在EAX中。

(2)stdcall。callee清理栈,其他与cdecl相同。这是windows API的标准调用约定,即清理栈的责任由实现API函数代码的DLL程序所负责。

(3)fastcall。传参的时候,前两个参数由寄存器(ECX/EDX)传递,剩下的参数由右向左压到栈上。

 switch的跳转表,若switch很小,那么使用if就可处理,当switch很大,那么就需要使用跳转表。

 结构体识别。比较重要,例如下面的程序:

image-20231205214057815

 其相应的汇编代码为:

(1)main函数:

image-20231205214208897

(2)test函数:

image-20231205214940228

 之后是分析链表。也是比较重要,例如下面的代码:

image-20231205215137219

 其对应的汇编代码为:

image-20231205220211358

 直接用栈来传参,而不单独开启栈帧的例子:

image-20231205215828705

留言

© 2024 wd-z711

⬆︎TOP