本文最后更新于: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来进行测试,不进行任何的优化设置。
#include <Windows.h> int main () { MessageBoxA (NULL , "yara rule test" , "b1ackie!" , NULL ); return 0 ; }
machine machine字段是运行平台,可以在编写时指定对应的值来判断运行平台,具体的值请去官方文档进行查看。
查看编写的测试文件的machine,可以看到是Intel386。
编写相应的规则
import "pe" rule machine { condition: pe.machine == pe.MACHINE_I386 }
timestamp 时间戳
在前面可以看到时间戳是6150116D
import "pe" rule time { condition: pe.timestamp == 0x6150116D }
可选头的大小
从前面的图片可以看到,可选头的大小是0xE0,编写规则。
import "pe" rule size_of_optional_header { condition: pe.size_of_optional_header == 0xE0 }
size_of_code 这是IMAGE_OPTIONAL_HEADER::SizeOfCode的值
import "pe" rule size_of_code { condition: pe.size_of_code == 0xE00 }
entry_point 入口点
使用OD打开测试程序,可以看到入口点处的代码十六进制值
E8 C5 03 00 00
import "pe" rule test { strings: $a = {E8 C5 03 00 00 } condition : $a at pe.entry_point }
entry_point_raw 入口点的地址
查看入口点地址
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 的值
现在来一起测试一下,查看示例程序这三项的值
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
import "pe" rule test { condition: pe.data_directories [pe.IMAGE_DIRECTORY_ENTRY_IMPORT] .virtual_address == 0 x2594 and pe.data_directories [pe.IMAGE_DIRECTORY_ENTRY_IMPORT] .size == 0 xb4 }
number_of_sections pe文件中的节的数量
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大小
查看示例程序这几项的数据
import "pe" rule test { condition: pe.sections [0] .name == ".text" and pe.sections [0] .virtual_address == 0 x1000 and pe.sections [0] .virtual_size == 0 xca1 and pe.sections [0] .raw_data_offset == 0 x400 and pe.sections [0] .raw_data_size == 0 xe00 }
resources 一个从0开始的资源对象数组,PE拥有的每个资源对应一个。可以使用[]访问单个资源。每个资源对象具有以下属性:
rva
资源数据的RVA
offset
资源数据偏移量
length
资源数据的长度
type
资源类型(整数)
id
资源的ID(整数)
language
资源的语言(整数)
type_string
资源类型为字符串
name_string
字符串的资源名称
language_string
字符串的资源语言
根据PE结构的资源部分可以知道,资源节是树状的结构,一层一层,而第一层就是资源类型,第二层是资源ID,第三层是资源语言,然后之后是具体的数据,具体这部分可以去学习一下PE结构。
看下示例文件的资源结构,如图可以看到这三项分别是24、1、1033。
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 == 0 x17D }
再拿一个别的文件来进行测试,可以看到其资源类型是字符串类型“MYRES”
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都是字符串。
import "pe" rule test { condition: pe.resources[0].type_string == "U\x 00N\x 00I\x 00C\x 00O\x 00D\x 00E\x 00" and pe.resources[0].name_string == "L\x 00O\x 00C\x 00A\x 00L\x 00I\x 00Z\x 00A\x 00T\x 00I\x 00O\x 00N\x 00" and pe.resources[0].language == 0 }
pdb_path pdb文件的路径
查看示例程序的PDB路径
import "pe" rule test { condition: pe.pdb_path == "D:\\ programing\\ something\\ YaraRule_Test\\ Release\\ YaraRule_Test.pdb" }
exports PE文件导出的函数名,可以使用函数名也可以使用序号。
测试一下user32.dll,我们知道这个DLL会导出函数MessageBoxA
import "pe" rule test { condition: pe.exports ("MessageBoxA" ) }
imports 如果一个PE文件从某DLL中导入了某个函数,则函数会返回TRUE,否则返回FALSE。我们的测试程序调用了MessageBoxA,这个函数是从user32.dll中导入的。也可以使用序号,也可以只是dll名称,具体用法还有很多,请查看官方文档。
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() }