yara-x 1.19.0

A pure Rust implementation of YARA.
Documentation
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
use indexmap::IndexMap;
use nom::{
    IResult, Parser,
    bytes::complete::take,
    combinator::verify,
    error::{Error as NomError, ErrorKind},
    multi::count,
    number::complete::{le_u16, le_u32},
};

const OLECF_SIGNATURE: &[u8] =
    &[0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
const SECTOR_SHIFT: u16 = 9;
const MINI_SECTOR_SHIFT: u16 = 6;
const DIRECTORY_ENTRY_SIZE: u64 = 128;
const MAX_STREAM_SIZE: u64 = 256 * 1024 * 1024;

// Directory Entry Types
const STORAGE_TYPE: u8 = 1;
const STREAM_TYPE: u8 = 2;
const ROOT_STORAGE_TYPE: u8 = 5;

// Special sectors
const ENDOFCHAIN: u32 = 0xFFFFFFFE;
const FREESECT: u32 = 0xFFFFFFFF;
const MAX_REGULAR_SECTOR: u32 = 0xFFFFFFFA;

pub struct OLECFParser<'a> {
    data: &'a [u8],
    sector_size: usize,
    mini_sector_size: usize,
    fat_sectors: Vec<u32>,
    directory_sectors: Vec<u32>,
    mini_fat_sectors: Vec<u32>,
    dir_entries: IndexMap<String, DirectoryEntry>,
    mini_stream_start: u32,
    mini_stream_size: u64,
}

pub struct DirectoryEntry {
    pub name: String,
    pub size: u64,
    pub start_sector: u32,
    pub stream_type: u8,
}

impl<'a> OLECFParser<'a> {
    pub fn new(data: &'a [u8]) -> Result<Self, &'static str> {
        let mut parser = OLECFParser {
            data,
            sector_size: 1 << SECTOR_SHIFT,
            mini_sector_size: 1 << MINI_SECTOR_SHIFT,
            fat_sectors: Vec::new(),
            directory_sectors: Vec::new(),
            mini_fat_sectors: Vec::new(),
            dir_entries: IndexMap::new(),
            mini_stream_start: 0,
            mini_stream_size: 0,
        };

        match parser.parse(data) {
            Ok((_rest, ())) => Ok(parser),
            Err(_) => Err("Failed to parse OLECF data"),
        }
    }

    fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], ()> {
        // (A) Check the 8-byte OLECF signature.
        let (input, _) =
            verify(take(8_usize), |sig: &[u8]| sig == OLECF_SIGNATURE)
                .parse(input)?;

        // (B) Parse the rest of the header fields.
        let (input, ()) = self.parse_header(input)?;

        // (C) Parse the directory chain.
        let (input, ()) = self.parse_directory(input)?;

        Ok((input, ()))
    }

    fn parse_header(&mut self, input: &'a [u8]) -> IResult<&'a [u8], ()> {
        let (
            mut input,
            (
                _skip_20,
                byte_order,
                _skip_14,
                num_fat_sectors,
                first_dir_sector,
                _skip_8,
                first_mini_fat,
                mini_fat_count,
                first_difat_sector,
                difat_count,
            ),
        ) = (
            take(20usize), // skip 20 bytes
            le_u16,        // parse byte_order
            take(14usize), // skip 14 bytes
            le_u32,        // parse num_fat_sectors
            le_u32,        // parse first_dir_sector
            take(8usize),  // skip 8 bytes
            le_u32,        // parse first_mini_fat
            le_u32,        // parse mini_fat_count
            le_u32,        // parse first_difat_sector
            le_u32,        // parse difat_count
        )
            .parse(input)?;

        // (A) Verify `byte_order == 0xFFFE`.
        if byte_order != 0xFFFE {
            return Err(nom::Err::Error(NomError::new(
                input,
                ErrorKind::Verify,
            )));
        }

        // (B) Parse up to 109 DIFAT entries from `input`
        //     109 is the max allowed number of DIFAT entries in the header.
        let rest = input;
        if rest.len() < 109 * 4 {
            let possible = rest.len() / 4;
            let (rest2, entries) = count(le_u32, possible).parse(rest)?;
            let mut filtered = entries
                .into_iter()
                .filter(|&x| x < MAX_REGULAR_SECTOR)
                .collect::<Vec<_>>();
            self.fat_sectors.append(&mut filtered);
            input = rest2;
        } else {
            let (rest2, entries) = count(le_u32, 109).parse(rest)?;
            let mut filtered = entries
                .into_iter()
                .filter(|&x| x < MAX_REGULAR_SECTOR)
                .collect::<Vec<_>>();
            self.fat_sectors.append(&mut filtered);
            input = rest2;
        }

        // Follow the DIFAT chain if present.
        let mut next_difat_sector = first_difat_sector;
        let entries_per_sector = self.sector_size / 4;

        // Bound fat_sectors to what a file of this size can legitimately need.
        let max_fat_sectors =
            (self.data.len() / self.sector_size / entries_per_sector) + 2;

        let mut visited_difat = std::collections::HashSet::new();

        for _ in 0..difat_count {
            if next_difat_sector >= MAX_REGULAR_SECTOR
                || next_difat_sector == ENDOFCHAIN
            {
                break;
            }

            // Stop if this DIFAT sector was already processed (cycle detection).
            if !visited_difat.insert(next_difat_sector) {
                break;
            }

            let sector_data = match self.read_sector(next_difat_sector) {
                Ok(data) => data,
                Err(_) => break,
            };

            // The first (entries_per_sector - 1) entries point to FAT sectors
            for i in 0..(entries_per_sector - 1) {
                if self.fat_sectors.len() >= max_fat_sectors {
                    break;
                }
                let fat_sec = match parse_u32_at(sector_data, i * 4) {
                    Ok(sec) => sec,
                    Err(_) => break,
                };
                if fat_sec < MAX_REGULAR_SECTOR {
                    self.fat_sectors.push(fat_sec);
                }
            }

            if self.fat_sectors.len() >= max_fat_sectors {
                break;
            }

            // The last entry points to the next DIFAT sector
            next_difat_sector = match parse_u32_at(
                sector_data,
                (entries_per_sector - 1) * 4,
            ) {
                Ok(sec) => sec,
                Err(_) => break,
            };
        }

        // (C) Directory chain
        if first_dir_sector < MAX_REGULAR_SECTOR {
            self.directory_sectors = self.follow_chain(first_dir_sector);
        } else {
            return Err(nom::Err::Error(NomError::new(
                input,
                ErrorKind::Verify,
            )));
        }

        // (D) MiniFAT chain
        if mini_fat_count > 0 && first_mini_fat < MAX_REGULAR_SECTOR {
            self.mini_fat_sectors = self.follow_chain(first_mini_fat);
        }

        // (E) If no FAT sectors but num_fat_sectors != 0 => error
        if self.fat_sectors.is_empty() && num_fat_sectors > 0 {
            return Err(nom::Err::Error(NomError::new(
                input,
                ErrorKind::Verify,
            )));
        }

        Ok((input, ()))
    }

    fn parse_directory(&mut self, _input: &'a [u8]) -> IResult<&'a [u8], ()> {
        if self.directory_sectors.is_empty() {
            return Err(nom::Err::Error(NomError::new(
                _input,
                ErrorKind::Verify,
            )));
        }

        for &sector in &self.directory_sectors {
            let mut entry_offset = 0u64;

            while entry_offset + DIRECTORY_ENTRY_SIZE
                <= self.sector_size as u64
            {
                let abs_offset =
                    self.sector_to_offset(sector).saturating_add(entry_offset);

                if abs_offset.saturating_add(DIRECTORY_ENTRY_SIZE)
                    > self.data.len() as u64
                {
                    break;
                }

                if let Ok(entry) =
                    self.read_directory_entry(abs_offset as usize)
                {
                    if entry.stream_type == ROOT_STORAGE_TYPE {
                        self.mini_stream_start = entry.start_sector;
                        self.mini_stream_size = entry.size;
                    }
                    if entry.stream_type == STORAGE_TYPE
                        || entry.stream_type == STREAM_TYPE
                        || entry.stream_type == ROOT_STORAGE_TYPE
                    {
                        let overwrite = match self.dir_entries.get(&entry.name) {
                            Some(existing) => {
                                entry.stream_type == STREAM_TYPE
                                    || existing.stream_type != STREAM_TYPE
                            }
                            None => true,
                        };
                        if overwrite {
                            self.dir_entries.insert(entry.name.clone(), entry);
                        }
                    }
                }
                entry_offset += DIRECTORY_ENTRY_SIZE;
            }
        }

        Ok((_input, ()))
    }

    pub fn is_valid_header(&self) -> bool {
        self.data.len() >= OLECF_SIGNATURE.len()
            && &self.data[..OLECF_SIGNATURE.len()] == OLECF_SIGNATURE
    }

    pub fn get_stream_names(&self) -> Result<Vec<String>, &'static str> {
        if self.dir_entries.is_empty() {
            return Err("No streams found");
        }
        Ok(self.dir_entries.keys().cloned().collect())
    }

    pub fn get_streams(
        &self,
    ) -> impl Iterator<Item = (&str, &DirectoryEntry)> {
        self.dir_entries.iter().map(|(k, v)| (k.as_str(), v))
    }

    pub fn get_stream_size(
        &self,
        stream_name: &str,
    ) -> Result<u64, &'static str> {
        self.dir_entries
            .get(stream_name)
            .map(|e| e.size)
            .ok_or("Stream not found")
    }

    pub fn get_stream_data(
        &self,
        stream_name: &str,
    ) -> Result<Vec<u8>, &'static str> {
        let entry =
            self.dir_entries.get(stream_name).ok_or("Stream not found")?;

        if entry.size < 4096 && entry.stream_type != ROOT_STORAGE_TYPE {
            self.get_mini_stream_data(entry.start_sector, entry.size)
        } else {
            self.get_regular_stream_data(entry.start_sector, entry.size)
        }
    }

    fn sector_to_offset(&self, sector: u32) -> u64 {
        // The first sector begins at byte offset 512. The theoretical maximum
        // offset for a v3 OLE file (512-byte sectors, max sector number
        // 0xFFFFFFF9) is ~2 TB, so the result must be u64 — it does not fit
        // in a 32-bit usize on 32-bit targets.
        512u64 + sector as u64 * self.sector_size as u64
    }

    fn read_sector(&self, sector: u32) -> Result<&'a [u8], &'static str> {
        let offset = self.sector_to_offset(sector);
        // Narrow to usize for slice indexing; any offset that doesn't fit in
        // usize is necessarily beyond the in-memory data slice.
        let offset = usize::try_from(offset)
            .map_err(|_| "Sector offset exceeds address space")?;
        if offset + self.sector_size > self.data.len() {
            return Err("Sector read out of bounds");
        }
        Ok(&self.data[offset..offset + self.sector_size])
    }

    fn get_fat_entry(&self, sector: u32) -> Result<u32, &'static str> {
        let entry_index = sector as usize;
        let entries_per_sector = self.sector_size / 4;
        let fat_sector_index = entry_index / entries_per_sector;
        if fat_sector_index >= self.fat_sectors.len() {
            return Err("FAT entry sector index out of range");
        }
        let fat_sector = self.fat_sectors[fat_sector_index];
        let fat = self.read_sector(fat_sector)?;
        let fat_entry_offset = (entry_index % entries_per_sector) * 4;
        parse_u32_at(fat, fat_entry_offset)
    }

    fn follow_chain(&self, start_sector: u32) -> Vec<u32> {
        let mut chain = Vec::new();
        if start_sector >= MAX_REGULAR_SECTOR {
            return chain;
        }

        let mut current = start_sector;
        while current < MAX_REGULAR_SECTOR {
            // Prevent cycles by keeping track of visited sectors
            if chain.contains(&current) {
                // We've seen this sector before - it's a cycle
                break;
            }

            chain.push(current);

            let next = match self.get_fat_entry(current) {
                Ok(n) => n,
                Err(_) => break,
            };

            // Check validity of next sector
            if next >= MAX_REGULAR_SECTOR
                || next == FREESECT
                || next == ENDOFCHAIN
            {
                break;
            }

            current = next;
        }
        chain
    }

    fn read_directory_entry(
        &self,
        offset: usize,
    ) -> Result<DirectoryEntry, &'static str> {
        if offset + 128 > self.data.len() {
            return Err("Incomplete directory entry");
        }

        let name_len = parse_u16_at(self.data, offset + 64)? as usize;
        if !(2..=64).contains(&name_len) {
            return Err("Invalid name length");
        }

        let name_bytes = &self.data[offset..offset + name_len];

        // The name length stored is in bytes (including the null terminator).
        // Since it is UTF-16 LE, each character takes 2 bytes.
        // The length of units should exclude the trailing null character (2 bytes).
        let name_units_len = (name_len / 2).saturating_sub(1);
        let mut utf16_units = Vec::with_capacity(name_units_len);
        for i in 0..name_units_len {
            let unit = parse_u16_at(name_bytes, i * 2)?;
            utf16_units.push(unit);
        }

        let mut name = String::from_utf16(&utf16_units)
            .map_err(|_| "Invalid UTF-16 stream name")?;

        // According to Microsoft OLE Compound File Binary Format specifications,
        // standard system streams are prefixed with leading control bytes:
        // - U+0005 (e.g., \u{0005}SummaryInformation, \u{0005}DocumentSummaryInformation)
        //   defined in [MS-OLEPS] Section 2.21.
        // - U+0001 (e.g., \u{0001}CompObj, \u{0001}Ole)
        //   defined in standard OLE Compound Document specs for system streams.
        //
        // We strip these leading control characters (values < '\u{20}') to
        // prevent control characters from breaking standard JSON/YAML
        // serializers.
        if name.starts_with(|c: char| c < '\u{20}') {
            name.remove(0);
        }

        let stream_type = self.data[offset + 66];
        let start_sector = parse_u32_at(self.data, offset + 116)?;
        let size_32 = parse_u32_at(self.data, offset + 120)?;
        let size = size_32 as u64;

        Ok(DirectoryEntry { name, size, start_sector, stream_type })
    }

    fn get_regular_stream_data(
        &self,
        start_sector: u32,
        size: u64,
    ) -> Result<Vec<u8>, &'static str> {
        if size > MAX_STREAM_SIZE {
            return Err("Stream size exceeds maximum allowed size");
        }

        let mut data = Vec::with_capacity(size as usize);
        let mut current_sector = start_sector;
        let mut total_read = 0;

        while current_sector < MAX_REGULAR_SECTOR && total_read < size as usize
        {
            let sector_data = self.read_sector(current_sector)?;
            let bytes_to_read =
                std::cmp::min(self.sector_size, size as usize - total_read);

            data.extend_from_slice(&sector_data[..bytes_to_read]);
            total_read += bytes_to_read;

            if total_read < size as usize {
                let next = self.get_fat_entry(current_sector)?;
                if next == ENDOFCHAIN || next >= MAX_REGULAR_SECTOR {
                    break;
                }
                current_sector = next;
            }
        }

        if data.len() != size as usize {
            return Err("Incomplete stream data");
        }

        Ok(data)
    }

    fn get_root_mini_stream_data(&self) -> Result<Vec<u8>, &'static str> {
        self.get_regular_stream_data(
            self.mini_stream_start,
            self.mini_stream_size,
        )
    }

    fn get_minifat_entry(
        &self,
        mini_sector: u32,
    ) -> Result<u32, &'static str> {
        if self.mini_fat_sectors.is_empty() {
            return Ok(ENDOFCHAIN);
        }

        let entry_index = mini_sector as usize;
        let entries_per_sector = self.sector_size / 4;
        let fat_sector_index = entry_index / entries_per_sector;
        if fat_sector_index >= self.mini_fat_sectors.len() {
            return Ok(ENDOFCHAIN);
        }
        let sector = self.mini_fat_sectors[fat_sector_index];
        let fat = self.read_sector(sector)?;
        let offset = (entry_index % entries_per_sector) * 4;
        parse_u32_at(fat, offset)
    }

    fn get_mini_stream_data(
        &self,
        start_mini_sector: u32,
        size: u64,
    ) -> Result<Vec<u8>, &'static str> {
        if size > MAX_STREAM_SIZE {
            return Err("Stream size exceeds maximum allowed size");
        }

        if self.mini_stream_size == 0 {
            return Err("No mini stream present");
        }

        let mini_stream_data = self.get_root_mini_stream_data()?;
        let mini_data_len = mini_stream_data.len();

        let mut data = Vec::with_capacity(size as usize);
        let mut current = start_mini_sector;
        let mut visited = Vec::new();

        while current < MAX_REGULAR_SECTOR && data.len() < size as usize {
            if visited.contains(&current) {
                return Err("Circular reference detected in MiniFAT chain");
            }
            visited.push(current);

            let mini_offset =
                usize::try_from(current as u64 * self.mini_sector_size as u64)
                    .map_err(|_| "Mini sector offset exceeds address space")?;

            if mini_offset >= mini_data_len {
                return Err("Mini stream offset out of range");
            }

            let bytes_to_read = std::cmp::min(
                self.mini_sector_size,
                size as usize - data.len(),
            );
            if mini_offset + bytes_to_read > mini_data_len {
                return Err("Mini stream extends beyond available data");
            }

            data.extend_from_slice(
                &mini_stream_data[mini_offset..mini_offset + bytes_to_read],
            );

            if data.len() < size as usize {
                let next = self.get_minifat_entry(current)?;
                if next == ENDOFCHAIN || next >= MAX_REGULAR_SECTOR {
                    break;
                }
                current = next;
            }
        }

        if data.len() != size as usize {
            return Err("Incomplete mini stream data");
        }

        Ok(data)
    }
}

fn parse_u16_at(data: &[u8], offset: usize) -> Result<u16, &'static str> {
    if offset + 2 > data.len() {
        return Err("Buffer too small for u16");
    }
    let slice = &data[offset..offset + 2];
    match le_u16::<&[u8], NomError<&[u8]>>(slice) {
        Ok((_, val)) => Ok(val),
        Err(_) => Err("Failed to parse u16"),
    }
}

fn parse_u32_at(data: &[u8], offset: usize) -> Result<u32, &'static str> {
    if offset + 4 > data.len() {
        return Err("Buffer too small for u32");
    }
    let slice = &data[offset..offset + 4];
    match le_u32::<&[u8], NomError<&[u8]>>(slice) {
        Ok((_, val)) => Ok(val),
        Err(_) => Err("Failed to parse u32"),
    }
}