[免杀学习]Yara规则学习
Yara简介
yara规则是一种用于描述恶意软件样本特征的语言,它由三个主要部分组成:规则名称、元数据和条件。
yara规则由三个主要部分组成:规则名称、元数据和条件。
- 规则名称是一个唯一的标识符,用于描述规则的目的或匹配的样本类型。
- 元数据是一些键值对,用于提供规则的额外信息,如作者、日期、描述、引用等。
- 条件是一个布尔表达式,用于定义规则匹配的逻辑,它通常包含一些字符串或模块函数。
yara规则的基本语法如下:
1 | rule <name> { |
Yara编写/使用
环境:python
下载python的yara依赖
1 | pip install yara-python |
创建一个txt文件,里面写入一些内容
1 | echo 'I am Lanb0' > test.txt |
写入以下代码:
1 | import yara |
匹配到会显示规则名称:
1 | [match_lanb0] |
编码问题
yara规则中的字符串默认是ASCII字符串,它只能匹配ASCII编码的文件,如果要匹配其他编码的文件,如Unicode,需要在字符串后加上wide
修饰符
1 | $flag = "Lanb0" wide |
语法/修饰符总结
strings块
YARA规则中的strings是用来定义软件中可能出现的字符串或者字节序列,它们可以是文本字符串,十六进制字符串,或者正则表达式字符串。
修饰符 | 作用 |
---|---|
fullword | 表示严格匹配完整的单词,即字符串的前后不能有字母、数字或下划线。 |
wide | 表示匹配宽字节字符串(Unicode) |
ascii | 表示匹配ASCII字符 |
nocase | 表示忽略字符串的大小写 |
{} | 表示一个十六进制字符串 |
? | 表示通配符,可以匹配任意一个hex字符。(在{}中使用) |
() | 表示可选的字节序列,可以使用|表示”或”。(在{}中使用) |
[] | 表示任意填充的字节数,可以指定一个范围。(在{}中使用) |
// | 表示要使用正则表达式 |
注意:在strings块中声明的每个变量都必须在condition块中被使用,否则会报错unreferenced string “$xxx”
condition块
uint
uint是一个函数用法,它表示读取文件中指定位置的字节,并转换为无符号整数。
uint16(0) 表示读取文件开头(第0索引)的两个字节,并转换为无符号整数。
例如,如果文件开头的两个字节是 4D 5A,那么 uint16(0) 的值就是 0x5A4D(小端序)。
1 | condition: |
这个函数可以用于检查文件的类型,例如 uint16(0) == 0x5A4D 就是检查文件是否是 Windows 可执行文件。
**uint32(0)**同理
#修饰符是用来判断变量出现次数
1 | strings: |
这里就是判断4D 5A这个字节序列是否出现了1次以上
filesize
filesize表示文件大小
1 | condition: |
也可以用KB,MB来自动换算,但必须是大写
1 | condition: |
of
… of ….语法用来表示在一个范围中匹配一个,两个或者多个甚至全部
例如
all of them
表示所有声明的变量都匹配时,条件成立。
1 | strings: |
any of them
表示任意一个声明的变量匹配时,条件成立。
them表示在strings块中声明的所有的变量
of的左边可以换成任意正整数,右边也可以换成特定范围的变量,用括号包裹起来即可
- 1 of ($a,$b),表示匹配到$a或者$b任何一个变量时,条件成立
- all of ($a,$b),表示匹配到$a和$b,即包含了全部括号中的变量时,条件城里
- any of ($a,$b)与第一条规则效果相同
not
not表示某个字符串不匹配时,条件成立。
例如
not $b
表示$b不匹配时,条件成立。
and
and表示左右两个条件都成立时,条件成立
or
or表示任意一个条件成立时,条件成立
*
*代表通配符,以如下规则为例,表示是匹配变量名以‘a’开头的任意变量。
1 | strings: |
注意,以下写法是错误的
1 | condition: |
库
可以导入写好的库来使用库函数进行匹配
比如PE库,
1 | import "pe" |
一些常用的PE库函数
pe方法 | 说明 | 示例 |
---|---|---|
pe.is_dll() | 判断文件是否是DLL文件 | condition: pe.is_dll |
pe.is_32bit() | 判断文件是否是32位PE文件 | condition: pe.is_32bit |
pe.is_64bit() | 判断文件是否是64位PE文件 | condition: pe.is_64bit |
pe.number_of_sections | 获取文件的节区数量 | condition: pe.number_of_sections > 5 |
pe.sections[i] | 获取第i个节区的信息,如名称、大小、虚拟地址等 | condition: pe.sections[0].name == ".text" |
pe.entry_point | 获取文件的入口点地址 | condition: pe.entry_point == 0x401000 |
pe.imports(library, function) | 判断文件是否导入了指定的库和函数 | condition: pe.imports("kernel32.dll", "CreateFileA") |
pe.number_of_imports | 获取文件导入的函数数量 | condition: pe.number_of_imports > 10 |
pe.imports[i] | 获取第i个导入的函数的信息,如库名、函数名等 | condition: pe.imports[0].library == "kernel32.dll" |
pe.exports(function) | 判断文件是否导出了指定的函数 | condition: pe.exports("DllRegisterServer") |
pe.number_of_exports | 获取文件导出的函数数量 | condition: pe.number_of_exports > 0 |
pe.exports[i] | 获取第i个导出的函数的信息,如名称、地址等 | condition: pe.exports[0].name == "DllRegisterServer" |
pe.resources[i] | 获取第i个资源的信息,如类型、名称、语言等 | condition: pe.resources[0].type == "RT_ICON" |
pe.number_of_resources | 获取文件资源的数量 | condition: pe.number_of_resources > 1 |
pe.version_info[key] | 获取文件版本信息中指定键的值,如CompanyName, FileVersion等 | condition: pe.version_info["CompanyName"] == "Microsoft Corporation" |
pe.checksum | 获取文件校验和 | condition: pe.checksum == 0x12345678 |
pe.imphash() | 获取文件导入表哈希值 | condition: pe.imphash() == "f34d5f2d4577ed6d9ceec516c1f5a744" |
训练:用Yara规则写一个远控检测
要求:
- 误杀率低
样本源码
1 |
|
代码不细说了,总体流程如下:
(准备工作)隐藏窗口 —-> 初始化 Windows 套接字协议 —-> 创建一个win套接字连接 —-> 初始化此连接(目的ip,port,ip协议等) —->
(正式开始)接收一个代表payload长度的数据 —–> 根据长度开辟对应的可读可写可执行的内存空间 —–> 接收payload —->调用内联汇编jmp到shellcode开始执行 —-> 执行完毕后释放空间
找出特征
要使得误杀率低,就要尽可能的区分开正常的PE文件和病毒木马的特征
我们不能只凭5A 4D,或者见到VirtualAlloc就杀,而是要找到他们之间的阶段关联性。比如,正常的程序可能会用VirtualAlloc开辟一片内存,但对于远控类型的木马,可能在VirtualAlloc之前会进行一次网络通信,并且把通信负载赋值给这片新开辟的内存空间。
因为字符串常量存储在PE结构中的.rdata节,并且是以明文存储的,在这个样本里对IP进行正则匹配可以成为一个特征点
Yara编写
1 | import yara |
这里的误杀率其实挺高的,目前我还没有深入,包括PE结构还没有摸清。