陌路人 发表于 2015-4-5 14:10:04

x64 SSDT正确的偏移计算,一键恢复SSDT表中的所有HOOK

本帖最后由 陌路人 于 2015-4-5 19:33 编辑

首先感谢 Tesla.Angela的这套教程 才让我学到这么多.

在写这篇帖子之前我也思考了很久,

因为我是新手,所以新的东西搞不出来,旧的东西把也都是论坛有的

尤其是SSDT坛主的这个教程已经讲的 明明白白了

但是不发个帖子把,感觉来论坛学了这么多东西 没有一点贡献也过不去

所以没办法了硬着头皮 把自己学习时的心得 发出来吧 !如有不正确的请指教

大牛 勿喷啊...........................................


进入正题:                      需要过PG或者 内核越狱   测试环境 W7 x64

下面的代码 是我看了坛主的教程后突发奇想. 实现了一点功能

1, 卸载时 一个函数 恢复所有SSDT表中的偏移HOOK.

2, 函数支持 一次性最多HOOK 10个SSDT中的函数, 只需要传递一个新的 函数地址 跟要HOOK的索引号;

其次 就是SSDT偏移正确计算 ,和64位 驱动加载内核,基地址重定位遇到的一点问题;

一键恢复SSDT的实现就是   先重载内核基地址重定位后 计算出SSDT表中的正确偏移然后存放在 ULONG的0x191最小的数组中
等卸载的时候只要跟 原始内核中的SSDT中的偏移 比较 如果 不对就换成我们计算好的 偏移, ( 判断是需要都转换成真实地址在判断)

x64基地址重定位的时候有个低 4位的判断 要换成 IMAGE_REL_BASED_DIR64
其次 就是修改写 变量的大小   基本就很32位的重载内核一样的了

还有些问题 都在代码中注释表明的
        /*
                SSDT的偏移非常有意思.内核初始化的时候ServiceTableBase数组中的值
                都是每8个字节存放一个函数的真实地址,然后再经过一些算法
                把ServiceTableBase数组中每4个字节存放一个函数偏移的值

                (以ServiceTableBase * 8 * 0x191)\2 ==后的地址 可以用windbg查看
                dq 后 内存中存放的是函数的绝对地址 函数的名称则是从
                SSDT总序列号0x191\2 的函数开始,有兴趣的可以查看下;

                然后说下算法,它是以ServiceTableBase地址为例,然后让每个
                ULONG A=(绝对函数地址 - ServiceTableBase ==的偏移)+ServiceTableBase的低4字节
                存放在一个4字节的变量里. 然后按照下面的算法后才是正确的地址
                (ServiceTableBase * i *4)= (A - ServiceTableBase的低4字节)<<4;
               
                如果ULONG64 B > ULONG64 C , C - B =的偏移L ;
                如果L 直接+B ==的值 将是一个错误的值.
                因为B是 8个字节, 计算出的偏移是4个字节低4节相加将会溢出到8字节的高4字节
                所以地址就会错误.果断的让2个4字节的变量计算出正确的偏移后再写入内存
        */





#pragma once //只编译一次
#ifdef __cplusplus
extern"C"
{
#endif
#include "ssdt.h"

#ifdef __cplusplus
}
#endif

#defineMyDbgPrint//DbgPrint   //需要打印信息是 把前面的 {//} 去掉   
#define _max(a,b)a>b?a:b //计算ab值那个大用哪个
ULONG gu_SSDT = { NULL };
ULONG64 gu64_Raw_SSDT = { NULL };
ULONG COUNT=0;
BOOLEAN FLAG = FALSE;
KIRQL WPOFFx64()
{
        /*
        提升IRQL等级到Dispatch_Level
        关闭页面保护,CR0寄存器的值16位清00~16
        硬件中断标志位清0 ,不响应可屏蔽中断
        实现了,多线程安全性   _disable为硬件中断不响应
        Dispatch_Level为线程不可切换软中断
        */
        KIRQL irql = KeRaiseIrqlToDpcLevel();
        UINT64 cr0 = __readcr0();
        _disable();
        cr0 &= 0xfffffffffffeffff;
        __writecr0(cr0);
        return irql;
}

void WPONx64(KIRQL irql)
{
        /*
        恢复页面保护. CRO寄存器值16位值10~16
        IF中断标志至1可以响应可屏蔽中断
        降低软中断级别
        */
        UINT64 cr0 = __readcr0();
        cr0 |= 0x10000;
        __writecr0(cr0);
        _enable();
        KeLowerIrql(irql);
}

/*
直接   在 DriverEntry 文件中
externULONG64 gu64_Raw_SSDT[];

然后声明下函数 后 就可以直接使用了
HookSSDT((ULONG64)Fake_NtTerminateProcess, 41);

卸载时   UnhookSSDT();

*/

/*
       输入要替换的函数地址,已经SSDT中要HOOK的函数索引;
       成功 返回真    最多支持 HOOK 10 个函数
*/
BOOLEAN HookSSDT(ULONG64 NewFunction,ULONG index)
{
       
        if (FLAG == FALSE)
        {
                BOOLEANBoo;
                Boo = Relocation();
                if (!Boo)
                {
                        MyDbgPrint("HookSSDT()->if (!Boo),");
                        return FALSE;
                }
                FLAG = TRUE;
        }
        if (COUNT>=10)
        {
                MyDbgPrint("hookssdt上限,只支持10个");
                return FALSE;
        }
        if (!MmIsAddressValid((PVOID)NewFunction))
        {
                MyDbgPrint("HookSSDT()->输入的函数地址无效");
                return FALSE;
        }
        if (index>0x191)
        {
                MyDbgPrint("要输入的函数索引无效");
                return FALSE;
        }
        /*
                \x48\xB8\ == mov rax
                \xFF\xE0\ == jmp rax
                0x90==nop
        */
        UCHAR jmp_code[] = "\x48\xB8\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xE0";
        KIRQL irql;
        ULONG64 add,address;
        add = ((ULONG64)KeBugCheckEx + COUNT * 15);
        address = NewFunction;
        RtlCopyMemory(jmp_code + 2, &address, 8);
        irql = WPOFFx64();
        memset((PVOID)add , 0x90, 15);
        RtlCopyMemory((PVOID)add, jmp_code, 12);
        WPONx64(irql);
        MyDbgPrint("add%llx\n", add);

        /*
                然后计算出这个地址与SSDT中的偏移
        */
        ULONG offset=0;
        offset = FuncOffset(add);
        if (offset==0)
        {
                MyDbgPrint("HookSSDT()->获得函数偏移失败");
                return FALSE;
        }
        PSYSTEM_SERVICE_TABLE ssdt = NULL;
        ssdt = (PSYSTEM_SERVICE_TABLE)MyGetKeServiceDescriptorTable64();
        if (ssdt == NULL)
        {
                MyDbgPrint("HookSSDT()->if (ssdt==NULL)");
                return 0;
        }
        irql = WPOFFx64();

        *(ULONG *)((ULONG64)ssdt->ServiceTableBase + index * 4) = offset;

        WPONx64(irql);
        COUNT++;
        return TRUE;
}

/*
        根据传入的地址计算相对与SSDT的偏移
*/
ULONG FuncOffset(ULONG64 FuncAdd)
{
        PSYSTEM_SERVICE_TABLE ssdt = NULL;
        ssdt = (PSYSTEM_SERVICE_TABLE)MyGetKeServiceDescriptorTable64();
        if (ssdt == NULL)
        {
                MyDbgPrint("FuncOffset()->if (ssdt==NULL)");
                return 0;
        }
        typedef        struct ssdt_recover
        {
                ULONG64 add;
        }shuzu, *pshuzu;
        typedef unionasdf
        {
                ULONGa;
                shuzus;
        }myunion, *pmyunion;
        myunion   add;
        add.s.add = (ULONG64)ssdt->ServiceTableBase;
        ULONG address;
        address = FuncAdd - add.s.add;
        address += add.a;
        address = (address - add.a) << 4;
        return address;
}

/*
        一键恢复SSDT的所有HOOK
*/
BOOLEAN UnhookSSDT()
{
        if (FLAG == FALSE)
        {
                BOOLEANBoo;
                Boo = Relocation();
                if (!Boo)
                {
                        MyDbgPrint("UnhookSSDT()->if (!Boo),");
                        return FALSE;
                }
                FLAG = TRUE;
        }
        PSYSTEM_SERVICE_TABLE ssdt = NULL;
        ULONG i = 0;
        ULONG judge;//判断
        if (gu_SSDT==NULL)
        {
                MyDbgPrint("UnhookSSDT()->全局数组变量无效");
                return FALSE;
        }
        ssdt = (PSYSTEM_SERVICE_TABLE)MyGetKeServiceDescriptorTable64();
        if (ssdt==NULL)
        {
                MyDbgPrint("UnhookSSDT()->if (ssdt==NULL)");
                return FALSE;
        }
        KIRQL irql;
        for (i = 0; i < ssdt->NumberOfServices;i++)
        {
                judge = *(ULONG*)((ULONG64)ssdt->ServiceTableBase + i * 4) >> 4;
                if (judge!= gu_SSDT>>4)
                {
                        irql=WPOFFx64();
                        *(ULONG*)((ULONG64)ssdt->ServiceTableBase + i * 4) = gu_SSDT;
                        WPONx64(irql);
                        MyDbgPrint("被HOOK的SSDT序列号=%d,正确的函数地址%llx:\n", i, GetSSDTFunctionAddress64_2(i));
                }

        }
       
        MyDbgPrint("UnhookSSDT()->SSDT恢复完毕");
        return TRUE;

}


/*
    读取SSDTtable地址
        返回0 表示失败;否则返回地址KeServiceDescriptorTable
*/
ULONGLONG MyGetKeServiceDescriptorTable64() //我的方法
{
        //获取 KiSystemCall64 的地址
        PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082);
        PUCHAR EndSearchAddress = StartSearchAddress + 0x500;
        PUCHAR i = NULL;
        UCHAR b1 = 0, b2 = 0, b3 = 0;
        ULONG templong = 0;
        ULONGLONG addr = 0;
        for (i = StartSearchAddress; i < EndSearchAddress; i++)
        {
                if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
                {
                        b1 = *i;
                        b2 = *(i + 1);
                        b3 = *(i + 2);
                        if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15) //4c8d15   SSDT 的特征码
                        {
                                memcpy(&templong, i + 3, 4);
                                addr = (ULONGLONG)templong + (ULONGLONG)i + 7;
                                return addr;
                        }
                }
        }
        return 0;
}

/*
        根据序列号返回函数地址;
*/
ULONGLONG GetSSDTFunctionAddress64_2(ULONGLONG Index)
{
        LONG dwTemp = 0;
        ULONGLONG qwTemp = 0, stb = 0, ret = 0;
        PSYSTEM_SERVICE_TABLE ssdt = (PSYSTEM_SERVICE_TABLE)MyGetKeServiceDescriptorTable64();

        /*
                先获取KeServiceDescriptorTable的地址
                然后获取数组的地址KiServiceTable 基地址
                这个数组里边方的是一个函数的偏移地址,
                数组首地址+对应索引内的地址=实际的函数地址.
                (因为这里并没有执行任何指令所以不需要加上指令长度)

                值得注意的是,这个偏移要转化成二进制后右移4位才是正确的偏移地址
        */
        stb = (ULONGLONG)(ssdt->ServiceTableBase);
        qwTemp = stb + 4 * Index;
        dwTemp = *(PLONG)qwTemp;
        dwTemp = dwTemp >> 4;
        ret = stb + (LONG64)dwTemp;
        return ret;
}

VOIDPrintSSDT()
{
       
        ULONG i;
        ULONG64 j;
        PSYSTEM_SERVICE_TABLEa;
        a = (PSYSTEM_SERVICE_TABLE)MyGetKeServiceDescriptorTable64();
        for (i = 0; i < a->NumberOfServices;i++)
        {
                j = GetSSDTFunctionAddress64_2(i);
                DbgPrint("%d:地址=%llx\r\n", i, j);
        }

        return;
}

/*
        获取SSDT正确偏移,成功返回真;
*/
BOOLEAN Relocation()
{
        ULONG a = 0;
        char module_path = { NULL }; //内核路径
        ULONG_PTR base=NULL;//内核地址
        PULONGnew_module_pointer; //新内核
        base=MyModuleQuery(module_path);
        if (base==NULL)
        {
                MyDbgPrint("Relocation()->获取模块地址失败\n");
                return FALSE;
        }
        new_module_pointer = MyPeLoad(module_path, NULL);
        if (new_module_pointer==0)
        {
                MyDbgPrint("Relocation()->new_module_pointer==0)\n");
                return FALSE;
        }
        MyDbgPrint("新地址%p\n", new_module_pointer);
       

        a = MyRelocationTable(new_module_pointer, (PVOID)base);
       if (a==0)
       {
               MyDbgPrint("Relocation()->基地址重定位数据失败\n");
               ExFreePool(new_module_pointer);
               return FALSE;
       }
        a= MySSDTRelocation((ULONG_PTR)new_module_pointer, base);
        if (a == 0)
        {
                MyDbgPrint("Relocation()->SSDT重定位失败\n");
                ExFreePool(new_module_pointer);
                return FALSE;
        }
        if (gu_SSDT==NULL)
        {
                MyDbgPrint("Relocation()->获取SSDT偏移失败\n");
                ExFreePool(new_module_pointer);
                return FALSE;
        }
        if (gu64_Raw_SSDT == NULL)
        {
                MyDbgPrint("Relocation()->获取SSDT真实地址失败\n");
                ExFreePool(new_module_pointer);
                return FALSE;
        }
        ExFreePool(new_module_pointer);
        return TRUE;
}

/*
   获取内核模块 路径跟地址
*/
ULONG_PTR MyModuleQuery(OUT char* ModulePath )
{
        PSYSTEM_MODULE_INFORMATIONmodule = NULL;
        ULONG_PTR Base;
        module = (PSYSTEM_MODULE_INFORMATION)MyZwQuerySystemInformation(11);
        if (module==NULL)
        {
                MyDbgPrint("MyModuleQuery(),获取模块错误\n\r");
                return 0;
        }

        MyDbgPrint("模块路径:%s\n", module->Module->ImageName);
        MyDbgPrint("%s:%p\n", module->Module->ImageName + module->Module->ModuleNameOffset, module->Module->Base);

        memcpy(ModulePath, module->Module->ImageName, strlen(module->Module->ImageName));
        Base = (ULONG_PTR)module->Module->Base;
        ExFreePool(module);
        return Base;
}

ULONG_PTR MyZwQuerySystemInformation(IN ULONG Number)
{
        VOID * Buffer;
        NTSTATUS status;
        ULONG   buffer1 = 20;

        Buffer = ExAllocatePool(NonPagedPool, buffer1);
        //或许实际需要的缓冲区大小传入buffer1中
        status = ZwQuerySystemInformation(Number, Buffer, buffer1, &buffer1);
        ExFreePool(Buffer);
        if (status == STATUS_INFO_LENGTH_MISMATCH)
        {
                //分配一个无分页缓冲区NonPagedPool
                Buffer = ExAllocatePool(NonPagedPool, buffer1);
                if (!MmIsAddressValid(Buffer))
                {
                        MyDbgPrint("GetSystemIniformation(),if (!MmIsAddressValid(Buffer)),Error\r\n");
                        return 0;
                }
                memset(Buffer, 0, sizeof(Buffer));
                status = ZwQuerySystemInformation(Number, Buffer, buffer1, &buffer1);
                if (!NT_SUCCESS(status))
                {
                        MyDbgPrint("错误状态=%d\r\n", RtlNtStatusToDosError(status));
                        //分配缓冲区后如果失败就必须把分配的内存释放掉
                        ExFreePool(Buffer);
                        MyDbgPrint("GetSystemIniformation(),if (!NT_SUCCESS(status)),Error\r\n");
                        return 0;
                }
                return (ULONG_PTR)Buffer;
        }
        MyDbgPrint("GetSystemIniformation(),错误状态=%d\r\n", RtlNtStatusToDosError(status));
        return 0;
}

/*
        输入导出函数名字 L""
        获取函数地址
*/
ULONG_PTR QueryFunction(PWCHAR name)
{
        UNICODE_STRING   na;
        ULONG_PTR add;
        RtlInitUnicodeString(&na, name);
        add = (ULONG_PTR)MmGetSystemRoutineAddress(&na);
        return add;
}

/*
        加载PE到内存
        传入ascii 字符格式文件路径,或者是unicode 字符格式文件路径
        只能二选一,其中一个为0;
*/
PULONG MyPeLoad(IN char* cStr, IN WCHAR *wcSrt)
{
        /*
        读取pe文件到内存的流程,   (是先从硬盘读取文件,然后加载到内存)
        1,先通过一个路径打开一个文件,
        2,然后开始读取PE文件的各个信息
        3,然后从获得的信息中获取文件需要的大小后 分配同样的大小的缓冲区
        4,然后在依次按照规定,在把文件从DOS头 到模块依次读取到刚分配的缓冲区内存中
        */

        HANDLE    hfile;   //接受句柄
        NTSTATUSstatus;    //状态
        PVOID sizeof_image=NULL;//载入内存后的地址指针
        IO_STATUS_BLOCK      io_status_block;    //接受状态结构
        OBJECT_ATTRIBUTES    object_attributes;//句柄属性
        UNICODE_STRING       path_name;      
        ANSI_STRINGansi_path;
        ULONG flag = 0;//标志
       

        /*
                判断传入的参数后初始化字符串
        */
        if (cStr != NULL&&wcSrt != NULL)
        {
                MyDbgPrint("请输入一个路径,MyPeLoad()");
                return 0;
        }
        if (cStr == NULL&&wcSrt != NULL)
        {
                RtlInitUnicodeString(&path_name, wcSrt);
        }
        else if (cStr != NULL&&wcSrt == NULL)
        {
                RtlInitAnsiString(&ansi_path, cStr);
                status = RtlAnsiStringToUnicodeString(&path_name, &ansi_path, TRUE);
                if (!NT_SUCCESS(status))
                {
                        MyDbgPrint("MyLoadModule(),(cStr != NULL&&wcSrt == NULL)错误=%d\n", RtlNtStatusToDosError(status));
                        return 0;
                }
                flag = 1;
        }
        else
        {
                MyDbgPrint("输入参数无效,MyLoadModule,else\n");
                return 0;
        }




        /*
          初始化对象属性
          ZwCreateFile函数呢不直接接受字符串只接受一个OBJECT_ATTRIBUTES的一个结构
          所以我们要用InitializeObjectAttributes函数来初始化这个结构
                然后在当成参数传给ZwCreateFile
        */
        InitializeObjectAttributes(&object_attributes, //对象属性变量 POBJECT_ATTRIBUTES OUT
                &path_name,                                                                   //文件名   PUNICODE_STRING
                OBJ_CASE_INSENSITIVE,                      //表示不区分大小写
                NULL,                                    //NULL
                NULL);                                     //NULL
        MyDbgPrint("OBJECT_ATTRIBUTES后验证路径:%wZ\n", object_attributes.ObjectName);
        /*
                打开文件后,开始读取到内存
        */
        status = ZwCreateFile(
                &hfile,                  //返回的句柄OUT PHANDLE
                FILE_ALL_ACCESS,         //访问权限->所有权限
                &object_attributes,      //POBJECT_ATTRIBUTES 该结构包含要打开的文件名
                &io_status_block,      //PIO_STATUS_BLOCK 返回结果状态 OUT
                0,                     //初始分配大小,0是动态分配
                FILE_ATTRIBUTE_NORMAL,   //文件属性 一般为<-或者0;
                FILE_SHARE_READ,         //指定共享方式一般<- 或者0;
                FILE_OPEN,               //这个参数指定要对文件干嘛
                FILE_NON_DIRECTORY_FILE, //指定控制打开操作和句柄使用的附加标志位
                NULL,                  //指向可选的扩展属性区
                0);                      //扩展属性区的长度
        if (!NT_SUCCESS(status))
        {
                MyDbgPrint("MyLoadModule(),status = ZwCreateFile错误=%d\n", RtlNtStatusToDosError(status));
                return 0;
        }
        /*
        InitializeObjectAttributes初始化对象属性,用的全局路径名字还是个指针
        如果释放字符串缓冲区太早的话就会因路径参数为空而打开文件失败       
        */
        if (flag == 1) //表示如果传入的是ansi码需要把转换时分配的缓冲区释放掉
        {
                RtlFreeUnicodeString(&path_name);
        }





        //读取DOS头******
        IMAGE_DOS_HEADER        image_dos_header;//dos头结构
        LARGE_INTEGER       large_integer;//记录偏移
        large_integer.QuadPart = 0;
        status = ZwReadFile(hfile,       //ZwCreateFile成功后得到的句柄
                NULL,                        //一个事件NULL
                NULL,                        //回调例程。NULL
                NULL,                        //NULL
                &io_status_block,            //PIO_STATUS_BLOCK 返回结果状态 OUT ,同上
                &image_dos_header,         //存放读取数据的缓冲区 OUT PVOID
                sizeof(IMAGE_DOS_HEADER),    //试图读取文件的长度
                &large_integer,            //要读取数据相对文件的偏移量PLARGE_INTEGER
                0);                        //NULL
        if (!NT_SUCCESS(status))
        {
                ZwClose(hfile);
                MyDbgPrint("MyLoadModule(),status = ZwCreateFile错误=%d\n", RtlNtStatusToDosError(status));
                return 0;
               
        }
        //读取NT头*******
        IMAGE_NT_HEADERS64   image_nt_header;//NT头
        large_integer.QuadPart = image_dos_header.e_lfanew; //PE头偏移
        status = ZwReadFile(hfile,       //ZwCreateFile成功后得到的句柄
                NULL,                        //一个事件NULL
                NULL,                        //回调例程。NULL
                NULL,                        //NULL
                &io_status_block,            //PIO_STATUS_BLOCK 返回结果状态 OUT ,同上
                &image_nt_header,         //存放读取数据的缓冲区 OUT PVOID
                sizeof(IMAGE_NT_HEADERS64),    //试图读取文件的长度
                &large_integer,            //要读取数据相对文件的偏移量PLARGE_INTEGER
                0);                        //NULL
        if (!NT_SUCCESS(status))
        {
                ZwClose(hfile);
                MyDbgPrint("MyLoadModule(),status = ZwCreateFile错误=%d\n", RtlNtStatusToDosError(status));
                return 0;
        }
        //读取区块*****
        IMAGE_SECTION_HEADER * p_image_section_header;//指向多个区块结构
        //分配所有模块总大小
        p_image_section_header = (IMAGE_SECTION_HEADER*)ExAllocatePool(NonPagedPool,      //NonPagedPool从非分页内存池中分配内存
                sizeof(IMAGE_SECTION_HEADER)*image_nt_header.FileHeader.NumberOfSections);
        memset(p_image_section_header, 0, sizeof(p_image_section_header));
        //读
        large_integer.QuadPart += sizeof(IMAGE_NT_HEADERS64); //区块偏移
        status = ZwReadFile(hfile,       //ZwCreateFile成功后得到的句柄
                NULL,                        //一个事件NULL
                NULL,                        //回调例程。NULL
                NULL,                        //NULL
                &io_status_block,            //PIO_STATUS_BLOCK 返回结果状态 OUT ,同上
                p_image_section_header,         //存放读取数据的缓冲区 OUT PVOID
                sizeof(IMAGE_SECTION_HEADER)*image_nt_header.FileHeader.NumberOfSections,    //试图读取文件的长度
                &large_integer,            //要读取数据相对文件的偏移量PLARGE_INTEGER
                0);                        //NULL
        if (!NT_SUCCESS(status))
        {
                ExFreePool(p_image_section_header);
                ZwClose(hfile);
                MyDbgPrint("MyLoadModule(),large_integer.QuadPart +=错误=%d\n", RtlNtStatusToDosError(status));
                return 0;
        }
        sizeof_image = ExAllocatePool(NonPagedPool, image_nt_header.OptionalHeader.SizeOfImage);//NonPagedPool从非分页内存池中分配内存
        if (sizeof_image == 0)
        {
                ZwClose(hfile);
                KdPrint(("sizeof_image ExAllocatePool Failed!"));
                ExFreePool(p_image_section_header);//释放内存
                MyDbgPrint("MyLoadModule(),sizeof_image ExAllocatePool=错误=%d\n", RtlNtStatusToDosError(status));
                return 0;
        }
        //初始化下内存
        memset(sizeof_image, 0, image_nt_header.OptionalHeader.SizeOfImage);
        RtlCopyMemory(sizeof_image, &image_dos_header, sizeof(IMAGE_DOS_HEADER));      //dos头
        RtlCopyMemory((PVOID)((ULONG_PTR)sizeof_image + image_dos_header.e_lfanew),
                &image_nt_header, sizeof(IMAGE_NT_HEADERS));                                 //nt头
        RtlCopyMemory((PVOID)((ULONG_PTR)sizeof_image + image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS)),       //区块
                p_image_section_header, sizeof(IMAGE_SECTION_HEADER)*image_nt_header.FileHeader.NumberOfSections);//计算区块总大小
        //读取各个数据段的实际地址
        ULONG sizeof_raw_data;
        for (ULONG i = 0; i < image_nt_header.FileHeader.NumberOfSections; i++)
        {   //磁盘占用的大小选择最大的
                sizeof_raw_data = _max(p_image_section_header.Misc.VirtualSize, p_image_section_header.SizeOfRawData);
                large_integer.QuadPart = p_image_section_header.PointerToRawData;   //各个磁盘的偏移地址
                //读
                status = ZwReadFile(hfile,       //ZwCreateFile成功后得到的句柄
                        NULL,                        //一个事件NULL
                        NULL,                        //回调例程。NULL
                        NULL,                        //NULL
                        &io_status_block,            //PIO_STATUS_BLOCK 返回结果状态 OUT ,同上
                        (PVOID)((ULONG_PTR)sizeof_image + p_image_section_header.VirtualAddress), //区块装入内存后的相对文件头偏移量         
                        sizeof_raw_data,             //试图读取文件的长度
                        &large_integer,            //要读取数据相对文件的偏移量PLARGE_INTEGER
                        0);                        //NULL
                if (!NT_SUCCESS(status))
                {
                        MyDbgPrint("循环区块出错[%s]%x\n",
                                p_image_section_header.Name,
                                (ULONG_PTR)sizeof_image + p_image_section_header.VirtualAddress);

                        ExFreePool(sizeof_image);
                        ExFreePool(p_image_section_header);//释放内存
                        ZwClose(hfile);
                        MyDbgPrint("MyLoadModule(),错误=%d\n", RtlNtStatusToDosError(status));
                        return 0;
                }
        }
        ExFreePool(p_image_section_header);//释放内存
        ZwClose(hfile);
        return (PULONG)sizeof_image;
}

/*
        模块全局变量地址重定位表
*/
ULONGMyRelocationTable(PVOIDNewImage,PVOID RawImage)
{

        //_IMAGE_OPTIONAL_HEADER64
        ULONG                                        i;                     //for循环变量
        ULONG                                        uRelocTableSize = 0;   //存放数据块中的数据总个数
        ULONG                                        Type;                  //16位数据高4位
        PVOID                                         uRelocAddress = 0;       //指向需要修改内容的地址
        PIMAGE_BASE_RELOCATION        pImageBaseRelocation = 0;//重定位表
        ULONG_PTR size;
        ULONG_PTRoffset;//偏移
        /*
                RtlImageNtHeader可以直接获取模块的NT头
        */
        PIMAGE_NT_HEADERS64 nt_header=NULL;
        nt_header = RtlImageNtHeader(RawImage);
        if (!nt_header)
        {
                MyDbgPrint("MyRelocationTable()->if(!nt_header)错误\n");
                return 0;
        }
        //MyDbgPrint("重载模块ImageBase地址%p", nt_header->OptionalHeader.ImageBase);
        MyDbgPrint("旧模块ImageBase地址%p\n", nt_header->OptionalHeader.ImageBase);
        /*
                这里我们用的原始内核的偏移,目的就是定位到原始内核里的数据
        */
        offset = (ULONG_PTR)RawImage - nt_header->OptionalHeader.ImageBase;
        /*
             RtlImageDirectoryEntryToData函数
               可以获取数据目录表中的任意结构
               这里IMAGE_DIRECTORY_ENTRY_BASERELOC ==5 重定位表
        */
        pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)RtlImageDirectoryEntryToData(NewImage, TRUE, IMAGE_DIRECTORY_ENTRY_BASERELOC, (PULONG)&size);
        if (pImageBaseRelocation == NULL)
        {
                MyDbgPrint("MyRelocationTable(),if (pImageBaseRelocation == NULL\n");
                return 0;
        }

        /*
                开始修改数据 重新定位

                等pImageBaseRelocation->VirtualAddress的值为空的时候就结束循环
        */
        while (pImageBaseRelocation->VirtualAddress)
        {   //得到需要更改数据的个数

                uRelocTableSize = (pImageBaseRelocation->SizeOfBlock - 8) / 2;
                //循环遍历
                for (i = 0; i < uRelocTableSize; i++)
                {//判断高4位是否等于3
                        Type = pImageBaseRelocation->TypeOffset >> 12;
                        if (Type == IMAGE_REL_BASED_DIR64)
                        {
                                //让指针指向要重定位的数据
                                uRelocAddress = (PVOID)((pImageBaseRelocation->TypeOffset & 0xfff) + pImageBaseRelocation->VirtualAddress + (ULONG_PTR)NewImage);

                                //因为接下来的事要进行对地址的写入,所以判断下地址是否有效
                                if (!MmIsAddressValid(uRelocAddress))
                                {
                                        continue;//跳过本次循环然后,继续循环
                                }

                                /*
                                        偏移里边的数据才是要重新定位的全局变量数据
                                        计算出uRelocAddress是需要重定位数据的地址
                                        而要替换数据的地址(里的内容)需要加上的是   新模块基址-映像基址的偏移
                                */
                                *(ULONG_PTR*)uRelocAddress +=offset;
                                //DbgPrint("重定位后的地址%llx\r\n", *(ULONG_PTR*)uRelocAddress);

                        }
                }
                //把指针移到下一个快,如果->SizeOfBlock为空了,表示没有块了退出循环
                pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pImageBaseRelocation + pImageBaseRelocation->SizeOfBlock);
        }

        MyDbgPrint("基地址重定位成功\n");
        return 1;
}

/*
        SSDT重定位
*/
ULONG MySSDTRelocation(IN ULONG_PTR NewAddress, IN ULONG_PTR RawAddress)
{            
        /*
              实验结果
                新的内核地址是从磁盘读取到内存的,SSDT中的值是空的
          PSYSTEM_SERVICE_TABLE中的成员变量值都是空的
                所以要进行一些关键数值复制
        */
        ULONG_PTR offset; //函数偏移
        offset = NewAddress - RawAddress;
        PSYSTEM_SERVICE_TABLEraw_ssdt=NULL,new_ssdt=NULL;
        raw_ssdt = (PSYSTEM_SERVICE_TABLE)MyGetKeServiceDescriptorTable64();
        if (raw_ssdt ==NULL)
        {
                MyDbgPrint("MySSDTRelocation()->获取SSDT结构失败\n");
                return 0;
        }
        new_ssdt = (PSYSTEM_SERVICE_TABLE)((ULONG_PTR)raw_ssdt + offset);
        /*
                关键数值替换
        */
        new_ssdt->NumberOfServices = raw_ssdt->NumberOfServices;
        new_ssdt->ServiceTableBase = (PVOID)((ULONG_PTR)raw_ssdt->ServiceTableBase + offset);

typedef        struct ssdt_recover
        {          
                ULONG64 add;
        }shuzu,*pshuzu;
        typedef unionasdf
        {
                ULONGa;
                shuzus;
        }myunion, *pmyunion;
        myunion   add;
        ULONG i;
        ULONGz; //用于过渡转换

        /*
                SSDT的偏移非常有意思.内核初始化的时候ServiceTableBase数组中的值
                都是每8个字节存放一个函数的真实地址,然后再经过一些算法
                把ServiceTableBase数组中每4个字节存放一个函数偏移的值

                (以ServiceTableBase * 8 * 0x191)\2 ==后的地址 可以用windbg查看
                dq 后 内存中存放的是函数的绝对地址 函数的名称则是从
                SSDT总序列号0x191\2 的函数开始,有兴趣的可以查看下;

                然后说下算法,它是以ServiceTableBase地址为例,然后让每个
                ULONG A=(绝对函数地址 - ServiceTableBase ==的偏移)+ServiceTableBase的低4字节
                存放在一个4字节的变量里. 然后按照下面的算法后才是正确的地址
                (ServiceTableBase * i *4)= (A - ServiceTableBase的低4字节)<<4;
               
                如果ULONG64 B > ULONG64 C , C - B =的偏移L ;
                如果L 直接+B ==的值 将是一个错误的值.
                因为B是 8个字节, 计算出的偏移是4个字节低4节相加将会溢出到8字节的高4字节
                所以地址就会错误.果断的让2个4字节的变量计算出正确的偏移后再写入内存
        */
        //记录SSDT真实的函数地址
        for (i = 0; i < new_ssdt->NumberOfServices; i++)
        {
                gu64_Raw_SSDT = *(ULONG64 *)((ULONG_PTR)new_ssdt->ServiceTableBase + i * 8);
                //DbgPrint("%llx\n", gu64_Raw_SSDT);
        }

        add.s.add = (ULONG64)raw_ssdt->ServiceTableBase;
        for (i = 0; i < raw_ssdt->NumberOfServices; i++)
        {
                z = *(ULONG64 *)((ULONG_PTR)new_ssdt->ServiceTableBase + i * 8) - add.s.add;
                z += add.a;
                *(ULONG *)((ULONG64)new_ssdt->ServiceTableBase + i * 4) = (z-add.a)<<4;

        }
        MyDbgPrint("raw_ssdt%p\n", raw_ssdt->ServiceTableBase);
        MyDbgPrint("new_ssdt%p\n", new_ssdt->ServiceTableBase);
       
        for (i = 0; i < new_ssdt->NumberOfServices; i++)
        {
                gu_SSDT = *(ULONG *)((ULONG64)new_ssdt->ServiceTableBase + i * 4);
                //DbgPrint("0x%08x\n", gu_SSDT);
        }

        return 1;
}


BOOLEAN UnhookSSDT();
BOOLEAN HookSSDT(ULONG64 NewFunction, ULONG index);
//获取进程名称函数
extern"C" NTKERNELAPI UCHAR * PsGetProcessImageFileName(IN PEPROCESS Process);
typedef NTSTATUS(__fastcall *NTTERMINATEPROCESS)(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus);
NTSTATUS __fastcall Fake_NtTerminateProcess(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus);


externULONG64 gu64_Raw_SSDT[];

NTSTATUS __fastcall Fake_NtTerminateProcess(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus)
{
        PEPROCESS Process = NULL;
        NTTERMINATEPROCESSfunction;
        function = (NTTERMINATEPROCESS)gu64_Raw_SSDT;
        NTSTATUS st = ObReferenceObjectByHandle(ProcessHandle, 0, *PsProcessType, KernelMode, (PVOID*)&Process, NULL);

        if (NT_SUCCESS(st))
        {
                DbgPrint("%s\n", (char *)PsGetProcessImageFileName(Process));
                if (!_stricmp((char *)PsGetProcessImageFileName(Process), "notepad.exe") || !_stricmp((char *)PsGetProcessImageFileName(Process), "calc.exe"))
                        return STATUS_ACCESS_DENIED;
                else
                        ObDereferenceObject(Process);
                returnfunction(ProcessHandle, ExitStatus);
        }
        else
                return STATUS_ACCESS_DENIED;
}

VOID MyDriverUnload(IN PDRIVER_OBJECT pDriverObject)
{
        KdPrint(("成功进入卸载函数"));
       
        UnhookSSDT();
        KdPrint(("全部卸载完成"));
}

#pragma INITCODE
extern"C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
        IN PUNICODE_STRING pRegistryPath)
{
        KdPrint(("加载驱动成功\r\n"));
        pDriverObject->DriverUnload = MyDriverUnload;
       
        /*
                为了验证可以HOOK的10个 SSDT取中间一个数5个
                每次HOOK 一次 然后内核空间内JMP的 那个地址是不一样的
                如果能正常的进入Fake_NtTerminateProcess 这个函数
                而且可以 无法结束进程证明 这个通用性是可靠的
        */
        HookSSDT((ULONG64)Fake_NtTerminateProcess, 41);
        HookSSDT((ULONG64)Fake_NtTerminateProcess, 41);
        HookSSDT((ULONG64)Fake_NtTerminateProcess, 41);
        HookSSDT((ULONG64)Fake_NtTerminateProcess, 41);
        HookSSDT((ULONG64)Fake_NtTerminateProcess, 41);

        return STATUS_SUCCESS;
}



陌路人 发表于 2015-4-5 14:13:35

第一次发帖, 给自己顶一个

Tesla.Angela 发表于 2015-4-5 19:13:48

我就看到PoHuai_64了。。。{:soso_e113:}

陌路人 发表于 2015-4-5 19:29:22

Tesla.Angela 发表于 2015-4-5 19:13
我就看到PoHuai_64了。。。

````.哪看到的?    糗大了!!!    英语不会`` 只能用拼音了`````
我是刚看了你后边的教程,才发现.那个重定位哪 你有讲```
不过这部分代码,是我自己实现的还有计算偏移哪(想说明的就是 我不是拿你的代码 然后整体粘贴过来混经验)
我有认真你的代码啊,大部分都注释了 比如
KIRQL WPOFFx64()
{
        /*
        提升IRQL等级到Dispatch_Level
        关闭页面保护,CR0寄存器的值16位清00~16
        硬件中断标志位清0 ,不响应可屏蔽中断
        实现了,多线程安全性   _disable为硬件中断不响应
        Dispatch_Level为线程不可切换软中断
        */
        KIRQL irql = KeRaiseIrqlToDpcLevel();
        UINT64 cr0 = __readcr0();
        _disable();
        cr0 &= 0xfffffffffffeffff;
        __writecr0(cr0);
        return irql;
}

void WPONx64(KIRQL irql)
{
        /*
        恢复页面保护. CRO寄存器值16位值10~16
        IF中断标志至1可以响应可屏蔽中断
        降低软中断级别
        */
        UINT64 cr0 = __readcr0();
        cr0 |= 0x10000;
        __writecr0(cr0);
        _enable();
        KeLowerIrql(irql);
}

Tesla.Angela 发表于 2015-4-6 00:06:25

陌路人 发表于 2015-4-5 19:29
````.哪看到的?    糗大了!!!    英语不会`` 只能用拼音了`````
我是刚看了你后边的教程,才发现.那个 ...

我不是贴出了一个新版本的代码来计算正确地址么。。。旧版本的代码对于HOOK参数小于4个的函数是没问题的,HOOK参数多于4个的函数才有问题。。。。

不过看出你的学习态度很认真,赞一个。

顺带说句,WPOFFx64和WPONx64是X64ASM的坛主教我的。可惜这个站点已经关闭。

陌路人 发表于 2015-4-6 01:23:46

Tesla.Angela 发表于 2015-4-6 00:06
我不是贴出了一个新版本的代码来计算正确地址么。。。旧版本的代码对于HOOK参数小于4个的函数是没问题的 ...

新版本那个没看懂,```
刚弄了2小时机器码,想弄到IDA里反编译下 结果 半天没弄成.
然后发现,64的.如果把写好的函数 全部读取内存,把机器码存放在一个数组里.
然后动态分配内存,定义函数指针.这样的话 想要用IDA 静态分析基本 是不可能的了.
分析出来都是 db 数组
dwtmp=(LONG)(FuncAddr-(ULONGLONG)ServiceTableBase);
return dwtmp<<4   

这样计算是有问题的,如果啊
FuncAddr 这个函数地址小于ServiceTableBase[?]这个函数地址
得出的结果dwtmp 直接<<4 位是错误的
比如 FuncAddr =FFFFF80011111111
ServiceTableBase=FFFFF80022222222
FFFFF80011111111--FFFFF80022222222=
=EEEEEEEF;
FFFFF80022222222+EEEEEEEF=
=FFFFF80031111111; 显然这是错误的
另一个函数这里不正好是这么计算的吗
dwtmp=dwtmp>>4;
return dwtmp + (ULONGLONG)ServiceTableBase;

Tesla.Angela 发表于 2015-4-6 05:21:55

陌路人 发表于 2015-4-6 01:23
新版本那个没看懂,```
刚弄了2小时机器码,想弄到IDA里反编译下 结果 半天没弄成.
然后发现,64的.如果把 ...

假设一个SSDT函数地址为X,那么(理论上)SSDT里记录的地址就是A=X-ServiceTableBase

但你要知道,这个A的低4位,肯定是0。简单点说,就是每个SSDT函数之间有一定距离,不会一个贴着一个。文字形式就是:0x12345670(最后一个数字为0,文本形式的最后一个数字对应的就是低4位)

既然大家都知道这个数字是0,干脆就废物利用,利用这个低4位的里存储一下数据,这个数据是:函数参数个数-4(如果函数参数大于4才记录,否则记为0)。

所以获取SSDT函数地址的时候,把这个低4位清零,当你要HOOK(填写代理函数地址)时,就要把这个低4位的正确值(函数参数个数-4)写入回去。

(自认为描述得不错了,如果还不理解我也没办法。)

陌路人 发表于 2015-4-6 13:02:50

Tesla.Angela 发表于 2015-4-6 05:21
假设一个SSDT函数地址为X,那么(理论上)SSDT里记录的地址就是A=X-ServiceTableBase

但你要知道,这个A ...

嗯,这么一说 看明白了, 我也纳闷过 最后一位的 数字是咋出来的
受教了 , 谢谢了!
不过我确实发现了一个小问题 不过问题的实用性不大
昨天晚上太困了 没有拿 实例 来说明,就大概的比喻了一下意思

0: kd> dd KiServiceTable
fffff800`018c080004134b00 02f53600 fff6f000 02e80205

拿第 3个函数做例子fff6f000    fff6f000>>4=ffff6f00
0: kd> ufffff800`018c0800 + 0xffff6f00
fffff800`118b7700 ??            ???
                                       ^ Memory access error in 'ufffff800`018c0800 + 0xfff6f00 '

按照计算实际函数的地址方法 是这样的

dwtmp=dwtmp>>4;
return dwtmp + (ULONGLONG)ServiceTableBase;

0xffff6f00 + fffff800`018c0800=FFFFF801018AE600;

这样结果就是错误的,所以计算实际地址时,需要

0xffff6f00 +018c0800 =0x018b7700dwtmp 4字节变量

0x018b7700+fffff800`00000000 = FFFFF800018B7700

1: kd> u FFFFF800018B7700
nt!NtCallbackReturn:
fffff800`018b7700 4883ec18      sub   rsp,18h

注: 我们自己去SSDT 提起函数地址显然是很少用的
所以这个问题也忽略不计了,

upring 发表于 2015-5-14 12:07:50

很好很好 问题辩论才明了

萌新 发表于 2017-7-21 21:42:22

啊,SSDT那部分看了好几天都没太明白为什么hook的时候是4位的偏移地址,而unhook的时候是8位的偏移地址,原来unhook读取的保存在磁盘里面的KiServiceTable,这里面保存的是8位的偏移地址,而被加载到内存之后就变成了4位的偏移地址了。

376408384 发表于 2023-11-11 20:03:30

唐大佬都有提过 一切都在KiSystemCall64 都有算法用windbg逆向一下就知道了
页: [1]
查看完整版本: x64 SSDT正确的偏移计算,一键恢复SSDT表中的所有HOOK