YARA
推荐YARA相关文章
通用
文件属性
SIZE(如66KB)
文件大小判断:filesize < 100KB
注意,因为想当然,容易把区间写成“33KB < filesize < 99KB”,会报错。
正确的区间写法:(33KB < filesize and filesize < 99KB)
condition:
33KB < filesize and filesize < 99KB
条件(Condition)
字符串多次出现
字符串出现的次数可以由一个变量表示,变量名是用#代替$的字符串标识符
condition:
//包含String
$String
//String出现2次以上,字符串符号$改为变量符号#
#String > 2
字符串位置
地址
yara中数值默认是十进制的,如果是十六进制的需要在数字前加上“0x”。
具体位置
有时知道字符串是否位于特定的位置,或偏移量在文件或进程地址空间内的某个虚拟地址上时,可通过at进行匹配:
rule AtAddress
{
strings:
$Address0x100 = "Address0x100"
condition:
$Address0x100 at 0x100
}
位置区间
当字符串Digits2偏移量在0-100之间,而字符串Above100位于100到文件末尾之间的偏移量处时:
rule InInterval
{
strings:
$Digits2= "0-99"
$Above100 = "100<"
condition:
$Digits2 in (0..100) and $Above100 in (100..filesize)
}
偏移
如果确定某字符串出现在另一字符串固定位置的偏移处,可以通过@取得地址值表示,格式为@String1[Num]。
[Num]表示的是,对应字符串第一次出现的位置,并且Num计数是从1开始的
当字符串String1地址往后偏移0x11处为String2时:
condition:
@String1[1] + 0x11 == @String2[1]
除此之外,也可以通过加减运算和比较运算符表示。如:
condition:
//取值是支持负数的
(@String1[1] - @String2[1] == -0x11)
//或者也可以写为
(@String2[1] - @String1[1] > 0x11)
PE模块
需要对PE结构有比较深的认识和理解,包括分析时对数据的敏感性。
下面仅对常见的一些字段进行介绍,但是一些小众冷门的字段,可能效用更强,如证书(signatures),链接器版本(linker_version),节表(sections),资源(resources)等。
使用该模块之前需要导入PE模块(注意PE必须小写):
import "pe"
condition:
pe.is_dll()
pe.is_32bit()
pe.is_64bit()
pe.pdb_path == "Y:\建瓯最坏\ProjectName\2021\Project2021RAT.pdb"
pe.imphash() == "b8bb385806b89680e13fc0cf24f4431e"
pe.imports("kernel32.dll", "WriteProcessMemory")
pe.exports("ExportTableName")
文件类型判断
Dll文件:pe.is_dll()
32位:pe.is_32bit()
64位:pe.is_64bit()
导入表
导入表哈希值:pe.imphash() == “b8bb385806b89680e13fc0cf24f4431e”
*注意值要小写
- 导入表中的函数:pe.imports(“kernel32.dll”, “WriteProcessMemory”),尽量找比较特殊的函数
- 导入表Library的个数:pe.number_of_imports == 6
延迟导入表
在编写海莲花后门Salgorea发现一个比较特殊的情况,有个样本的导入表函数条件匹配不上:
IDA的导入表内却又有“ShellExecuteW”,定位来源发现不同处为,匹配不上的函数显示来自延迟导入表(Delayed imports from SHELL32.dll):pe.imports( "Shell32.dll" , "ShellExecuteW" )
搜了很久都没有找到YARA怎么匹配延迟导入表中函数的写法。22.03.10更新:4.2.0支持啦!
换个思路,暂时选择通过增加其他的函数条件来检测。方式为:对比两种样本的函数,找到相同且比较特殊的函数、
从导入表中,复制所有列:
利用Excel或其他方式(如文本对比工具,Linux的命令uniq
),选中范围后,设置高亮Name
列的重复项:
我个人还是更倾向于Excel:
Office的Excel好像没有提取重复数据,WPS这个功能要钱,那么推荐安装Git或者在Linux下执行命令求交sort Data.txt | uniq -d
或sort Data1.txt Data2.txt | uniq -d
。
编写好后,使用YARA规则命令复查一下是否有无法匹配的样本:
yara.exe .\YaraRule.txt -r -n [存放需要匹配样本的目录]
导出表
导出表中的函数名:pe.exports(“ExportTableName”)
导出表中的函数个数等于明确数字:pe.number_of_exports == [导出表最下方的数字减去1]
导出表中的函数个数处于某个区间:(200 < pe.number_of_exports and pe.number_of_exports < 300)
还可以匹配正则等,检测单个用名称匹配比较多,预测性用正则
PDB
PDB:pe.pdb_path == "Y:\建瓯最坏\ProjectName\2021\Project2021RAT.pdb"
只单个样本可以考虑
但是从APT跟踪角度来说,一般以字符串的方式+正则匹配用来预测可能变化的情况。
分解上面的PDB分别是:
- 盘符:一个大写英文,[A-Z]{1}
- 用户名:固定长度的中文,[\u4e00-\u9fa5]{4}
项目名:不定长(暂取Max50)大小写英文,[a-zA-Z]{,50}
年份:定长数字,20XX年,20[0-9]{2};201X年,201[0-9]{1};202X年,202[0-9]{1}
源文件名称:当做是
随机搭配
的数字和大小写英文,[\w]{,50}
可以写成以下几种格式搭配,一般建议写宽松写,或者根据历史样本的变化规律可以对应的修改。rule PDB
{
strings:
//如果以“建瓯最坏”不变匹配:建瓯最坏\([路径(包括大小写英文和\)]).pdb
$StringsPDB_JianOu = /[\u4e00-\u9fa5]{4}[\w\\]{,50}.pdb/
//ProjectName\[不同年份]\Project2021RAT.pdb
$StringsPDB_Year = /ProjectName\\20[0-9]{2}\\Project2021RAT.pdb/
//[不同项目名]\2021\[不同源文件名].pdb
$StringsPDB_ProjectName = /[a-zA-Z]{,50}\\20[0-9]{2}\\[\w]{,50}.pdb/
condition:
all of them
}
资源
如iCon:
import "pe"
import "hash"
condition:
for any i in (0..pe.number_of_resources) :
(
( pe.resources[i].type == pe.RESOURCE_TYPE_ICON )
and
( hash.md5(pe.resources[i].offset,pe.resources[i].length) == "834c709455bfefb9b0e8976bad13a8f4" )
)
我一般判断的值是导入“hash”计算的MD5,VT等平台用的SHA-256比较多(hash.sha256),自己注意:
证书
以APT组织白象的木马从21年7月捕获的样本使用的证书为例(值要小写):
```python import “pe”
rule APT_HangOver_Signatures_AccelerateTechnologies_21July { condition: for any i in ( 0 .. pe.number_of_signatures -1 ): ( pe.signatures[i].thumbprint == “83e2488e16fd5d1ca018ff0c04e826ec27f4a2a0” ) }
<a name="94c24e5f"></a>
# 写YARA规则的一些思路
<a name="3d74084d"></a>
## IDA导出表窗口
```yaml
condition:
pe.exports("ExportTableName")
除非是比较特殊,而且长期大量的样本都是同一个名称,不然其实检测的精度比较高,但是通用性较低:
字符串
IDA的字符串窗口
PDB
在IDA的字符串窗口搜索PDB,没有就算了
有的话参考上述的规则,判断PDB路径结构,对一些变量(如年份2018和日期/版本号0328)替换为正则
:
这种通用性较低,没有比较特殊的字符串,勉强可以写一个,条件判断里可能也不会作为主要逻辑:
strings:
$StringsPDB = /F:\\[\w\\]{36,60}\\Doc.pdb/
字符型格式符
在IDA的字符串窗口搜索字符型格式符,如%s
,基本会有一些比较自定义的字符串:
函数参数
还有部分函数的参数需要传入字符串,可以试着从这里面看看有无比较特殊的字符串或者格式。
创建(Create)
此类函数,需要传入创建的名字:
如Create
DirectoryA:
设置(Set)
如RegSet
ValueExA:
获取(Get)
大多数获取功能的函数是没有什么参数,但是要处理获取到的数据,此时会有一些参与处理的字符串:
文件(File)
文件操作类大多数需要带文件路径或者文件名,如打开某文件,或者遍历某文件等:
字符串
字符串操作(str)
格式化输出函数(printf)
实例
字符串多次出现
考虑到误报率和检测率,可以增加一些检测条件,下图中的“%ls”可以看出有多次出现:
但是需要通过Hex十六进制窗口确认一下字符串的类型,此部分一共有1个“%ls”和两个宽字节的“%ls”:
或者通过浮IDA提示的字符串类型判断,char为普通字符串:
wchar为宽字符。需要将字符串加上wide 定义:
可以写字符串多次出现的规则检测如下:
strings:
$LS = "%ls" nocase
$LS_Wide = "%ls" nocase wide
condition:
$LS and #LS_Wide > 1
//或者如果字符串多,可以:
//all of them and #LS_Wide > 1
不过大于小于的偏差值可能会比较大,YARA有一参数-s:
-s, —print-strings print matching strings
打印出匹配的字符串
在数量不多的情况下,可以作为计数方式使用:
上图可知“%ls”出现3次,宽字节的“%ls”出现5次,加上精确数字,规则可以写为:
strings:
$LS = "%ls" nocase
$LS_Wide = "%ls" nocase wide
condition:
#LS ==3 and #LS_Wide == 5
特殊函数
有些恶意软件,可能字符串常见,但是用的函数很特殊,或者这两个叠加的集合比较准确,可以考虑在条件处增加函数条件。格式为:pe.imports(dll_name, function_name),如:pe.imports(“Kernel32.dll”, “Sleep”)
恶意软件常通过创建互斥体,保持运行的唯一性,这本质是通过创建锁来形成的,故除了互斥体外,还有其他的方式,如:临界区,事件和信号量。
如APT蔓灵花的常用下载器使用的方式是调用“CreateSemaphoreA”创建信号量:
import "pe"
rule APT_Bitter_ArtraDownloader_Jul20
{
meta:
author = "建瓯最坏"
description = "蔓灵花ArtraDownloader - 2020.07新变种"
sampleMD5 = "cgc.exe - 808849DE500179EC0FBC82C862F62333"
strings:
$Strings = "ExRng" nocase
$StringsPost1 = "info" nocase
$StringsPost2 = "Test" nocase
$StringsPHP = ".php" nocase
$StringsFile = "wb" nocase
$StringsFileOpen = "Open" nocase
condition:
all of them
and pe.is_32bit()
and pe.imports("Kernel32.dll","CreateSemaphoreA") //创建信号量
and pe.imports("SHLWAPI.dll","PathAddBackslashA") //比较特殊
and pe.imports("WS2_32.dll","connect")
and pe.imports("SHELL32.dll","ShellExecuteA")
}
函数多次出现
特殊代码
分离赋值-StrongPity
APT组织StrongPity常用的Exfiltrate模块中有字符串分离赋值的特殊代码如下:
上面截图中是一个很有特殊性的代码,Exfiltrate模块会拼接卷序列号和分离赋值的版本字符串。在后续载荷中,代码的具体实现稍有不同,直接通过“GetVolumeInformationW”快速定位到了此处:
可以发现有规律为通过分离赋值的方式,拼接处“v[1-9][0-9]{2}kt[0-9]{2}p[0-9]”,那么一定有对“v”“_”“kt”“p”的赋值,对应着二进制代码如下:
strings:
//v赋值,push 0x76
$HexVerNumV = { 6A 76 }
//下划线
$HexVerNumUnderscore = { 6A 5F }
//k
$HexVerNumK = { 6A 6B }
//p
$HexVerNumP = { 6A 70 }
对目前已知发现的版本检测率为100%:
又考虑到赋值都在一定范围的代码处,可以再加上字符串地址取值和偏移量判断,但又不是非常固定,是处于一个比较临近的区间,比如“v”和“_”偏移,在V20版本为0xD的,在V21版本为0x11的,所以取一个比较宽松的值为0x33(3是我的幸运数字):
import "pe"
rule APT_StrongPity_Exfiltrate_Dec20
{
meta:
author = "建瓯最坏"
description = "2020-12-25,StrongPity联网插件"
sample = "81390CE601D34F384BFF9198EEF793A9"
strings:
//版本号标识,格式如"v33_kt33p3"
$HexVerNumV = { 6A 76 }
$HexVerNumUnderscore = { 6A 5F }
$HexVerNumK = { 6A 6B }
$HexVerNumP = { 6A 70 }
$StringsName = "name=%ls" nocase
$StringsDel = "delete=" nocase
//有些版本SFT字符串被加密处理了,无法通用
//$StringsSFT = "*.sft" nocase wide
condition:
all of them
and ( @HexVerNumUnderscore[1] - @HexVerNumV[1] < 0x33 )
and pe.imports("Kernel32.dll","GetVolumeInformationW")
and pe.imports("Kernel32.dll","ReadFile")
and pe.imports("Kernel32.dll","CreateProcessW")
and pe.imports("WinHTTP.dll","WinHttpSendRequest")
}
如果仅根据上面两个版本的偏移数字,甚至可以写作:
condition:
( 0xD <= @HexVerNumUnderscore[1] - @HexVerNumV[1] )
and
( @HexVerNumUnderscore[1] - @HexVerNumV[1] <= 0x11 )
}
但是对于示例的这个样本,不是很有必要,因为这些分离赋值的Hex值都比较特殊,每个在IDA里面搜索后的结果都只有一个:
直接通过定义String,即可以扫描到几乎所有的对应样本。
偏移计算对特殊的字符串意义不大,不过对于通用的字符串,可以大大的提高准确率。
版本更新
v4.2.0-rc1
新增
- 用于在偏移范围内计算字符串出现次数的新语法。示例:
#a in (0..100)
(#1565)。 - 用于检查是否在偏移范围内找到一组字符串的新语法
all of them in (0..100)
( #1554 )。 of
运算符现在接受规则集,例如:2 of (rule1, rule2, rule3)
、2 of (rule*)
(#1597 )- 新的语法糖允许
none of ($a*)
写成(0 of ($a)
(#1559 )。 %
字符串集的新运算符。示例:20% of them
(#1434)。(“将 ‘percent’ 关键字替换为 ‘%’”看不懂,也不知道怎么用)- 新运算符
defined
(#1529)。 - 新运算符
iequals
(#1536)。(不区分大小写的字符串比较) - 向
match
模块添加了函数abs
、count
、percentage
和mode
(#1483 ) - 添加了新
console
模块(#1594)。(🥳🥳🥳调试/测试好用) - 添加了对
**pe**
模块延迟导入的支持(#1523)。(椰丝椰丝🥳🥳🥳) - 在 Linux ( #1470 )中扫描进程内存时减少内存压力。
- 在匹配某些十六进制字符串(#1526,#1552)时提高性能。
- 在 Windows ( #1491 )中实现对 unicode 文件名的支持。
- 添加新的 API 函数
yr_get_configuration_uintXX
和yr_set_configuration_uintXX
( #1621 )。 - 添加
--max-process-memory-chunk
用于在扫描进程内存时控制块大小的选项(#1393)。 添加
--skip-larger
用于在扫描目录时跳过大于特定大小的文件的选项。修复
BUGFIX:fullword修饰符在所有语言环境下都不能正常工作(#1544)。
- BUGFIX:当文件具有被解释为 PID 编号的数字名称时修复边缘情况(#1541)。
- BUGFIX:修复模块中的内存泄漏magic。