├── .github └── dependabot.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md └── src ├── bin ├── config │ └── mod.rs └── wasmonkey.rs ├── errors.rs ├── functions_ids.rs ├── functions_names.rs ├── lib.rs ├── map.rs ├── patcher.rs ├── sections.rs ├── symbols.rs └── tests ├── mod.rs └── test_1.wasm /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - stable 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmonkey" 3 | version = "0.1.17" 4 | authors = ["Frank Denis "] 5 | description = "Patch a WASM object file to replace a set of exported functions with imported functions from another library" 6 | license = "ISC" 7 | homepage = "https://github.com/jedisct1/wasmonkey" 8 | repository = "https://github.com/jedisct1/wasmonkey" 9 | categories = ["wasm"] 10 | edition = "2018" 11 | 12 | [dependencies] 13 | clap = { version = "3.2.23", default-features = false, features = [ 14 | "std", 15 | "cargo", 16 | "wrap_help", 17 | ] } 18 | anyhow = "1.0.68" 19 | goblin = "0.6.0" 20 | lazy_static = "1.4.0" 21 | parity-wasm = "0.45.0" 22 | serde = "1.0.152" 23 | serde_derive = "1.0.152" 24 | serde_json = "1.0.91" 25 | siphasher = "0.3.10" 26 | thiserror = "1.0.38" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WASMonkey 2 | 3 | The WASMonkey patches a WASM object file to replace a set of exported 4 | functions with imported functions from another library. 5 | 6 | ## Usage 7 | 8 | ```text 9 | USAGE: 10 | wasmonkey [FLAGS] [OPTIONS] --input --output 11 | 12 | FLAGS: 13 | -n, --original-names Use the original name as a key in the builtins map 14 | -h, --help Prints help information 15 | -V, --version Prints version information 16 | 17 | OPTIONS: 18 | -B, --builtins-additional ... Additional builtins function names to replace 19 | -b, --builtins Path to the builtins library 20 | -m, --builtins-map Path to the builtins map file 21 | -i, --input Path to the input file 22 | -o, --output Path to the output file 23 | ``` 24 | 25 | `builtins_file` is an object containing alternative implementations to 26 | functions called in the wasm code. 27 | 28 | Symbols starting with a `builtin_` prefix will be used for substitution. 29 | 30 | For example, a `memmove()` function defined as an external in the WASM 31 | object will be replaced by calls to an imported `builtin_memmove()` 32 | function, if `builtin_memmove()` is present in the builtins file. 33 | 34 | A JSON-encoded map of the performed substitutions can be optionally written 35 | into `builtins_map_file`. 36 | -------------------------------------------------------------------------------- /src/bin/config/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::Arg; 4 | 5 | use crate::{PatcherConfig, WError}; 6 | 7 | #[derive(Default, Clone, Debug)] 8 | pub struct Config { 9 | pub input_path: PathBuf, 10 | pub output_path: PathBuf, 11 | pub patcher_config: PatcherConfig, 12 | } 13 | 14 | impl Config { 15 | pub fn parse_cmdline() -> Result { 16 | let matches = clap::command!() 17 | .arg( 18 | Arg::new("input_file") 19 | .short('i') 20 | .long("input") 21 | .takes_value(true) 22 | .required(true) 23 | .help("Path to the input file"), 24 | ) 25 | .arg( 26 | Arg::new("output_file") 27 | .short('o') 28 | .long("output") 29 | .takes_value(true) 30 | .required(true) 31 | .help("Path to the output file"), 32 | ) 33 | .arg( 34 | Arg::new("builtins_file") 35 | .short('b') 36 | .long("builtins") 37 | .takes_value(true) 38 | .required(false) 39 | .help("Path to the builtins library"), 40 | ) 41 | .arg( 42 | Arg::new("builtins_additional") 43 | .short('B') 44 | .long("builtins-additional") 45 | .takes_value(true) 46 | .required(false) 47 | .multiple_occurrences(true) 48 | .help("Additional builtins function names to replace"), 49 | ) 50 | .arg( 51 | Arg::new("builtins_map_file") 52 | .short('m') 53 | .long("builtins-map") 54 | .takes_value(true) 55 | .required(false) 56 | .help("Path to the builtins map file"), 57 | ) 58 | .arg( 59 | Arg::new("builtins_map_original_names") 60 | .short('n') 61 | .long("original-names") 62 | .takes_value(false) 63 | .required(false) 64 | .help("Use the original name as a key in the builtins map"), 65 | ) 66 | .get_matches(); 67 | let input_path = PathBuf::from( 68 | matches 69 | .value_of("input_file") 70 | .ok_or(WError::UsageError("Input file required"))?, 71 | ); 72 | let output_path = PathBuf::from( 73 | matches 74 | .value_of("output_file") 75 | .ok_or(WError::UsageError("Output file required"))?, 76 | ); 77 | let builtins_path = matches.value_of("builtins_file").map(PathBuf::from); 78 | let builtins_map_path = matches.value_of("builtins_map_file").map(PathBuf::from); 79 | let builtins_map_original_names = matches.is_present("builtins_map_original_names"); 80 | let builtins_additional = matches 81 | .values_of("builtins_additional") 82 | .unwrap_or_default() 83 | .map(|name| name.to_string()) 84 | .collect(); 85 | let config = Config { 86 | input_path, 87 | output_path, 88 | patcher_config: PatcherConfig { 89 | builtins_path, 90 | builtins_map_path, 91 | builtins_map_original_names, 92 | builtins_additional, 93 | }, 94 | }; 95 | Ok(config) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/bin/wasmonkey.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | 3 | use wasmonkey::*; 4 | 5 | use crate::config::*; 6 | 7 | fn main() -> Result<(), Error> { 8 | let config = Config::parse_cmdline()?; 9 | let patcher = Patcher::from_file(config.patcher_config, config.input_path)?; 10 | patcher.store_to_file(config.output_path)?; 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub use anyhow::{anyhow, bail, ensure, Error}; 4 | use parity_wasm::elements; 5 | 6 | #[allow(dead_code)] 7 | #[derive(Debug, thiserror::Error)] 8 | pub enum WError { 9 | #[error("Internal error: {0}")] 10 | InternalError(&'static str), 11 | #[error("Incorrect usage: {0}")] 12 | UsageError(&'static str), 13 | #[error("{0}")] 14 | Io(#[from] io::Error), 15 | #[error("{0}")] 16 | WAsmError(#[from] elements::Error), 17 | #[error("Parse error")] 18 | ParseError, 19 | #[error("Unsupported")] 20 | Unsupported, 21 | } 22 | -------------------------------------------------------------------------------- /src/functions_ids.rs: -------------------------------------------------------------------------------- 1 | use parity_wasm::elements::{ 2 | CodeSection, ElementSection, ExportSection, FuncBody, Instruction, Instructions, Internal, 3 | Module, 4 | }; 5 | 6 | use crate::errors::*; 7 | 8 | fn shift_function_ids_in_code_section( 9 | code_section: &mut CodeSection, 10 | shift: u32, 11 | ) -> Result<(), WError> { 12 | let code_bodies = code_section.bodies_mut(); 13 | for code_body in code_bodies.iter_mut() { 14 | let opcodes = code_body.code_mut().elements_mut(); 15 | for opcode in opcodes.iter_mut() { 16 | if let Instruction::Call(function_id) = *opcode { 17 | *opcode = Instruction::Call(function_id + shift) 18 | } 19 | } 20 | } 21 | Ok(()) 22 | } 23 | 24 | fn shift_function_ids_in_exports_section(export_section: &mut ExportSection, shift: u32) { 25 | for entry in export_section.entries_mut() { 26 | let internal = entry.internal_mut(); 27 | if let Internal::Function(function_id) = *internal { 28 | *internal = Internal::Function(function_id + shift) 29 | } 30 | } 31 | } 32 | 33 | fn shift_function_ids_in_elements_section(elements_section: &mut ElementSection, shift: u32) { 34 | for elements_segment in elements_section.entries_mut() { 35 | for function_id in elements_segment.members_mut() { 36 | *function_id += shift; 37 | } 38 | } 39 | } 40 | 41 | pub fn shift_function_ids(module: &mut Module, shift: u32) -> Result<(), WError> { 42 | shift_function_ids_in_code_section(module.code_section_mut().expect("No code section"), shift)?; 43 | if let Some(export_section) = module.export_section_mut() { 44 | shift_function_ids_in_exports_section(export_section, shift) 45 | } 46 | if let Some(elements_section) = module.elements_section_mut() { 47 | shift_function_ids_in_elements_section(elements_section, shift) 48 | } 49 | Ok(()) 50 | } 51 | 52 | fn replace_function_id_in_code_section(code_section: &mut CodeSection, before: u32, after: u32) { 53 | let code_bodies = code_section.bodies_mut(); 54 | for code_body in code_bodies.iter_mut() { 55 | let opcodes = code_body.code_mut().elements_mut(); 56 | for opcode in opcodes.iter_mut() { 57 | match *opcode { 58 | Instruction::Call(function_id) if function_id == before => { 59 | *opcode = Instruction::Call(after) 60 | } 61 | _ => {} 62 | } 63 | } 64 | } 65 | } 66 | 67 | fn replace_function_id_in_elements_section( 68 | elements_section: &mut ElementSection, 69 | before: u32, 70 | after: u32, 71 | ) { 72 | for elements_segment in elements_section.entries_mut() { 73 | for function_id in elements_segment.members_mut() { 74 | if *function_id == before { 75 | *function_id = after; 76 | } 77 | } 78 | } 79 | } 80 | 81 | pub fn replace_function_id(module: &mut Module, before: u32, after: u32) -> Result<(), WError> { 82 | if let Some(code_section) = module.code_section_mut() { 83 | replace_function_id_in_code_section(code_section, before, after) 84 | } 85 | 86 | if let Some(elements_section) = module.elements_section_mut() { 87 | replace_function_id_in_elements_section(elements_section, before, after) 88 | }; 89 | 90 | Ok(()) 91 | } 92 | 93 | #[allow(dead_code)] 94 | pub fn disable_function_id(module: &mut Module, function_id: u32) -> Result<(), WError> { 95 | let base_id = match module.import_section() { 96 | None => 0, 97 | Some(import_section) => import_section.entries().len() as u32, 98 | }; 99 | let code_section = module.code_section_mut().expect("No code section"); 100 | let code_bodies = code_section.bodies_mut(); 101 | let opcodes = Instructions::new(vec![Instruction::Unreachable, Instruction::End]); 102 | let func_body = FuncBody::new(vec![], opcodes); 103 | code_bodies[(function_id - base_id) as usize] = func_body; 104 | Ok(()) 105 | } 106 | -------------------------------------------------------------------------------- /src/functions_names.rs: -------------------------------------------------------------------------------- 1 | use parity_wasm::elements::{FunctionNameSubsection, IndexMap}; 2 | 3 | use crate::errors::*; 4 | 5 | pub fn prepend_function_name( 6 | function_names_subsection: &mut FunctionNameSubsection, 7 | name: String, 8 | ) -> Result<(), WError> { 9 | let mut map_new = IndexMap::with_capacity(function_names_subsection.names().len() + 1_usize); 10 | for (idx, name) in function_names_subsection.names() { 11 | map_new.insert(idx + 1, name.clone()); 12 | } 13 | map_new.insert(0, name); 14 | *function_names_subsection.names_mut() = map_new; 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg_attr(test, macro_use)] 2 | extern crate lazy_static; 3 | 4 | #[macro_use] 5 | extern crate serde_derive; 6 | 7 | #[cfg(test)] 8 | extern crate siphasher; 9 | 10 | mod errors; 11 | mod functions_ids; 12 | mod functions_names; 13 | mod map; 14 | mod patcher; 15 | mod sections; 16 | mod symbols; 17 | 18 | #[cfg(test)] 19 | mod tests; 20 | 21 | pub use crate::errors::*; 22 | pub use crate::patcher::*; 23 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs::File; 3 | use std::io::prelude::*; 4 | use std::path::Path; 5 | 6 | use crate::errors::*; 7 | 8 | #[derive(Clone, Debug, Default, Serialize)] 9 | pub struct PatchedBuiltinsMap { 10 | pub env: HashMap, 11 | } 12 | 13 | impl PatchedBuiltinsMap { 14 | pub fn with_capacity(capacity: usize) -> Self { 15 | PatchedBuiltinsMap { 16 | env: HashMap::with_capacity(capacity), 17 | } 18 | } 19 | 20 | pub fn insert(&mut self, name: String, imported_name: String) -> Option { 21 | self.env.insert(name, imported_name) 22 | } 23 | 24 | pub fn write_to_file>( 25 | &self, 26 | builtins_map_path: P, 27 | original_names: bool, 28 | ) -> Result<(), WError> { 29 | let mut map_with_original_names; 30 | let map = if original_names { 31 | self 32 | } else { 33 | map_with_original_names = PatchedBuiltinsMap::default(); 34 | for imported_name in self.env.values() { 35 | map_with_original_names 36 | .env 37 | .insert(imported_name.clone(), imported_name.clone()); 38 | } 39 | &map_with_original_names 40 | }; 41 | let json = serde_json::to_string_pretty(map).map_err(|_| WError::ParseError)?; 42 | File::create(builtins_map_path)?.write_all(json.as_bytes())?; 43 | Ok(()) 44 | } 45 | 46 | pub fn builtins_map( 47 | &self, 48 | module: &str, 49 | original_names: bool, 50 | ) -> Result, Error> { 51 | if module != "env" { 52 | bail!(WError::UsageError("Empty module")); 53 | } 54 | if original_names { 55 | return Ok(self.env.clone()); 56 | } 57 | let mut env_map_with_original_names = HashMap::new(); 58 | for imported_name in self.env.values() { 59 | env_map_with_original_names.insert(imported_name.clone(), imported_name.clone()); 60 | } 61 | Ok(env_map_with_original_names) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/patcher.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use parity_wasm::elements::{ 5 | self, External, FunctionNameSubsection, ImportEntry, ImportSection, Internal, Module, 6 | NameSection, Section, 7 | }; 8 | 9 | use crate::errors::*; 10 | use crate::functions_ids::*; 11 | use crate::functions_names::*; 12 | use crate::map::*; 13 | use crate::sections::*; 14 | use crate::symbols::{self, ExtractedSymbols}; 15 | 16 | pub const BUILTIN_PREFIX: &str = "builtin_"; 17 | 18 | #[derive(Default, Clone, Debug)] 19 | pub struct PatcherConfig { 20 | pub builtins_path: Option, 21 | pub builtins_map_path: Option, 22 | pub builtins_map_original_names: bool, 23 | pub builtins_additional: Vec, 24 | } 25 | 26 | pub struct Patcher { 27 | pub config: PatcherConfig, 28 | patched_module: Module, 29 | patched_builtins_map: PatchedBuiltinsMap, 30 | } 31 | 32 | impl Patcher { 33 | pub fn new(config: PatcherConfig, module: Module) -> Result { 34 | let symbols = match &config.builtins_path { 35 | None => ExtractedSymbols::from(vec![]), 36 | Some(builtins_path) => symbols::extract_symbols(builtins_path)?, 37 | } 38 | .merge_additional(&config.builtins_additional); 39 | let builtins_names = symbols.builtins_names(); 40 | let (patched_module, patched_builtins_map) = patch_module(module, &builtins_names)?; 41 | let patcher = Patcher { 42 | config, 43 | patched_module, 44 | patched_builtins_map, 45 | }; 46 | Ok(patcher) 47 | } 48 | 49 | pub fn from_bytes(config: PatcherConfig, bytes: &[u8]) -> Result { 50 | let module = parity_wasm::deserialize_buffer(bytes)?; 51 | Self::new(config, module) 52 | } 53 | 54 | pub fn from_file>(config: PatcherConfig, path_in: P) -> Result { 55 | let module = parity_wasm::deserialize_file(path_in)?; 56 | Self::new(config, module) 57 | } 58 | 59 | pub fn into_bytes(self) -> Result, WError> { 60 | let bytes = elements::serialize(self.patched_module)?; 61 | Ok(bytes) 62 | } 63 | 64 | pub fn store_to_file>(self, path_out: P) -> Result<(), WError> { 65 | elements::serialize_to_file(path_out, self.patched_module)?; 66 | if let Some(builtins_map_path) = self.config.builtins_map_path { 67 | self.patched_builtins_map 68 | .write_to_file(builtins_map_path, self.config.builtins_map_original_names)?; 69 | } 70 | Ok(()) 71 | } 72 | 73 | pub fn patched_builtins_map(&self, module: &str) -> Result, Error> { 74 | self.patched_builtins_map 75 | .builtins_map(module, self.config.builtins_map_original_names) 76 | } 77 | 78 | pub fn patched_module(self) -> Module { 79 | self.patched_module 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | pub struct Builtin { 85 | pub name: String, 86 | pub original_function_id: Option, 87 | pub function_type_id: Option, 88 | } 89 | 90 | impl Builtin { 91 | pub fn new(name: String) -> Self { 92 | Builtin { 93 | name, 94 | original_function_id: None, 95 | function_type_id: None, 96 | } 97 | } 98 | 99 | pub fn import_name(&self) -> String { 100 | format!("{}{}", BUILTIN_PREFIX, self.name) 101 | } 102 | } 103 | 104 | fn function_type_id_for_function_id(module: &Module, function_id: u32) -> Option { 105 | let offset = module 106 | .import_section() 107 | .map(|import_section| import_section.entries().len() as u32) 108 | .unwrap_or(0); 109 | if function_id < offset { 110 | return None; 111 | } 112 | let functions_section_type_ids = module.function_section().unwrap().entries(); 113 | Some(functions_section_type_ids[(function_id - offset) as usize].type_ref()) 114 | } 115 | 116 | fn add_function_type_id_to_builtins( 117 | module: &Module, 118 | builtins: &mut Vec, 119 | ) -> Result<(), WError> { 120 | for builtin in builtins.iter_mut() { 121 | let function_type_id = 122 | function_type_id_for_function_id(module, builtin.original_function_id.unwrap()) 123 | .expect("Function ID not found"); 124 | builtin.function_type_id = Some(function_type_id); 125 | } 126 | Ok(()) 127 | } 128 | 129 | fn retain_only_used_builtins(module: &Module, builtins: &mut Vec) -> Result<(), WError> { 130 | let export_section = module.export_section().expect("No export section"); 131 | 132 | for entry in export_section.entries() { 133 | let internal = entry.internal(); 134 | let function_id = match internal { 135 | Internal::Function(function_id) => *function_id, 136 | _ => continue, 137 | }; 138 | let field = entry.field(); 139 | for builtin in builtins.iter_mut() { 140 | if field == builtin.name { 141 | assert!(builtin.original_function_id.is_none()); 142 | builtin.original_function_id = Some(function_id); 143 | break; 144 | } 145 | } 146 | } 147 | 148 | builtins.retain(|builtin| builtin.original_function_id.is_some()); 149 | Ok(()) 150 | } 151 | 152 | fn add_import_section_if_missing(module: &mut Module) -> Result<(), WError> { 153 | if module.import_section().is_some() { 154 | return Ok(()); 155 | } 156 | let import_section = ImportSection::with_entries(vec![]); 157 | let import_section_idx = find_type_section_idx(module).unwrap() + 1; 158 | module 159 | .sections_mut() 160 | .insert(import_section_idx, Section::Import(import_section)); 161 | Ok(()) 162 | } 163 | 164 | fn prepend_builtin_to_import_section(module: &mut Module, builtin: &Builtin) -> Result<(), WError> { 165 | let import_name = builtin.import_name(); 166 | let external = External::Function(builtin.function_type_id.unwrap()); 167 | let import_entry = ImportEntry::new("env".to_string(), import_name, external); 168 | module 169 | .import_section_mut() 170 | .unwrap() 171 | .entries_mut() 172 | .insert(0, import_entry); 173 | Ok(()) 174 | } 175 | 176 | fn prepend_builtin_to_names_section(module: &mut Module, builtin: &Builtin) -> Result<(), Error> { 177 | let import_name = builtin.import_name(); 178 | if module.names_section().is_none() { 179 | let sections = module.sections_mut(); 180 | let function_names_subsection = FunctionNameSubsection::default(); 181 | let name_section = NameSection::new(None, Some(function_names_subsection), None); 182 | sections.push(Section::Name(name_section)); 183 | } 184 | let names_section = module 185 | .names_section_mut() 186 | .expect("Names section not present"); 187 | let function_names_subsection = match names_section.functions_mut() { 188 | Some(function_names_subsection) => function_names_subsection, 189 | _ => bail!(WError::InternalError("Unexpected names section")), 190 | }; 191 | prepend_function_name(function_names_subsection, import_name)?; 192 | Ok(()) 193 | } 194 | 195 | fn patch_module( 196 | module: Module, 197 | builtins_names: &[&str], 198 | ) -> Result<(Module, PatchedBuiltinsMap), Error> { 199 | let mut module = module 200 | .parse_names() 201 | .map_err(|_| WError::InternalError("Unable to parse names"))?; 202 | 203 | let mut builtins: Vec<_> = builtins_names 204 | .iter() 205 | .map(|x| Builtin::new(x.to_string())) 206 | .collect(); 207 | 208 | retain_only_used_builtins(&module, &mut builtins)?; 209 | add_function_type_id_to_builtins(&module, &mut builtins)?; 210 | 211 | add_import_section_if_missing(&mut module)?; 212 | for (builtin_idx, builtin) in builtins.iter_mut().enumerate() { 213 | prepend_builtin_to_import_section(&mut module, builtin)?; 214 | prepend_builtin_to_names_section(&mut module, builtin)?; 215 | shift_function_ids(&mut module, 1)?; 216 | let original_function_id = builtin.original_function_id.unwrap() + builtin_idx as u32 + 1; 217 | let new_function_id = 0; 218 | replace_function_id(&mut module, original_function_id, new_function_id)?; 219 | } 220 | 221 | let mut patched_builtins_map = PatchedBuiltinsMap::with_capacity(builtins.len()); 222 | for builtin in builtins { 223 | patched_builtins_map.insert(builtin.name.clone(), builtin.import_name()); 224 | } 225 | Ok((module, patched_builtins_map)) 226 | } 227 | -------------------------------------------------------------------------------- /src/sections.rs: -------------------------------------------------------------------------------- 1 | use parity_wasm::elements::{Module, Section}; 2 | 3 | pub fn find_type_section_idx(module: &Module) -> Option { 4 | module.sections().iter().position(|section| match section { 5 | Section::Type(_) => true, 6 | _ => false, 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/symbols.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::path::Path; 4 | 5 | use goblin::elf::Elf; 6 | use goblin::mach::{self, Mach, MachO}; 7 | use goblin::Object; 8 | 9 | use crate::errors::*; 10 | use crate::patcher::BUILTIN_PREFIX; 11 | 12 | #[derive(Clone, Debug, Default)] 13 | pub struct ExtractedSymbol { 14 | pub name: String, 15 | } 16 | 17 | #[derive(Clone, Debug, Default)] 18 | pub struct ExtractedSymbols { 19 | pub symbols: Vec, 20 | } 21 | 22 | impl From> for ExtractedSymbols { 23 | fn from(symbols: Vec) -> Self { 24 | ExtractedSymbols { symbols } 25 | } 26 | } 27 | 28 | impl ExtractedSymbols { 29 | pub fn builtins_names(&self) -> Vec<&str> { 30 | let builtins_names: Vec<&str> = self 31 | .symbols 32 | .iter() 33 | .filter(|symbol| symbol.name.starts_with(BUILTIN_PREFIX)) 34 | .map(|symbol| &symbol.name[BUILTIN_PREFIX.len()..]) 35 | .collect(); 36 | builtins_names 37 | } 38 | 39 | pub fn merge_additional(mut self, additional_names: &[String]) -> Self { 40 | let mut additional_symbols: Vec<_> = additional_names 41 | .iter() 42 | .map(|name| ExtractedSymbol { 43 | name: name.to_string(), 44 | }) 45 | .collect(); 46 | self.symbols.append(&mut additional_symbols); 47 | self.symbols.dedup_by(|a, b| a.name == b.name); 48 | self 49 | } 50 | } 51 | 52 | fn parse_elf(elf: &Elf<'_>) -> Result { 53 | let mut symbols = vec![]; 54 | 55 | for symbol in elf 56 | .dynsyms 57 | .iter() 58 | .filter(|symbol| symbol.st_info == 0x12 || symbol.st_info == 0x22) 59 | { 60 | #[allow(deprecated)] 61 | let name = elf 62 | .dynstrtab 63 | .get(symbol.st_name) 64 | .ok_or(WError::ParseError)? 65 | .map_err(|_| WError::ParseError)? 66 | .to_string(); 67 | let extracted_symbol = ExtractedSymbol { name }; 68 | symbols.push(extracted_symbol); 69 | } 70 | Ok(symbols.into()) 71 | } 72 | 73 | fn parse_macho(macho: &MachO<'_>) -> Result { 74 | let mut symbols = vec![]; 75 | 76 | // Start by finding the boundaries of the text section 77 | let mut text_offset = None; 78 | let mut text_size = None; 79 | for section in macho.segments.sections() { 80 | for segment in section { 81 | if let Ok(( 82 | mach::segment::Section { 83 | sectname: [b'_', b'_', b't', b'e', b'x', b't', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 84 | segname: [b'_', b'_', b'T', b'E', b'X', b'T', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 85 | size, 86 | offset, 87 | .. 88 | }, 89 | _, 90 | )) = segment 91 | { 92 | text_offset = Some(offset as usize); 93 | text_size = Some(size as usize); 94 | } 95 | } 96 | } 97 | let text_offset = text_offset.ok_or(WError::ParseError)?; 98 | let text_size = text_size.ok_or(WError::ParseError)?; 99 | 100 | // Extract the symbols we are interested in 101 | for symbol in macho.symbols.as_ref().ok_or(WError::ParseError)?.iter() { 102 | match symbol { 103 | Ok(( 104 | name, 105 | mach::symbols::Nlist { 106 | n_type: 0xf, 107 | n_sect: 1, 108 | n_value, 109 | .. 110 | }, 111 | )) if name.len() > 1 && name.starts_with('_') => { 112 | let extracted_symbol = ExtractedSymbol { 113 | name: name[1..].to_string(), 114 | }; 115 | let offset = n_value as usize; 116 | if offset < text_offset || offset >= text_offset + text_size { 117 | continue; 118 | } 119 | symbols.push(extracted_symbol); 120 | } 121 | _ => {} 122 | } 123 | } 124 | Ok(symbols.into()) 125 | } 126 | 127 | pub fn extract_symbols>(path: P) -> Result { 128 | let mut buffer = Vec::new(); 129 | File::open(path)?.read_to_end(&mut buffer)?; 130 | let symbols = match Object::parse(&buffer).map_err(|_| WError::ParseError)? { 131 | Object::Mach(Mach::Binary(macho)) => parse_macho(&macho), 132 | Object::Elf(elf) => parse_elf(&elf), 133 | _ => bail!(WError::Unsupported), 134 | }?; 135 | Ok(symbols) 136 | } 137 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hasher; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use siphasher::sip::SipHasher13; 5 | 6 | use super::*; 7 | 8 | lazy_static! { 9 | static ref TESTS_DIR: PathBuf = Path::new(file!()).parent().unwrap().canonicalize().unwrap(); 10 | } 11 | 12 | #[test] 13 | fn patch_nothing() { 14 | let path_in = TESTS_DIR.join("test_1.wasm"); 15 | let config = PatcherConfig::default(); 16 | let patcher = Patcher::from_file(config, path_in).unwrap(); 17 | let mut hasher = SipHasher13::new(); 18 | hasher.write(&patcher.into_bytes().unwrap()); 19 | assert_eq!(hasher.finish(), 1401932366200566186); 20 | } 21 | 22 | #[test] 23 | fn patch_one() { 24 | let path_in = TESTS_DIR.join("test_1.wasm"); 25 | let mut config = PatcherConfig::default(); 26 | config.builtins_additional = ["builtin_memmove", "builtin_nonexistent", "not_a_builtin"] 27 | .iter() 28 | .map(|s| s.to_string()) 29 | .collect(); 30 | let patcher = Patcher::from_file(config, path_in).unwrap(); 31 | let mut hasher = SipHasher13::new(); 32 | hasher.write(&patcher.into_bytes().unwrap()); 33 | assert_eq!(hasher.finish(), 12884721342785729260); 34 | } 35 | 36 | #[test] 37 | fn patch_some() { 38 | let path_in = TESTS_DIR.join("test_1.wasm"); 39 | let mut config = PatcherConfig::default(); 40 | config.builtins_additional = ["builtin_memmove", "builtin_memcpy", "builtin_strcmp"] 41 | .iter() 42 | .map(|s| s.to_string()) 43 | .collect(); 44 | let patcher = Patcher::from_file(config, path_in).unwrap(); 45 | let mut hasher = SipHasher13::new(); 46 | hasher.write(&patcher.into_bytes().unwrap()); 47 | assert_eq!(hasher.finish(), 13205801729184435761); 48 | } 49 | -------------------------------------------------------------------------------- /src/tests/test_1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedisct1/wasmonkey/6843e5dd783d046894b09a5b28faecd67720fdee/src/tests/test_1.wasm --------------------------------------------------------------------------------