yara规则学习笔记(三)

本文最后更新于:2021-09-26 晚上

前言

基本的一些语法,规则的应用基本已经学习完了,现在来练习一下模块的用法,主要先是PE这个模块,具体的每一个值的用法在官方文档中已经描述的非常详细了,我就不再过多的去介绍其用法了,我只是拿过来部分去进行实际的编写测试。在本篇之前,应该至少要对PE文件结构有了解,如果对PE还不是非常的了解的话,建议先去学习PE结构。

官方文档PE模块地址:

https://yara.readthedocs.io/en/v4.1.2/modules/pe.html

之前的学习笔记

yara规则学习笔记(一)

yara规则学习笔记(二)

PE模块

导入PE模块非常简单,加入

import “pe”

这样即可导入。

该模块中公布了PE结构中大部分存在的字段,并提供了可用于编写更加具有针对性的功能。

官方示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import "pe"

rule single_section
{
condition:
pe.number_of_sections == 1
}

rule control_panel_applet
{
condition:
pe.exports("CPlApplet")
}

rule is_dll
{
condition:
pe.characteristics & pe.DLL
}

rule is_pe
{
condition:
pe.is_pe
}

首先先编写一个弹窗的exe来进行测试,不进行任何的优化设置。

1
2
3
4
5
6
#include <Windows.h>
int main()
{
MessageBoxA(NULL, "yara rule test", "b1ackie!", NULL);
return 0;
}

machine

machine字段是运行平台,可以在编写时指定对应的值来判断运行平台,具体的值请去官方文档进行查看。

查看编写的测试文件的machine,可以看到是Intel386。

编写相应的规则

1
2
3
4
5
6
7
import "pe"

rule machine
{
condition:
pe.machine == pe.MACHINE_I386
}

timestamp

时间戳

在前面可以看到时间戳是6150116D

1
2
3
4
5
6
7
import "pe"

rule time
{
condition:
pe.timestamp == 0x6150116D
}

size_of_optional_header

可选头的大小

从前面的图片可以看到,可选头的大小是0xE0,编写规则。

1
2
3
4
5
6
7
import "pe"

rule size_of_optional_header
{
condition:
pe.size_of_optional_header == 0xE0
}

size_of_code

这是IMAGE_OPTIONAL_HEADER::SizeOfCode的值

1
2
3
4
5
6
7
import "pe"

rule size_of_code
{
condition:
pe.size_of_code == 0xE00
}

entry_point

入口点

使用OD打开测试程序,可以看到入口点处的代码十六进制值

E8 C5 03 00 00

1
2
3
4
5
6
7
8
9
import "pe"

rule test
{
strings:
$a = {E8 C5 03 00 00}
condition:
$a at pe.entry_point
}

entry_point_raw

入口点的地址

查看入口点地址

1
2
3
4
5
6
7
import "pe"

rule test
{
condition:
pe.entry_point_raw == 0x126C
}

base_of_code

IMAGE_OPTIONAL_HEADER::BaseOfCode 的值

base_of_data

IMAGE_OPTIONAL_HEADER::BaseOfData 的值

image_base

IMAGE_OPTIONAL_HEADER::ImageBase 的值

现在来一起测试一下,查看示例程序这三项的值

1
2
3
4
5
6
7
8
9
import "pe"

rule test
{
condition:
pe.base_of_code == 0x1000 and
pe.base_of_data == 0x2000 and
pe.image_base == 0x400000
}

data.directories

数据目录表中的IMAGE_DATA_DIRECTORY结构信息,RVA和SIZE。具体的字段参考官方文档。

这里用导入表(IMAGE_DIRECTORY_ENTRY_IMPORT)来做测试,查看示例程序的导入表的RVA和size

1
2
3
4
5
6
7
8
9
10
11
12
13
import "pe"

rule test
{
condition:
pe.data_directories
[pe.IMAGE_DIRECTORY_ENTRY_IMPORT].virtual_address
== 0x2594
and
pe.data_directories
[pe.IMAGE_DIRECTORY_ENTRY_IMPORT].size
== 0xb4
}

number_of_sections

pe文件中的节的数量

1
2
3
4
5
6
7
import "pe"

rule test
{
condition:
pe.number_of_sections == 5
}

sections

PE文件中的节的信息,PE 的每个部分对应一个。可以使用 [] 运算符访问各个部分。具体的信息可以去官方文档进行查看。这里介绍部分。

name,名称

virtual_address,虚拟地址

virtual_size,虚拟大小

raw_data_offset,raw地址

raw_data_size,raw大小

查看示例程序这几项的数据

1
2
3
4
5
6
7
8
9
10
11
import "pe"

rule test
{
condition:
pe.sections[0].name == ".text" and
pe.sections[0].virtual_address == 0x1000 and
pe.sections[0].virtual_size == 0xca1 and
pe.sections[0].raw_data_offset == 0x400 and
pe.sections[0].raw_data_size == 0xe00
}

resources

一个从0开始的资源对象数组,PE拥有的每个资源对应一个。可以使用[]访问单个资源。每个资源对象具有以下属性:

rva 资源数据的RVA

offset 资源数据偏移量

length 资源数据的长度

type 资源类型(整数)

id 资源的ID(整数)

language 资源的语言(整数)

type_string 资源类型为字符串

name_string 字符串的资源名称

language_string 字符串的资源语言

根据PE结构的资源部分可以知道,资源节是树状的结构,一层一层,而第一层就是资源类型,第二层是资源ID,第三层是资源语言,然后之后是具体的数据,具体这部分可以去学习一下PE结构。

看下示例文件的资源结构,如图可以看到这三项分别是24、1、1033。

1
2
3
4
5
6
7
8
9
import "pe"
rule test
{
condition:
pe.resources[0].type == 24 and
pe.resources[0].id == 1 and
pe.resources[0].language == 1033 and
pe.resources[0].length == 0x17D
}

再拿一个别的文件来进行测试,可以看到其资源类型是字符串类型“MYRES”

1
2
3
4
5
6
7
8
9
10
import "pe"
rule test
{
condition:
pe.resources[0].type_string == "M\x00Y\x00R\x00E\x00S\x00" and
pe.resources[0].id == 102 and
pe.resources[0].language == 2052
}


再测试一个文件,看到资源类型和资源ID都是字符串。

1
2
3
4
5
6
7
8
9
10
import "pe"
rule test
{
condition:
pe.resources[0].type_string == "U\x00N\x00I\x00C\x00O\x00D\x00E\x00" and
pe.resources[0].name_string == "L\x00O\x00C\x00A\x00L\x00I\x00Z\x00A\x00T\x00I\x00O\x00N\x00" and
pe.resources[0].language == 0
}


pdb_path

pdb文件的路径

查看示例程序的PDB路径

1
2
3
4
5
6
import "pe"
rule test
{
condition:
pe.pdb_path == "D:\\programing\\something\\YaraRule_Test\\Release\\YaraRule_Test.pdb"
}

exports

PE文件导出的函数名,可以使用函数名也可以使用序号。

测试一下user32.dll,我们知道这个DLL会导出函数MessageBoxA

1
2
3
4
5
6
import "pe"
rule test
{
condition:
pe.exports("MessageBoxA")
}

imports

如果一个PE文件从某DLL中导入了某个函数,则函数会返回TRUE,否则返回FALSE。我们的测试程序调用了MessageBoxA,这个函数是从user32.dll中导入的。也可以使用序号,也可以只是dll名称,具体用法还有很多,请查看官方文档。

1
2
3
4
5
6
import "pe"
rule test
{
condition:
pe.imports("user32.dll","MessageBoxA")
}

is_pe

如果文件是PE文件,返回true

is_dll()

如果是DLL文件,返回true

is_32bit()

如果PE文件是32位的,返回true

is_64bit()

如果PE文件是64位,返回true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import "pe"
rule ispe
{
condition:
pe.is_pe
}
rule isdll
{
condition:
pe.is_dll()
}
rule is32
{
condition:
pe.is_32bit()
}
rule is64
{
condition:
pe.is_64bit()
}