📄 29a-7.024
字号:
=========================[ Invisibility on NT boxes ]===========================
How to become unseen on Windows NT
----------------------------------
Author: Holy_Father <holy_father@phreaker.net>
Version: 1.2 english
Date: 05.08.2003
=====[ 1. Contents ]============================================================
1. Contents
2. Introduction
3. Files
3.1 NtQueryDirectoryFile
3.2 NtVdmControl
4. Processes
5. Registry
5.1 NtEnumerateKey
5.2 NtEnumerateValueKey
6. System services and drivers
7. Hooking and spreading
7.1 Rights
7.2 Global hook
7.3 New processes
7.4 DLL
8. Memory
9. Handle
9.1 Naming handle and getting type
10. Ports
10.1 Netstat, OpPorts on WinXP, FPort on WinXP
10.2 OpPorts on Win2k and NT4, FPort on Win2k
11. Ending
=====[ 2. Introduction ]========================================================
This document is about technics of hiding objects, files, services,
processes etc. on OS Windows NT. These methods are based on hooking Windows API
functions which are described in my document "Hooking Windows API".
Everything here was get from my own research during writing rootkit
code, so there is a chance it can be written more effectively or it can be
written much more easily. This also involve my implementation.
Hiding arbitrary object in this document mean to change some system
functions which name this object in the way they would skip its naming. In
the case this object is only return value of that function we would return
value as the object does not exist.
Basic method (excluding cases of telling different) is that we would
call original function with original arguments and then we would change its
output.
In this version of this text are described methods of hiding files,
processes, keys and values in registry, system services and drivers, allocated
memory and handles.
=====[ 3. Files ]===============================================================
There are serveral possibilities of hiding files in the way OS would
not see it. We would aim only changing API and leave out technics like those
which play on features of filesystem. It also is much easier because we dont
need to know how particular filesystem works.
=====[ 3.1 NtQueryDirectoryFile ]===============================================
Looking for a file on wNT in some directory is based on searching in
all its files and files in all its subdirectories. For file enumeration is used
function NtQueryDirectoryFile.
NTSTATUS NtQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG FileInformationLength,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);
Important parameters for us are FileHandle, FileInformation
and FileInformationClass. FileHandle is a handle of directory object which
can be get from NtOpenFile. FileInformation is a pointer on allocated memory,
where this function write wanted data to. FileInformationClass determines type
of record written in FileInformation.
FileInformationClass is varied enumerative type, but we need only
four values which are used for enumerating directory content:
#define FileDirectoryInformation 1
#define FileFullDirectoryInformation 2
#define FileBothDirectoryInformation 3
#define FileNamesInformation 12
structure of recoed written in FileInformation for FileDirectoryInformation:
typedef struct _FILE_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;
for FileFullDirectoryInformation:
typedef struct _FILE_FULL_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
WCHAR FileName[1];
} FILE_FULL_DIRECTORY_INFORMATION, *PFILE_FULL_DIRECTORY_INFORMATION;
for FileBothDirectoryInformation:
typedef struct _FILE_BOTH_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
UCHAR AlternateNameLength;
WCHAR AlternateName[12];
WCHAR FileName[1];
} FILE_BOTH_DIRECTORY_INFORMATION, *PFILE_BOTH_DIRECTORY_INFORMATION;
and for FileNamesInformation:
typedef struct _FILE_NAMES_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;
This function writes a list of these structures in FileInformation.
Only three vairiables are important for us in any of these structure types.
NextEntryOffset is the length of particular list item. First item
can be found on address FileInformation + 0. So the second item is on address
FileInformation + NextEntryOffset of first one. Last item has NextEntryOffset
set on zero.
FileName is a full name of the file.
FileNameLength is a length of file name.
If we want to hide a file, we need to tell apart these four types
and for each returned record we need to compare its name with the one which
we want to hide. If we want to hide first record, we have to move following
structures by the size of the first. This will cause the first record would
be rewritten. If we want to hide another record, we can easily change the value
of NextEntryOffset of previous record. New value of NextEntryOffset would be
zero if we want to hide the last record, otherwise the value would be the sum
of NextEntryOffset of the record we want to hide and of previous record.
Then we should change the value of Unknown of previous record which is prolly
an index for next search. The value of Unknown of previous record should have
a value of Unknown of the record we want hide.
If no record which should be seen was found, we will return error
STATUS_NO_SUCH_FILE.
#define STATUS_NO_SUCH_FILE 0xC000000F
=====[ 3.2 NtVdmControl ]=======================================================
From unknown reason DOS emulation NTVDM can get a list of files also
with function NtVdmContol.
NTSTATUS NtVdmControl(
IN ULONG ControlCode,
IN PVOID ControlData
);
ControlCode specifies the subfunction which is applied on data in
ControlData buffer. If ControlCode equals to VdmDirectoryFile this function
does the same as NtQueryDirectoryFile with FileInformationClass set on
FileBothDirectoryInformation.
#define VdmDirectoryFile 6
Then ControlData is used like FileInformation. The only difference here
is that we do not know the length of this buffer. So we have to count it
manually. We have to add NextEntryOffset of all records and FileNameLength
of the last record and 0x5E as a length of the last record excluding the name
of the file. Hiding methods are the same as in NtQueryDirectoryFile then.
=====[ 4. Processes ]===========================================================
Various system info is available using NtQuerySystemInformation.
NTSTATUS NtQuerySystemInformation(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
SystemInformationClass specifies the type of information which we want
to get, SystemInformation is a pointer to the function output buffer,
SystemInformationLength is the length of this buffer and ReturnLength is
number of written bytes.
For the enumeration of running processes we use SystemInformationClass
set on SystemProcessesAndThreadsInformation.
#define SystemInformationClass 5
Returned structure in SystemInformation buffer is:
typedef struct _SYSTEM_PROCESSES {
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters; // Windows 2000 only
SYSTEM_THREADS Threads[1];
} SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;
Hiding processes is similiar as in the case of hiding files.
We have to change NextEntryDelta of previous record of that we want to hide.
Usually we will not want to hide the first record here because it is Idle
process.
=====[ 5. Registry ]============================================================
Windows registry is quite big tree structure containing two important
types of records for us which we could want to hide. First type is registry
keys, second is values. Owing to registry structure hiding registry keys is
not as trivial as hiding file or process.
=====[ 5.1 NtEnumerateKey ]=====================================================
Owing to its structure we are not able to ask for a list of all keys
in the specific part of registry. We can get only information about one key
specified by its index in some part of registry. This provides NtEnumerateKey.
NTSTATUS NtEnumerateKey(
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG KeyInformationLength,
OUT PULONG ResultLength
);
KeyHandle is a handle to a key in which we want to get information
about a subkey specified by Index. Type of returned information is specified
by KeyInformationClass. Data are written to KeyInformation buffer which length
is KeyInformationLength. Number of written bytes is returned in ResultLength.
The most important think we need to perceive is that if we hide a key,
indexes of all following keys woould be shifted. And because we are able to get
information about a key with higher index with asking for key with lower index
we always have to count how many records before were hidden and then return
the right one.
Let's have a look on the example. Assume we have some keys called A, B,
C, D, E and F in any part of registry. Indexing starts from zero which mean
index 4 match E key. Now if we want to hide B key and the hooked application
call NtEnumerateKey with Index 4 we should return information about F key
because there is an index shift. The problem is that we don't know that there
is a shift. And if we didn't care about shifting and return E instead of F when
asking for key with index 4 we would return nothing when asking for key with
index 1 or we would return C. Both cases are errors. This is why we have to
care about shifting.
Now if we counted the shift by recalling the function for each index
from 0 to Index we would sometimes wait for ages (on 1GHz processor it could
take up to 10 seconds with standard registry which is too much). So we have to
think out more sophisticated method.
We know that keys are (except of references) sorted alphabetically.
If we neglect references (which we don't want to hide) we can count the shift
by following method. We will sort alphabetically our list of key names which we
want to hide (RtlCompareUnicodeString can be used), then when application calls
NtEnumerateKey we will not recall it with unchanged arguments but we will find
out the name of the record specified by Index.
NTSTATUS RtlCompareUnicodeString(
IN PUNICODE_STRING String1,
IN PUNICODE_STRING String2,
IN BOOLEAN CaseInSensitive
);
String1 and String2 are strings which will be compared, CaseInSensitive
is True if we want to compare with neglecting character case.
Function result describes relation between String1 and String2:
result > 0: String1 > String2
result = 0: String1 = String2
result < 0: String1 < String2
Now we have to find a border. We will compare alphabetically the name of
the key specified by Index with the names in our list. The border would be
the last lesser name from our list. We know that the shift is at most
the number of the border in our list. But not all items from our list have to
be a valid key in the part of registry we are in. So we have to ask for all
items from our list up to border if they are in this part of the registry.
This can be done using NtOpenKey.
NTSTATUS NtOpenKey(
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -