├── icon.ico ├── Cargo.toml ├── .gitignore ├── README.md ├── LICENSE └── src └── main.rs /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SecSamDev/self-modifying-malware/HEAD/icon.ico -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "self-modifying-malware" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | memmap2 = "0.5" 8 | object = "0.28" 9 | anyhow = "1.0" 10 | 11 | [target.'cfg(windows)'.build-dependencies] 12 | winres = "0.1" 13 | static_vcruntime = "2.0" -------------------------------------------------------------------------------- /.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 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # self-modifying-malware 2 | Self-modifying malware in Rust 3 | 4 | ``` 5 | PS Z:\self-modifying-malware> cargo build --release 6 | Compiling serde v1.0.144 7 | Compiling crc32fast v1.3.2 8 | Compiling memchr v2.5.0 9 | Compiling cfg-if v1.0.0 10 | Compiling adler v1.0.2 11 | Compiling memmap2 v0.5.7 12 | Compiling toml v0.5.9 13 | Compiling winres v0.1.12 14 | Compiling self-modifying-malware v0.1.0 (Z:\self-modifying-malware) 15 | Finished release [optimized] target(s) in 1m 30s 16 | PS Z:\self-modifying-malware> .\target\release\self-modifying-malware.exe 17 | Previous run count: 17413829644165553245 18 | PS Z:\self-modifying-malware> .\target\release\self-modifying-malware.exe 19 | Previous run count: 17413829644165553246 20 | PS Z:\self-modifying-malware> .\target\release\self-modifying-malware.exe 21 | Previous run count: 17413829644165553247 22 | PS Z:\self-modifying-malware> .\target\release\self-modifying-malware.exe 23 | Previous run count: 17413829644165553248 24 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Samuel Garcés Marín 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use memmap2::{MmapOptions, MmapMut}; 2 | use object::{File, Object, ObjectSection}; 3 | use std::env; 4 | use std::error::Error; 5 | use std::fs::{self, OpenOptions}; 6 | use std::path::PathBuf; 7 | use anyhow::{Result, anyhow}; 8 | 9 | const ICON_COUNTER_POSITION : usize = 256; 10 | 11 | fn get_section(file: &File, name: &str) -> Option<(u64, u64)> { 12 | for section in file.sections() { 13 | match section.name() { 14 | Ok(n) if n == name => { 15 | return section.file_range(); 16 | } 17 | _ => {} 18 | } 19 | } 20 | None 21 | } 22 | 23 | fn localize_counter_section(buf : &MmapMut, section_size : usize, section_base : usize) -> Result { 24 | let base_buff = &buf[section_base..(section_base +section_size)]; 25 | // https://lief-project.github.io/doc/stable/tutorials/07_pe_resource.html 26 | let resource_image_directory = &base_buff[..16]; 27 | let named_entries = u16::from_le_bytes(resource_image_directory[12..14].try_into()?); 28 | let id_entries = u16::from_le_bytes(resource_image_directory[14..].try_into()?); 29 | let resource_entries = named_entries + id_entries; 30 | 31 | let mut actual_offset = 16; 32 | let mut section_contents = Vec::new(); 33 | 34 | let mut max_offset = 0; 35 | for resouce in 0..resource_entries { 36 | // Type entry 37 | let resource_entry_buffer = &base_buff[actual_offset..actual_offset + 8]; 38 | let resource_type = u16::from_le_bytes(resource_entry_buffer[0..2].try_into()?); 39 | let _name_is_string = u16::from_le_bytes(resource_entry_buffer[2..4].try_into()?); 40 | let name_dir_offset = u16::from_le_bytes(resource_entry_buffer[4..6].try_into()?) as usize; 41 | 42 | // Name Directory entry 43 | let language_buffer = &base_buff[name_dir_offset..name_dir_offset+24]; 44 | let language_offset = u16::from_le_bytes(language_buffer[20..22].try_into()?) as usize; 45 | 46 | // Language Directory entry 47 | let language_buffer = &base_buff[language_offset..language_offset+24]; 48 | let data_entry_offset = u16::from_le_bytes(language_buffer[20..22].try_into()?) as usize; 49 | 50 | if data_entry_offset > max_offset { 51 | max_offset = data_entry_offset; 52 | } 53 | // Data entry 54 | let data_entry_buffer = &base_buff[data_entry_offset..data_entry_offset+16]; 55 | let file_entry_offset = u32::from_le_bytes(data_entry_buffer[0..4].try_into()?) as usize; 56 | let file_entry_size = u32::from_le_bytes(data_entry_buffer[4..8].try_into()?) as usize; 57 | section_contents.push((resouce, file_entry_offset, file_entry_size, resource_type)); 58 | actual_offset += 8; 59 | } 60 | section_contents.sort_by(|a,b| (a.1).cmp(&b.1)); 61 | let file_reposition = section_contents.get(0).unwrap().1 - (max_offset + 16 + 8); 62 | for section in §ion_contents { 63 | if section.3 == 3 { //RT_ICON 64 | return Ok(section.1 - file_reposition + ICON_COUNTER_POSITION) 65 | } 66 | } 67 | return Err(anyhow!("Icon is missing!")) 68 | } 69 | 70 | 71 | fn run_count() -> Result { 72 | let exe = env::current_exe()?; 73 | let file = OpenOptions::new().read(true).write(true).open(&exe)?; 74 | let buf = unsafe { MmapOptions::new().map_mut(&file)? }; 75 | let file = File::parse(&*buf)?; 76 | 77 | match get_section(&file, ".rsrc") { 78 | Some(range) => { 79 | let section_size = range.1 as usize; 80 | let section_base = range.0 as usize; 81 | let base_buff = &buf[section_base..(section_base +section_size)]; 82 | let counter_position = localize_counter_section(&buf,section_size, section_base )?; 83 | let counter = &base_buff[counter_position..counter_position + 8]; 84 | let counter = u64::from_le_bytes(counter.try_into()?); 85 | return Ok(counter) 86 | }, 87 | None => Err(anyhow!("Resource section is missing!")) 88 | } 89 | } 90 | 91 | 92 | fn edit_run_count(exe : &PathBuf, counter : u64) -> Result<()> { 93 | let file = OpenOptions::new().read(true).write(true).open(&exe)?; 94 | let mut buf = unsafe { MmapOptions::new().map_mut(&file)? }; 95 | let file = File::parse(&*buf)?; 96 | 97 | match get_section(&file, ".rsrc") { 98 | Some(range) => { 99 | let section_size = range.1 as usize; 100 | let section_base = range.0 as usize; 101 | let counter_position = localize_counter_section(&buf,section_size, section_base )?; 102 | buf[(section_base + counter_position)..(section_base + counter_position + 8)].copy_from_slice(&(counter).to_ne_bytes()); 103 | return Ok(()) 104 | }, 105 | None => Err(anyhow!("Resource section is missing!")) 106 | } 107 | 108 | } 109 | 110 | fn main() -> Result<(), Box> { 111 | let run_count = run_count()?; 112 | println!("Previous run count: {}", run_count); 113 | let exe = env::current_exe()?; 114 | let tmp = exe.with_extension("tmp"); 115 | fs::copy(&exe, &tmp)?; 116 | edit_run_count(&tmp, run_count + 1)?; 117 | let perms = fs::metadata(&exe)?.permissions(); 118 | fs::set_permissions(&tmp, perms)?; 119 | fs::rename(&tmp, &exe)?; 120 | 121 | Ok(()) 122 | } --------------------------------------------------------------------------------