malicious-code-analysis-1
恶意代码分析实战笔记-1
诸葛建伟老师翻译的书,泽华大佬推荐的,看里面有好多作业,感觉不错,拿来读读。
0x00 恶意代码分析技术入门
被感染的主机可以用特征码来辨别是否被感染。特征码分为基于主机的特征码、基于网络的特征码。基于主机的特征码关注的是恶意代码对系统做了什么(例如创建或修改的文件,对注册表的特定修改),而不是恶意代码本身的特性(类似于病毒特征码)。晚会罗特征码是通过检测网络流量来检测恶意代码。
恶意代码可以分为:
(1)后门:攻击者无需认证即可连接到远程计算机上,并可以在本地系统执行命令。
(2)僵尸网络:被同一个僵尸网络感染的计算机会听从一台服务器的控制。
(3)下载器:用于下载其它恶意代码。
(4)启动器:用于启动其他恶意程序的恶意代码。
(5)内核套件:用来隐藏其他恶意代码,使得代码很难被受害者发现。
(6)蠕虫:用于自我复制和感染其他计算机的恶意代码。
(7)勒索软件、间谍软件、发送垃圾邮件等。
分析恶意代码的通用规则(感觉很有用):
(1)不要过于陷入细节,大多数恶意程序是复杂的,不可能了解每一个细节,要关注最关键的功能。
(2)不同的任务有不同的方法,没有通用的方法。
(3)不断学习新技术。
0x01 静态分析基础技术
拿到一个恶意程序,步骤为:
(1)上传VirusTotal看看。
(2)计算哈希值,之后在网上搜一搜是否有这个恶意样本。
(3)使用Strings程序查看文件中的可打印字符串(ASCII与Unicode)。
很多恶意程序都会加壳,这样的话其可打印字符串就会很少。加壳代码通常会包含LoadLibrary
与GetProcAddress
函数,用于加载和使用其他函数。可以使用PEiD检测加壳。但是注意,很多PEiD会在没有警告的情况下运行恶意代码。
一些知识点复习:
(1)导入函数:程序使用的但存储在另一程序中的函数。代码库通过链接方式被链接到主程序中,然后程序员将一些导入函数链接到他们编写的程序中。链接分为静态链接、动态链接、运行时链接,运行时链接在恶意代码中经常使用,此时,只有当需要使用函数时,才链接到库,而不是像动态链接那样在程序启动时链接。一些windows API允许程序员导入并没有在程序文件头中列出的链接函数,这些API有:LoadLibrary
、GetProcAddress
、LdrGetProcAddress
、LdrLoadDll
。可以使用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.dll
、oleaut32.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 在虚拟机中分析恶意代码
主机模式逻辑结构:
桥接模式:允许虚拟机和物理机器一样连接到相同的物理网卡上。
0x03 动态分析基础技术
沙箱:Norman沙箱、GFI沙箱。
沙箱的缺点:只能简单的运行程序,不能带有命令行选项;可能无法记录所有事件,因为有的恶意代码等待一天后才执行;等等。
如何运行一个dll文件?
方法1:rundll32.exe
1 | rundll32.exe dll_name, func_name |
方法2:修改PE头
在IMAGE_FILE_HEADER
的特征域中擦除IMAGE_FILE_DLL(0x200)
标记,这样的话点击就会调用DLLMain
函数。
方法3:安装为服务的dll
有的dll会被安装为服务,例如它导出了InstallService
函数,那么就可以运行:
1 | rundll32.exe dll_name, InstallService 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记录网络行为。
虚拟网络的配置架构如下所示:
分析过程见P55。
实验1
1. 此程序或许被加壳,仅有ExitProcess
导入函数,字符串列表如下:
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体系结构如下:
- 控制单元:使用指令指针寄存器IP从内存中取指令。
- 算术逻辑单元ALU:执行从内存中取来的质量,并将结果放到寄存器或内存中。
程序内存分布与增长方向:
表示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 |
|
IDAPython:
(1)感觉比idc更方便,比idc强大很多。idapython可以访问idaapi,idc与idautils 3个模块
。
(2)例子,对idb中的所有call指令上色:
1 | from idautils import * |
实验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。
13. 图好复杂,看不出来。答案:
(1)View->Graph->User xrefs chart,选择递归深度为1。
(2)同理,选择递归深度为2。
14. 30s。
15. 2,1,6。
16. 分别定义为AF_INET、Sock_stream、IPPROTO_TCP。但是IDA没有这个符号。答案:选择,右击,use standard symbolic constant。
17. 被使用了。如下所示:
字符串表示:找到虚拟机,安装中止。
18. 发现了一段字符。
19. 脚本有些过时,对于ida7.7而言,更改如下:
1 | from idautils import * |
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很大,那么就需要使用跳转表。
结构体识别。比较重要,例如下面的程序:
其相应的汇编代码为:
(1)main
函数:
(2)test
函数:
之后是分析链表。也是比较重要,例如下面的代码:
其对应的汇编代码为:
直接用栈来传参,而不单独开启栈帧的例子:
留言
- 文章链接: https://wd-2711.tech/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处!