Hooking the kernel directly

利用MATLAB 4f光学系统,在Python中进行弗劳恩霍夫衍射和傅里叶面滤波——用镜头模拟光学图像处理。.zip 1.版本:matlab2014a/2019b/2024b 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 立即下载

By Anton Bassov

How to hook the kernel functions directly. 

Introduction

Sometimes, we run into a situation when we badly need to hook some kernel function, but are unable to do it via conventional PE-based hooking. This article explains how kernel functions can be directly hooked. As a sample project, we are going to present a removable USB storage device as a basic disk to the system, so that we can create and manage multiple partitions on it (for this or that reason, Windows does not either allow or recognize multiple partitions on removable storage devices, so we are going to cheat the system). On this particular occasion, we will hook only one function, but the approach described in this article can be extended to handle multiple functions (for example, one of my projects required direct hooking of quite a few functions from the NDIS library). You should clearly realize that this article is about direct hooking and not about dealing with USB storage, so please don't tell me that the sample problem may have been solved differently.

The sample problem

The way the USB device is presented to the system is defined by the RemovableMedia field of the STORAGE_DEVICE_DESCRIPTOR structure that USBSTOR.SYS returns in response to a IOCTL_STORAGE_QUERY_PROPERTY request. If the device manufacturer wants the device to present itself as a basic disk, they make the driver set the RemovableMedia field of the STORAGE_DEVICE_DESCRIPTOR structure that it returns in response to the IOCTL_STORAGE_QUERY_PROPERTY request to FALSE. As a result, the device gets presented to the system as a basic disk - DISK.SYS clients will have no idea as to whether they are actually dealing with the hard drive or with a USB device.

Therefore, if we hook the IRP_MJ_DEVICE_CONTROL routine of USBSTOR.SYS, we can present the removable disk as a basic one to the system, simply by modifying the return value of the IOCTL_STORAGE_QUERY_PROPERTY request - luckily for us, no more checks are done. It can be done the following way:

typedef NTSTATUS (__stdcall*ProxyDispatch)
       (IN PDEVICE_OBJECT device,IN PIRP Irp);
ProxyDispatch realdispatcher;

///Proxy function
NTSTATUS Dispatch(IN PDEVICE_OBJECT device,IN PIRP Irp)
{
    NTSTATUS status=0; ULONG a=0;PSTORAGE_PROPERTY_QUERY query;
    PSTORAGE_DEVICE_DESCRIPTOR descriptor;

    PIO_STACK_LOCATION loc= IoGetCurrentIrpStackLocation(Irp);

    if(loc->Parameters.DeviceIoControl.IoControlCode
                         ==IOCTL_STORAGE_QUERY_PROPERTY)
    {
        query=(PSTORAGE_PROPERTY_QUERY)
               Irp->AssociatedIrp.SystemBuffer;
        if(query->PropertyId==StorageDeviceProperty)
        {
            descriptor=(PSTORAGE_DEVICE_DESCRIPTOR)
                        Irp->AssociatedIrp.SystemBuffer;
            status=realdispatcher(device,Irp);
            descriptor->RemovableMedia=FALSE;
            return status;
        }
    }
    return realdispatcher(device,Irp);
}

// somewhere in the code...
realdispatcher=(ProxyDispatch)
  driver->MajorFunction[IRP_MJ_DEVICE_CONTROL];
driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]=Dispatch;

As you can see, a removable USB device can be presented as a basic disk to the system pretty easily. However, there is a "small" complication - the USBSTOR.SYS gets loaded only when you plug a device into the USB port, and stays loaded until you unplug it. Therefore, we cannot hook the USBSTOR.SYS in advance - we have to plug in a device first. If we hook the USBSTOR.SYS after it has already handled the IOCTL_STORAGE_QUERY_PROPERTY request, then it already is a bit too late to do anything. We cannot plug the device, hook USBSTOR.SYS, unplug it, and then plug it in again either - when you unplug the device, USBSTOR.SYS gets unloaded, so that our hooking will be just a waste of effort. The most appropriate time to hook USBSTOR.SYS is the moment when it is about to create its device objects - on one hand, we know that USBSTOR.SYS has been already loaded, and, on the other hand, we know that the IOCTL_STORAGE_QUERY_PROPERTY request has not been handled yet. If we manage to capture calls to IoCreateDevice() that USBSTOR.SYS makes, our task gets simplified dramatically - IoCreateDevice() takes a pointer to the DRIVER_OBJECT of the newly created device as an argument. At this point, we can replace a pointer in the driver's MajorFunction[IRP_MJ_DEVICE_CONTROL], and that's it.

In order to be able to do the above, we are going to hook IoCreateDevice() directly by inserting instructions into its executable code, i.e., do the so-called "hooking-by-overwriting". In fact, we could have done it by hooking the ntoskrnl.exe's export directory, but, for one this article is about direct hooking, and we are going to hook the IoCreateDevice() directly. However, first of all, we have to learn about something that seems to be unrelated to our task, at the first glance - we are going to learn about interrupt hooking.

Dealing with interrupts and exceptions

In response to hardware interrupts or exceptions, the CPU saves the execution context of the currently running thread, and transfers the execution to a special kernel-mode procedure, called a handler. The way the execution context is saved depends on the privilege level of the interrupted code. If the interrupted code is non-privileged, the processor has to switch to the privileged stack and the code segments in order to be able to execute a kernel-mode handler procedure. Therefore, the CPU pushes the values of the user-mode SS, ESP, EFLAGS, and CS registers, plus the return address (all pushes occur in the above described order) on the kernel stack before it transfers the execution to the appropriate handler. In the case of some exceptions, the CPU may also push an error code on top of the stack above the return address. If the interrupted code is privileged, the stack switch is not needed. Therefore, in such cases, only EFLAGS, CS, the return address, and, possibly, the error code are pushed on the stack - SS and ESP registers are not saved on the stack if the interrupted code is privileged.

Each interrupt and exception has its own associated number, called a vector. There are 256 interrupt vectors. The addresses of all the interrupt and exception handlers are stored in a kernel-mode data structure called the Interrupt Descriptor Table (IDT), which is nothing more than an array of 256 8-byte entries, called Gate Descriptors. On a SMP machine, each processor has is own IDT, although the addresses of all the interrupt and exception handlers are, certainly, the same for all CPUs in the system. Each IDT entry is associated with its corresponding vector. An IDT can hold Interrupt Gate Descriptors, Trap Gate Descriptors, and Task Gate Descriptors. The binary layout of the interrupt and trap gate descriptors is described by the following structure:

struct GATE
{
  WORD    OffsetLow;      
  WORD    Selector;       
  WORD Unused:8;
  WORD Type:5;
  WORD DPL:2;
  WORD Present:1;
  WORD    OffsetHigh;    
} ;

As you can see, the binary layout of the interrupt and trap gate descriptors is quite similar to that of call gate descriptors, presented in my article Entering the kernel without a driver and getting the interrupt information from APIC. The difference between an interrupt and a trap gate lies in the state of the IF flag in the EFLAGS register at the time when the interrupt or the exception handler starts execution. If the interrupt or the exception is vectored via an interrupt gate, the IF flag gets automatically cleared by the processor. If the interrupt or the exception is vectored via a trap gate, the IF flag does not get affected. In all other respects, interrupt and trap gates are the same - no wonder they are described by the same structure. The binary layout of task gate descriptors is different. Although, for performance reasons, all user processes run in the context of a single task under Windows NT, there are a few task gate descriptors in IDT. They are mainly reserved for "exceptional circumstances", like a system crash - their task is to make sure that the system is able to operate long enough to throw a blue screen before the CPU resets itself. They are of no interest to us anyway, so I won't present their binary layout.

The way Windows maps hardware interrupts to interrupt vectors is presented in my article: Entering the kernel without a driver and getting the interrupt information from APIC, so we are not going to discuss hardware interrupts here. Instead, we will concentrate on exceptions. The first 32 entries of the IDT deal with the exception handlers (their mapping to particular vectors is pre-defined by Intel). Exceptions can be classified as traps, faults, and aborts. Exceptions of the abort class do not allow the failing task to be resumed. A typical example of an abort-class exception is the Machine-Check exception (INT 0x12). Traps and faults allow the failing task to continue its execution after the exception has been dealt with. The difference between a trap and a fault lies in the return address that is saved on the stack. In the case of a fault-class exception, this address points to the instruction that caused the exception, i.e., one more attempt to execute the failing instruction will be made after the exception handler returns control. A typical example of a fault-class exception is the Page Fault exception (INT 0xE). In the case of a trap-class exception, the return address points to the instruction following the one that caused the exception. A typical example of a trap-class exception is the Debug Breakpoint exception (INT 3).

A Debug Exception (INT 1) is a quite interesting exception in itself - depending on the reason for the exception, it may be raised as either a trap or a fault. A Debug Exception may be raised for any of the following reasons:

  1. Breakpoint on execution.
  2. Breakpoint on memory access.
  3. Breakpoint on IO port access.
  4. General detect condition.
  5. The TF flag in the EFLAGS register is set. In such a case, a Debug Exception is raised upon every instruction's execution.
  6. Task switch (irrelevant under Windows).
  7. INT 1 instruction.

In cases 1 and 4, INT 1 is raised as a fault, in all other cases it is raised as a trap. The reason why an exception has been raised can be discovered by the INT 1 handler from the DR6 register. A Debug Exception may be raised for more than one reason - for example, a breakpoint on execution may be reached at the time when the TF flag is set. In such a case, a breakpoint on execution has a higher priority than the TF flag, so that INT 1 is raised as a fault, rather than as a trap.

So, what does all the above have to do with hooking functions??? We are going to copy the first few bytes (8 bytes is more than enough) from the beginning of the target function into the array that we are going to allocate from the non-paged pool, hook INT 1 and INT 3 handlers, and write a 0xCC opcode (which represents the INT 3 instruction) to the beginning of the target function. As a result, when the target function tries to execute its very first instruction, our proxy INT 3 handler will get invoked. The stack layout at the time when our proxy INT 3 handler enters execution can be described by the following structure:

struct INTTERUPT_STACK
{
    ULONG InterruptReturnAddress;
    ULONG SavedCS;
    ULONG SavedFlags;
    ULONG FunctionReturnAddress;
    ULONG Argument;
};

On top of the stack, there is a frame that has been set up by the CPU in response to an INT 3 instruction (i.e., the address, to which the INT 3 handler is supposed to return control, plus the CS and EFLAGS registers); the address, to which the target function is supposed to return control, comes next; and the array of function arguments is below the return address on the stack (I think that, for practical reasons, it makes sense to treat all arguments as ULONGs - we can always cast them to their actual type if it is needed). At this point, we can do whatever we want - we can inspect and/or modify function arguments, change its return address, i.e., do everything we normally do when we hook functions. For the purpose of our task, we are interested only in the first argument, i.e., PDRIVER_OBJECT that has been passed to IoCreateDevice(), so we have presented the structure as if the target function had only one argument.

Before our proxy INT 3 handler returns, it will change the InterruptReturnAddress field of the structure on top of the stack to that of the array with the instructions that we have copied, and set the TF flag in the SavedFlags field. After our proxy INT 3 handler returns, the InterruptReturnAddress and SavedFlags fields of the structure that is saved on the stack will get popped into, respectively, the EIP and EFLAGS registers. As a result, execution will be resumed at the beginning of the array with the instructions that we have copied, and, once we have modified the TF flag, it will be resumed in single-step mode, i.e., INT 1 will get raised upon every instruction's execution.

If INT 1 gets raised because of the TF flag, it is processed as a trap. Therefore, after the very first instruction in the array gets executed, our proxy INT 1 handler will get invoked, and EIP that is saved on the stack will point to the second instruction in the array. At this point, by subtracting the address of our array from the return address that is saved on top of the stack, we will be able to discover the size of the instruction that has been just executed. Therefore, before our proxy INT 1 handler returns, it will change the return address to (beginning of the target function + size of the executed instruction), and clear the TF flag in EFLAGS that is saved on the stack. As a result, execution will be resumed at the location of the target function's second instruction, with the TF flag cleared after our proxy INT 1 handler returns. In other words, the target function will continue its execution as if nothing has ever happened.

Apparently, our approach seems to be rather convoluted, at first glance - we could do things differently. For example, we could copy a few instructions from the beginning of the target function into our array, and then overwrite the beginning of the target function with a JMP instruction, so that execution would jump to our hooking code. In such a case, we would have to figure out the offset within the target function at which execution has to be resumed after our hooking code executes. Therefore, we would have to figure out the instruction size. However, it is easier said than done - in order to do something like that, we would have to write a full-fledged disassembler program. To make things even more complex, the instruction may reference memory relative to the given instruction's location, and, in such a case, we would have to adjust the instruction's operand after we have relocated it. In other words, if we choose to overwrite the beginning of the function with a JMP, rather than a INT 3 instruction, our program would be huge, and around 95% of our code would deal with disassembly, rather than with hooking in itself. Therefore, I think that hooking INT 1 and INT 3 is a much more reasonable thing to do - by taking advantage of INT 1 and INT 3, we can make the CPU do all "dirty" jobs for us.

Now, let's proceed to the actual work.

Solving the sample problem

For the purpose of our particular project, we can do all the hooking-related work right in DriverEntry(). Let's look at the code:

// this routine hooks and restores IDT.
// We have to make sure that this function runs only
//on one CPU, so that we disable interrupts throughout
// its execution in order to avoid context
// swithches
void HookIDT()
{
    ULONG handler1,handler2,idtbase,tempidt,a;
    UCHAR idtr[8];

    //get the addresses that we have write to IDT
    handler1=(ULONG)&replacementbuff[0];
    handler2=(ULONG)&replacementbuff[32];

    //allocate temp. memory. This should be our first 
    //step - from the moment we disable interrupts
    //till return we don't risk to call any code 
    //that has not been written by ourselves
    //(theoretically this code may re-enable 
    //interrupts without our knowledge, and then.....)
    tempidt=(ULONG)ExAllocatePool(NonPagedPool,2048);

    _asm
    {
        cli
        
        sidt idtr
        lea ebx,idtr
        mov eax,dword ptr[ebx+2]
        mov idtbase,eax
    }

    //check whether our IDT has already been hooked. 
    //If yes, re-enable interrupts and return
    for(a=0;a<IdtsHooked;a++)
    {
        if(idtbases[a]==idtbase)
        {
            _asm sti
            ExFreePool((void*)tempidt);
            KeSetEvent(&event,0,0);
            PsTerminateSystemThread(0);
        }
    }

    _asm
    {
        //now we are going to load the copy of IDT into IDTR register
        // in my experience, modifying memory,
        //pointed to by IDTR register, is unsafe
        mov edi,tempidt
        mov esi,idtbase
        mov ecx,2048
        rep movs

        lea ebx,idtr
        mov eax,tempidt
        mov dword ptr[ebx+2],eax
        lidt idtr

        //now we can safely modify IDT. Get ready
        mov ecx,idtbase

        //hook INT 1
        add ecx,8
        mov ebx,handler1

        mov word ptr[ecx],bx
        shr ebx,16
        mov word ptr[ecx+6],bx

        ///hook INT 3
        add ecx,16
        mov ebx,handler2

        mov word ptr[ecx],bx
        shr ebx,16
        mov word ptr[ecx+6],bx

        //reload the original idt
        lea ebx,idtr
        mov eax,idtbase
        mov dword ptr[ebx+2],eax
        lidt idtr
        sti
    }

    //now add the address of IDT we just 
    //hooked to the list of hooked IDTs
    idtbases[IdtsHooked]=idtbase;
    IdtsHooked++;
    ExFreePool((void*)tempidt);
    KeSetEvent(&event,0,0);
    PsTerminateSystemThread(0);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT driver,IN PUNICODE_STRING path)
{
    ULONG a;PUCHAR pool=0;
    UCHAR idtr[8];HANDLE threadhandle=0;

    //fill the array with machine codes
    replacementbuff[0]=255;replacementbuff[1]=37;
    a=(long)&replacementbuff[6];
    memmove(&replacementbuff[2],&a,4);
    a=(long)&INT1Proxy;
    memmove(&replacementbuff[6],&a,4);

    replacementbuff[32]=255;replacementbuff[33]=37;
    a=(long)&replacementbuff[38];
    memmove(&replacementbuff[34],&a,4);
    a=(long)&BPXProxy;
    memmove(&replacementbuff[38],&a,4);

    //save the original addresses of INT 1 and INT 3 handlers
    _asm
    {
        sidt idtr
        lea ebx,idtr
        mov ecx,dword ptr[ebx+2]

        /////save INT1
        add ecx,8
        mov ebx,0
        mov bx,word ptr[ecx+6]
        shl ebx,16
        mov bx,word ptr[ecx]
        mov Int1RealHandler,ebx

        /////save INT3
        add ecx,16
        mov ebx,0
        mov bx,word ptr[ecx+6]
        shl ebx,16
        mov bx,word ptr[ecx]
        mov BPXRealHandler,ebx
    }

    //hook INT 1 and INT 3 handlers - it has 
    //to be done before overwriting NDIS
    //Run HookUnhookIDT() as a separate 
    //thread until all IDTs get hooked
    KeInitializeEvent(&event,SynchronizationEvent,0);

    RtlZeroMemory(&idtbases[0],64);
    a=KeNumberProcessors[0];
    while(1)
    {
        PsCreateSystemThread(&threadhandle,
                (ACCESS_MASK) 0L,0,0,0,
                (PKSTART_ROUTINE)HookIDT,0); 
        KeWaitForSingleObject(&event, 
           Executive,KernelMode,0,0);
        if(IdtsHooked==a)
            break;
    }

    KeSetEvent(&event,0,0);

    //fill the structure...
    a=(ULONG)&IoCreateDevice;
    HookedFunctionDescriptor.RealCode=a;
    pool=ExAllocatePool(NonPagedPool,8);
    memmove(pool,a,8);
    HookedFunctionDescriptor.ProxyCode=(ULONG)pool;

    //now let's proceed to overwriting memory
    _asm
    {
        //remove protection before overwriting
        mov eax,cr0
        push eax
        and eax,0xfffeffff
        mov cr0,eax

        //insert breakpoint (0xCC opcode)

        mov ebx,a
        mov al,0xcc

        mov byte ptr[ebx],al

        //restore protection
        pop eax
        mov cr0,eax
    }

    return 0;
}

Apparently, quite a few explanations are needed here. First of all, we fill two chunks of memory with an indirect jump instruction - we are going to use it when we hook the IDT. I've got no logical explanation to this phenomenon, but when I try to write the address of the function itself into the IDT, I always see the blue-screen. However, if I write the address of the array with an indirect jump instruction into the IDT, i.e., make the execution jump to my function, everything works fine - it looks like computers have their own unserstanding of things. Then, we save the addresses of the actual handlers of INT 1 and INT 3 in global variables, and then proceed to hooking the IDT. The way it is done requires a bit more attention.

As I have already said, on a SMP machine, each processor has is own IDT, and, with the advent of the HT technology (as far as I am concerned, Intel does not produce CPUs without HT support any more), we live in the age of SMP machines - a single CPU with support for HT is treated as two independent CPUs by the system. Therefore, we have to hook all IDTs in the system, so we create threads that run HookIDT() until all IDTs in the system get hooked.

First of all, HookIDT() allocates memory so that it can copy the contents of IDT to it - all my experience shows that writing to memory, pointed to by the IDTR register, is unsafe, even if interrupts are disabled. Therefore, we copy the IDT to the memory that we have allocated, and load a pointer to this memory into the IDTR register by using the LIDT instruction. At this point, we can safely modify the original IDT. After this job is done, we will reload IDTR with the address of the original IDT. It is understandable that, from the moment HookIDT() discovers that IDT has not yet been hooked, until it modifies and reloads IDT, it has to run on the same CPU, so that we disable interrupts in order to avoid context switches. However, this should be done only after having allocated memory for the temporary IDT. Why? Because, in our situation, calling any(!!!) code that we have not written ourselves is an unwise thing to do - if this code re-enables interrupts, we are more than likely to make a mess. Therefore, we avoid calling any code that we have not written ourselves - as you can see, even the contents of the original IDT are copied to the memory that we have allocated by the REP MOVS instruction, rather than by the conventional memcpy().

After having hooked the INT 1 and INT 3 handlers in IDT, we copy the first eight bytes from the beginning of the target function (i.e., IoCreateDevice()) to the memory that we have to allocate from the non-paged pool, and insert the 0xCC opcode to the beginning of the target function. It is understandable that the target function's executable code resides in read-only memory. Therefore, we have to change either the page protection in the page table, or clear the WP flag in the CR0 register before we can overwrite the function (for the sake of simplicity, we choose to clear the WP flag). As a result of all our manipulations, our code that hooks INT 3 gets executed every time a call to IoCreateDevice() is made.

Now, let's look at the code that hooks INT 1 and INT 3.

//this function is needed in order to make our hooking work 
ULONG __stdcall INT1check(INTTERUPT_STACK * savedstack)
{
    ULONG offset=0,stepping=savedstack->SavedFlags&0x100;

    //if INT 1 has been raised for some 
    //reason other than single-stepping, return 0,
    //so that execution will eventually 
    //go to the real handler of INT 1
    if(!stepping)return 0;

    //check if single-stepping is somewhow 
    //related to our hooking. If not,return 0

    if(savedstack->InterruptReturnAddress<=
            HookedFunctionDescriptor.ProxyCode)
        return 0;
    if(savedstack->InterruptReturnAddress>=
            HookedFunctionDescriptor.ProxyCode+8)
        return 0;

    //change the return address on the stack, and clear TF flag
    offset=savedstack->InterruptReturnAddress-
              HookedFunctionDescriptor.ProxyCode;
    savedstack->InterruptReturnAddress=
              HookedFunctionDescriptor.RealCode+offset;
    savedstack->SavedFlags &=0xfffffeff;

    //clear DR6
    _asm
    {
        mov eax,0
        mov dr6,eax
    }

    return 1;
}

ULONG __stdcall BPXcheck(INTTERUPT_STACK * savedstack)
{ 
    PDRIVER_OBJECT driver;char buff[1024]; HANDLE handle=0;
    PUNICODE_STRING unistr=(PUNICODE_STRING)&buff[0];ULONG a=0;

    //if breakpoint has nothing to do 
    //with our hooking,return 0
    if(savedstack->InterruptReturnAddress!=
       HookedFunctionDescriptor.RealCode+1)return 0;
    
    //make INT1 return to the code that 
    //we have copied, and set TF flag
    savedstack->SavedFlags|=0x100;
    savedstack->InterruptReturnAddress=
       HookedFunctionDescriptor.ProxyCode;

    //All x86-related stuff has been done above. 
    //Now let's proceed to actual job

    driver=(PDRIVER_OBJECT)savedstack->Arg;

    if(ObOpenObjectByPointer(driver,0, NULL, 0, 
                0,KernelMode,&handle))return 1;
    ZwQueryObject(handle,1,buff,256,&a);
    if(!unistr->Buffer){ZwClose(handle);return 1;}
    if(_wcsicmp(unistr->Buffer,L"//Driver//USBSTOR"))
        {ZwClose(handle);return 1;}

    ZwClose(handle);

    a=(ULONG)driver->MajorFunction[IRP_MJ_DEVICE_CONTROL];

    if(a==(ULONG)Dispatch)return 1;

    realdispatcher=(ProxyDispatch)a;
    driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]=Dispatch;
    return 1;
}

_declspec(naked) INT1Proxy()
{
    _asm    
    {
        pushfd
        pushad
        mov ebx,esp
        add ebx,36
        push ebx
        call INT1check

        cmp eax,0
        je fin

        popad
        popfd
        iretd

        fin: popad
             popfd
             jmp Int1RealHandler
    }
}

_declspec(naked) BPXProxy()
{

    _asm    
    {
        pushfd
        pushad
        mov ebx,esp
        add ebx,36
        push ebx
        call BPXcheck

        cmp eax,0
        je fin
    
        popad
        popfd
        iretd

        fin: popad
             popfd
             jmp BPXRealHandler

    }
}

When a call to IoCreateDevice() is made, the BPXProxy() gets invoked. BPXProxy() saves the registers and flags, pushes the value ESP had at the time when BPXProxy() started execution, and calls BpxCheck(). Therefore, BpxCheck() receives a pointer to the INTTERUPT_STACK structure that we mentioned above, as an argument. First of all, by matching the InterruptReturnAddress of the structure against the address of the target function, BpxCheck() checks whether the INT 3 invocation is related to our hooking. If not, it returns 0. Otherwise, it changes the InterruptReturnAddress field of the structure to that of the array with the instructions that we have copied, and sets the TF flag in the SavedFlags field. At this point, we can do the job that is related to the hooking itself. In our particular case, we check whether the PDEVICE_OBJECT that is passed to IoCreateDevice() is the one of //Driver//USBSTOR (which means USBSTOR.SYS has already been loaded), and replace the IRP_MJ_DEVICE_CONTROL handler with the address of our function (certainly, only if it has not yet been done already). As a result, we are able to monitor all IRP_MJ_DEVICE_CONTROL requests that are sent to USBSTOR by the system, i.e., accomplish our original goal. The way an interrupt is processed after BpxCheck() returns depends on its return value. If INT 3 was raised because of our hooking, BpxCheck() returns 1, otherwise it returns 0. If it returns 0, we transfer control to the INT 3 actual handler, otherwise we just return with the IRETD instruction, so that execution will be resumed at the beginning of the array with the instructions that we have copied. Once we have modified the TF flag, it will be resumed in single-step mode, i.e., INT1Proxy() gets invoked.

Implementation of INT1Proxy() is almost identical to that of BPXProxy() - the only difference is that it calls INT1Check(), rather than BpxCheck(). First of all, INT1Check() examines the TF flag in the EFLAGS register that is saved on the stack, and, if it discovers that INT 1 has been raised for some reason, other than single-stepping, it returns 0 (as you may remember, INT 1 may get raised for various reasons). Otherwise, it checks whether the return address is somewhere in the array that holds the instructions we have copied. If not, it returns 0 - after all, the TF flag may be on just because some program is being debugged. Otherwise, by subtracting the address of the array from the return address on the stack, it discovers the size of the target function's first instruction (i.e., the one that just got executed), changes the return address on the stacks to (beginning of the target function + size of its first instruction), clears the DR6 register and the TF flag in EFLAGS that are saved on the stack, and returns 1. As a result, if INT 1 has been raised for some reason, unrelated to our hooking, INT1Proxy() transfers control to the actual handler of INT 1; otherwise, it returns with the IRETD instruction, so that the target function (i.e., IoCreateDevice()) resumes its execution as if nothing had happened.

In order to run the sample driver, you have to create an on-demand-start service, and start it manually on the command prompt. If you plug a USB storage device while the service runs, you will see a basic disk rather than a removable storage, on the MyComputer panel. Therefore, if you start the Disk Manager from the Control Panel, you will be able to create multiple partitions on the device.

Notice: This driver has been built using the Windows 2000 DDK so that it treats the KeNumberProcessors exported symbol as a pointer. If you use the XP DDK, the KeNumberProcessors has to be treated as a variable - otherwise, you will be unable to compile the sample. However, the above applies only to compilation - the sample is going to work on both Windows 2000 and Windows XP, regardless of the DDK version that you use.

Conclusion

In conclusion, I must say that, although in our particular case we have hooked just one function, the same approach can be extended to dealing with multiple functions. Furthermore, we made a bold assumption that the target function's first instruction is not JMP. In practical terms, I think it makes sense to do this check, and, in case if the target function's first instruction is JMP, to adjust the sample (all you have to do is to just figure out the location to which execution is about to jump, and to hook the kernel at this location). In other words, you can easily adjust this sample to the particular needs of your project.

I would highly appreciate if you send me an e-mail with your comments and suggestions.

利用MATLAB和Excel对逆流双管热交换器的热性能进行分析。.zip 1.版本:matlab2014a/2019b/2024b 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 立即下载

相关推荐

利用MATLAB对5G NR波束成形技术的性能分析.zip

1.版本:matlab2014a/2019b/2024b 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

对内核的直接挂钩 分类: windows驱动程序WDM ...

简介 有时在开发中,会遇到这样一种情况,当非常需要对某些内核函数进行挂钩时,而常规基于PE的挂钩,往往达不到目的。在本文中将要探讨的,是怎样直接挂钩内核函数,另外,在示例中,还要演示在系统中显示为一个基本磁盘的可移动USB存储设备,并在其上创建及管理多个分区(因为这样或那样的原因,Windows既不允许,也不能识别可移动存储设备上的多个分区,所以我们要“欺骗”...

weixin_30732487的博客 246

上市公司赫芬达尔指数、企业勒纳指数(1999-2025年)

上市公司赫芬达尔指数(HHI)和勒纳指数(LI)是衡量市场结构和市场势力(垄断力量)的两个重要指标 赫芬达尔指数(HHI)可衡量一个公司在市场中的相对份额或集中度,通过计算公司在市场中份额的平方和得出。数值越小,说明市场份额分布越分散,竞争程度越高;数值越大,说明市场集中度越高,少数企业占据较大份额,垄断程度越高 勒纳指数(LI)用于衡量企业的市场势力,即企业偏离完全竞争价格、制定高于边际成本的价格的能力,数值越接近1,表示企业市场势力越强,垄断程度越高 本文借鉴刘贯春等(2021)、孙茹峰和李楠博(2024)的思路和方法,分别测算上市公司赫芬达尔指数、勒纳指数: 赫芬达尔指数 = (公司营业收入/同行业同年营业收入合计)的平方和 企业勒纳指数 =(营业收入-营业成本-销售费用-管理费用)/营业收入 一、数据介绍 数据名称:上市公司赫芬达尔指数、企业勒纳指数 数据范围:上市公司 时间范围:1999-2025年 样本数量:85810条 数据来源:上市公司年报 更新时间:2026年11月 数据说明:内含原始数据、测算结果 二、数据指标 股票代码 股票简称 年份 省份 城市 区县 省份代码 城市代码 区县代码 行业代码 行业名称 赫芬达尔指数 勒纳指数 营业收入 营业成本 销售费用 管理费用 三、参考文献 [1]刘贯春,叶永卫,张军.社会保险缴费、企业流动性约束与稳就业——基于《社会保险法》实施的准自然实验[J].中国工业经济,2021,(05):152-169. [2]孙茹峰,李楠博.金融化对企业绿色转型意向的影响研究[J].管理学报,2024,21(11):1612-1620+1677. [3]张倩肖,段义学.数字赋能、产业链整合与全要素生产率[J].经济管理,2023,45(04):5-21. [4]伍兆祥,代吉林,

Linux内核模块指南(第九章===>第十章完)。。。翻译完。。。

第九章 阻塞进程 9.1阻塞进程 当有人需要你做某些事但是你不能马上做的时候你会怎么办?如果你是一个人类并且你也被另一个人打扰,你需要做的仅仅是说一声:“现在,我很忙,走开”。但是如果你是一个内核模块,并且你被一个进程打扰了,你就另外一种可能了。你能把进程sleep直到你能服务他的时候。毕竟,进程被内核sleep后能随时醒来,(这也是单核CPU在同时处理多进程的

lyc 887

海康威视后台摄像头视频管理系统

海康威视后台摄像头视频管理系统,vue+py框架

esp-labs-web- 把 ESP32-S3 实验台搬进浏览器:交互式学习指南

esp-labs-web项目把 ESP32-S3 的实验台教程做成了一个网页应用,叫 ESP32 Labs,里面有 32 个动手实验,带实时模拟器、3D 板子、引脚图,还有一个能逐行讲固件的代码阅读器。 每个实验就是一张单子,写着目标、用到的引脚在实时引脚图上的位置、接线、烧录命令和要打勾的步骤,旁边还有个模拟器,能先在浏览器里看到应该出现的效果。 模拟串口也挺像回事,在里面敲个命令,板子会像真固件一样回你,不用先把硬件接好就能先练一遍。 代码阅读器读的是实验真正烧进去的固件,Tour 模式一次只走几行,还先让你猜,Story 模式跟着程序从 setup() 一路往下走,Explore 是点哪行讲哪行,生词一次只给三个,词典还会记住你见过的词。 语言做了 8 种,英法西意德中日阿都有,阿拉伯语还是从右往左排版,手机 390 像素宽也能用,浅色深色、减动效都照顾到了。 里面还有个 3D 的 DevKitC-1 板子,六个标注点一点就亮,还会列出用它的实验,实验台日志就是 public/data 里的一份普通 JSON,改了不用重新编译,学习进度只存在你自己浏览器里,不用注册账号。 实验主题分得挺全,入门、显示和按键、Wi-Fi、RFID、蓝牙、声音都有,传感器那块还是计划中,每个实验的固件都是 esp-labs 里独立的 PlatformIO 工程。

Linux Kernel 2.6 Syscall Hooking via the Interrupt Descriptor Table

The symbol of sys_call_table is no longer exported since Linux 2.6.x, we have to HACK it http://www.elliotbradbury.com/linux-syscall-hooking-interrupt-descriptor-table/ Ever since the system cal

realduke2000的专栏 1565

工业互联网工控安全与工业质检AI大模型建设方案.pptx

工业互联网工控安全与工业质检AI大模型建设方案.pptx

不平衡电网下自抗扰顺序模型预测PWM整流器控制仿真模型(Simulink仿真实现)

内容概要:本文围绕面向城市空中交通的多无人机路径规划问题展开研究,提出了一套融合全局规划、动态避障与局部协同调整的综合路径规划方法。研究内容涵盖基于改进RRT算法与图论的全局路径规划、基于动态风险场和动态窗口法(DWA)的模型预测控制实现动态避障,以及基于分布式协同控制的多无人机局部路径协同策略。通过Matlab平台构建仿真环境,在多种典型城市交通场景下对所提方法进行了系统性验证,结果表明该方法在路径安全性、编队稳定性与任务效率方面表现优异。文章还深入探讨了算法的创新点与未来研究方向,为复杂城市环境下多无人机系统的自主协同提供了理论支撑与技术路径。; 适合人群:具备自动化、控制理论、计算机科学或相关领域基础知识,从事无人机系统、智能交通、路径规划、多智能体协同控制等方向研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①为城市空中交通系统中的多无人机安全、高效飞行提供可靠的路径规划解决方案;②支持在复杂、动态的城市场景中开展无人机自主导航、编队飞行与协同控制的技术研究与仿真验证。; 阅读建议:建议结合提供的Matlab代码进行实践操作,重点关注算法的具体实现细节、参数配置对性能的影响以及不同仿真场景下的实验设计,以深入理解各模块的集成逻辑与实际应用效果。

通信行业CentOS 7内核网络栈优化:高并发连接下UDP/TCP性能调优与系统资源管理

内容概要:本文聚焦于CentOS 7系统在通信行业高并发场景下的性能调优实践,重点围绕内核网络栈优化展开,涵盖UDP/TCP缓冲区调整、文件句柄与连接跟踪表扩容、TIME_WAIT状态复用、软中断分布优化及tuned工具的自动化调优。通过具体配置参数和实际案例(如VoIP软交换系统)深入解析各项调优措施的设计原理与实施效果,有效解决高CPS下丢包、连接失败等问题,并展望了向云化、eBPF等动态智能调优方向的发展趋势。; 适合人群:从事通信系统运维、网络开发或Linux系统性能优化的技术人员,具备一定操作系统和网络基础知识,工作经验1年以上的中初级工程师。; 使用场景及目标:①应用于软交换、媒体网关、信令网关等高并发通信设备的系统调优;②解决UDP丢包、conntrack表满、TCP连接队列溢出等典型性能瓶颈;③提升系统在小包高频、低时延要求场景下的稳定性和吞吐能力。; 阅读建议:建议结合实际生产环境或测试平台同步验证文中配置,重点关注参数背后的物理意义(如BDP、softirq、conntrack机制),并配合ss、conntrack、dmesg等命令进行调优前后对比分析,确保安全可控地实施变更。

从Elasticsearch的jvm.options读懂JVM参数的生效核验、内存分账与诊断兜底代码包

拿到一台机器和一个 Java 服务,怎样确认 JVM 实际吃进去了哪些参数,怎样给堆和堆外分配内存,怎样把 GC 日志、堆转储、异常栈这些“事后取证”的参数配齐,以及怎样在 JDK 8 与 JDK 9+ 之间安全迁移。 讲解的抓手是 Elasticsearch(下文简称 ES)的 jvm.options。这份文件经过 ES 团队多年打磨,又被无数生产集群验证过,几乎每一行都对应一个踩过的坑,是学习 JVM 参数最好的“样板间”。

【选址和定容】模拟退火改进多目标粒子群算法在分布式电源选址和定容中的应用【IEEE69节点】(Matlab代码实现)

内容概要:本文研究了模拟退火改进多目标粒子群算法在分布式电源选址和定容中的应用,以IEEE69节点配电系统为算例进行仿真分析。通过融合模拟退火算法的强全局搜索能力与粒子群算法的高效局部收敛特性,构建了一种改进的多目标优化算法,用于解决分布式电源在配电网中的最优位置选择与容量配置问题。研究旨在实现多重优化目标,包括最小化网络有功损耗、提升节点电压质量、增强系统运行的经济性与稳定性。文中详细阐述了多目标优化模型的数学构建、算法的改进机制与实现流程,并基于Matlab平台完成了算法编程与仿真验证,结果表明所提方法在求解质量和收敛性能上均优于传统算法。; 适合人群:具备电力系统分析、优化理论基础及Matlab编程能力,从事配电网规划、分布式能源系统优化、智能优化算法应用等相关领域的科研人员、工程师以及高校研究生。; 使用场景及目标:①解决高比例分布式电源接入背景下配电网的科学规划问题;②为新型电力系统中源网协调设计提供有效的优化工具;③为相关领域的科研工作提供可复现、可拓展的算法模型与仿真代码实例,支持进一步的算法对比与功能升级。; 阅读建议:建议读者结合提供的Matlab代码与IEEE69节点标准数据,动手复现实验结果,深入理解算法的参数设置、迭代过程与多目标折衷分析方法,并可根据实际研究需求修改目标函数或引入新的约束条件以适应不同的应用场景。

2024 《计算机网络》实验教学指导书.doc

2024 《计算机网络》实验教学指导书.doc

无刷直流电机BLDC无感+滑模观测器+转速电流双闭环PI控制(总共三个仿真模型)(Simulink仿真实现)

内容概要:本文档系统性地提供了无刷直流电机(BLDC)的高级控制策略仿真资源,聚焦于无位置传感器控制技术,重点实现了基于滑模观测器(SMO)的转子位置估算方法,并结合转速与电流双闭环PI控制结构,构建了完整的Simulink仿真模型体系。资源包含三个核心仿真模型:无感控制下的滑模观测器设计、双闭环PI控制器的参数整定与动态调节,以及系统整体的运行性能仿真分析。文档深入剖析了滑模观测器在反电动势提取与相位补偿中的关键技术,展示了其在不同工况下对电机启动、调速及负载扰动响应的鲁棒性表现,旨在帮助研究人员掌握BLDC在无传感器条件下的高精度控制实现路径,并为进一步引入模糊PID等先进控制策略提供对比基准与技术支撑。; 适合人群:具备电机控制理论基础、自动控制原理知识及Simulink建模经验,从事电气工程、自动化、电力电子或新能源驱动系统研究的科研人员、工程技术人员及高校研究生。; 使用场景及目标:①深入理解无刷直流电机无位置传感器控制的基本原理与实现流程;②掌握滑模观测器在转子位置与速度估算中的设计方法及其抗干扰特性;③学习并优化转速电流双闭环PI控制结构,提升系统动态响应性能与运行稳定性。; 阅读建议:建议结合Simulink模型进行同步仿真操作,重点关注滑模观测器的抖振抑制效果、系统启动过程的稳定性以及PI参数调节对动态性能的影响,同时可参照文档中提及的模糊PID等对比方案,拓展对先进控制算法的研究与实践应用。

【教程】安装 iterm2 打造漂亮且高性能的 mac 终端

最近电脑重新安装系统,开发环境和软件都需要重新安装和配置,其中终端工具是必不可少的,正好利用这个机会,介绍一下 macOS 终端神器 iTerm2 的安装和配置,并推荐几款好用的插件和工具。在IT领域中,工具的选择和使用对于开发者和运维人员来说至关重要。而在众多终端模拟器中,ITerm2无疑是一个出色的选择,希望本篇的文章能对你有些帮助,感兴趣的朋友可以登录官网查看官方文档。我是刘大大,持续分享AI相关领域的知识和工具,喜欢点点关注。

刘大大AI的博客 3399

INCA_ProF脚本教程

一、首先INCA软件是当前主流标定开发软件之一,尤其在动力系统控制器的标定中应用非常广泛;与此同时ProF编程脚本是INCA软件提供各种实用功能之一,使用者可根据需求编写ProF脚本语言对ECU闪存编程等各种自定义操作。 二、然而ProF脚本语言在ETAS的官网或其他网络上并没有专门的使用教程,通常只有ETAS技术支持人员提供的ProF Documentation文档或少量可参考的Demo案例,然而ProF Documentation文档仅涉及ProF语言的函数的操作说明,没有从全局的框架上介绍Pro

weixin_29389627的博客 1万+
上一篇: 替换GINA.DLL实现自己的登陆界面
下一篇: Entering the kernel without a driver and getting interrupt information from APIC
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值