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

  #1  
Старый 29.01.2026, 11:49
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
Провел на форуме:
145166

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

Руткиты и малварь в целом активно пытаются скрыть своё присутствие в системе, для чего используют всё новые способы маскировки. Как правило стратегия начинается с того, что вредоносный код пытается скрыть свой процесс от аверов, и прочих посторонних глаз типа "Диспетчер задач". В данной статье мы рассмотрим несколько таких методов, которые являются базовыми для всего класса вирусов и червей.

1. Основная идея
2. Объект "Process" и его дескрипторы "Handle"
3. Практическая часть - пишем софт
4. Альтернативные методы
5. Заключение

1. Основная идея

Для создания списка активных процессов из под юзера, система предлагает нам несколько своих WinAPI - в классическом варианте это такие функции как
Код:
EnumProcess()
,
Код:
Process32First/Next()
с предварительно созданным снапшотом
Код:
CreateToolhelp32Snapshot()
, а так-же бронебойная
Код:
NtQuerySystemInformation()
с флагом ProcessAndThreads=5. Как видим выбор не велик, и чтобы замаскировать своё присутствие, малварь просто хукает эти функции, возвращая нам в ответ фейковый результат.

Такой расклад ограничивает наши возможности и приходится искать иные способы вылавливания блох, которые руткиты могли не учесть. Ситуацию усугубляет и то, что мы находимся в сессии(1) пространства юзера, в то время как все сервисы системы с привилегией "System" закрыты на замок в сессии(0). Как результат мы не сможем открыть некоторые процессы системы даже имея права админа, пока не напишем свою службу (о драйверах ядра можно забыть, т.к. на х64 они требуют подписи майков).

Поэтому всё-что нам остаётся, это создать список активных процессов двумя разными способами, после чего сравнить их на соответствие. То-есть прикинувшись байтом создаём первый лист штатными средствами типа
Код:
Process32Next()
, а для второго списка нужно будет придумать нестандартный ход конём, который малварь пропустила между ног. Если поразмыслить, можно в обход вызовов API использовать прямой вызов ядерных сервисов посредством инструкции
Код:
syscall
, тогда мы опустимся ниже перехваченной малварью функции, и сможем обойти её дворами. Вариант хороший, но для кроссплатформенности требует много телодвижений, т.к. номера сервисов в ядре отличаются даже внутри одной линейки Win с разными версиями, не говоря уже о Win7/8/10/11.

В силу перечисленных особенностей мы пойдём другим путём, и для создания второго/эталонного списка процессов соберём ..все дескрипторы в системе. У каждого процесса своя таблица дескрипторов, где будут хаотично разбросаны не только дескрипторы процессов, но и буквально всех ядерных объектов, например файлов, потоков, таймеров, портов, драйверов и многое другое (на Win7 это аж 42 типа объектов, а на Win10 все 50). Единственная проблема здесь в том, чтобы среди этой кучи отфильтровать только принадлежащие процессам дескрипторы (на инглише Handle), для чего необходимо будет узнать системную константу "ObjectType".

Список всех активных на данный момент дескрипторов возвращает та-же
Код:
NtQuerySystemInformation()
, только с аргументом "SystemHandleInfo=16". Учитывая, что кроме 5 и 16 эта API может принимать запросы на возврат аж 53 различных типов информации, малварь обычно перехватывает лишь запрос под номером(5) "ProcessesAndThreadInfo", не обращая внимания на "HandleInfo=16". Это даём нам надежду, что таким способом мы сможем обхитрить гадкого вредоноса.

Вот аргументы
Код:
NtQuerySystemInfo()
, и возвращаемые ею данные. Поскольку в каждый момент времени в системе может быть различное кол-во дескрипторов, мы не можем определить точный размер буфера для приёма данных в аргументе
Код:
NtQuerySystemInfo()
. Поэтому лучше указать его сразу с запасом, например 1 МБ.

C-подобный:


Код:
invoke  VirtualAlloc
,
0
,
1024
*
1024
,
MEM_RESERVE
+
MEM_COMMIT
,
PAGE_READWRITE
  invoke  NtQuerySystemInformation
,
SysHandleInfo
,
rax
,
1024
*
1024
,
0
Так в буфере получим массив структур "SYSTEM_HANDLE_INFO_ENTRY", а кол-во этих структур в массиве будет прописано в первом поле "NumberOfHandles". На своём узле я получил порядка 18.000 действительных хэндлов, и учитывая размер одной структуры =24 байт, мне нужен буф объёмом 432 КБ.

C-подобный:


Код:
;
// Info Class = 16
;
//-----------------------------
struct SYSTEM_HANDLE_INFORMATION
   NumberOfHandles   dd
0
;
// dq для х64
Entries           SYSTEM_HANDLE_INFO_ENTRY32
;
// массив структур
ends 

struct SYSTEM_HANDLE_INFO_ENTRY32
;
// размер = 14 байт
ProcessId         dw
0
;
// PID процесса, кому принадлежит хэндл
ObjectTypeNumber  db
0
;
// Тип объекта - нам нужен "Process"
Flags             db
0
;
// Флаг наследования
Handle            dw
0
;
// Номер дескриптора объекта
Object            dd
0
;
// Его адрес в ядерной памяти
GrantedAccess     dd
0
;
// Маска доступа
ends
struct SYSTEM_HANDLE_INFO_ENTRY64
;
// размер = 24 байта
ProcessId         dd
0
ObjectTypeNumber  db
0
Flags             db
0
Handle            dw
0
Object            dq
0
GrantedAccess     dq
0
ends
2. Объект "Process" и его дескрипторы

Теперь нам нужно найти константу "ObjectType", которая олицетворяет дескриптор процесса в системе. Это даст возможность искать хэндлы по полю "ObjectTypeNumber" структуры выше SYSTEM_HANDLE_INFO_ENTRY. Для начала запросим у отладчика WinDbg все типы ядерных объектов, чтобы получить адреса их структур OBJECT_TYPE. Как видим объект типа "Process" описывает структура по адресу
Код:
0xfffffa80`0c6f8f30
:

Код:


Код:
0: kd> !object \ObjectTypes
Object: fffff8a0000065b0  Type: (fffffa800c6f7f30) Directory
    ObjectHeader: fffff8a000006580 (new version)
    HandleCount: 0  PointerCount: 44
    Directory Object: fffff8a0000045d0  Name: ObjectTypes

    Hash  Address           Type    Name
    ----  ----------------  ----    ----
     00   fffffa800c762f30  Type    TmTm
     01   fffffa800c760c90  Type    Desktop
          fffffa800c6f8f30  Type    Process
Для надёжности проверим содержимое структуры по этому адресу, и точно - в поле "Name" указано "Process".

Код:


Код:
0: kd> dt nt!_object_type fffffa800c6f8f30
   +0x000 TypeList                 : _LIST_ENTRY [ 0xfffffa80`0c6f8f30 - 0xfffffa80`0c6f8f30 ]
   +0x010 Name                     : _UNICODE_STRING "Process"
   +0x020 DefaultObject            : (null)
   +0x028 Index                    : 0x7 ''   x nt!ObTypeIndexTable
fffff800`0247c100  nt!ObTypeIndexTable = 

0: kd> dps fffff800`0247c100
fffff800`0247c100  00000000`00000000
Кстати этот-же индекс хранится и в структуре заголовка объекта OBJECT_HEADER, которая имеет размер 0x30 байт, и всегда предваряет сам объект. Например файловый объект описывает структура FILE_OBJECT, объект устройства DEVICE_OBJECT, а процессы - нашумевшая EPROCESS. Так вот если запросить адрес EPROCESS любого экзешника, то отняв от него 0x30 получим адрес заголовка, где будет маячить поле
Код:
"TypeIndex=7"
:

Код:


Код:
0: kd> !process 0 0 fasmw.exe
PROCESS fffffa800cac6060
    SessionId: 1  Cid: 0bc8    Peb: fffdf000  ParentCid: 1164
    DirBase: 05e90000  ObjectTable: fffff8a00513d960  HandleCount: 92.
    Image: FASMW.EXE

0: kd> dt _object_header fffffa800cac6060-0x30
nt!_OBJECT_HEADER
   +0x000 PointerCount       : 0n49
   +0x008 HandleCount        : 0n4
   +0x008 NextToFree         : 0x00000000`00000004 Void
   +0x010 Lock               : _EX_PUSH_LOCK
   +0x018 TypeIndex          : 0x7
Таким образом мы узнали, что для перечисления всех процессов по глобальной базе хэндлов, нам нужно искать их по флагу(7) в структурах HANDLE_INFO_ENTRY64 функции
Код:
NtQuerySystemInformation()
:

C-подобный:


Код:
struct SYSTEM_HANDLE_INFO_ENTRY64
   ProcessId         dd
0
ObjectTypeNumber  db
0

invoke  CreateToolhelp32Snapshot
,
TH32CS_SNAPPROCESS
,
0
mov
[
snapHndl
]
,
rax
;
// Парсим все активные процессы, и дампим их в ListBox
invoke  Process32First
,
[
snapHndl
]
,
ppe
@@
:
invoke  Process32Next
,
[
snapHndl
]
,
ppe
          or      eax
,
eax
          jz      @f
;
// На выход, если ошибка
inc
[
counter
]
cinvoke  wsprintf
,
strBuff
,

,
\
[
ppe
.
th32ProcessID
]
,
\
[
ppe
.
th32ParentProcessID
]
,
\
                                     ppe
.
szExeFile

          invoke  SendDlgItemMessage
,
[
hwnddlg
]
,
ID_LISTBOX1
,
LB_ADDSTRING
,
0
,
strBuff
          jmp     @b

@@
:
invoke  CloseHandle
,
[
snapHndl
]
invoke  SetDlgItemInt
,
[
hwnddlg
]
,
ID_Count1
,
[
counter
]
,
0
;
// Сдампили все процессы в первый ListBox!
;
// Теперь выделяем 1 МБ памяти, и заполняем её дескрипторами
invoke  VirtualAlloc
,
0
,
1024
*
1024
,
MEM_RESERVE
+
MEM_COMMIT
,
PAGE_READWRITE
          mov
[
HandleBuff
]
,
rax
          invoke  NtQuerySystemInformation
,
SysHandleInfo
,
rax
,
1024
*
1024
,
0
mov
[
counter
]
,
0
;
// Счётчик найденных в ноль
mov     rcx
,
[
HandleBuff
]
;
// Адрес буфера с хэндлами
mov     rsi
,
rcx
;
//
mov     rcx
,
[
rcx
]
;
// Кол-во структур в массиве
add     rsi
,
8
;
// RSI = указатель на первую структуру
@CompareHandle
:
push    rcx rsi
          cmp     byte
[
rsi
+
SYSTEM_HANDLE_INFO_ENTRY64
.
ObjectTypeNumber
]
,
7
jnz     @f
;
// Пропустить, если это не дескриптор процесса
xor     eax
,
eax
          mov     qword
[
buff
]
,
rax
          movzx   eax
,
word
[
rsi
+
SYSTEM_HANDLE_INFO_ENTRY64
.
ProcessId
]
mov
[
pid
]
,
rax
;
// Иначе возьмём его PID
invoke  _ltoa
,
eax
,
buff
,
10
;
// Переведём PID в строку для поиска в ListBox(1)
invoke  SendDlgItemMessage
,
[
hwnddlg
]
,
ID_LISTBOX1
,
LB_FINDSTRING
,
-
1
,
buff
          cmp     eax
,
LB_ERR
          jnz     @f
;
// Если нет совпадения, значит процесс скрытый!
;
// Открываем его, и запрашиваем путь до файла
invoke  OpenProcess
,
PROCESS_QUERY_INFORMATION
,
0
,
[
pid
]
push    rax
          mov     dword
[
strBuff
]
,
0
invoke  QueryFullProcessImageName
,
eax
,
0
,
strBuff
,
retVal
          pop     rax
          invoke  CloseHandle
,
eax
;
// Закрыть процесс,
inc
[
counter
]
;
// ..и вывести его имя в ListBox(2)
invoke  SendDlgItemMessage
,
[
hwnddlg
]
,
ID_LISTBOX2
,
LB_ADDSTRING
,
0
,
strBuff

@@
:
pop     rsi rcx
;
// Восстановить данные цикла
add     rsi
,
24
;
// сл.структура в массиве..
dec     rcx
;
// Это конец массива?
jnz     @CompareHandle
;
// Нет = на повтор
invoke  VirtualFree
,
[
HandleBuff
]
,
0
,
MEM_DECOMMIT
          cmp
[
counter
]
,
0
jnz     @exit
          invoke  SendDlgItemMessage
,
[
hwnddlg
]
,
ID_LISTBOX2
,
LB_ADDSTRING
,
0
,

invoke  SendDlgItemMessage
,
[
hwnddlg
]
,
ID_LISTBOX2
,
LB_ADDSTRING
,
0
,
\

4. Альтернативные методы

Конечно-же это не единственный способ поиска скрытых процессов в системе, хотя всё сводится к сравнению двух (полученных разными способами) списков. Здесь главное придумать вариант, который с вероятностью хотя-бы 70% может упустить из виду малварь. Хорошие результаты даёт так-же создание полного списка потоков Thread в системе функцией
Код:
Thread32First/Next()
, чтобы получить PID процесса-родителя.

Более того, системные процессы System и CSRSS.EXE хранят в себе дескрипторы всех запущенных процессов, а потому совсем необязательно собирать глобальную базу по рассмотренной выше схеме - достаточно пропарсить только хэндлы в System или Csrss.exe на выбор. На скрине ниже видно, что у System аж 541 открытых дескрипторов, а у csrss.ехе вообще 626, среди которых непременно будут и дескрипторы процессов.

5. Заключение

Здесь мы рассмотрели только базовые методы обнаружения процессов, и если у кого есть идеи на этот счёт, просьба поделиться ими в комментах. В скрепку кладу исходник для сборки ассемблером FASM, и готовый EXE для тестов. Код сырой и мне не удалось его протестировать на разных машинах. Поэтому если софт у кого-нибудь найдёт скрытые процессы, то плиз дайте об этом мне знать. Всем удачи, и спокойной жизни без руткитов!
 
Ответить с цитированием

  #2  
Старый 04.02.2026, 00:34
Gemfory
Новичок
Регистрация: 03.01.2026
Сообщений: 0
Провел на форуме:
0

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

Ого, Marylin пишет под x64, редкое явление однако
 
Ответить с цитированием

  #3  
Старый 04.02.2026, 05:51
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
Провел на форуме:
145166

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

Да, в дефолте я привык к х32, и собираю х64 только в случае крайней необходимости. Здесь для страховки выбрал х64, т.к. имеются известные проблемы с перенаправлением FS и реестра, когда обращения идут из wow. В общем если в прототипе какой-либо функции указано "Начиная с Vista", то компилирую в х64, чтобы где-нибудь не всплыл баг.
 
Ответить с цитированием

  #4  
Старый 04.02.2026, 12:38
Ахимов
Новичок
Регистрация: 21.12.2025
Сообщений: 0
Провел на форуме:
0

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

SYSTEM_OBJECT_INFORMATION.HandleCount для обьектов, созданных на этапе инит. процесса наверно покажет наличие скрытых процессов. Может быть трудность с указателями на обьекты aslr

Проще потоки скрывать.
 
Ответить с цитированием

  #5  
Старый 04.02.2026, 14:35
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
Провел на форуме:
145166

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

Цитата:

Ахимов сказал(а):

SYSTEM_OBJECT_INFORMATION.HandleCount

Спасибо, интересный вариант, нужно будет протестить.
 
Ответить с цитированием
Ответ





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


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




ANTICHAT ™ © 2001- Antichat Kft.