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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#[cfg(feature = "gpu")]
use bytemuck::{Pod, Zeroable};
use crate::error::{Error, Result};
/// VM opcode.
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Opcode {
/// Push `true` onto the evaluation stack.
PushTrue = 1,
/// Push `false` onto the evaluation stack.
PushFalse = 2,
/// Push whether the current string matched.
PushStringMatched = 3,
/// Push the match count for the current string.
PushStringCount = 4,
/// Push the offset of the current string match.
PushStringOffset = 5,
/// Push the length of the current string match.
PushStringLength = 6,
/// Push the file size in bytes.
PushFileSize = 7,
/// Push the number of rule entries.
PushEntryCount = 8,
/// Push an immediate operand value.
PushImmediate = 9,
/// Push the total number of strings.
PushNumStrings = 10,
/// Boolean AND of the top two stack values.
And = 11,
/// Boolean OR of the top two stack values.
Or = 12,
/// Boolean NOT of the top stack value.
Not = 13,
/// Equality comparison.
Eq = 14,
/// Inequality comparison.
Neq = 15,
/// Less-than comparison.
Lt = 16,
/// Greater-than comparison.
Gt = 17,
/// Less-than-or-equal comparison.
Lte = 18,
/// Greater-than-or-equal comparison.
Gte = 19,
/// Arithmetic addition.
Add = 20,
/// Arithmetic subtraction.
Sub = 21,
/// Count the number of matching items.
CountOf = 22,
/// True when all items match.
AllOf = 23,
/// True when any item matches.
AnyOf = 24,
/// Check whether a string exists at a specific offset.
StringAt = 25,
/// Check whether a string exists in a range.
StringIn = 26,
/// Begin a `for any` iteration block.
ForAny = 27,
/// Begin a `for all` iteration block.
ForAll = 28,
/// End the current `for` block.
EndFor = 29,
/// Stop execution.
Halt = 30,
/// Arithmetic multiplication.
Mul = 31,
/// Arithmetic division.
Div = 32,
/// Arithmetic modulo.
Mod = 33,
/// Bitwise AND.
BitAnd = 34,
/// Bitwise OR.
BitOr = 35,
/// Bitwise XOR.
BitXor = 36,
/// Bitwise shift left.
Shl = 37,
/// Bitwise shift right.
Shr = 38,
/// Read a little-endian integer at the given offset.
ReadIntAt = 39,
/// Begin a `for N` iteration block.
ForN = 40,
/// Push the file entropy bucket.
PushEntropy = 41,
/// Push whether the file is a PE.
PushIsPe = 42,
/// Push whether the PE is a DLL.
PushIsDll = 43,
/// Push the number of PE sections.
PushNumSections = 44,
/// Push the number of PE imports.
PushNumImports = 45,
/// Push the PE entry point RVA.
PushEntryPoint = 46,
/// Push whether the PE has a signature.
PushHasSignature = 47,
/// Push the first four bytes of the file as a `u32`.
PushMagicU32 = 48,
/// Push whether the PE is 64-bit.
PushIs64bit = 49,
}
impl Opcode {
/// Decode an opcode from its serialized `u32` form.
///
/// # Examples
/// ```
/// use rulefire::Opcode;
///
/// assert_eq!(Opcode::from_u32(1).unwrap(), Opcode::PushTrue);
/// ```
pub fn from_u32(value: u32) -> Result<Self> {
const ALL: &[(u32, Opcode)] = &[
(1, Opcode::PushTrue), (2, Opcode::PushFalse), (3, Opcode::PushStringMatched),
(4, Opcode::PushStringCount), (5, Opcode::PushStringOffset),
(6, Opcode::PushStringLength), (7, Opcode::PushFileSize), (8, Opcode::PushEntryCount),
(9, Opcode::PushImmediate), (10, Opcode::PushNumStrings), (11, Opcode::And),
(12, Opcode::Or), (13, Opcode::Not), (14, Opcode::Eq), (15, Opcode::Neq),
(16, Opcode::Lt), (17, Opcode::Gt), (18, Opcode::Lte), (19, Opcode::Gte),
(20, Opcode::Add), (21, Opcode::Sub), (22, Opcode::CountOf), (23, Opcode::AllOf),
(24, Opcode::AnyOf), (25, Opcode::StringAt), (26, Opcode::StringIn),
(27, Opcode::ForAny), (28, Opcode::ForAll), (29, Opcode::EndFor), (30, Opcode::Halt),
(31, Opcode::Mul), (32, Opcode::Div), (33, Opcode::Mod), (34, Opcode::BitAnd),
(35, Opcode::BitOr), (36, Opcode::BitXor), (37, Opcode::Shl), (38, Opcode::Shr),
(39, Opcode::ReadIntAt), (40, Opcode::ForN), (41, Opcode::PushEntropy),
(42, Opcode::PushIsPe), (43, Opcode::PushIsDll), (44, Opcode::PushNumSections),
(45, Opcode::PushNumImports), (46, Opcode::PushEntryPoint),
(47, Opcode::PushHasSignature), (48, Opcode::PushMagicU32),
(49, Opcode::PushIs64bit),
];
ALL.iter()
.find_map(|(raw, opcode)| (*raw == value).then_some(*opcode))
.ok_or_else(|| Error::BytecodeValidation {
message: format!("unknown opcode {value}"),
})
}
}
/// Single 8-byte VM instruction.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "gpu", derive(Pod, Zeroable))]
pub struct Instruction {
/// Opcode as `u32`.
pub opcode: u32,
/// Instruction operand.
pub operand: u32,
}
impl Instruction {
/// Create an instruction.
///
/// # Examples
/// ```
/// use rulefire::{Instruction, Opcode};
///
/// let instruction = Instruction::new(Opcode::PushImmediate, 7);
/// assert_eq!(instruction.operand, 7);
/// ```
pub const fn new(opcode: Opcode, operand: u32) -> Self {
Self {
opcode: opcode as u32,
operand,
}
}
/// Parse the opcode kind for this instruction.
///
/// # Examples
/// ```
/// use rulefire::{Instruction, Opcode};
///
/// let instruction = Instruction::new(Opcode::PushTrue, 0);
/// assert_eq!(instruction.kind().unwrap(), Opcode::PushTrue);
/// ```
pub fn kind(self) -> Result<Opcode> {
Opcode::from_u32(self.opcode)
}
}