macos_unifiedlogs/
uuidtext.rs

1// Copyright 2022 Mandiant, Inc. All Rights Reserved
2// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
3// http://www.apache.org/licenses/LICENSE-2.0
4// Unless required by applicable law or agreed to in writing, software distributed under the License
5// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6// See the License for the specific language governing permissions and limitations under the License.
7
8use log::error;
9use nom::Needed;
10use nom::bytes::complete::take;
11use nom::number::complete::le_u32;
12use serde::{Deserialize, Serialize};
13use std::mem::size_of;
14
15#[derive(Debug, Serialize, Deserialize, Default)]
16pub struct UUIDText {
17    pub uuid: String,
18    pub signature: u32,
19    pub unknown_major_version: u32,
20    pub unknown_minor_version: u32,
21    pub number_entries: u32,
22    pub entry_descriptors: Vec<UUIDTextEntry>,
23    pub footer_data: Vec<u8>, // Collection of strings containing sender process/library with end of string characters
24}
25#[derive(Debug, Serialize, Deserialize, Default)]
26pub struct UUIDTextEntry {
27    pub range_start_offset: u32,
28    pub entry_size: u32,
29}
30impl UUIDText {
31    /// Parse the UUID files in uuidinfo directory. Contains the base log message string
32    pub fn parse_uuidtext(data: &[u8]) -> nom::IResult<&[u8], UUIDText> {
33        let mut uuidtext_data = UUIDText::default();
34
35        let expected_uuidtext_signature = 0x66778899;
36        let (input, signature) = take(size_of::<u32>())(data)?;
37        let (_, uuidtext_signature) = le_u32(signature)?;
38
39        if expected_uuidtext_signature != uuidtext_signature {
40            error!(
41                "[macos-unifiedlogs] Incorrect UUIDText header signature. Expected {expected_uuidtext_signature}. Got: {uuidtext_signature}"
42            );
43            return Err(nom::Err::Incomplete(Needed::Unknown));
44        }
45
46        let (input, unknown_major_version) = take(size_of::<u32>())(input)?;
47        let (input, unknown_minor_version) = take(size_of::<u32>())(input)?;
48        let (mut input, number_entries) = take(size_of::<u32>())(input)?;
49
50        let (_, uuidtext_unknown_major_version) = le_u32(unknown_major_version)?;
51        let (_, uuidtext_unknown_minor_version) = le_u32(unknown_minor_version)?;
52        let (_, uuidtext_number_entries) = le_u32(number_entries)?;
53
54        uuidtext_data.signature = uuidtext_signature;
55        uuidtext_data.unknown_major_version = uuidtext_unknown_major_version;
56        uuidtext_data.unknown_minor_version = uuidtext_unknown_minor_version;
57        uuidtext_data.number_entries = uuidtext_number_entries;
58
59        let mut count = 0;
60        while count < uuidtext_number_entries {
61            let (entry_input, range_start_offset) = take(size_of::<u32>())(input)?;
62            let (entry_input, entry_size) = take(size_of::<u32>())(entry_input)?;
63
64            let (_, uuidtext_range_start_offset) = le_u32(range_start_offset)?;
65            let (_, uuidtext_entry_size) = le_u32(entry_size)?;
66
67            let entry_data = UUIDTextEntry {
68                range_start_offset: uuidtext_range_start_offset,
69                entry_size: uuidtext_entry_size,
70            };
71            uuidtext_data.entry_descriptors.push(entry_data);
72
73            input = entry_input;
74            count += 1;
75        }
76        uuidtext_data.footer_data = input.to_vec();
77        Ok((input, uuidtext_data))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::uuidtext::UUIDText;
84    use std::fs;
85    use std::path::PathBuf;
86
87    #[test]
88    fn test_parse_uuidtext_big_sur() {
89        let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
90        test_path.push("tests/test_data/UUIDText/Big Sur/1FE459BBDC3E19BBF82D58415A2AE9");
91
92        let buffer = fs::read(test_path).unwrap();
93
94        let (_, uuidtext_data) = UUIDText::parse_uuidtext(&buffer).unwrap();
95        assert_eq!(uuidtext_data.signature, 0x66778899);
96        assert_eq!(uuidtext_data.unknown_major_version, 2);
97        assert_eq!(uuidtext_data.unknown_minor_version, 1);
98        assert_eq!(uuidtext_data.number_entries, 2);
99        assert_eq!(uuidtext_data.entry_descriptors[0].entry_size, 617);
100        assert_eq!(uuidtext_data.entry_descriptors[1].entry_size, 2301);
101
102        assert_eq!(uuidtext_data.entry_descriptors[0].range_start_offset, 32048);
103        assert_eq!(uuidtext_data.entry_descriptors[1].range_start_offset, 29747);
104        assert_eq!(uuidtext_data.footer_data.len(), 2987);
105    }
106
107    #[test]
108    fn test_parse_uuidtext_high_sierra() {
109        let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
110        test_path.push("tests/test_data/UUIDText/High Sierra/425A2E5B5531B98918411B4379EE5F");
111
112        let buffer = fs::read(test_path).unwrap();
113
114        let (_, uuidtext_data) = UUIDText::parse_uuidtext(&buffer).unwrap();
115        assert_eq!(uuidtext_data.signature, 0x66778899);
116        assert_eq!(uuidtext_data.unknown_major_version, 2);
117        assert_eq!(uuidtext_data.unknown_minor_version, 1);
118        assert_eq!(uuidtext_data.number_entries, 1);
119        assert_eq!(uuidtext_data.entry_descriptors[0].entry_size, 2740);
120        assert_eq!(uuidtext_data.entry_descriptors[0].range_start_offset, 21132);
121
122        assert_eq!(uuidtext_data.footer_data.len(), 2951);
123    }
124
125    #[test]
126    #[should_panic(expected = "Incomplete(Unknown)")]
127    fn test_bad_header() {
128        let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
129        test_path
130            .push("tests/test_data/Bad Data/UUIDText/Bad_Header_1FE459BBDC3E19BBF82D58415A2AE9");
131
132        let buffer = fs::read(test_path).unwrap();
133        let (_, _) = UUIDText::parse_uuidtext(&buffer).unwrap();
134    }
135
136    #[test]
137    #[should_panic(expected = "Eof")]
138    fn test_bad_content() {
139        let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
140        test_path
141            .push("tests/test_data/Bad Data/UUIDText/Bad_Content_1FE459BBDC3E19BBF82D58415A2AE9");
142
143        let buffer = fs::read(test_path).unwrap();
144        let (_, _) = UUIDText::parse_uuidtext(&buffer).unwrap();
145    }
146
147    #[test]
148    #[should_panic(expected = "Incomplete(Unknown)")]
149    fn test_bad_file() {
150        let mut test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
151        test_path.push("tests/test_data/Bad Data/UUIDText/Badfile.txt");
152
153        let buffer = fs::read(test_path).unwrap();
154        let (_, _) = UUIDText::parse_uuidtext(&buffer).unwrap();
155    }
156}