# Hook SSDT(Shadow)

\[TOC]

## Basic struct

```c
struct SSDTStruct
{
    LONG* pServiceTable;
    PVOID pCounterTable;
#ifdef _WIN64
    ULONGLONG NumberOfServices;
#else
    ULONG NumberOfServices;
#endif
    PCHAR pArgumentTable;
};
```

## Find SSDT(Shadow) base address

### x86

**SSDT:**

```
1: kd> dps nt!KeServiceDescriptorTable
83f86b00  83e7ccbc nt!KiServiceTable
83f86b04  00000000
83f86b08  00000191
83f86b0c  83e7d304 nt!KiArgumentTable

1: kd> dd /c 1 nt!KiServiceTable l2
83e7ccbc  84098ffa    # Function offset
83e7ccc0  83ed5217
```

**SSDT Shadow:**

```
1: kd> dps nt!KeServiceDescriptorTableShadow
83f86b40  83e7ccbc nt!KiServiceTable    # SSDT 
83f86b44  00000000
83f86b48  00000191
83f86b4c  83e7d304 nt!KiArgumentTable
83f86b50  9be58000 win32k!W32pServiceTable    # SSDT Shadow
83f86b54  00000000
83f86b58  00000339
83f86b5c  9be5902c win32k!W32pArgumentTable

1: kd> dd /c 1 win32k!W32pServiceTable l2
9be58000  9bde19df
9be58004  9bdfa31b
```

**Before dump SSDT Shadow, you have to attach to any user-mode GUI process (like winlogon.exe)**

```
1: kd> !process 0 0 winlogon.exe
PROCESS 882d3d20  SessionId: 1  Cid: 01d4    Peb: 7ffdc000  ParentCid: 0188
    DirBase: bf153080  ObjectTable: 9b33f300  HandleCount:  55.
    Image: winlogon.exe

1: kd> .process /p 882d3d20  
Implicit process is now 882d3d20
.cache forcedecodeuser done
```

**Function Index to real function address:**

```c
realAddress = (PVOID)ntTable[FunctionIndex]
```

**Find table address in a driver**

Because **nt!KeServiceDescriptorTable** is exported by kernel, we can get its address in the program

```c
UNICODE_STRING routineName;
RtlInitUnicodeString(&routineName, L"KeServiceDescriptorTable");
pServiceDescriptorTable = (SSDTStruct*)MmGetSystemRoutineAddress(&routineName);
```

Then we can get the address of **nt!KeServiceDescriptorTableShadow** by add the offset (like previously windbg shows)

```c
pServiceDescriptorTableShadow = (PVOID)((ULONG_PTR)pServiceDescriptorTable + 0x40)
```

Then Get the addresses of two tables

```c
pNtTable = pServiceDescriptorTable;
pW32kTable = (PVOID)((ULONG_PTR)pServiceDescriptorTable + 0x10)
```

### x64

**SSDT:**

```
1: kd> dps nt!KeServiceDescriptorTable
fffff800`040c7840  fffff800`03e97300 nt!KiServiceTable    # SSDT base address
fffff800`040c7848  00000000`00000000
fffff800`040c7850  00000000`00000191
fffff800`040c7858  fffff800`03e97f8c nt!KiArgumentTable

1: kd> dd /c 1 nt!KiServiceTable l10
fffff800`03e97300  040d9a00    # Function offset
fffff800`03e97304  02f55c00
fffff800`03e97308  fff6ea00
fffff800`03e9730c  02e87805
fffff800`03e97310  031a4a06
fffff800`03e97314  03116a05
fffff800`03e97318  02bb9901
...
```

**SSDT Shadow:**

```
1: kd> dps nt!KeServiceDescriptorTableShadow
fffff800`040c7880  fffff800`03e97300 nt!KiServiceTable        # SSDT base address
fffff800`040c7888  00000000`00000000
fffff800`040c7890  00000000`00000191
fffff800`040c7898  fffff800`03e97f8c nt!KiArgumentTable
fffff800`040c78a0  fffff960`00111f00 win32k!W32pServiceTable    # SSDT Shadow base address
fffff800`040c78a8  00000000`00000000
fffff800`040c78b0  00000000`0000033b
fffff800`040c78b8  fffff960`00113c1c win32k!W32pArgumentTable

1: kd> dd /c 1 win32k!W32pServiceTable l10
fffff960`00111f00  fff3a740        # GDI Function offset
fffff960`00111f04  fff0b501
fffff960`00111f08  000206c0
fffff960`00111f0c  001021c0
...
```

**Before dump SSDT Shadow, you have to attach to any user-mode GUI process (like winlogon.exe)**

```
1: kd> !process 0 0 winlogon.exe
PROCESS fffffa80042a8b30
    SessionId: 1  Cid: 01ec    Peb: 7fffffde000  ParentCid: 0198
    DirBase: 1bf29000  ObjectTable: fffff8a000a01580  HandleCount: 111.
    Image: winlogon.exe

1: kd> .process /p fffffa80042a8b30
Implicit process is now fffffa80`042a8b30
.cache forcedecodeuser done

1: kd> .reload
```

**Function Index to real function address:**

```c
readAddress = (ULONG_PTR)(ntTable[FunctionIndex] >> 4) + SSDT(Shadow)BaseAddress;
```

**Find table address in a driver**

Find **nt!KeServiceDescriptorTable** and **nt!KeServiceDescriptorTableShadow** in kernel function **nt!KiSystemServiceStart**

```
0: kd> u nt!KiSystemServiceStart
nt!KiSystemServiceStart:
fffff800`03e9575e 4889a3d8010000  mov     qword ptr [rbx+1D8h],rsp
fffff800`03e95765 8bf8            mov     edi,eax
fffff800`03e95767 c1ef07          shr     edi,7
fffff800`03e9576a 83e720          and     edi,20h
fffff800`03e9576d 25ff0f0000      and     eax,0FFFh
nt!KiSystemServiceRepeat:
fffff800`03e95772 4c8d15c7202300  lea     r10,[nt!KeServiceDescriptorTable (fffff800`040c7840)]
fffff800`03e95779 4c8d1d00212300  lea     r11,[nt!KeServiceDescriptorTableShadow
```

As shown above, we can search the whole kernel address from the kernel base for **KiSystemServiceStart** 's pattern

```c
const unsigned char KiSystemServiceStartPattern[] = { 
    0x8B, 0xF8,                     // mov edi,eax
    0xC1, 0xEF, 0x07,               // shr edi,7
    0x83, 0xE7, 0x20,               // and edi,20h
    0x25, 0xFF, 0x0F, 0x00, 0x00    // and eax,0fffh  
};

bool found = false;
ULONG KiSSSOffset;
for(KiSSSOffset = 0; KiSSSOffset < kernelSize - signatureSize; KiSSSOffset++)
{
    if(RtlCompareMemory(
        ((unsigned char*)kernelBase + KiSSSOffset), 
        KiSystemServiceStartPattern, signatureSize) == signatureSize)
    {
        found = true;
        break;
    }
}
if(!found)
    return NULL;
```

After find the target function, we can retrieve the address of **nt!KeServiceDescriptorTableShadow** in the instruction:

```
4c8d1d00212300  lea     r11,[nt!KeServiceDescriptorTableShadow
```

```c
ULONG_PTR addressAfterPattern = kernelBase + kiSSSOffset + signatureSize
ULONG_PTR address = addressAfterPattern + 7 // Skip lea r10,[nt!KeServiceDescriptorTable]
LONG relativeOffset = 0;
// lea r11, KeServiceDescriptorTableShadow
if((*(unsigned char*)address == 0x4c) &&
   (*(unsigned char*)(address + 1) == 0x8d) &&
   (*(unsigned char*)(address + 2) == 0x1d))
{
    relativeOffset = *(LONG*)(address + 3);
}
if(relativeOffset == 0)
    return NULL;

SSDTStruct* shadow = (SSDTStruct*)( address + relativeOffset + 7 );
```

Then we can get the addresses of **nt!KiServiceTable(SSDT)** and **win32k!W32pServiceTable(SSDT Shadow)**

```c
PVOID ntTable = (PVOID)shadow;
PVOID win32kTable = (PVOID)((ULONG_PTR)shadow + 0x20);    // Offset showed in Windbg
```

## Find the base address of ntoskrnl.exe and win32k.sys

First, we use the undocumented function **ZwQuerySystemInformation** with parameter of ***SystemModuleInformation*** to get information of system modules

```c
// Get SystemInfo size first
ZwQuerySystemInformation(SystemModuleInformation,
                      &SystemInfoBufferSize,
                      0,
                      &SystemInfoBufferSize);
// Allocate buffer for system info
pSystemInfoBuffer = (PSYSTEM_MODULE_INFORMATION)ExAllocatePool(NonPagedPool, SystemInfoBufferSize * 2);
// Query SystemIfno
status = ZwQuerySystemInformation(SystemModuleInformation,
             pSystemInfoBuffer,
             SystemInfoBufferSize * 2,
             &SystemInfoBufferSize);
```

while the struct of **SYSTEM\_MODULE\_INFORMATION** is

```c
typedef struct _SYSTEM_MODULE_INFORMATION
{
    ULONG Count;
    SYSTEM_MODULE_ENTRY Module[0];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

typedef struct _SYSTEM_MODULE_ENTRY
{
    HANDLE Section;
    PVOID MappedBase;
    PVOID ImageBase;
    ULONG ImageSize;
    ULONG Flags;
    USHORT LoadOrderIndex;
    USHORT InitOrderIndex;
    USHORT LoadCount;
    USHORT OffsetToFileName;
    UCHAR FullPathName[256];
} SYSTEM_MODULE_ENTRY, *PSYSTEM_MODULE_ENTRY;
```

Usually the module **ntoskrnl.exe** is always the first module in the buffer, which is`Module[0]`, so:

```c
PVOID ntBase = pSystemInfoBuffer->Module[0].ImageBase;
```

But the position of **win32k.sys** in the buffer is not sure, so we have to traverse the buffer to find it by compare the **FullPathName** field of each **SYSTEM\_MODULE\_ENTRY** with **"win32k.sys"**

```c
PSYSTEM_MODULE_ENTRY moduleInfo = pSystemInfoBuffer->Module;
UNICODE_STRING win32kPath = RTL_CONSTANT_STRING(L"\\SystemRoot\\system32\\win32k.sys");
UNICODE_STRING moduleName;
ANSI_STRING ansiModuleName;
while ( TRUE )
{
    RtlInitAnsiString( &ansiModuleName, (PCSZ)moduleInfo->FullPathName );
    RtlAnsiStringToUnicodeString( &moduleName, &ansiModuleName, TRUE );
    if ( RtlCompareUnicodeString( &win32kPath, &moduleName, TRUE ) == 0)
    {
        PVOID win32kBase = moduleInfo->ImageBase;
        break;
    }
    moduleInfo = moduleInfo + 1;    // Iterate to next module entry
}
```

## Find SSDT function index by function name

#### Index in ntdll.dll

Almost all SSDT function have a stub function which has the same name and is exported by **NTDLL.DLL**

eg. the first few lines of user-mode function **NtCreateFile** exported by NTDLL.DLL (*You have to attach to user-mode process to view symbols in ntdll*)

```
0: kd> u ntdll!NtCreateFile
ntdll!ZwCreateFile:
00000000`774d1860 4c8bd1          mov     r10,rcx
00000000`774d1863 b852000000      mov     eax,52h        # This is the SSDT index 
00000000`774d1868 0f05            syscall
00000000`774d186a c3              ret
00000000`774d186b 0f1f440000      nop     dword ptr [rax+rax]
```

As above shows, `mov eax, 52h` transfers the system call number to **EAX**, and call `syscall` to trap into the kernel, then kernel will use the number `52h` to find corresponding kernel function in SSDT

```
0: kd> dd /c 1 nt!KiServiceTable l53    # Function index begin from 0
fffff800`03e97300  040d9a00
fffff800`03e97304  02f55c00
fffff800`03e97308  fff6ea00
fffff800`03e9730c  02e87805
fffff800`03e97310  031a4a06
......
fffff800`03e97438  031bab01
fffff800`03e9743c  02efec80
fffff800`03e97440  02d52300
fffff800`03e97444  04637102
fffff800`03e97448  03071007            # This is the index of nt!NtCreateFile (52h)

0: kd> u FFFFF8000419E400
nt!NtCreateFile:
fffff800`0419e400 4c8bdc          mov     r11,rsp
fffff800`0419e403 4881ec88000000  sub     rsp,88h
fffff800`0419e40a 33c0            xor     eax,eax
fffff800`0419e40c 498943f0        mov     qword ptr [r11-10h],rax
fffff800`0419e410 c744247020000000 mov     dword ptr [rsp+70h],20h
```

**SO, we can get any SSDT function's index from its name by looking into its user-mode stub function exported by NTDLL.DLL!**

#### Get SSDT index from ntdll.dll

First we must read **ntdll.dll** into memory

```c
UNICODE_STRING FileName;
OBJECT_ATTRIBUTES ObjectAttributes;
RtlInitUnicodeString(&FileName, L"\\SystemRoot\\system32\\ntdll.dll");
InitializeObjectAttributes(&ObjectAttributes, &FileName,
                           OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                           NULL, NULL);

if(KeGetCurrentIrql() != PASSIVE_LEVEL)
{
    return STATUS_UNSUCCESSFUL;
}

HANDLE FileHandle;
IO_STATUS_BLOCK IoStatusBlock;
// Open ntdll.dll file 
NTSTATUS NtStatus = ZwCreateFile(&FileHandle,        
                                 GENERIC_READ,
                                 &ObjectAttributes,
                                 &IoStatusBlock, NULL,
                                 FILE_ATTRIBUTE_NORMAL,
                                 FILE_SHARE_READ,
                                 FILE_OPEN,
                                 FILE_SYNCHRONOUS_IO_NONALERT,
                                 NULL, 0);
if(NT_SUCCESS(NtStatus))
{
    // Get ntdll.dll file size
    FILE_STANDARD_INFORMATION StandardInformation = { 0 };
    NtStatus = ZwQueryInformationFile(FileHandle, &IoStatusBlock, &StandardInformation, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
    if(NT_SUCCESS(NtStatus))
    {
        FileSize = StandardInformation.EndOfFile.LowPart;
        FileData = (unsigned char*)RtlAllocateMemory(true, FileSize);

        LARGE_INTEGER ByteOffset;
        ByteOffset.LowPart = ByteOffset.HighPart = 0;
        // Read ntdll.dll into buffer
        NtStatus = ZwReadFile(FileHandle,
                              NULL, NULL, NULL,
                              &IoStatusBlock,
                              FileData,
                              FileSize,
                              &ByteOffset, NULL);
    }

    ZwClose(FileHandle);
}
```

Then get the real function address of any exported function .

To do this, we have to get its **Export Address Table** from its **PE header**

```c
 //Verify DOS Header
PIMAGE_DOS_HEADER pdh = (PIMAGE_DOS_HEADER)FileData;
//Verify PE Header
PIMAGE_NT_HEADERS pnth = (PIMAGE_NT_HEADERS)(FileData + pdh->e_lfanew);
//Verify Export Directory
PIMAGE_DATA_DIRECTORY pdd = NULL;
if(pnth->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
    pdd = ((PIMAGE_NT_HEADERS64)pnth)->OptionalHeader.DataDirectory;
else
    pdd = ((PIMAGE_NT_HEADERS32)pnth)->OptionalHeader.DataDirectory;
ULONG ExportDirRva = pdd[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
ULONG ExportDirSize = pdd[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
ULONG ExportDirOffset = RvaToOffset(pnth, ExportDirRva, FileSize);

//Read Export Directory
PIMAGE_EXPORT_DIRECTORY ExportDir = (PIMAGE_EXPORT_DIRECTORY)(FileData + ExportDirOffset);
ULONG NumberOfNames = ExportDir->NumberOfNames;
ULONG AddressOfFunctionsOffset = RvaToOffset(pnth, ExportDir->AddressOfFunctions, FileSize);
ULONG AddressOfNameOrdinalsOffset = RvaToOffset(pnth, ExportDir->AddressOfNameOrdinals, FileSize);
ULONG AddressOfNamesOffset = RvaToOffset(pnth, ExportDir->AddressOfNames, FileSize);

ULONG* AddressOfFunctions = (ULONG*)(FileData + AddressOfFunctionsOffset);
USHORT* AddressOfNameOrdinals = (USHORT*)(FileData + AddressOfNameOrdinalsOffset);
ULONG* AddressOfNames = (ULONG*)(FileData + AddressOfNamesOffset);
```

and get the file offset of the function address from its name

```c
// Find RVA of exported function whose name is ExportName
ULONG ExportOffset = PE_ERROR_VALUE;
for(ULONG i = 0; i < NumberOfNames; i++)
{
    ULONG CurrentNameOffset = RvaToOffset(pnth, AddressOfNames[i], FileSize);
    if(CurrentNameOffset == PE_ERROR_VALUE)
        continue;
    const char* CurrentName = (const char*)(FileData + CurrentNameOffset);
    ULONG CurrentFunctionRva = AddressOfFunctions[AddressOfNameOrdinals[i]];
    if(CurrentFunctionRva >= ExportDirRva && CurrentFunctionRva < ExportDirRva + ExportDirSize)
        continue; //we ignore forwarded exports
    //compare the export name to the requested export
    if(!strcmp(CurrentName, ExportName))  
    {
        // Convert function RVA to offset
        ExportOffset = RvaToOffset(pnth, CurrentFunctionRva, FileSize);
        break;
    }
}
```

Then get the real address of exported function in memory ( in ntdll.dll )

```c
ExportFunctionAddress = (PVOID)((ULONG_PTR)FileData + ExportOffset)
```

finally search the whole function for pattern `mov eax, xx`, retrieve the SSDT index

```c
for(int i = 0; i < 32 && ExportOffset + i < FileSize; i++)
{
    if(ExportData[i] == 0xC2 || ExportData[i] == 0xC3)  //RET
        break;
    if(ExportData[i] == 0xB8)  //mov eax,XX    -- b8 FFFFFFFF
    {
        SsdtOffset = *(int*)(ExportData + i + 1);
        break;
    }
}
```

#### Combine index with SSDT(Shadow)

x64

```c
realNtFunction = (PVOID)(ntTable[SsdtOffset] >> 4 + ntTable);
```

x86

```c
realNtFunction = (PVOID)(ntTable[SsdtOffset]);
```

**Shadow is similar**

## Hook SSDT

{% hint style="info" %}
**!!! Yon have to first disable PatchGuard or enable DebugMode on your system !!!** :grinning:&#x20;
{% endhint %}

In order to unhook SSDT in future, define some hook structs as below (**HOOKOPCODES** is our shellcode for x64 hook)

```c
#pragma pack(push,1)        // This is very important !!!!
struct HOOKOPCODES
{
#ifdef _WIN64
    unsigned short int mov;
#else
    unsigned char mov;
#endif
    ULONG_PTR addr;
    unsigned char push;
    unsigned char ret;
};
#pragma pack(pop)

typedef struct HOOKSTRUCT
{
    ULONG_PTR addr;
    HOOKOPCODES hook;
    unsigned char orig[sizeof(HOOKOPCODES)];
    //SSDT extension
    int SSDTindex;
    LONG SSDTold;
    LONG SSDTnew;
    ULONG_PTR SSDTaddress;
}HOOK, *PHOOK;
```

### Disable Write Protection

Because SSDT are read-only kernel memory, so you have to disable **write protection** before modifying it , we use **MDL** to do this. Below is the helper function to copy data to any read-only kernel memory

```c
NTSTATUS SuperCopyMemory(
    IN VOID UNALIGNED* Destination, 
    IN CONST VOID UNALIGNED* Source, 
    IN ULONG Length)
{
    //Change memory properties.
    PMDL g_pmdl = IoAllocateMdl(Destination, Length, 0, 0, NULL);
    if(!g_pmdl)
        return STATUS_UNSUCCESSFUL;
    MmBuildMdlForNonPagedPool(g_pmdl);
    unsigned int* Mapped = (unsigned int*)MmMapLockedPages(g_pmdl, KernelMode);
    if(!Mapped)
    {
        IoFreeMdl(g_pmdl);
        return STATUS_UNSUCCESSFUL;
    }
    KIRQL kirql = KeRaiseIrqlToDpcLevel();
    RtlCopyMemory(Mapped, Source, Length);
    KeLowerIrql(kirql);
    //Restore memory properties.
    MmUnmapLockedPages((PVOID)Mapped, g_pmdl);
    IoFreeMdl(g_pmdl);
    return STATUS_SUCCESS;
}
```

Also, we can modify the "**WP"** flags of the `cr0`register to **enable** or **disable** kernel memory write protection

**To disable WP:**

```c
KIRQL DisableWP(){
    KIRQL irql=KeRaiseIrqlToDpcLevel();
    ULONG_PTR cr0=__readcr0();
#ifdef _AMD64_        
    cr0 &= 0xfffffffffffeffff;
#else
    cr0 &= 0xfffeffff;
#endif
    __writecr0(cr0);
    _disable();    // Disable interrupts
    return irql;
}
```

there we can modify the kernel memory without access violation. :face\_with\_monocle:

**To enable WP after writing:**

```c
void EnableWP(KIRQL irql){
	ULONG_PTR cr0=__readcr0();
	cr0 |= 0x10000;
	_enable();		// Enable interrupts
	__writecr0(cr0);
	KeLowerIrql(irql);
}
```

### x86 Hook

Just replace the SSDT index with your own function

```c
PVOID realNtFunction = ntTable[FunctionIndex];

hHook = (HOOK)RtlAllocateMemory(true, sizeof(HOOK));
hHook.SSDTold = realNtFunction;
hHook.SSDTnew = YourFunction;
hHook.SSDTindex = FunctionIndex;
hHook.SSDTaddress = realNtFunction;

SuperCopyMemory(&realNtFunction, YourFunction);
```

### x64 Hook

In x64 platform, because `realFunction = ntTable[FunctionIndex] >> 4 + ntTable`, the only thing you can modify is the value of `ntTable[FunctionIndex]`, and it's **long** type(32 bit), which means you can only jump within the range **-2GB + ntTable \~ 2GB + ntTable**, Obviously the address is inside the **ntoskrnl.exe module address space**. So to jump to the function located in our own module, we have to make a **"indirect jump"**.

Jump from SSDT to our stub function, then jump to our real function. Below is the stub shellcode

```
movabs rax, xxxxxxxxxxxxxxxx ; 48B8 XXXXXXXXXXXXXXXX (move our real function to rax)
push rax ; 50
ret ; c3 (return to rax, which is our function)
```

As you can see, the shellcode is only 12 bytes, so we only need to find a executable memory block which is larger than 12 bytes. Well in every code page(4KB size) there are many **nop** instruction where we can insert our shellcode, So we try to insert the shellcode into the code page where the real kernel function to be hooked is.

Get the ntoskrnl.exe's **section header** first

```c
ULONG dwRva = (ULONG)((unsigned char*)realNtFunction - (unsigned char*)ntBase);
IMAGE_DOS_HEADER* pdh = (IMAGE_DOS_HEADER*)ntBase;
if(pdh->e_magic != IMAGE_DOS_SIGNATURE)
    return 0;
IMAGE_NT_HEADERS* pnth = (IMAGE_NT_HEADERS*)((unsigned char*)ntBase + pdh->e_lfanew);
if(pnth->Signature != IMAGE_NT_SIGNATURE)
    return 0;
IMAGE_SECTION_HEADER* psh = IMAGE_FIRST_SECTION(pnth);
```

find out in which section the real function is

```c
static ULONG RvaToSection(IMAGE_NT_HEADERS* pNtHdr, ULONG dwRVA)
{
    USHORT wSections;
    PIMAGE_SECTION_HEADER pSectionHdr;
    pSectionHdr = IMAGE_FIRST_SECTION(pNtHdr);
    wSections = pNtHdr->FileHeader.NumberOfSections;
    for(int i = 0; i < wSections; i++)
    {
        if(pSectionHdr[i].VirtualAddress <= dwRVA &&
           (pSectionHdr[i].VirtualAddress + pSectionHdr[i].Misc.VirtualSize) > dwRVA)
                return i;
    }
    return (ULONG) - 1;
}

int section = RvaToSection(pnth, dwRva);
if(section == -1)
    return 0;

CodeSize = psh[section].SizeOfRawData;
CodeStart = (PVOID)((ULONG_PTR)ntBase + psh[section].VirtualAddress);
```

then try to find a at least 12 bytes large nop block in the code page

```c
unsigned char* Code = (unsigned char*)CodeStart;
unsigned int CaveSize = sizeof(HOOKOPCODES);
for(unsigned int i = 0, j = 0; i < CodeSize; i++)
{
    if(Code[i] == 0x90 || Code[i] == 0xCC)  //NOP or INT3
        j++;
    else
        j = 0;
    if(j == CaveSize)
        ShellcodeCave = (PVOID)((ULONG_PTR)CodeStart + i - CaveSize + 1);
}
```

Next we need to write the shellcode to the cave address we just got, and insert our own function address

initialize the hook struct

```c
//allocate structure
PHOOK hook = (PHOOK)RtlAllocateMemory(true, sizeof(HOOK));
//set hooking address
hook->addr = ShellcodeCave;        // Store the cave address
//set hooking opcode
#ifdef _WIN64
hook->hook.mov = 0xB848;
#else
hook->hook.mov = 0xB8;
#endif
hook->hook.addr = (ULONG_PTR)YourFunction;    // Insert our own function
hook->hook.push = 0x50;
hook->hook.ret = 0xc3;
//set original data
RtlCopyMemory(&hook->orig, (const void*)addr, sizeof(HOOKOPCODES));
```

write to the cave address

```c
SuperCopyMemory((void*)ShellcodeCave, &hook->hook, sizeof(HOOK));
```

After that, we can change the corresponding SSDT offset to point to shellcode cave.

**You have to convert cave address to the relative offset to SSDT first !!**

```c
oldOffset = ntTable[FunctionIndex];
newOffset = (LONG)((ULONG_PTR)ShellcodeCave - ntTable);
newOffset = (newOffset << 4) | oldOffset & 0xF;
```

update SSDT

```c
hook.SSDTold = oldOffset;
hook.SSDTnew = newOffset;
hook.SSDTIndex = FunctionIndex;
hook.SSDTaddress = realNtFunction;

SuperCopyMemory(&ntTable[FunctionIndex], newOffset);
```

**DONE!**

## Hook Shadow SSDT

The steps of hooking Shadow is similar with SSDT hook, except that you have to attach to a user-mode GUI process before getting the base address of the Shadow.&#x20;

```c
// Get the process id of the "winlogon.exe" process
GetProcessIdByName(ProcessId, "winlogon.exe");
PsLookupProcessByProcessId( ProcessId, &Process );
APC_STATE oldApc;
KeStackAttachProcess(Process, &oldApc);

// Find Shadows SSDT and do dirty things

KeUnstackDetachProcess(&oldApc);
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://m0uk4.gitbook.io/notebooks/mouka/windowsinternal/ssdt-hook.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
