Jump to content
Nytro

[r00tkit] SSDT Hook for dummies

Recommended Posts

[r00tkit] SSDT Hook for dummies

Oct.10, 2010

Here I am again with my first tutorial which only focus on kerneland. We will do a simple approach to the System Service Dispatch Table (SSDT) Hook. The SSDT is in fact an array in which are stored all the syscalls addresses. A syscall is a function supplied straight by the kernel (kerneland) and usable by all userland processes. In order to hook a syscall in the SSDT, we will have thus to replace its address in the SSDT by the address of our function.

The syscall we hook in the script will be ZwSetValueKey and will be our main thread all along this article.

Code:

/* Author: Shp
* Website: http://www.shp-box.fr
* Date: the 9th of October 2010
* Name: ssdt hook ZwSetValueKey

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/

*/

#include <wdm.h>

/****************/
/* Declarations */
/****************/

#pragma pack(1)
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} SSDT_Entry;
#pragma pack()

__declspec(dllimport) SSDT_Entry KeServiceDescriptorTable; // We import KeServiceDescriptorTable (ntoskrnl.exe)

// SYSTEMSERVICE returns the address of the Nt* function corresponding to the Zw* function we put in argument
#define SYSTEMSERVICE(_func) \
KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)]

typedef NTSTATUS (*ZWSETVALUEKEY)( // The type of the target function
HANDLE KeyHandle,
PUNICODE_STRING ValueName,
ULONG TitleIndex OPTIONAL,
ULONG Type,
PVOID Data,
ULONG DataSize
);

ZWSETVALUEKEY ZwSetValueKeyOriginal; // We will call this function to call the original target function when its address will be replaced by our hook function address in the SSDT


/*******************/
/* The Hook Function */
/*******************/

// Our hook function will avoid values writing for "Run" and "RunOnce" key: in this way it prevents malwares from writing their path in those keys in order to open up at each reboot.
NTSTATUS ZwSetValueKeyHook(
IN HANDLE KeyHandle,
IN PUNICODE_STRING ValueName,
IN ULONG TitleIndex OPTIONAL,
IN ULONG Type,
IN PVOID Data,
IN ULONG DataSize
)
{
PKEY_BASIC_INFORMATION pKeyInformation = NULL;
int i, flag = 1;
NTSTATUS ret;
WCHAR targetKey1[] = L"Run"; // first key target
WCHAR targetKey2[] = L"RunOnce"; // second key target
unsigned long size = 0, sizeNeeded = 0;

DbgPrint("[+] In da hook function =)\n");

ret = ZwQueryKey(KeyHandle, KeyBasicInformation, pKeyInformation, size, &sizeNeeded); // We use this function in order to get the current key name. If it Run or RunOnce we prevent from writing.
if((ret == STATUS_BUFFER_TOO_SMALL) || (ret == STATUS_BUFFER_OVERFLOW)) { // If size not enough => we allocate more space memory
size = sizeNeeded;
pKeyInformation = (PKEY_BASIC_INFORMATION) ExAllocatePoolWithTag(NonPagedPool, sizeNeeded, 'aaaa');

ret = ZwQueryKey(KeyHandle, KeyBasicInformation, pKeyInformation, size, &sizeNeeded);
}

if(ret != STATUS_SUCCESS)
return ZwSetValueKeyOriginal(KeyHandle, ValueName, TitleIndex, Type, Data, DataSize);

if( (pKeyInformation->NameLength / sizeof(WCHAR)) == 3) { // 3 == strlen("Run")
for(i = 0; i < strlen(targetKey1); i++) {
if(pKeyInformation->Name[i] != targetKey1[i]) { // if one character is different from Run key name, flag = 0
flag = 0;
break;
}
}
}
else if((pKeyInformation->NameLength / sizeof(WCHAR)) == 7) { // 7 == strlen("RunOnce")
for(i = 0; i < strlen(targetKey2); i++) {
if(pKeyInformation->Name[i] != targetKey2[i]) { // if one character is different from RunOnce key name, flag = 0
flag = 0;
break;
}
}
}
else flag = 0;

if(!flag) // If flag == 0 => normal work ...
return ZwSetValueKeyOriginal(KeyHandle, ValueName, TitleIndex, Type, Data, DataSize);

DbgPrint("[+] Bypassing Run key writing\n");

return STATUS_SUCCESS; // ... else the function will not be executed so no value writing ...
}

/*****************/
/* SSDT Functions */
/*****************/

void HookSSDT()
{
DbgPrint("[+] SSDTHOOK: in HookSSDT()\n");

ZwSetValueKeyOriginal = (ZWSETVALUEKEY) SYSTEMSERVICE(ZwSetValueKey); // We save target function address

// unprotect CR0
__asm
{
push eax
mov eax, CR0
and eax, 0FFFEFFFFh
mov CR0, eax
pop eax
}
//

SYSTEMSERVICE(ZwSetValueKey) = (unsigned long *) ZwSetValueKeyHook; // We replace target function address by the address of our hook function

// protect cr0
__asm
{
push eax
mov eax, CR0
or eax, NOT 0FFFEFFFFh
mov CR0, eax
pop eax
}
//

}

void UnHookSSDT()
{
DbgPrint("[+] SSDTHOOK: in UnHookSSDT()\n");

// unprotect CR0
__asm
{
push eax
mov eax, CR0
and eax, 0FFFEFFFFh
mov CR0, eax
pop eax
}
//

SYSTEMSERVICE(ZwSetValueKey) = (ZWSETVALUEKEY) ZwSetValueKeyOriginal; // We delete hook by rewriting the good function address instead of our hook function address

// protect cr0
__asm
{
push eax
mov eax, CR0
or eax, NOT 0FFFEFFFFh
mov CR0, eax
pop eax
}
//
}

VOID unloadFunction(PDRIVER_OBJECT pDriverObject)
{
UnHookSSDT(); // unhook function
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
HookSSDT(); // hook function

pDriverObject->DriverUnload = unloadFunction;

return STATUS_SUCCESS;
}


The principle

ntoskrnl.exe process exports KeServiceDescriptorTable table hence we will import it. But what does it contain?

typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} SSDT_Entry;

ServiceTableBase is the SSDT address and ParamTableBase the System Service Parameter Table (SSPT) address. The SSPT contains for each function in the SSDT the number of bytes taken in arguments.

Here is a very useful macro for our code:

#define SYSTEMSERVICE(_func) \
KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)]

It allows us to get the address of the place where the address of the _func function in the SSDT is stored since ServiceTableBase (so the SSDT) is an array of addresses.This macro is firstly hardly understandable but WinDbg makes our task easier.

To begin with, we are sure that *(PULONG)((PUCHAR)_func+1) corresponds to the index of _func in the SSDT (between the []).

Let’s see what is hidden at _func+1 therefore at ZwSetValueKey+1 in our case:

kd> u ZwSetValueKey l 1
nt!ZwSetValueKey:
804dda08 b8f7000000 mov eax,0F7h

Mov inserts 0xF7 value in eax: for each syscall, the first instruction is so:

mov eax, SSDT_INDEX_VALUE

Let’s now try to understand the casts: PULONG is the addresses type (PUCHAR cannot contain addresses such as FFFFFFFF because it is one byte size). The problem with PULONG during incrementation of our function (_func+1) is that _func will be incremented by 4 and not by 1! Indeed when we increments a pointer by 1, it is not incremented by 1 but by 1* sizeof(POINTER_TYPE) and we obviously know that sizeof(unsigned long) is 4. Therefore PUCHAR cast increments the pointer by 1 * sizeof(char) so by 1.

Disable read-only SSDT protection

At last we must know that SSDT writing is by default impossible: it is in most of cases in read-only mode. There are two technicals to modify this protection: one simple and another one less simple. We will choose the simplest one for our script but I will explain in few words how the second one works.

CR0 Trick

The first technical is called CR0 trick: CR0 is a register that when set to 0 disable all SSDT protections.

Here is the assembler script which allows us to disable the protection:

__asm
{
push eax // we save eax
mov eax, CR0 // we put CR0 value into eax
and eax, 0FFFEFFFFh // we apply the reverser filter
mov CR0, eax // we update CR0
pop eax
}

And to enable it again:

__asm
{
push eax
mov eax, CR0
or eax, NOT 0FFFEFFFFh // the reverse operation to get CR0 state as before the hook
mov CR0, eax
pop eax // we get the eax save
}

Memory Descriptor List (MDL)

The second technical is the use of MDL which allows our script to describe a part of the memory (here the SSDT) and therefore to modify its properties (here write access). The useful functions are MmCreateMdl, MmBuildMdlForNonPagedPool, MmMapLockedPages and MDL_MAPPED_TO_SYSTEM_VA flag. I have not really understood the interest of this method yet but if you are curious I suggest you to go to check out Ivanlef0u article (french).

Now let us look at my script. Here is its working: he is going to hook ZwSetValueKey function that is used to modify or add values in a specified registry key. My hook function check if the current key is Run or RunOnce; if it is we prevent from writing. In the other case we let the function work normally.

The purpose of this script is to protect the system from malwares that write their path in keys such as HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run. It would allow them, if they had the permission, to start up again at each computer reboot.

Sursa: [r00tkit] SSDT Hook for dummies - Shp Labz

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...