0x00 前言
首先,YARA规则是VT的开发人员发布的,用于同类样本的批量检索和查杀。
通过Yara引擎和我们编写的Yara规则,可以快速对一批样本进行扫描和识别,从而找到我们希望得到的样本。
官方的github库地址:https://github.com/VirusTotal/yara/releases
官方文档说明:https://yara.readthedocs.io/en/v3.7.0/index.html
0x01 Yara的安装和使用
如何安装Yara
Yara的安装非常简单,在Windows操作系统下,我们可以直接在yara的github库下载可执行文件到本地,配置环境变量即可用
可以看到,当前(2020-07-20)的最新版本是Yara4.0.2
这里下载yara-v3.11.0-994-win64.zip,下载到本地之后直接将压缩包中了两个exe解压并到C:\Windows目录下并分别重命名为yarac.exe和yara.exe
然后启动cmd 直接输入yara —help 即可查看yara的参数
在Mac下,可以直接使用pip工具或者brew直接安装。
我是通过brew install yara的方式直接安装的。安装完成之后,使用方式也是一样的。
Yara食用
现在,以官方提供的yara规则示例我们来看看yara可以做什么。
官方给出的示例yara如下:
rule silent_banker : banker
{
meta:
description = "This is just an example"
thread_level = 3
in_the_wild = true
strings:
$a = {6A 40 68 00 30 00 00 6A 14 8D 91}
$b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9}
$c = "UVODFRYSIHLNWPEJXQZAKCBGMT"
condition:
$a or $b or $c
}
首先,第一行的rule silent_banker : banker是声明该规则用于检出banker类型的样本。
meta 后面的是一些描述信息,比如规则说明、作者信息等。
strings 定义了$a $b $c 两个十六进制字符串(十六进制字符串用大括号括起来)和一个文本字符串(文本字符串直接用双引号括起来)
最后condition 规定了匹配的条件,这里写的是or,表明样本中只要匹配到了$a $b $c 三个字符串中的任意一个,那么样本就会被识别为banker
我们新建一个demo1.yara文件,然后将这部分内容复制进去。
然后在当前目录下新建一个文件夹<111> 在该文件夹中分别存放三个文件,一个py文件,一个json文件和一个exe文件。
现在我们回到demo1.yara所在的目录,执行yara demo1.yara 111/
执行完之后,没有任何输出,根据Unix内核的无回显则运行成功原则,我们可以知道该指令已经成功执行。
而没有输出是因为我们的yara规则没有命中111文件夹下的任何文件。于是我们修改规则如下:
我们增加了一个十六进制字符串$d 所匹配的值是4D 5A。 而4D 5A是PE文件(包括exe、dll等)的文件头,也就是说一个正常的PE文件中是一定会包含4D 5A这个十六进制的数据的。
且我们在最后的condition中加入了or $d,表示如果$d条件满足,样本也可以成功识别。
demo1.yara保存之后我们重新对111文件夹下的文件进行扫描:
这里可以看到,yara规则已经成功识别到了111/svchost.exe文件命中了我们的yara特征,并且提扫描之后自动标记为silent_banker,这个silent_banker就是我们在最上面定义的检出名称。
现在我们将更多的PE文件拷贝到111文件夹下以及111文件夹下的子目录中。
然后再次对111文件夹进行扫描
这里可以看到还是只有一条结果,于是我们通过yara —hlep查看一些帮助文档:
-t, --tag=TAG print only rules tagged as TAG
-i, --identifier=IDENTIFIER print only rules named IDENTIFIER
-n, --negate print only not satisfied rules (negate)
-D, --print-module-data print module data
-g, --print-tags print tags
-m, --print-meta print metadata
-s, --print-strings print matching strings
-L, --print-string-length print length of matched strings
-e, --print-namespace print rules' namespace
-p, --threads=NUMBER use the specified NUMBER of threads to scan a directory
-l, --max-rules=NUMBER abort scanning after matching a NUMBER of rules
-d VAR=VALUE define external variable
-x MODULE=FILE pass FILE's content as extra data to MODULE
-a, --timeout=SECONDS abort scanning after the given number of SECONDS
-k, --stack-size=SLOTS set maximum stack size (default=16384)
-r, --recursive recursively search directories
-f, --fast-scan fast matching mode
-w, --no-warnings disable warnings
--fail-on-warnings fail on warnings
-v, --version show version information
-h, --help show this help and exit
这些参数很重要,我们可以翻译了之后多看,多用
-t、 --tag=tag只打印标记为tag的规则
-i、 --identifier=identifier只打印名为identifier的规则
-n、 --negate只打印不满足的规则(negate)
-D、 --打印模块数据打印模块数据
-g、 --打印标签打印标签
-m、 --打印元数据
-s、 --打印字符串打印匹配的字符串
-L、 --打印字符串长度打印匹配字符串的长度
-e、 --打印命名空间打印规则的命名空间
-p、 --threads=NUMBER使用指定的线程数扫描目录
-l、 --max rules=NUMBER匹配多个规则后中止扫描
-d VAR=值定义外部变量
-x MODULE=文件将文件内容作为额外数据传递到模块
-a、 --timeout=秒在给定秒数后中止扫描
-k、 --堆栈大小=插槽设置的最大堆栈大小(默认值=16384)
-r、 --递归递归搜索目录
-f、 ——快速扫描快速匹配模式
-w、 --无警告禁用警告
--警告失败警告失败
-v、 --版本显示版本信息
-h、 --help显示此帮助并退出
这里可以看到 -r recursively search directories
表示递归遍历所有子目录。
于是我们重新运行,带上-r 参数:yara -r demo1.yara 111/
这样就可以对指定目录进行递归扫描了。
我们还可以集合-r和-m参数,在扫描时输出详细信息:
通过上面的例子,我们对Yara有了一个大概的了解,接下来我们来看一下Yara的一些更方便的特性。
我们上面提到,Yara可以匹配文本字符串、十六进制字符串。其实除了这两种,Yara还支持正则表达式的写法。
在上面,我们已经测试了十六进制字符串的匹配。匹配的方式是扫描文件是否包含了4D 5A。这里都不能算是一条规则,因为这条规则没有意义。其实十六进制还包含了很多高级的写法,包括通配符、可变长度等。这里空讲无用,之后遇到比较好的样本再做分享。
我们来测试一下字符串的写法,我们新建一个demo2.yara,内容如下:
这条yara的规则很简单,就是扫描的文件中是否包含了if name == ‘main‘:这个字符串,如果有,则将其标记为python_file
然后还是对111目录进行扫描:
可以看到成功扫描到python文件。这里需要注意,我们在yara中直接写字符串的话,是区分大小写的,如果想要扫描时不区分大小写,可以加入nocase关键字,比如:
接下来我们看看yara中的正则是如何写的。
Yara的正则其实和字符串写法很类似,并且可以用在字符串上的特性都可以用在正则表达式中。
以匹配身份证号为例,我就直接以一个非常简单粗暴的方式来匹配身份证,就是直接查看文件中是否包含了18位连续的数字(我这种匹配当然不标准,只是为了方便测试)。
此外,我还加上了一个$peflag的标志条件,在condition的地方通过 and not 的方式过滤掉所有的PE文件。
demo3.yara如下:
扫描结果如下:
这里出现了一个warning:demo3.yara(11): warning: $reg1 is slowing down scanning (critical!)
提示demo3.yara规则中的$reg1这个匹配条件写的不好,会引起yara的性能下降。
这是因为我们直接使用正则表达式瞎匹配,这样肯定是不对的,因为如果扫描一个1M或是2M的脚本文件,这个yara规则一跑,基本上就会卡死。所以在书写yara规则的时候,在可达成目的的情况下,也要考虑扫描的性能。关于yara的优化,我们在后面的实战样本中会提到。
0x02 CobaltStrike安装和使用
CobaltStrike安装
CobaltStrike的安装非常简单,只需要在本机装好JAVA环境,然后分别运行CobaltStrike的客户端和服务器端即可。
以3.14为例,3.14版本的CobaltStrike解压后文件结构如下:
其中的teamserver就是CobaltStrike的服务器端,CobaltStrike文件就是客户端。
只需要
./teamserver 服务器ip地址 cs密码
即可成功启动CobaltStrike的服务器。
然后在客户端使用./cobaltstrike 然后填写服务器的ip地址和密码即可(端口一般不变)成功登录。
4.0的结构稍微有所不同
在4.0中不再有CobaltStrike客户端文件,取而代之的是CobaltStrike.bat文件
所以我们首先启动CobaltStrike服务器,这里的ip地址就是本机的ip地址,后面的123456是登录密码
服务器成功启动之后,我们启动客户端:
成功登录上cs客户端:
大概介绍一下CobaltStrike各个菜单的的功能:
CobaltStrike的监听器主要分为beacon和foreign,其中bacon是CobaltStrike内置的,我们使用CobaltStrike生成的payload反弹shell回来直接就可以用
foreign是外置的,比如想通过CobaltStrike反弹到msf,就可以使用foreign
CobaltStrike生成马
我们可以通过上面介绍的图标快捷菜单生成木马,也可以通过Attacks菜单栏生成我们想要的木马。
我这里分别生成了beacon、foreign、HTML、powershell、VBS、office宏等cs马。接下里先分析分析这些木马尝试提取CobaltStrike特征,生成马如下(3.14):
0x03 CobaltStrike样本查杀
Win32_PE
我们首先来看看32位的PE文件,包括exe和dll。
为了防止样本在Windows操作系统下被误执行,我这里给所有逇样本都加上了.bin后缀,然后统计这些文件的时候可以发现,通常情况下CobaltStrike默认生成的beacon的PE马大小都在278kb到303kb的样子。红框部分的foreign马的大小却只有十多kb。
所以我们可以尝试编写第一个filter来过滤掉其他的样本。
为什么要编写filter呢,因为比如我们扫描Windows的系统目录或是一个程序目录,该目录下有上千,上万甚至上十万的文件,如果我们不写filter,那么Yara在扫描的时候将会每个文件都完整的去匹配我们写的规则,在上面的时候我们提到,yara规则是会全文扫描的,那么这样将会大大降低扫描效率,如果我们能够写一些filter过滤掉大部分的不相关样本,那么yara在扫描的时候将会只对疑似的文件进行有效的扫描而不会浪费资源。
Yara提供了文件大小的变量,标准写法为
rule FileSizeExample
{
condition:
filesize > 200KB
}
第一个条件写好之后,可以看到基本上就筛选了刚才我们看到的PE我文件,但是多了一个beacon3.ps1,没关系,我们在后面的规则中过滤掉。
现在我们随便加载两个exe到IDA中分析。
IDA加载之后,一对比发现两个马的入口点是几乎完全一样的,唯一细微的区别就是这里mov语句的参数2不同。
空格转换为汇编代码显示,可以看到样本此时默认是main函数。这里目前至少是说明CobaltStrike的两个beacon马的main函数入口点结构相同。
Ctrl + E ,然后跳转到start函数,看看两个样本的入口点是否相同:
来到start函数之后,我们可以发现两个CobaltStrike样本的入口点是完全一致的,程序入口点都是004014B0
减去基地址00400000,那么入口点应该就是偏移14B0的位置。
我们再加载一个beacon的样本,看看入口点是否还是一致,这里发现三个样本的入口点都是14b0的地方,所以我们推测CobaltStrike的样本入口点都在14b0的地方,所以我们编写第二个规则去验证一下。
在yara3.0以下的版本中,可以直接使用entry_point变量获取入口点,在3.0以上的版本中,该变量已经被弃用。取而代之的是一个pe模块。现在可以使用pe.entry_point来获取入口点,但是需要注意的是,这里是获取到的文件偏移,也就是offset,并不是我们在IDA中看到的entry_point,这里需要注意一下。
程序的Offset是0x8b0:
所以我们可以将条件写成如下形式:
官方文档给的写法是用关键字at:
但是需要注意,使用at是取值,并不是匹配地址。
也就是说上图这种写法是去找一下$a变量的值是否在entrypoint这个地址能找到。
也就是在我们这条规则中,如果要用at,可以写成如下:
这里的$name中的十六进制值需要大写,匹配的值就是IDA入口点的十六进制数据:
但是我们同时也可以看到,at的写法没有那么有通用性,我们还是使用上面 == 的写法。
那么现在第二条规则就已经写好了,我们继续来看其他的内容。
我们静态分别看下这个样本的main函数:
在Main函数中可以看到,关键就调用了两个call,分别是4027b0和401800
我们大概看一下就可以知道sub_401800才是关键函数,在sub_401800函数中,程序会通过CreateThread创建一个新线程,并且可以看到有一个奇怪的%c%c%c%c%c%c%c%c%cMSSE-%d-server看起来像是通信协议。
这里CreateThread的lpStartAddress是sub_4016D3,在sub_4016D3的sub_401608函数中可以看到正在通过CreateNamedPipeA的方式创建管道准备通信。
我们调试器里直接过来可以看到跟我们想的应该一样,这里的确是跟通信相关的内容。
查看多个样本,可以发现%c%c%c%c%c%c%c%c%cMSSE-%d-server 这个值是固定的。
所以可以直接尝试匹配这个串试试:
可以看到再次成功匹配。到这里,我们关于CobaltStrike的Beacon木马的yara规则基本上就提取完成了。
最后,我们给规则加上一个PE的判断条件:
现在这个条件就比较完整了。
- 通过uint16(0) 的方式取文件的前两个字节,判断是否等于0x5A4D 如果等于则说明是PE,这里用于过滤PE文件
- 判断程序的pe.entry_point是否等于0x8B0,这里是文件的file offset,这里也算是一个很好的过滤条件,可以直接过滤掉大部分的文件。
- 通过两个filesize的比较限制文件的大小
- 最后用一个关键的字符串来做最终的验证。
这里是看到这个串的确是CobaltStrike使用来通信的,且一批CobaltStrike的马都包含了这个串,相对来说,应该不会命中到其他的正常文件,这个通信协议应该是cs专用的,所以可以直接这样写,如果不想误报,想要提高检测的精准性,可以继续找其他的特征提取。
反正在写yara规则的时候,通用性和精准性,是分析人员需要权衡的条件,如果我们想要提高特征的通用性,势必就需要减少一些匹配条件,精准性就会下降,至于到底是通用性优先还是精准性优先就得看具体的应用场景。
powershell马特征
处理了CobaltStrike的PE马,我们再来看看其他类型的木马规则如何提取。
首先是powershell木马,也就是ps1的文件。
我这里只生成了两个。
分别查看一下文件内容。
32位:
64位:
这里可以看到,32位和64位的powershell马结构其实是一样的,只是32位的马使用了$DoIt= @’’的方式来定义两个函数,其实和64位这里是一样的。
这里很明显,程序最后关键执行的数据在24行开始的地方,这里定义了超级大的base64串。经过观察可以发现32为和64执行的串的头部是不一样的:
32位:
64位:
经过分析我们可以发现,代码的最下面,上面定义的这个$var_code的执行语句,且两个版本的ps马都是这样写的,于是我们直接尝试用字符串匹配这一段试试。
就直接在原有yara的基础上增加一个rule即可:
然后命令行测试,可以看到两个ps1脚本都被成功匹配
html_pe马特征
CobaltStrike的html木马分为了三种,分别是包含PE文件的hta 包含VBS的hta 包含powers的Hta,我们分别来看看。
首先是包含PE的hta:
该类别的cs马会直接将一个PE文件的十六进制数据流硬编码到文件中:
在马最下面,程序会定义一个var_tempexe文件用于接受上面的数据流,将该文件写入到本地并且通过run执行之后删除该文件。这里其实写入的文件应该也是beacon的木马,在14行代码的位置,有着var_tempexe变量的赋值语句,经过与多个样本的对比分析发现,后面的beacon_evil.exe不是固定的,而是攻击者在使用CobaltStrike生成木马的时候给定的文件名。所以我们可以考虑用正则来匹配这一段,作为条件之一。
编写yara特征之后进行扫描,成功捕获对应的样本
完整的正则表达式如下:
/var_tempexe = var_basedir & \”\\” & \”[A-z]{1,20}.exe\”\s*Set var_stream = var_obj.CreateTextFile(var_tempexe, true , false)/
我们来一点点拆解。
首先,我们在yara中使用正则表达式要使用
//
标识,我们将正则写到两个斜杠之间。
var_tempexe = var_basedir & 这段是我们直接从原始代码中复制出来的。
\”\\” 用于转义匹配中间的 “\”
由于我们刚刚已经分析过,后面的beacon_evil.exe不是固定的,是又攻击者指定,所以我这里写了一个范围,1-20,然后通过\s*将后面的语句给组合起来进行匹配。
html_VBS特征
接下来我们看看html的VBS马,结构如下:
经过分析,我们可以看出来,下面的一大段代码,其实是带轻微混淆的VBA代码。
混淆方法很简单,就是通过&符号拼接由chr函数转换的ascii
“&Chr(61)
我们尽量找一个比较通用的地方来提取特征,稍微分析一下,可以发现,上面部分都是一些函数的导入,变量的定义,没有做神什么比较奇怪的操作,在57行的位置,定义了一个很大的数组,数组名为myAr&ray 后面的Chr(61)转换之后是等于符号 = 。 后面的一大段数据都是用于给myArray赋值的。所以我们可以考虑将特征提取到这里。
这里应该可以直接写字符串进行匹配了:
html_powershell
最后,html类别的马就只剩下powershell类型的了,还是先来看看代码结构
cs的powershell马结构相对来说就很简单了,就是通过powershell执行了超级长一段base64编码的脚本命令。
直接取这个base64串的头部出来解码看看是是不是常见的语句
JABzAD0ATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8Acg
解码之后发现是$s=New-Object IO.Memor
结合代码分析,这里是将后面的数据加载到内存中执行,这里是在做准备工作。
结合前面的powershell -nop -w hidden -encodedcommand本身也不是常见的powershell语句,正经人谁会这样干,所以我们直接尝试提这里的字符串看能否通杀。
貌似可以
Macro
现在还有一类Macro样本,也就是Office宏代码。这个其实也是简单,但是这里我生成的时候偷了一个懒,没有按照cs的文档将宏代码存储到Office文档中。关于Office文档的查杀,在后面再结合oledump等工具继续介绍。
0x04 查杀效果
我们回到上级目录,看看同时查杀3.14和4.0的效果怎样
我们将结果写入到1.txt中
一共是查杀了24个样本
可以看到,一共27个文件,除了vba和这里的两个raw没写规则外,全部查杀了。所以,对CobaltStrike木马基本查杀的yara规则就这样写好了。后面我们学习了yara的高级写法或是遇到其他cs的样本可以再继续完善,但是目前这个yara应该可以识别绝大多数的正常cs马了。
最后,CobaltStrike解压之后,目录中会有一个CobaltStrike.jar文件
使用解压缩工具将这个文件也解压:
这里面有一个名为resources的文件夹,就是CobaltStrike的配置信息,我们在CobaltStrike控制台生成的木马都来源于这个文件夹。
所以我们也可以直接分析这里面的样本,提取规则进行查杀。
0x05 完整yara
import "pe"
rule beacon32
{
meta:
description = "This rule is used for discovery CobaltStrike’s beacon32PE Trojan"
author = "int3"
date = "2020-07-21"
reference = "reference"
hash = "hash"
strings:
$name = "%c%c%c%c%c%c%c%c%cMSSE-%d-server"
condition:
uint16(0) == 0x5A4D and pe.entry_point == 0x8b0 and filesize > 277KB and filesize < 304KB and $name
}
rule ps
{
meta:
description = "This rule is used for discovery CobaltStrike’s powershell Trojan"
author = "int3"
date = "2020-07-21"
reference = "reference"
hash = "hash"
strings:
$str1 = "$var_va.Invoke([IntPtr]::Zero, $var_code.Length, 0x3000, 0x40)"
$str2 = "[System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length)"
condition:
uint16(0) != 0x5A4D and $str1 and $str2
}
rule CobaltStrike_hta_pe
{
meta:
description = "This rule is used for discovery CobaltStrike’s hta'pe Trojan"
author = "int3"
date = "2020-07-21"
reference = "reference"
hash = "hash"
strings:
$reg1 = /var_tempexe = var_basedir & \"\\\" & \"[A-z]{1,20}.exe\"\s*Set var_stream = var_obj.CreateTextFile\(var_tempexe, true , false\)/
condition:
uint16(0) != 0x5A4D and $reg1
}
rule hta_VBS
{
meta:
description = "This rule is used for discovery CobaltStrike’s hta'vbs Trojan"
author = "int"
date = "2020-07-21"
reference = "reference"
hash = "hash"
strings:
$str = "myAr\"&\"ray \"&Chr(61)&\" Array\"&Chr(40)&Chr(45)&\"4\"&Chr(44)&Chr(45)&\"24\"&Chr(44)&Chr(45)&\"119\"&Chr(44)"
condition:
uint16(0) != 0x5A4D and $str
}
rule hta_ps1
{
meta:
description = "This rule is used for discovery CobaltStrike’s hta'vbs Trojan"
author = "int"
date = "2020-07-21"
reference = "reference"
hash = "hash"
strings:
$str = "var_shell.run \"powershell -nop -w hidden -encodedcommand JABzAD0ATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8A"
condition:
uint16(0) != 0x5A4D and $str
}
0x06 总结
本篇是YARA规则的入门篇,通过本节的内容我们可以发现,Yara在恶意样本检测中有着至关重要的作用(虽然本节中写的YARA很简单,但是Yara可以做的远远不止于此)。此外,我们还可以通过别人写好的yara,辅助我们分析恶意软件,阅读大佬写的Yara我们可以知道大佬在分析对应的样本的时候,关注点在哪里,他提取到了目标木马/家族的哪些特征等等。