Creation time: 2004-03-06
Article attribute: original
Article submission:
Sobeit (kinsephi_at_hotmail.com)
=================== [disappear "in the NT series operating system] =========================================================================================================================================
Sobeit
Author: Holy_Father
Version: 1.2 ENGLISH
Date: 05.08.2003
===== [1. content] ======================================== ====
Content
2. Introduction
3. File
3.1 NTQueryDirectoryFile
3.2 NTVDMControl
4. Process
5. Registration
5.1 nTenumerateKey
5.2 NTenumerateValueKey
6. System services and drivers
7. Hook and expand
7.1 Permissions
7.2 global hook
7.3 new process
7.4 DLL
8. Memory
9. Handle
9.1 Name your handle and get the type
10. Port
10.1 NetStat, Opports and Fportwinxp
10.2 Opports under Win2K and NT4, fport under Win2K
11. End
===== [2. Introduction] ========================================= ==========
This document is a technique that hides objects, files, services, and processes, etc. under Windows NT operating systems. This method is based on the hook of Windows API functions.
The techniques described in this article are from the research results of rootkit, so it can write rootkit more effect and simpler. It also includes my practice.
Hidden objects in this document means changing some system functions used to name these objects, so that they will ignore the names of these objects. This way we changed the return value of those functions of our changes indicate that these objects do not exist.
The most basic method (removing a few different) is the original function we call the original parameter, then we change their output.
In this article, the hidden file, process, registry key, and key value, system service, and driver are allocated, and there is handle.
===== [3. File] ======================================== There are many hidden files that make the system unable to discover. We only use the way to change the API, but do not use technologies such as involving the file system. This will be more easier because we cannot know the uniqueness of the file system.
===== [3.1 ntQuerydirectoryFile] =============================
The way to find a file in some directories in some directories is to enumerate all files in it and all files in its subdirectory. The enumeration of the file is to use the NTQueryDirectoryFile function.
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 is FileHandle, Fileinformation and FileinformationClass. FileHandle is a directory object handle obtained from NTOPENFILE. FILEINFORMATION is a pointer that points to the allocated memory of the data to be written. FILEINFORMATIONCLASS determines the type of record that is written to FileImformation.
FileInformationClass is a change type, we only need 4 values to enumerate content:
#define fileDirectoryInformation 1
#define FileFullDirectoryInformation 2
#define filebothdirectoryInformation 3
#define filenamesinformation 12
To write the structure of FileIndiRecoryInformation record:
Typedef struct _file_directory_information {
Ulong nextentryoffset;
Ulong unknown;
Large_integer credectime;
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;
FILEFULLDIRECTORYINFORMATION:
Typedef struct _file_full_directory_information {
Ulong nextentryoffset;
Ulong unknown;
Large_integer credectime;
Large_integer lastaccesstime;
Large_integer lastwritetime;
Large_integer changetime;
Large_integer endoffile;
Large_integer allocationsize;
Ulong FileAttributes;
Ulong filenamelength;
Ulong electionLength;
Wchar filename [1];
} File_full_directory_information, * pfile_full_directory_information;
FILEBOTHDIRECTORYINFORMATION:
Typedef struct _file_both_directory_information {
Ulong nextentryoffset;
Ulong unknown;
Large_integer credectime;
Large_integer lastaccesstime;
Large_integer lastwritetime;
Large_integer changetime;
Large_integer endoffile;
Large_integer allocationsize;
Ulong FileAttributes;
Ulong filenamelength;
Ulong electionLength;
Uchar alternatenamelength;
Wchar alternatename [12];
Wchar filename [1];
} File_both_directory_information, * pfile_both_directory_information;
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. It is important to have only three variables in these structural types.
NEXTENTRYOFFSET is the offset address of this list. The first item is at the address fileinformation 0, so the second item is the NEXTENTRYOFSET of the Fileinformation first item. The last item NEXTENTRYOFFSET is 0.
FileName is a full name.
FileNameLength is the length of the file name.
If we want to hide a file, we need to notify these four types, and return to each type. We need to compare the file comparison with us. If we intend to hide the first record, we can move the back of the structure forward, move the length of the first structure, which will cause the first record to be rewritten. If we want to hide anything else, you only need to change the value of the previous recorded NexTenTryOffset. If we want to hide the last record, change its NexTenTryOffset, otherwise the value of NexTenTryOffset should be the sum of the records we want to hide and the previous NEXTENTRYOFFSET value. Then modify the value of the previous recorded UNKNOWN variable, which is the index of the next search. To hide the recorded unknown variable value to the value of the recorded unkown variable we have to hide. If there is no record that should be visible, we return to status_no_such_file.
#define status_no_such_file 0xc000000F
===== [3.2 ntvdmControl] ========================================
I don't know why DOS enumeration NTVDM can obtain a list of files through the function NTVDMControl.
NTSTATUS NTVDMCONTROL
In Ulong Controlcode,
In Pvoid ControlData
);
Concrolcode indicates a sub-function to apply for data in the buffer ControlData. If ControlCode is the functionality of this function, the functionality of this function is set to FileBothDirectoryIctureFile function, the function ntquerydirectoryFile function.
#define vdmdirectoryFile 6
The use of ControlData at this time is like FileinFormation. The only difference here is that we don't know the length of the buffer. So we need to manually calculate its length. We also have 0x5E for all recorded NEXTENTRYOFFSET and the last record of FileNameLength (the last record removing the length of the file name). The hidden method is the same as the previous method of using NTQueryDirectoryFile.
===== [4. Process] =========================================
Various processes are obtained by NTQuerySystemInformation.
NTSTATUS NTQUERYSYSTEMINFORMATION
In system_information_class systeminformationclass,
In Out Pvoid SystemInformation,
In Ulong SystemInformationLength,
OUT Pulong ReturnLength Optional
);
SysteminformationClass indicates the category of the information we want to get. SystemInformation is a pointer to the function output buffer. SystemInformationLength is the length of this buffer. ReturnLength is the number of write bytes. For enumerations that are running, we use the systemInformationClass set to SystemProcesSandthreadsinformation.
#define systemInformationClass 5
The data structure returned in the SystemInformation buffer is:
Typedef struct _system_processes {
Ulong nextentryDelta;
Ulong threadcount;
Ulong reserved1 [6];
Large_integer createtime;
Large_integer usertime;
Large_integer kernetime;
Unicode_string processname;
KPRIORITY BASEPRIORITY
Ulong processid;
Ulong inheritedFromProcessId;
Ulong handlecount;
Ulong reserved2 [2];
VM_Counters vmcounters;
IO_COUNTERS IOCOUNTERS; // Windows 2000 unique
System_threads throughs [1];
} System_processes, * psystem_processes;
The hidden process and the hidden file method are basically the same, that is, change the NEXTENTRYDELTA that we need to hide the previous record of the record. Usually we don't have to hide the first record because it is an idle process.
===== [5. Registry] ======================================== =
Windows's registry is a large tree data structure that there are two important record types in us to hide. One type is the registry key and the other is the key value. Because the structure of the registry, the hidden registry key is not like hidden files or processes.
===== [5.1 nTenumerateKey] ================================
Because the structure of the registry we cannot request a list of all the keys specifying some of the keys. We can only pass the index of the specified key by querying some part of the registry to obtain its information. NTENUMERATEKEY is available here.
NTSTATUS NTENUMERATEKEY
In Handle KeyHandle,
In Ulong Index,
In key_information_class keyinformationclass,
Out pvoid keyInformation,
In Ulong KeyinformationLength,
Out Polong Resultlength
);
KeyHandle is a handle that has been labeled as a subkey that we want to get information from it. KeyInformationClass indicates the return information type. The data is last written to the KeyInFormaiton buffer, and the buffer length is KeyinformationLength. The number of bytes written is returned by ResultLength. The most important thing we need to realize is if we hide a key, the index of all keys after this key will change. Because we are using the high-level index to obtain the information, and request this key through the low index. So how many records we have to record before, and then return the correct value.
Let us look at an example. Suppose we have some key names in the registry, B, C, D, E and F. Their index starts from 0, that is, index 4 corresponds to key E. Now that we want to hide key B. When the hooked application is called with NTENUMERATEKEY, we should return to the F key because there is an index change. Now the problem is that we don't know if there will be an index is changed. If we don't pay attention to the change of the index and the request for index 4 is still returning to key E instead of key f, it is likely that there is no need to return or return to the key C when we use index 1 request. Both cases will cause errors. That's why we have to pay attention to the change of the index.
Now if we reuse the function by reinterping the function with an index 0 to Index, we may wait for a while (the normal registry on the 1GHz processor will have to wait for 10 seconds as a long time). So we have to come up with a more clever way.
We know that the key is sorted by alpha (except for reference). If we ignore references (we don't need to hide) We can use the following method to record changes. We list the list of key names we want to hide (using RTLCompareUnicodEString) through the alphabet, and then we do not need to re-call it with the unentered variable when the application calls NTenumerateKey, and the name of the record indicated by the index is.
NTSTATUS RTLCompareUnicodestring
In Punicode_String String1,
In Punicode_String String2,
In Boolean CaseinSensitive
);
String1 and string2 are strings that will be compared, and CASEINSENSITIVE is set to True when it is not ignored.
The function results describe the relationship between String1 and String2:
Result> 0: String1> String2
Result = 0: String1 = String2
Result <0: String1 Now we need to find an edge item. We are in the alphanumeric name in the list. The edge item is the last shorter name in our list. We know that the transfer is the number of edge items in our list. But not all items in our list are valid in the registry. So we have to request all of our items to reach the item in the registry in the list. These are done by calling NTOPENKEY. NTSTATUS NTOPENKEY Out phaldle keyhandle, IN Access_mask desidaccess, In POBJECT_ATTRIBUTES OBJECTATTRIBUTES ); KeyHandle is the handle of the high key, we use NTenumerateKey this value. DesaireAccess is access to power. Key_Enumerate_Sub_Keys is its correct value. ObjectAttributes describe the sub-keys we want to open (including its name). #define key_enumerate_sub_keys 8 If NTOPENKEY returns 0 indicates that the opening is successful, it means that this key from our list is exist. The opened button is turned off through NTClose. NTSTATUS NTCLOSE In Handle Handle ); For each NTenumaretekey, we want to calculate the change, the number is equivalent to the number of keys existing in the registry specified in our list. Then we add the number of changes to the variable index, and finally call the original NTenumerateKey. We use KeyInformationClass's KeyBasicInformation to get the name of the keys indicated by the index. #define KeybasicInformation 0 NTENUMERATEKEY Returns this structure in the Keyinformation buffer: Typedef struct _key_basic_information { Large_integer lastwritetime; Ulong TitleIndex; Ulong namelength; Wchar Name [1]; } Key_basic_information, * pkey_basic_information; Here we only need something is Name and its length Namelength. If we don't have the recorded index, we return to the error status_ea_list_inconsis. #define status_ea_list_inconsistent 0x80000014 ===== [5.2 nTenumerateValueKey] ============================ The registry key value is not classified by alphabet. Fortunately, the number of key values in one key is relatively small, so we can get the number of changes by the method of retraction. The API used to get a key value information is NTenumerateValueKey. NTSTATUS NTENUMERATEVALUEKEY In Handle KeyHandle, In Ulong Index, In key_value_information_class keyvalueInformationClass, Out pvoid keyvalueInformation, In Ulong KeyValueInformationLength, Out Polong Resultlength ); KeyHandle is also a handle of a hierarchical key. Index is an index of the key value in the button. KeyValueInformationClassClassClass describes the type of information, saved in the KeyValueInformation buffer, the buffer is based on byte to KeyValueInformationLength. The number of write bytes returns in ResultLength. We calculate the transfer by all index tuning functions of 0 to Index. The name of the key value is obtained by setting KeyValueInformationClassClass to KeyValueBasicInformation. #define keyvaluebasicinformation 0 Then we get the data structure that is next in the KeyValueInformation buffer: Typedef struct _key_value_basic_information { Ulong TitleIndex; Ulong Type; Ulong namelength; Wchar Name [1]; } Key_Value_basic_information, * pkey_value_basic_information; Here we are only interested in Name and NameLength. If there is no index that is not transferred here, we return to the error status_no_more_entries. #define status_no_more_entries 0x8000001A ===== [6. System service and driver] ==================================== system Services and drives are enumerated by four separate API functions. They are different in every Windows version. So we have to hink all 4 functions. Bool EnumserviceSstatusa SC_HANDLE HSCMANAGER, DWORD DWSERVICEPE, DWORD DWSERVICESTATE, Lpenum_Service_Status LPSERVICES, DWORD CBBUFSIZE, LPDWORD PCBBYTESNEED, LPDWORD LPSERVICESRETURNED, LPDWORD LPRESUMEHANDLE ); Bool EnumServiceGroupw SC_HANDLE HSCMANAGER, DWORD DWSERVICEPE, DWORD DWSERVICESTATE, Lpbyte lpservices, DWORD CBBUFSIZE, LPDWORD PCBBYTESNEED, LPDWORD LPSERVICESRETURNED, LPDWORD LPRESUMEHANDLE, DWORD DWUNKNOWN ); Bool EnumserviceSstatusexa ( SC_HANDLE HSCMANAGER, SC_ENUM_TYPE INFOLEVEL, DWORD DWSERVICEPE, DWORD DWSERVICESTATE, Lpbyte lpservices, DWORD CBBUFSIZE, LPDWORD PCBBYTESNEED, LPDWORD LPSERVICESRETURNED, LPDWORD LPRESUMEHANDLE, LPCTSTR PSZGroupname ); Bool EnumserviceSstusExw SC_HANDLE HSCMANAGER, SC_ENUM_TYPE INFOLEVEL, DWORD DWSERVICEPE, DWORD DWSERVICESTATE, Lpbyte lpservices, DWORD CBBUFSIZE, LPDWORD PCBBYTESNEED, LPDWORD LPSERVICESRETURNED, LPDWORD LPRESUMEHANDLE, LPCTSTR PSZGroupname ); The most important thing here is LPService, which points to the buffer of the saved service list. The LPSERVICESRETURNED that is recorded in the result is also important. The data structure in the output buffer depends on the function type. Function EnumServicesStatusa and EnumserviceSGroupW returns this structure: TYPEDEF STRUCT _ENUM_SERVICE_STATUS { LPTSTR LPSERVICENAME; LPTSTR LPDISPLAYNAME; Service_status serviceArstatus; } ENUM_SERVICE_STATUS, * LPENUM_SERVICE_STATUS; Typedef struct _service_status { DWORD DWSERVICEPE; DWORD DWCURRENTATE; DWORD DWCONTROLSACCEPTED; DWORD dwin32exitcode; DWORD DWSERVICESPECIFICEXITCODE; DWORD DWCHECKPOINT; DWORD DWWAITHINT; Service_status, * lpservice_status; Functions EnumserviceSstatusexa and EnumServicesStatusexw returns this: Typedef struct _enum_service_status_process { LPTSTR LPSERVICENAME; LPTSTR LPDISPLAYNAME; Service_status_process service service; } ENUM_SERVICE_STATUS_PROCESS, * LPENUM_SERVICE_STATUS_PROCESS; Typedef struct _service_status_process { DWORD DWSERVICEPE; DWORD DWCURRENTATE; DWORD DWCONTROLSACCEPTED; DWORD dwin32exitcode; DWORD DWSERVICESPECIFICEXITCODE; DWORD dwcheckpoint; DWORD dwaithint; DWORD DWPROCESSID; DWORD DWSERVICEFLAGS; Service_status_process, * lpservice_status_process; We only be interested in lpServicename because it is the name of the system service. All records have a static size, so we need to hide one if you need to move all records forward to it. Here we must distinguish the size of service_status and service_status_process. ===== [7. Dynamic hook and extension] ========================================= In order to achieve the expected effect, we need to hook all running processes and all processes that will be created. All new processes must be hooked before they run the first instruction, otherwise they can see hidden objects before being hidden enough. ===== [7.1 permission] ========================================== ==== First we know how to get at least administrator administrator permissions to get all the running processes. The best may be to run our process as a system service because it runs with System User Permissions. We first get special privileges for installing services. Gets the permissions for SedebugPrivilege is useful, by calling OpenProcessToken, LookuppprivileValue Unfinished with AdjustTokenPrivileges. Bool OpenProcesstoken Handle ProcessHandle, DWORD DESIREDACCESS, Phandle tokenhandle ); Bool LookuppprivilerageGevalue ( LPCTSTR LPSYSTEMNAME, LPCTSTR LPNAME, Pluid LPluid ); Bool AdjustTokenPrivileges (Handle tokenhandle, Bool DisableAllPrivileges, Ptoken_Privileges NewState, DWORD BUFFERLENGTH, Ptoken_Privileges PreviouState, PDWord ReturnLength ); code show as below: #define se_privilege_enabled 0x0002 #define token_Query 0x0008 #define token_adjust_privileges 0x0020 Handle htokeen; Luid DebugnameValue; Token_Privileges Privileges; DWORD DWRET; OpenProcessToken (GetCurrentProcess (), Token_adjust_privileges | token_Query, HToken; Lookuppprivilerage (Null, "Sedbugprivilege", & debugnameValue; PRIVILEGES.PRIVILEGECOUNT = 1; Privileges.privileges [0] .luid = DebugnameValue; Privileges.privileges [0] .attributes = se_privilege_enabled; AdjustTokenprivileges (HToken, False, & Privileges, Sizeof (Privileges), NULL, & DWRET CloseHandle (HTOKEN); ===== [7.2 global hook] ======================================== The enumeration process is done by the API function ntquerysysteminformation mentioned earlier. Because there are some internal native processes in the system, the method of rewriting the first instruction is used to hook. Everything we need to do for each running process. First assign a part of the memory in the target process to write new code we used to hook the function, then change the 5 bytes starting with each function to Jump Directive (JMP), this jump will turn to implement our Code. So the jump instruction can be executed immediately when the function being hooked. We need to save instructions that each function started to be overwritten, requiring them to call the original code of the hook function. The process of saving instructions is described in Section 3.2.3 of the Hook Windows API. First open the target process through NTOPROCESS and get the handle. If we don't have enough permissions, you will fail. NTSTATUS NTOPENPROCESS Out phaldle processhandle, IN Access_mask desidaccess, In Pobject_Attributes Objectttributes, In PClient_id ClientId Optional ); ProcessHandle is a pointer to the saving process object handle. DesignedAccess should be set to Process_All_Access. We want to set the pid of UniqueProcess as the target process in the ClientID structure, and UNIQUETHREAD should be 0. The opened handle can be closed through NTClose. #define process_all_access 0x0011f0fff Now we allocate some memory for our code. This is done by NTallocatevirtualMemory. NTSTATUS NTALLOCATEVIRTUALMEMORY In Handle ProcessHandle, In Out Pvoid Baseaddress, In Ulong ZeroBITS, In Out Polong Allocationsize, In Ulong AllocationType, In Ulong Protect ); ProcessHandle is the same parameter from NTOPENPROCESS. BaseAddress is a pointer, pointing to the beginning of the assigned virtual memory base, its input parameter should be NULL. Allocationsize points to variables of the number of bytes we have to assign, and it is also used to accept the number of bytes that actually allocate. It is best to add allocationType plus MEM_TOP_DOWN to MEM_COMMIT because the memory is as high as possible address close to the DLL address. #define mem_commit 0x00001000 #define mem_top_down 0x00100000 Then we can write our code by calling NTWRITEVIRTUALMEMORY. NTSTATUS NTWRITEVIRTUALMEMORY In Handle ProcessHandle, In Pvoid Baseaddress, In pvoid buffer, In ulong bufferlength, OUT Pulong ReturnLength Optional ); BaseAddress is the address returned by NTallocateVirtualMemory. Buffer points to the byte we have to write, BufferLength is the number of bytes we have to write. Now let's hook a single process. The dynamic link library loaded into all processes is only NTDLL.DLL. So we have to check if the function imported to hook is from NTDLL.DLL. However, the memory where these functions from other DLLs may have been allocated, and the code to override it will cause errors in the target process. This is why we must check if the function we want to hook is loaded by the dynamic link library is loaded by the target process. We need to get the PEB (Process Environment Block) of the target process through NTQueryInformationProcess. NTSTATUS NTQUERYINFORMATIONPROCESS In Handle ProcessHandle, In ProcessInfoclass ProcessInformationClass, Out Pvoid ProcessInformation, In Ulong ProcessInformationLength, OUT Pulong ReturnLength Optional ); We set ProcessInformationClass to ProcessBasicInformation, then the process_basic_information structure returns to the ProcessInformation buffer, the size is a given ProcessInformationLength. #define processbasicinformation 0 Typedef struct _process_basic_information { NTSTATUS EXITSTATUS; PPEB PebbaseAddress; Kaffinity affinitymask; KPRIORITY BASEPRIORITY Ulong uniqueprocessid; Ulong inheritedfromuniqueprocessid; } Process_basic_information, * pprocess_basic_information; Pebbaseaddress is something we have to find. At the PEBASEADDRESS 0C, it is the address of PPEB_LDR_DATA. These are obtained by calling NTREADVIRTUALMEMORY. NTSTATUS NTREADVIRTUALMEMORY In Handle ProcessHandle, In Pvoid Baseaddress, Out pvoid buffer, In ulong bufferlength, OUT Pulong ReturnLength Optional ); Variables are very similar to NTWRitevirtualMemory. At the PPEB_LDR_DATA 01c, it is the address of the INITIALIZATIONORDERMODULIST. It is a list of dynamic link libraries that are loaded into the process. We only interested in some parts of this structure. TYPEDEF STRUCT _IN_INITIALIZATION_ORDER_MODULE_LIST { PVOID NEXT, PVOID PREV, DWORD ImageBase, DWORD ImageEntry, DWORD ImageSize, ... ); Next refers to a pointer to the next record, the prev pointing forward, and the last record will point to the first one. ImageBase is the address of the module in memory. ImageEntry is a fast entry point. ImageSize is its size. We need to get their imagebase to all we want to hook. Then compare this imageBase and IninitializationOrderModuleList's ImageBase. Now we have been ready for the hook. Because we are hook that the process is running, it may be executed while we are overwriting the code, and it will cause errors. So first we have to stop all threads in the target process. Its thread list can be obtained by setting the NTQuerySystemInformation of SystemProcessandThreadInformation. Refer to Section 4 for the description of this function. However, you have to add a description of the SYSTEM_THREADS structure to save the thread. Typedef struct _system_threads { Large_integer kernetime; Large_integer usertime; Large_integer createtime; Ulong Waittime; Pvoid Startaddress; Client_id clientid; KPRIORITY PRIORITY KPRIORITY BASEPRIORITY Ulong contextswitchcount; Thread_State State; Kwait_reason waitreason; } System_threads, * psystem_threads; Calling NTOPENTHREAD for each thread to get their handle, by using the clientID. NTSTATUS NTOPENTHREAD Out phaldle threadhandle, IN Access_mask desidaccess, In Pobject_Attributes Objectttributes, In PClient_id ClientID ); The handle we need is saved in ThreadHandle. We need to set DesireDaccess to thread_suspend_resume. #define thread_suspend_resume 2 ThreadHandle is used to call NTSUSPENDTHREAD. NTSTATUS NTSUSPENDHREAD ( In Handle Threadhandle, OUT Pulong Previoussuspendcount Optional The process hang can be rewritten. We process the method described in Section 3.2.2 in the "Hook Windows API". The only difference is a function that uses other processes. After the hook is finished, we can call NTRESUMETHREAD to restore all threads. NTSTATUS NTRESUMETHREAD In Handle Threadhandle, OUT Pulong Previoussuspendcount Optional ); ===== [7.3 new process] ======================================== ========= Infected all running processes do not affect the process that will be run. We can get a list of processes every other time, and then infect the process in the new list. But this method is very unreliable. A better way is the function that will definitely call when the new process begins. Because the processes running in all systems have been hooked, this method will not miss any new processes. We can hook NTCREATTHREAD, but this is not the easiest way. We can hook NTRESUMETHREAD because it is also called whenever the new process is created, it is called after NTCReateThread. The only problem is that this function is not only called when the new process is created. But we can easily solve this. NTQueryInformationThread can give us a message that the thread belongs to which process. Finally, what we have to do is to check if the process has been hooked. This is done by reading the start of the function we want to hook. NTSTATUS NTQUERYINFORMATIONTHREAD In Handle Threadhandle, In ThreadInfoclass ThreadInformationClass, Out pvoid threadinformation, In ulong threadinformationLength, OUT Pulong ReturnLength Optional ); ThreadInformationClass is a category where it is set to ThreadBasicInformation. ThreadInformation is a buffer for saving results, and the size is calculated as ThreadInformationLength by byte. #define threadbasicinformation 0 Returns this structure for ThreadBasicinformation: Typedef struct _thread_basic_information { NTSTATUS EXITSTATUS; PNT_TIB TEBASEADDRESS; Client_id clientid; Kaffinity affinitymask; KPRIORITY PRIORITY KPRIORITY BASEPRIORITY } Thread_basic_information, * pthread_basic_information; ClientID is the PID of the process belonging thread. Now let's infect new processes. The problem is that there is only NTDLL.DLL in the address space of the new process, and the other modules are loaded after the NTRESUMETHREAD is called. There are several ways to solve this problem, saying that we can hook an API function named LDRINTIALIZETHUNK, which is called when the process is initialized. NTSTATUS LDRINTIALIZETHUNK (DWORD UNKNOWN1, DWORD UNKNOWN2, DWORD UNKNOWN3 ); First we first run the original code, then hook all functions to hook in the new process. But it is best to lift ldrinitializethunk, because this function is to be called many times, we don't need to re-hook all functions. The work has been completed before the program performs the first command. That's why it doesn't have a chance to call any of the hidden functions before we hook it. Like the process that hooks and dynamically hooks, just here, we don't need to care about the running thread. ===== [7.4 dll] ========================================= ======= Each process in the system is a NTDLL.DLL copy. This means we can hook any of the modules in the process initialization phase. But what should I do with other modules such as kernel32.dll or advapi32.dll? There are some processes only Ntdll.dll, and other modules are dynamically loaded during the running process after the process is hook. This is why we have to hook the function LDRLOADLL of the new module. NTSTATUS LDRLOADDLL ( Pwstr szcwpath, PDWORD PDWLDRERR, Punicode_String PunimodulenAme, Phinstance PRESULTINSTANCE ); The most important thing for us is Punimodulename, which saves the module name. The PRESULTINSTANCE saves the module address when the call is successful. We first call the original LDRLOADDLL and hook all functions in the load module. ===== [8. Memory] ======================================== === When we are hook a function we will modify the byte that it starts. You can detect the function hook by calling NTREADVIRTUALMEMORY anyone. So we also hook NTREADVIRTUALMEMORY to prevent detection. NTSTATUS NTREADVIRTUALMEMORY In Handle ProcessHandle, In Pvoid Baseaddress, Out pvoid buffer, In ulong bufferlength, OUT Pulong ReturnLength Optional ); We modified the bytes started by our function and assigned memory for our new code. We need to check that someone read these code. If our code appears in BaseEaddress to BaseEaddress BufferLength, we need to change some bytes in the buffer. If someone queries bytes in the memory we all, we return to empty buffers and errors status_partial_copy. This value is used to represent the requested byte and is not fully copied into the buffer, which is also used in the request of unallocated memory. At this time, ReturnLength should be set to 0. #define status_partial_copy 0x8000000D If someone queries the byte starting with the hooked function we call the original code and copy those bytes starting in the original code to the buffer. Now the new process has not been able to detect whether it is hooked by reading its memory. Also use questions if you debug-hooked process debugger, it will display the original code, but do our code. In order to make hidden perfect, we have to hook NTQueryVirtualMemory. This function is used to get information on virtual memory. We hook it to prevent the detection of the false memory we allocate. NTSTATUS NTQUERYVIRTUALMEMORY In Handle ProcessHandle, In Pvoid Baseaddress, In Memory_Information_Class MemoryInformationClass, Out pvoid memoryInformation, In Ulong MemoryInformationLength, OUT Pulong ReturnLength Optional ); MemoryInformationClass indicates the category of returning data. We are interested in the starting two types. #define memorybasicinformation 0 #define memoryworkingsetlist 1 Returns this structure for 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; Each section has its size regionsize and its type TYPE. The type of idle memory is MEM_FREE. (Section object is the file mapping object, which is an object that can be mapped to a process of virtual address space) #define mem_free 0x10000 If our code is a section of a section is MEM_FREE. We are in its RegionSize plus the size of our code. If the type of section after our code is also MEM_FREE, then add the size of the idle segment after the regions of the previous segment. If the section before our code is other type, we return to MEM_FREE from the section of our code. Its size is calculated according to the subsequent sections. Returns this structure for MemoryworkingSetList: Typedef struct _memory_working_set_list { Ulong Numberofpages; Ulong WorkingSetList [1]; } Memory_Working_Set_List, * PMemory_working_set_list; NumberOfpages is the number of lists in WorkingSetList. This number should be reduced. We found the section of our code in WorkingSetList and then record it before. WorkingSetList is an array arranged in DWORD. The high 20 digits of each element indicate the section address, and the low 12 bits are the flag. ===== [9. Handle] ======================================== = Call NTQuerySystemInformation with class SystemHandleinformation will get all an array of handles that open in the _system_handle_information_ex structure. #define systemhandleinformation 0x10 Typedef struct _system_handle_information { Ulong processid; Uchar ObjectTypenumber; Uchar flags; Ushort handle; PVOID Object; Access_mask grantedAccess; } System_handle_information, * psystem_handle_information; Typedef struct _system_handle_information_ex { Ulong Numberofhandles; System_handle_information information [1]; } System_handle_information_ex, * psystem_handle_information_ex; The ProcessID indicates the process of having handles. ObjectTypenumber is a handle type. Numberofhandles is the number of elements in the Information array. The hidden item is very troublesome, we have to remove all the elements and reduce Numberofhandles. All elements must be required after removal, because the handles in the array are packet packets by ProcessID. This means that all the handles from the same process are on one. The number of Handle for a process variable is increasing. Now I miss this function (NTQuerySystemInformation) using the SystemProcessandThreadSinformation class to call the structure returned when calling_system_processes. Here we can see that every process has its own handle in HandleCount. If we want to do more perfect, we should modify the HandleCount because this function is hidden when this function is called with the SystemProcesSandThreadThreadsinFormation class. But the correction is very wasteful. There will be a lot of handles that are turned on or off in a short period of time when the system is running normally. So the number of call handles next to this function twice is normal, so we do not need to change Handlecount. ===== [9.1 named handle and get the type] ========================================================== The hidden handle is very trouble, but it is more difficult to find out which handle is hidden. For example, we have to hide a process, you have to hide all your handles and hide all the handles that it has contact. We compare the processId parameters of the handle and the PID of the process you want to hide, if they are equal, hide this handle. But the handle of other processes has to be named first before we can compare anything. The number of handles in the system is usually very large, so it is best to compare the type of handle before trying to name. Named types can save a lot of time for our handles. The named handle and handle type are done by calling ntQueryObject. NTSTATUS ZWQUERYOBJECT In Handle ObjectHandle, In object_information_class objectinformationclass, Out pvoid ObjectInformation, In Ulong ObjectInformationLength, OUT Pulong ReturnLength Optional ); ObjectHandle is the handle we want to get information about information. ObjectInformationClass is the information type, saved in the buffer ObjectInformation in byte calculation length ObjectInformationLength. Our classes used by Object_information_Class are ObjectNameInformation and ObjectAllTypeSinformation. The ObjectNameInFromation class returns the object_name_information structure in the buffer, and the ObjectAllTypeSinformation class returns the object_all_types_information structure. #define ObjectNameInformation 1 #define ObjectAllTypesinformation 3 Typedef struct _object_name_information { Unicode_String Name; } Object_name_information, * pObject_name_information; Name determines the name of the handle. Typedef struct _object_type_information { Unicode_String Name; Ulong Objectcount; Ulong handlecount; Ulong reserved1 [4]; Ulong peakobjectcount; Ulong peakhandlecount; Ulong reserved2 [4]; Ulong invalidattribute; Generic_mapping genericmapping; Ulong validAccess; Uchar unknown; Boolean maintainhandaDatabase; Pool_Type PoolType; Ulong PagedPoolusage; Ulong nonpagedpoolusage; } Object_type_information, * pObject_type_information; TYPEDEF STRUCT _Object_all_types_information { Ulong NumberOfTypes; Object_type_information typeInformation; } Object_all_types_information, * pObject_all_types_information; Name determines the name object name, the type object is followed behind each Object_TYPE_INFORMATION structure. The next Object_Type_information structure is followed by this NAME, and the interval is 4 bytes. ObjectTyPenumber in the System_Handle_information structure is an index in the TypeInformation array. More difficult is to get the name of the handle in other processes. There are two kinds of naming this. First, copy the handle into our process by calling NTDUPLICATEOBJECT and then name it. This method will fail to certain types of handles. But because it fails to fail, we use this method. NTDUPLICATEOBJECT In Handle SourceProcessHandle, In Handle SourceHandle, In Handle TargetProcessHandle, Out Phandle TargetHandle Optional, IN Access_mask desidaccess, In Ulong Attributes, In Ulong Options ); SourceHandle is a handle we want to copy, SourceProcessHandle is a handle with a SourceHandle process. TargetProcessHandle is a handle that wants to copy to the process, here is the handle of our process. TargetHandle is pointing to a pointer to the original handle copy. DesignedAccess should be set to Process_Query_Information, Attributes, and Options set to 0. The second naming method is valid for all handles, that is, using the system driver. Source code can be Http://rootkit.host.sk's Ophaldle project is found. ===== [10. Port] ============================================ == The easiest way to enumerate open port is to call allocateandGetTcptableFromstack and AllocateAndgetudPTableFromstack functions, or AllocateandGetTcpextableFromstack and AllocateandGetudpextableFromstack functions, they are from iPhlpapi.dll. Functions with EX are valid from Windows XP. TypedEf struct _mib_tcprow { DWORD DWSTATE; DWORD DWLOCALADDR; DWORD DWLOCALPORT; DWORD DWREMOTEADDR; DWORD dwremoteport; } Mib_tcprow, * pmib_tcprow; Typedef struct _mib_tcptable { DWORD DWNUMENTRIES; MIB_TCPROW TABLE [Any_SIZE]; } MIB_TCPTABLE, * PMIB_TCPTABLE; Typedef struct _MIB_UDPROW { DWORD DWLOCALADDR; DWORD DWLOCALPORT; } MIB_UDPROW, * PMIB_UDPROW; Typedef struct _mib_udptable { DWORD DWNUMENTRIES; MIB_UDPROW TABLE [Any_SIZE]; } Mib_udptable, * pmib_udptable; TypedEf struct _mib_tcprow_ex { DWORD DWSTATE; DWORD DWLOCALADDR; DWORD DWLOCALPORT; DWORD DWREMOTEADDR; DWORD DWREMOTEPORT; DWORD DWPROCESSID; } MIB_TCPROW_EX, * PMIB_TCPROW_EX; TypedEf struct _MIB_TCPTABLE_EX { DWORD DWNUMENTRIES; MIB_TCPROW_EX TABLE [Any_SIZE]; } Mib_tcptable_ex, * pmib_tcptable_ex; Typedef struct _MIB_UDPROW_EX { DWORD DWLOCALADDR; DWORD DWLOCALPORT; DWORD DWPROCESSID; } MiB_udprow_ex, * pmib_udprow_ex; Typedef struct _MIB_UDPTABLE_EX { DWORD DWNUMENTRIES; MIB_UDPROW_EX TABLE [Any_SIZE]; } MiB_udptable_ex, * pmib_udptable_ex; DWORD WINAPI AllocateandGetTcptableFromstack OUT PMIB_TCPTABLE * PTCPTABLE, In Bool Border, In Handle Hallocheap, In dword dwallocflags, IN DWORD DWPROTOCOLVERSION; ); DWORD WINAPI AllocateAndgetudPtableFromstack Out pmib_udptable * pudptable, In Bool Border, In Handle Hallocheap, In dword dwallocflags, IN DWORD DWPROTOCOLVERSION; ); DWORD WINAPI AllocateandGetTcPextableFromstack OUT PMIB_TCPTABLE_EX * PTCPTABLEEX, In Bool Border, In Handle Hallocheap, In dword dwallocflags, IN DWORD DWPROTOCOLVERSION; ); DWORD WINAPI AllocateAndgetudpextableFromstack OUT PMIB_UDPTABLE_EX * PUDPTABLEEX, In Bool Border, In Handle Hallocheap, In dword dwallocflags, IN DWORD DWPROTOCOLVERSION; ); There is also another method. When the program creates a socket and starts listening, it will have an open handle for it and open the port. We enumerate all open handles in the system and send them to a specific buffer via NTDeviceiocontrolFile to find out if this handle is open. This will also give us information about ports. Because the handle is too much, we only detect the type of file and the name is / device / TCP or / Device / UDP. Opening only this type and name. If you look at the code in iPhlPapi.dll, you will find that these functions also call NTDEVICEIOCONTROLFILE and send them to a particular buffer to get a list of all open ports in the system. This means that we want to hide the port only need to hook the NtDeviceiocontrolFile function. NTSTATUS NTDEVICEIOCONTROLFILE In Handle FileHandle In Handle Event Optional, IN PIO_APC_ROUTINE APCROUTINE OPTIONAL, In Pvoid APCCONText Optional, OUT PIO_STATUS_BLOCK IOSTATUSBLOCK, In Ulong IoControlcode, in Pvoid InputBuffer Optional, In Ulong InputBufferLength, Out Pvoid OutputBuffer Optional, In Ulong OutputBufferlength ); We are interested in member variables have these few: FileHandle indicates the handle of the device to communicate, iostatusblock points to the variable that receives the final completion state and request operation information, IOCONTROLCODE is the number specified for the specific I / O control operation to be completed, InputBuffer contains input data, the length is InputBufferLength, which is calculated by byte, similar to OutputBuffer and OutputBufferLength. ===== [10.1 WinXP Using NetStat Opports fport] ========================================================================================================================= Get all open ports in WINDOES XP You can use some software than Opports, Fport, and NetStat. This is called NtDeviceioControlfile twice with IOControlCode0x000120003. The output buffer is filled in the second call. FileHandle's name is always / device / tcp. INPUTBUFFER is different from different types of calls: 1) It is like this to get the MIB_TCPROW array INPUTBUFFER: First call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 Second call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 2) In order to obtain a MIB_UDPROW array: First call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 Second call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 3) In order to obtain a MIB_TCPROW_EX array: First call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x000x00 0x00 0x00 0x00 Second call: 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 4) In order to obtain a MIB_UDPROW_EX array: First call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 Second call: 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 You can see that there is only a few bytes in the buffer. We now clearly explain: Our call is the invutbuffer [1] is 0x04 and inputbuffer [17] is 0x01. Only uses these input data to make the OutputBuffer for what we want. If we want to get TCP port information, we set the InputBuffer [0] to 0x00, and set it to 0x01 to get UDP port information. If we still need an additional output table (MIB_TCPROW_EX or MIB_UDPROW_EX) We will set InfputBufer [16] to 0x02 in the second call. If we find calls to use these parameters We modify the output buffer. Getting the number of rows in the output buffer can be easily separated from the Infotmation variable in the iostatusblock structure according to the size of the ROW. Hiding one of the ROW will change, just need to use the ROW to change him and delete the last ROW. Don't forget to modify OutputBufferLength and IostatusBlock. ===== [10.2 Win2K and NT4 Using OPPORTS, use fport] ============================================================================================================================================================================================ We call NTDEVICEICONTROLFILE with IOCONTROLCODE0X00210012 to determine whether this type file and name / device / TCP or / Device / UDP are handle open port. So the first we compare IOCONTROLCODE and then the type and handle name. If these are in line with the input buffer length, it should be the same as the structure TDI_CONNECTION_IN length, which is 0x18. The structure of the output buffer is TDI_CONNECTION_OUT. Typedef struct _tdi_connetion_in { Ulong UserDarata, Pvoid Userdata, Ulong OptionsLength, PVOID OPTIONS, Ulong RemoteaddressionLength, Pvoid RemoteAddress } TDI_CONNETION_IN, * PTDI_CONNETION_IN; TYPEDEF STRUCT _TDI_CONNETITON_OUT { Ulong State, Ulong Event, Ulong Transmittedtsdus, Ulong Receivedtsdus, Ulong TransmissionerRors, Ulong ReceiveErrorS, Large_integer throughput Large_integer delay, Ulong Sendbuffersize, Ulong ReceiveBuffersize, Ulong unreliable, Ulong unknown1 [5], USHORT UNKNOWN2 } TDI_CONNETITON_OUT, * PTDI_CONNETION_OUT The specific judgment is not a way to open the port, please refer to OPPORTS source code, You can find it at http://rookit.host.sk. We now hide the specified port. We have compared InputBufferLength and IOCONTROLCODE, and now compare RemoteadDressLength, which is always 3 or 4 for open ports. The last thing to do is comparing the ReceivetsDus in OutputBufferBuffer, compares with ports on the network and the port list to be hidden. Different TCPs and UDP practices are different from the name of the handle. We have hidden this port after deleting OutputBuffer, modifying iostatusblock and returning status_invalid_address. ===== [11. Conclusion] ========================================== ======= For details, please refer to the source code of HACKER Defender Rootkit Version 1.0.0, http://rootkit.host.sk and Http://www.rootkit.com can be found. In the future, I will also join more related technologies. This document updates will improve existing methods and join new ideas. Special Thanks Ratters provide a lot of technologies needed to complete this document and Hacker Defender code. =================================== [end] ============ ================== postscript: In fact, as long as we have a certain degree of understanding of Windows's kernels, we all know that simple relying on hook functions cannot be truly hidden. This is just a user who deceives the operating system, but can't deceive the operating system. Threads must be run, you must get the time slice, add yourself to the scheduled list, and expose yourself. The kernel maintains a decision of the data structure called the scheduler database to make thread scheduling. The most important structure is the scheduler ready queue (KidispatckerReadylisthead). There is 64 DWORDs, respectively correspond to the 32 thread priority queues, and the queue contains a ready-to-state thread, waiting for scheduling execution. There are also two queues KiwaitinListhead and KiwaitoutListhead holds threads in the waiting state. You can enjoy all the elements in these three lists to list all threads in the system. Therefore, we must completely "disappear" from the Windows system to start from the kernel of Windows (Windows's kernel is only responsible for thread scheduling, and other functions are completed by executable program components). This feature needs to be completed. Limited level, you are welcome to point out the mistakes. QQ: 27324838 Email: kinvis@hotmail.com