Память
Когда мы перехватываем функцию, мы изменяем ее первые байты. Вызвав
NtReadVirtualMemory, кто угодно сможет определить, что функция перехвачена.
Поэтому мы должны перехватить NtReadVirtualMemory, чтобы избежать обнаружения.
NTSTATUS NtReadVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID Buffer,
IN ULONG BufferLength,
OUT PULONG ReturnLength OPTIONAL
);
Мы заменили байты в начале тех функций, которые перехватили, и еще
выделили память для нового кода. Необходимо проверить читает ли функция
какие-либо из этих байт. Если наши байты находятся в диапазоне от BaseAddress
до (BaseAddress + BufferLength), мы должны заменить некоторые байты в Buffer.
Если кто-либо пытается прочитать байты из нашей выделенной памяти,
следует вернуть пустой Buffer и ошибку STATUS_PARTIAL_COPY. Это значение
говорит о том, что не все запрошенные байты были скопированы в буфер Buffer.
Это также происходит при попытке доступа к невыделенной памяти. ReturnLength
должно быть установлено в нуль в данном случае.
#define STATUS_PARTIAL_COPY 0x8000000D
Если кто-нибудь запрашивает первые байты перехваченной нами функции,
мы должны вызвать оригинальный код, а затем скопировать оригинальные байты
(мы их сохранили) в буфер Buffer.
Теперь процесс не сможет определить, что функции перехвачены, чтением
памяти. Также, если вы отлаживаете перехватываченные функции, у отладчика будут
проблемы. Он будет показывать оригинальные байты, но выполнять наш код.
Чтобы сделать скрытие совершенным, мы еще должны перехватить функцию
NtQueryVirtualMemory, которая используется для получения информации о
виртуальной памяти. Мы можем перехватить ее, чтобы предотвратить обнаружение
выделенной нами памяти.
NTSTATUS NtQueryVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
OUT PVOID MemoryInformation,
IN ULONG MemoryInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
MemoryInformationClass определяет тип возвращаемых данных. Нас
интересуют первые два типа.
#define MemoryBasicInformation 0
#define MemoryWorkingSetList 1
Для MemoryBasicInformation возвращается структура:
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress;
PVOID AllocationBase;
ULONG AllocationProtect;
ULONG RegionSize;
ULONG State;
ULONG Protect;
ULONG Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
Каждая секция памяти имеет размер - RegionSize и тип - Type. Свободная
память имеет тип MEM_FREE.
#define MEM_FREE 0x10000
Если секция перед нашей имеет тип MEM_FREE, следует прибавить размер
нашей секции к ее RegionSize. Если следующая секция также имеет тип MEM_FREE,
следует прибавить размер следующей секции к RegionSize.
Если секция перед нашей имеет другой тип, мы вернем MEM_FREE для нашей
секции. Ее размер должен быть вычислен, учитывая также следующую секцию.
Для MemoryWorkingSetList возвращается структура:
typedef struct _MEMORY_WORKING_SET_LIST {
ULONG NumberOfPages;
ULONG WorkingSetList[1];
} MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST;
NumberOfPages - количество элементов в WorkingSetList. Это число должно
быть уменьшено. Мы должны найти нашу секцию в WorkingSetList и передвинуть
следующие записи на нее. WorkingSetList - массив DWORD'ов, где старшие 20 бит -
это старшие 20 бит адреса секции, а младшие 12 бит содержат флаги.
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!