Теперь мы подходим к хорошему! Одна из моих любимых вещей в сообществе DFIR — это множество инструментов, которые постоянно создаются аналитиками. Теперь, это также один из более сложных аспектов. Выходные данные между инструментами могут различаться настолько сильно, что трудно поддерживать автоматизированную аналитическую систему. Если вы слышали наш доклад на OSDFCon в этом году, вы видели, как мы использовали выходные данные различных инструментов, чтобы связать артефакты, чтобы приписать Shellbags к Volumes через Linkfiles. Проблема в том, что эта система часто может сломаться при обновлении из-за изменения формата вывода. Отличным примером этого является то, что на этой неделе Вилли Баллентин выпустил новую версию EVTXtract. Удивительный инструмент для восстановления журналов событий. Инструмент, который очень помог Дейву и мне в CTF судебной экспертизы DEFCON. Затем мы добавили некоторые функции в EventMonkey, чтобы он мог анализировать восстановленные журналы событий из EVTXtract. Но в новой версии изменился выходной формат… так что теперь пришло время снова модифицировать EventMonkey, потому что он больше не совместим. Излишне говорить, что хотя это иногда и сложно, это не так уж и плохо. Это означает, что инструменты DFIR постоянно развиваются, и становится известно все больше данных! Все это, просто для того, чтобы сказать, что если вы создаете инструменты DFIR, это нужно, потому что вы хотите анализировать двоичные данные, чтобы сделать их удобочитаемыми для человека.

Так как же нам это сделать в Rust? Сначала нам нужно научиться создавать некоторые структуры для хранения данных и придавать им функциональность. Хотя, я должен предупредить вас, я до сих пор не знаю, реализую ли я это наилучшим образом. Может быть, кто-нибудь из сообщества Rust поможет мне сформировать передовые привычки.

Итак, первый вопрос: какой артефакт нам нужно разобрать? Для RustyUsn это Windows USN Journal. Опять же, я пошел с этим в качестве первого проекта, потому что структура данных чрезвычайно проста, и пока мы просто придерживаемся записей версии 2. Вот структура записи: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365722(v=vs.85).aspx. Это отличное упражнение, потому что мы научимся не только анализировать двоичные файлы, но и работать со структурами DateTime и UTF-16. Что-то, что содержится во многих структурах данных Windows.

Небольшое примечание: я хочу, чтобы этот код был ссылкой для различных компонентов, которые могут быть в инструменте синтаксического анализа. Я стараюсь хорошо документировать код, чтобы вы могли видеть, что я делаю. Просмотр исходного кода — лучший способ увидеть концепции в действии. Я не показываю полный код в этом посте, и он может измениться, поэтому вместо этого я просто просматриваю разные разделы и то, как он реализует основные концепции, которые создают инструмент.

Сегодня мы посмотрим на usn.rs, где происходит волшебство. Сначала мы импортируем в библиотеки, которые будем использовать (некоторые из них составляют наши структуры):

use usnpkg::chrono::*;
use usnpkg::byteorder::{ReadBytesExt, LittleEndian};
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::slice;
use std::mem;

chrono предназначен для объектов DateTime;
byteorder предназначен для чтения наших двоичных данных с прямым порядком байтов;
std::fs:: Файл — это наш дескриптор файла;
std::io::{Error, ErrorKind} для нашей обработки ошибок;
Read, Seek, SeekFrom для операций ввода-вывода для нашего std::fs::File;
std::slice для перехода от двоичных файлов к структурам;
std::mem для инициализации нашей структуры USN;

Структуры

Теперь давайте погрузимся в структуры.

#[derive(Debug)]
pub struct UsnRecordV2 {
    // 0
    record_length: u32,
    major_version: u16,
    minor_version: u16,
    file_reference_number: u64,
    parent_file_reference_number: u64,
    usn: u64,
    // 32
    timestamp: NaiveDateTime,
    // 40
    reason: u32,
    source_info: u32,
    security_id: u32,
    file_attributes: u32,
    file_name_length: u16,
    file_name_offset: u16,
    // 60
    file_name: String
}

Это наша структура USN. У нас есть два важных типа, помимо обычного целочисленного бизнеса без знака. NaiveDateTime для хранения нашей даты и времени и String для хранения нашего юникода. #[derive(Debug)] позволяет нам распечатать нашу структуру. Я добавил это, чтобы я мог выводить значения, пока не введу правильное форматирование вывода.

Далее идет структура, отслеживающая, откуда мы читаем в файле.

// maintain UsnConnection info
pub struct UsnConnection {
    filehandle: File,
    _offset: u64,
    _size: u64
}

Здесь я должен отметить, что Rust не является объектно-ориентированным. Вместо этого вы реализуете функции для своих структур или типов. Подробнее об этом читайте в документах.

Мы хотим, чтобы наш UsnConnection имел функцию с именем get_next_record(), которую мы можем использовать для итерации в журнал. Вы видите это в main.rs:

while let Ok(record) = usn_connection.get_next_record(){
    println!("USN structure {}: {:#?}",cnt,record);
    cnt += 1;
};

Как мы реализуем get_next_record() для работы с типом UsnConnection?

// implement UsnConnection Functionality
impl UsnConnection {
    // function for getting a record
    pub fn get_next_record(&mut self)->Result<UsnRecordV2,Error>{
        ...
    }
}

Если быть честным. Я не уверен, должен ли я реализовать это как итератор. Еще так многому предстоит научиться.

Функция get_next_record() возвращает Результат, что является одним из способов проверки ошибок в Rust. У вас либо OK, либо Err. В данном случае Ok имеет структуру UsnRecordV2, а Err имеет тип Error.

Итак, теперь у нас есть структуры, и мы реализовали некоторые функциональные возможности структур. Далее нужно разобрать в него бинарные данные!