├── .github └── dependabot.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── bin └── main.rs ├── lib.rs └── rsrc.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wevt_template" 3 | version = "0.1.0" 4 | authors = ["William Ballenthin "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | goblin = {version="0.5", features=["pe32", "pe64"]} 9 | log = "0.4" 10 | byteorder = "1" 11 | clap = "3" 12 | fern = "0.6" 13 | chrono = "0.4" 14 | better-panic = "0.3" 15 | anyhow = "1" 16 | thiserror = "1" 17 | hex = "0.4" 18 | tempfile = "3" 19 | simple_logger = "1" 20 | bitflags = "1" 21 | widestring = "0.5" 22 | uuid = "1.0" 23 | evtx = {version="0.7", features=[]} 24 | encoding = "0.2" 25 | lancelot = {git = "https://github.com/williballenthin/lancelot", rev = "1285a34937aaa42ed0b65e6d6ab95eb0daca44be" } 26 | 27 | [profile.release] 28 | lto = true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wevt_template 2 | extract and parse WEVT_TEMPLATEs from PE files 3 | 4 | 5 | ## references 6 | 7 | - [forensikblog.de/Linking Event Messages and Resource DLLs](https://computer.forensikblog.de/en/2010/10/linking-event-messages-and-resource-dlls.html) 8 | - [TZWorks/Event Log MessageTables Offline (elmo)](https://tzworks.net/prototype_page.php?proto_id=35) 9 | - [libfwevt](https://github.com/libyal/libfwevt/blob/main/documentation/Windows%20Event%20manifest%20binary%20format.asciidoc) 10 | - [medium.com/palatir/Tampering with Windows Event Tracing: Background, Offense, and Defense](https://medium.com/palantir/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63) 11 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use log::{debug, error, info}; 3 | 4 | extern crate anyhow; 5 | extern crate clap; 6 | extern crate log; 7 | 8 | use wevt_template::*; 9 | 10 | fn _main() -> Result<()> { 11 | better_panic::install(); 12 | 13 | let matches = clap::App::new("wevt_template") 14 | .author("Willi Ballenthin ") 15 | .about("extract and parse WEVT_TEMPLATEs from PE files") 16 | .arg( 17 | clap::Arg::with_name("verbose") 18 | .short("v") 19 | .long("verbose") 20 | .multiple(true) 21 | .help("log verbose messages"), 22 | ) 23 | .arg( 24 | clap::Arg::with_name("input") 25 | .required(true) 26 | .index(1) 27 | .help("path to input PE file"), 28 | ) 29 | .get_matches(); 30 | 31 | let log_level = match matches.occurrences_of("verbose") { 32 | 0 => log::LevelFilter::Info, 33 | 1 => log::LevelFilter::Debug, 34 | 2 => log::LevelFilter::Trace, 35 | _ => log::LevelFilter::Trace, 36 | }; 37 | 38 | fern::Dispatch::new() 39 | .format(move |out, message, record| { 40 | out.finish(format_args!( 41 | "{} [{:5}] {} {}", 42 | chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), 43 | record.level(), 44 | if log_level == log::LevelFilter::Trace { 45 | record.target() 46 | } else { 47 | "" 48 | }, 49 | message 50 | )) 51 | }) 52 | .level(log_level) 53 | .chain(std::io::stderr()) 54 | .filter(|metadata| !metadata.target().starts_with("goblin::pe")) 55 | .apply() 56 | .expect("failed to configure logging"); 57 | 58 | let buf = lancelot::util::read_file(matches.value_of("input").unwrap())?; 59 | let pe = lancelot::loader::pe::load_pe(&buf)?; 60 | 61 | for (i, template) in get_wevt_templates(&pe)?.iter().enumerate() { 62 | info!("template {}:", i); 63 | } 64 | 65 | Ok(()) 66 | } 67 | 68 | fn main() { 69 | if let Err(e) = _main() { 70 | error!("{:?}", e); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use byteorder::{ByteOrder, LittleEndian}; 3 | use lancelot::loader::pe::PE; 4 | use log::{debug, warn}; 5 | 6 | pub mod rsrc; 7 | 8 | pub struct WevtTemplate { 9 | pub langid: u32, 10 | buf: Vec, 11 | } 12 | 13 | #[derive(Debug)] 14 | struct CRIM { 15 | signature: u32, 16 | size: u32, 17 | major_version: u16, 18 | minor_version: u16, 19 | event_providers: Vec, 20 | } 21 | 22 | #[derive(Debug)] 23 | struct EventProviderDescriptor { 24 | guid: uuid::Uuid, 25 | offset: u32, 26 | } 27 | 28 | #[derive(Debug)] 29 | struct WEVT { 30 | signature: u32, 31 | size: u32, 32 | message_table_identifier: Option, 33 | element_descriptors: Vec, 34 | unk: Vec, 35 | } 36 | 37 | #[derive(Debug)] 38 | struct ElementDescriptor { 39 | offset: u32, 40 | unk: u32, 41 | } 42 | 43 | impl WevtTemplate { 44 | fn read_u8(&self, offset: usize) -> Result { 45 | // TODO: bounds check 46 | Ok(self.buf[offset]) 47 | } 48 | 49 | fn read_u16(&self, offset: usize) -> Result { 50 | // TODO: bounds check 51 | let buf = &self.buf[offset..offset + 2]; 52 | Ok(LittleEndian::read_u16(buf)) 53 | } 54 | 55 | fn read_u32(&self, offset: usize) -> Result { 56 | // TODO: bounds check 57 | let buf = &self.buf[offset..offset + 4]; 58 | Ok(LittleEndian::read_u32(buf)) 59 | } 60 | 61 | fn read_buf(&self, offset: usize, length: usize) -> Result> { 62 | // TODO: bounds check 63 | Ok(self.buf[offset..offset + length].to_vec()) 64 | } 65 | 66 | fn read_into(&self, offset: usize, buf: &mut [u8]) -> Result<()> { 67 | // TODO: bounds check 68 | buf.copy_from_slice(&self.buf[offset..offset + buf.len()]); 69 | Ok(()) 70 | } 71 | 72 | fn read_guid(&self, offset: usize) -> Result { 73 | let mut guid = [0u8; 16]; 74 | self.read_into(offset + 0, &mut guid); 75 | 76 | // u32be 77 | guid.swap(0, 3); 78 | guid.swap(1, 2); 79 | // u16be 80 | guid.swap(4, 5); 81 | // u16be 82 | guid.swap(6, 7); 83 | 84 | Ok(uuid::Builder::from_bytes(guid).build()) 85 | } 86 | } 87 | 88 | impl CRIM { 89 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 90 | let event_provider_count = tmpl.read_u32(offset + 12)?; 91 | let mut event_providers = vec![]; 92 | 93 | for i in 0..event_provider_count { 94 | let descriptor = EventProviderDescriptor::read(tmpl, offset + 16 + (20 * i as usize))?; 95 | event_providers.push(descriptor); 96 | } 97 | 98 | Ok(CRIM { 99 | signature: tmpl.read_u32(offset + 0)?, 100 | size: tmpl.read_u32(offset + 4)?, 101 | major_version: tmpl.read_u16(offset + 8)?, 102 | minor_version: tmpl.read_u16(offset + 10)?, 103 | // event_provider_count, offset + 12 104 | event_providers, // offset + 16 105 | }) 106 | } 107 | } 108 | 109 | impl EventProviderDescriptor { 110 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 111 | Ok(EventProviderDescriptor { 112 | guid: tmpl.read_guid(offset + 0)?, 113 | offset: tmpl.read_u32(offset + 16)?, 114 | }) 115 | } 116 | 117 | fn event_provider(&self, tmpl: &WevtTemplate) -> Result { 118 | WEVT::read(tmpl, self.offset as usize) 119 | } 120 | } 121 | 122 | impl WEVT { 123 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 124 | let message_table_identifier = match tmpl.read_u32(offset + 8)? { 125 | 0xFFFFFFFF => None, 126 | id @ 0..=0xFFFFFFFE => Some(id), 127 | }; 128 | 129 | let element_descriptor_count = tmpl.read_u32(offset + 12)?; 130 | let mut element_descriptors = vec![]; 131 | for i in 0..element_descriptor_count { 132 | element_descriptors.push(ElementDescriptor::read( 133 | tmpl, 134 | offset + 20 + (8 * i as usize), 135 | )?); 136 | } 137 | 138 | Ok(WEVT { 139 | signature: tmpl.read_u32(offset + 0)?, 140 | size: tmpl.read_u32(offset + 4)?, 141 | message_table_identifier, // offset + 8 142 | // element_descriptor_count, offset + 12 143 | // unk_count, offset + 16 144 | element_descriptors, // offset + 20 145 | unk: vec![], // offset + varies 146 | }) 147 | } 148 | } 149 | 150 | impl ElementDescriptor { 151 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 152 | Ok(ElementDescriptor { 153 | offset: tmpl.read_u32(offset + 0)?, 154 | unk: tmpl.read_u32(offset + 4)?, 155 | }) 156 | } 157 | 158 | fn element(&self, tmpl: &WevtTemplate) -> Result { 159 | let mut signature = [0u8; 4]; 160 | tmpl.read_into(self.offset as usize, &mut signature)?; 161 | 162 | match &signature[..] { 163 | b"CHAN" => Ok(Element::CHAN(CHAN::read(tmpl, self.offset as usize)?)), 164 | b"TTBL" => Ok(Element::TTBL(TTBL::read(tmpl, self.offset as usize)?)), 165 | _ => { 166 | warn!( 167 | "unsupported element: {}", 168 | String::from_utf8_lossy(&signature) 169 | ); 170 | todo!() 171 | } 172 | } 173 | } 174 | } 175 | 176 | #[derive(Debug)] 177 | struct KEYW {} 178 | #[derive(Debug)] 179 | struct LEVL {} 180 | #[derive(Debug)] 181 | struct MAPS {} 182 | #[derive(Debug)] 183 | struct BMAP {} 184 | #[derive(Debug)] 185 | struct VMAP {} 186 | 187 | #[derive(Debug)] 188 | struct ChannelDefinition { 189 | identifier: u32, 190 | offset: u32, 191 | message_table_identifier: Option, 192 | } 193 | 194 | impl ChannelDefinition { 195 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 196 | let message_table_identifier = match tmpl.read_u32(offset + 12)? { 197 | 0xFFFFFFFF => None, 198 | id @ 0..=0xFFFFFFFE => Some(id), 199 | }; 200 | 201 | Ok(ChannelDefinition { 202 | identifier: tmpl.read_u32(offset + 0)?, 203 | offset: tmpl.read_u32(offset + 4)?, 204 | message_table_identifier, 205 | }) 206 | } 207 | 208 | fn data(&self, tmpl: &WevtTemplate) -> Result { 209 | ChannelData::read(tmpl, self.offset as usize) 210 | } 211 | } 212 | 213 | #[derive(Debug)] 214 | struct ChannelData { 215 | value: Vec, 216 | } 217 | 218 | impl ChannelData { 219 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 220 | let size = tmpl.read_u32(offset + 0)?; 221 | let value = tmpl.read_buf(offset + 4, size as usize - 4)?; 222 | Ok(ChannelData { value }) 223 | } 224 | 225 | fn string(&self) -> Result { 226 | let chars: Vec = self 227 | .value 228 | .chunks_exact(2) 229 | .map(|buf| LittleEndian::read_u16(buf)) 230 | .collect(); 231 | 232 | widestring::U16String::from_vec(chars) 233 | .to_string() 234 | .map_err(|e| e.into()) 235 | } 236 | } 237 | 238 | #[derive(Debug)] 239 | struct CHAN { 240 | signature: u32, 241 | size: u32, 242 | channel_definitions: Vec, 243 | } 244 | 245 | impl CHAN { 246 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 247 | let channel_definition_count = tmpl.read_u32(offset + 8)?; 248 | let mut channel_definitions = vec![]; 249 | for i in 0..channel_definition_count { 250 | channel_definitions.push(ChannelDefinition::read( 251 | tmpl, 252 | offset + 12 + (16 * i as usize), 253 | )?); 254 | } 255 | 256 | Ok(CHAN { 257 | signature: tmpl.read_u32(offset + 0)?, 258 | size: tmpl.read_u32(offset + 4)?, 259 | channel_definitions, 260 | }) 261 | } 262 | } 263 | 264 | #[derive(Debug)] 265 | struct EVTN {} 266 | #[derive(Debug)] 267 | struct OPCO {} 268 | #[derive(Debug)] 269 | struct TASK {} 270 | 271 | #[derive(Debug)] 272 | struct VariableDescriptor { 273 | value_type: u8, 274 | offset: u32, 275 | } 276 | 277 | impl VariableDescriptor { 278 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 279 | Ok(VariableDescriptor { 280 | value_type: tmpl.read_u8(offset + 4)?, 281 | offset: tmpl.read_u32(offset + 16)?, 282 | }) 283 | } 284 | } 285 | 286 | #[derive(Debug)] 287 | struct VariableName { 288 | value: Vec, 289 | } 290 | 291 | impl VariableName { 292 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 293 | let size = tmpl.read_u32(offset + 0)?; 294 | let value = tmpl.read_buf(offset + 4, size as usize - 4)?; 295 | Ok(VariableName { value }) 296 | } 297 | 298 | fn string(&self) -> Result { 299 | let chars: Vec = self 300 | .value 301 | .chunks_exact(2) 302 | .map(|buf| LittleEndian::read_u16(buf)) 303 | .collect(); 304 | 305 | widestring::U16String::from_vec(chars) 306 | .to_string() 307 | .map_err(|e| e.into()) 308 | } 309 | } 310 | 311 | #[derive(Debug)] 312 | struct TEMP { 313 | signature: u32, 314 | size: u32, 315 | offset: u32, 316 | guid: uuid::Uuid, 317 | bxml: Vec, 318 | variable_descriptors: Vec, 319 | variable_names: Vec, 320 | } 321 | 322 | impl TEMP { 323 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 324 | let buf = tmpl.read_buf(offset + 40, 0x100)?; 325 | 326 | debug!("binxml:\n{}", lancelot::util::hexdump(&buf, 0x0)); 327 | 328 | let de = evtx::binxml::deserializer::BinXmlDeserializer::init( 329 | &buf, 330 | 0x0, 331 | None, 332 | false, 333 | encoding::all::WINDOWS_1252, 334 | ); 335 | 336 | let mut iterator = de.iter_tokens(None)?; 337 | 338 | loop { 339 | let token = iterator.next(); 340 | if let Some(t) = token { 341 | debug!("token: {:#x?}", t); 342 | } else { 343 | break; 344 | } 345 | } 346 | 347 | Ok(TEMP { 348 | signature: tmpl.read_u32(offset + 0)?, 349 | size: tmpl.read_u32(offset + 4)?, 350 | offset: tmpl.read_u32(offset + 16)?, 351 | guid: tmpl.read_guid(offset + 24)?, 352 | // TODO 353 | bxml: vec![], // how long is this?? 354 | variable_descriptors: vec![], 355 | variable_names: vec![], 356 | }) 357 | } 358 | } 359 | 360 | #[derive(Debug)] 361 | struct TTBL { 362 | signature: u32, 363 | size: u32, 364 | templates: Vec, 365 | } 366 | 367 | impl TTBL { 368 | fn read(tmpl: &WevtTemplate, offset: usize) -> Result { 369 | let count = tmpl.read_u32(offset + 8)?; 370 | let mut templates = vec![]; 371 | let mut bytes_read = 0; 372 | for i in 0..count { 373 | let temp = TEMP::read(tmpl, offset + 12 + bytes_read)?; 374 | bytes_read += temp.size as usize; 375 | templates.push(temp); 376 | } 377 | 378 | Ok(TTBL { 379 | signature: tmpl.read_u32(offset + 0)?, 380 | size: tmpl.read_u32(offset + 4)?, 381 | templates, 382 | }) 383 | } 384 | } 385 | 386 | #[derive(Debug)] 387 | enum Element { 388 | KEYW(KEYW), 389 | LEVL(LEVL), 390 | MAPS(MAPS), 391 | BMAP(BMAP), 392 | VMAP(VMAP), 393 | CHAN(CHAN), 394 | EVTN(EVTN), 395 | OPCO(OPCO), 396 | TASK(TASK), 397 | TTBL(TTBL), 398 | TEMP(TEMP), 399 | } 400 | 401 | pub fn get_wevt_templates(pe: &PE) -> Result> { 402 | // found at .rsrc node path: "WEVT_TEMPLATE" / 0x1 / ${langid} 403 | 404 | let mut ret = vec![]; 405 | 406 | let rsrc = match rsrc::ResourceSectionData::from_pe(pe)? { 407 | None => return Ok(vec![]), 408 | Some(rsrc) => rsrc, 409 | }; 410 | 411 | let wevt_template_node = match rsrc.root()?.get_child_by_name(&rsrc, "WEVT_TEMPLATE")? { 412 | Some(rsrc::NodeChild::Node(node)) => node, 413 | _ => return Ok(vec![]), 414 | }; 415 | 416 | let one_node = match wevt_template_node.get_child_by_id(&rsrc, 1)? { 417 | Some(rsrc::NodeChild::Node(node)) => node, 418 | _ => return Ok(vec![]), 419 | }; 420 | 421 | for (lang, lang_node) in one_node.children(&rsrc)?.iter() { 422 | match (lang.id(&rsrc)?, lang_node) { 423 | (rsrc::NodeIdentifier::ID(langid), rsrc::NodeChild::Data(descriptor)) => { 424 | debug!( 425 | "WEVT_TEMPLATE: lang: {:} offset: {:#x} size: {:#x}", 426 | langid, descriptor.rva, descriptor.size 427 | ); 428 | let buf = descriptor.data(&pe)?; 429 | debug!("\n{}", lancelot::util::hexdump(&buf[..0x400], 0x0)); 430 | 431 | let tmpl = WevtTemplate { langid, buf }; 432 | let crim = CRIM::read(&tmpl, 0x0)?; 433 | debug!("crim: {:#x?}", crim); 434 | 435 | for (i, event_provider_ref) in crim.event_providers.iter().enumerate() { 436 | let event_provider = crim.event_providers[0].event_provider(&tmpl)?; 437 | debug!("event_provider[{}]: {:#x?}", i, event_provider); 438 | 439 | for (j, elem_ref) in event_provider.element_descriptors.iter().enumerate() { 440 | let elem = elem_ref.element(&tmpl)?; 441 | debug!("element[{}]: {:#x?}", j, elem); 442 | 443 | if let Element::CHAN(chan) = elem { 444 | let cd = &chan.channel_definitions[0]; 445 | let data = cd.data(&tmpl)?; 446 | 447 | debug!("defintions.data: {:#x?}", data.string()?); 448 | } 449 | } 450 | } 451 | 452 | ret.push(tmpl); 453 | } 454 | _ => continue, 455 | } 456 | } 457 | 458 | Ok(ret) 459 | } 460 | -------------------------------------------------------------------------------- /src/rsrc.rs: -------------------------------------------------------------------------------- 1 | // https://github.com/libyal/libexe/blob/4b74c91226e7d174bdff74315129bc17b956d564/documentation/Executable%20(EXE)%20file%20format.asciidoc#5-resource-section-data opt_header.windows_fields. 2 | 3 | use anyhow::Result; 4 | use byteorder::{ByteOrder, LittleEndian}; 5 | use lancelot::aspace::AddressSpace; 6 | use lancelot::loader::pe::PE; 7 | use lancelot::RVA; 8 | use log::debug; 9 | 10 | pub struct ResourceSectionData { 11 | buf: Vec, 12 | } 13 | 14 | impl ResourceSectionData { 15 | fn read_u16(&self, offset: usize) -> Result { 16 | // TODO: bounds check 17 | let buf = &self.buf[offset..offset + 2]; 18 | Ok(LittleEndian::read_u16(buf)) 19 | } 20 | 21 | fn read_u32(&self, offset: usize) -> Result { 22 | // TODO: bounds check 23 | let buf = &self.buf[offset..offset + 4]; 24 | Ok(LittleEndian::read_u32(buf)) 25 | } 26 | 27 | fn read_buf(&self, offset: usize, length: usize) -> Result> { 28 | // TODO: bounds check 29 | Ok(self.buf[offset..offset + length].to_vec()) 30 | } 31 | 32 | pub fn root(&self) -> Result { 33 | ResourceNode::read(self, 0x0) 34 | } 35 | 36 | pub fn from_pe(pe: &PE) -> Result> { 37 | let opt_header = match pe.pe.header.optional_header { 38 | None => return Ok(None), 39 | Some(opt_header) => opt_header, 40 | }; 41 | 42 | let rsrc_table = match opt_header.data_directories.get_resource_table() { 43 | None => return Ok(None), 44 | Some(rsrc_table) => rsrc_table, 45 | }; 46 | 47 | debug!( 48 | "rsrc: table at {:#x}-{:#x}", 49 | pe.module.address_space.base_address + rsrc_table.virtual_address as RVA, 50 | pe.module.address_space.base_address 51 | + rsrc_table.virtual_address as RVA 52 | + rsrc_table.size as RVA 53 | ); 54 | 55 | let buf = pe.module.address_space.read_buf( 56 | // goblin calls this a "virtual address", but its actually an RVA. 57 | pe.module.address_space.base_address + rsrc_table.virtual_address as RVA, 58 | rsrc_table.size as usize, 59 | )?; 60 | 61 | Ok(Some(ResourceSectionData { buf })) 62 | } 63 | } 64 | 65 | struct ResourceNodeHeader { 66 | _flags: u32, 67 | _timestamp: u32, 68 | _major_version: u16, 69 | _minor_version: u16, 70 | named_entry_count: u16, 71 | id_entry_count: u16, 72 | } 73 | 74 | impl ResourceNodeHeader { 75 | fn read(rsrc: &ResourceSectionData, offset: usize) -> Result { 76 | Ok(ResourceNodeHeader { 77 | _flags: rsrc.read_u32(offset + 0)?, 78 | _timestamp: rsrc.read_u32(offset + 4)?, 79 | _major_version: rsrc.read_u16(offset + 8)?, 80 | _minor_version: rsrc.read_u16(offset + 10)?, 81 | named_entry_count: rsrc.read_u16(offset + 12)?, 82 | id_entry_count: rsrc.read_u16(offset + 14)?, 83 | }) 84 | } 85 | } 86 | 87 | #[derive(Clone)] 88 | pub struct ResourceNodeEntry { 89 | id: u32, 90 | offset: u32, 91 | } 92 | 93 | impl ResourceNodeEntry { 94 | fn read(rsrc: &ResourceSectionData, offset: usize) -> Result { 95 | Ok(ResourceNodeEntry { 96 | id: rsrc.read_u32(offset + 0)?, 97 | offset: rsrc.read_u32(offset + 4)?, 98 | }) 99 | } 100 | 101 | fn has_name(&self) -> bool { 102 | return self.id & 0x8000_0000 > 0; 103 | } 104 | 105 | fn is_branch_node(&self) -> bool { 106 | return self.offset & 0x8000_0000 > 0; 107 | } 108 | 109 | pub fn id(&self, rsrc: &ResourceSectionData) -> Result { 110 | let offset = self.id & 0x7FFF_FFFF; 111 | if self.has_name() { 112 | Ok(NodeIdentifier::Name( 113 | ResourceNodeName::read(rsrc, offset as usize)?.name()?, 114 | )) 115 | } else { 116 | Ok(NodeIdentifier::ID(offset)) 117 | } 118 | } 119 | 120 | pub fn child(&self, rsrc: &ResourceSectionData) -> Result { 121 | let offset = (self.offset & 0x7FFF_FFFF) as usize; 122 | if self.is_branch_node() { 123 | Ok(NodeChild::Node(ResourceNode::read(rsrc, offset)?)) 124 | } else { 125 | Ok(NodeChild::Data(ResourceDataDescriptor::read(rsrc, offset)?)) 126 | } 127 | } 128 | } 129 | 130 | pub struct ResourceNode { 131 | _header: ResourceNodeHeader, 132 | named_entries: Vec, 133 | id_entries: Vec, 134 | } 135 | 136 | impl ResourceNode { 137 | fn read(rsrc: &ResourceSectionData, offset: usize) -> Result { 138 | let header = ResourceNodeHeader::read(rsrc, offset + 0)?; 139 | let mut named_entries = vec![]; 140 | let mut id_entries = vec![]; 141 | 142 | let mut offset = offset + 16; 143 | for _ in 0..header.named_entry_count { 144 | named_entries.push(ResourceNodeEntry::read(rsrc, offset)?); 145 | offset += 8; 146 | } 147 | for _ in 0..header.id_entry_count { 148 | id_entries.push(ResourceNodeEntry::read(rsrc, offset)?); 149 | offset += 8; 150 | } 151 | 152 | Ok(ResourceNode { 153 | _header: header, 154 | named_entries, 155 | id_entries, 156 | }) 157 | } 158 | 159 | pub fn get_child_by_name( 160 | &self, 161 | rsrc: &ResourceSectionData, 162 | name: &str, 163 | ) -> Result> { 164 | for child in self.named_entries.iter() { 165 | match child.id(rsrc)? { 166 | NodeIdentifier::ID(_) => continue, 167 | NodeIdentifier::Name(child_name) => { 168 | if &child_name == name { 169 | return Ok(Some(child.child(rsrc)?)); 170 | } 171 | } 172 | } 173 | } 174 | Ok(None) 175 | } 176 | 177 | pub fn get_child_by_id( 178 | &self, 179 | rsrc: &ResourceSectionData, 180 | id: u32, 181 | ) -> Result> { 182 | for child in self.id_entries.iter() { 183 | match child.id(rsrc)? { 184 | NodeIdentifier::Name(_) => continue, 185 | NodeIdentifier::ID(i) => { 186 | if i == id { 187 | return Ok(Some(child.child(rsrc)?)); 188 | } 189 | } 190 | } 191 | } 192 | Ok(None) 193 | } 194 | 195 | // TODO: might want to make this an iterator, one day. 196 | pub fn children( 197 | &self, 198 | rsrc: &ResourceSectionData, 199 | ) -> Result> { 200 | let mut ret = vec![]; 201 | 202 | for entry in self.named_entries.iter().cloned() { 203 | let child = entry.child(rsrc)?; 204 | ret.push((entry, child)); 205 | } 206 | 207 | for entry in self.id_entries.iter().cloned() { 208 | let child = entry.child(rsrc)?; 209 | ret.push((entry, child)); 210 | } 211 | 212 | Ok(ret) 213 | } 214 | } 215 | 216 | pub enum NodeIdentifier { 217 | Name(String), 218 | ID(u32), 219 | } 220 | 221 | struct ResourceNodeName { 222 | character_buf: Vec, 223 | } 224 | 225 | impl ResourceNodeName { 226 | fn read(rsrc: &ResourceSectionData, offset: usize) -> Result { 227 | let character_count = rsrc.read_u16(offset + 0)?; 228 | let character_buf = rsrc.read_buf(offset + 2, 2 * character_count as usize)?; 229 | Ok(ResourceNodeName { character_buf }) 230 | } 231 | 232 | fn name(&self) -> Result { 233 | let chars: Vec = self 234 | .character_buf 235 | .chunks_exact(2) 236 | .map(|buf| LittleEndian::read_u16(buf)) 237 | .collect(); 238 | 239 | widestring::U16String::from_vec(chars) 240 | .to_string() 241 | .map_err(|e| e.into()) 242 | } 243 | } 244 | 245 | pub struct ResourceDataDescriptor { 246 | pub rva: u32, 247 | pub size: u32, 248 | } 249 | 250 | impl ResourceDataDescriptor { 251 | fn read(rsrc: &ResourceSectionData, offset: usize) -> Result { 252 | Ok(ResourceDataDescriptor { 253 | rva: rsrc.read_u32(offset + 0)?, 254 | size: rsrc.read_u32(offset + 4)?, 255 | }) 256 | } 257 | 258 | pub fn data(&self, pe: &PE) -> Result> { 259 | pe.module 260 | .address_space 261 | .relative 262 | .read_buf(self.rva as RVA, self.size as usize) 263 | } 264 | } 265 | 266 | pub enum NodeChild { 267 | Node(ResourceNode), 268 | Data(ResourceDataDescriptor), 269 | } 270 | --------------------------------------------------------------------------------