Anti-Hooking - Theory

    Most recently, I was puzzled by the protection of applications from intercepting system api, I decided to share and discuss what I came to. Many of you know that intercepting system api comes down to redirecting the original function to the right place, thanks to this you can modify the parameters of the function, return a result different from the original, store the original call with parameters and much more. Since this is a theoretical part, the examples in the article will be accompanied by pseudocode.

    The following interception methods are commonly used:
    1. Insert an unconditional jump at the function location (x86 - 5 bytes jmp addr, x64 - 12 bytes, mov rax addr, jmp rax)
    2. Insertion of a call statement at the place of the original function call (hooked_function_entry: call my_function)
    3. Modification of the application import table, implementation of the proxy DLL.
    4. Interception without modifying the code using hardware breakpoints.
    5. Interception through kernel driver of Zw / Nt functions.

    We can list the following methods for detecting interception from usermode:
    1. Comparison of the start of a function before calling it with opcodes of machine instructions:

    //0x90 - nop
    //0xE9 - jmp
    //0xE8 - call
    if (*mainFunc == 0xE9 || *mainFunc == 0x90 || *mainFunc == 0xE8 ...) 
    printf("Hook detected");
    

    The method is static and easily dispensed with more intelligent transitions.
    2. The above methods of interception are a modification of memory, and they can be detected through crc checksum checks, but api for reading memory can also be intercepted and then a false result will be returned.
    3. Generate a controlled process (with active debugging of itself), with the restoration of important api.
    4. Enumerating the api that are important to us, using the export table in the system libraries, compare the disassembled function length with the original one.
    5. Restoring modified api by copying bytes from the library, an approximate scheme is as follows:
    - getting the base of a specific module
    - export iteration
    - getting the necessary function RVA.
    - conversion RVA-> FileOffset.
    - reading the original and writing to memory.
    - in some cases, do not forget about relocks.
    6. View the library import table and, for each imported function from another system library, do a compliance check, if it does not match, read it from the disk and write it to memory.
    7. Simulation of the system functions of Nt / Zw using syscalls, int2e in (old versions of windows), the syscall table for all systems is easily found in Google.

    Let us dwell on the 7th method since Nt / Zw functions can also be intercepted, this method is also far from perfect, but in my opinion the best of all of the above.
    Let's look at the implementation of the NtCreateFile function in windows 7 sp1

    image

    the following happens:
    - in eax is the number syscall-a.
    - reset ecx, (wow64 index?).
    - edx contains a pointer to the parameters.
    - Further call, stack adjustment and return.

    x64 call in win7 and win8
    image

    Using the following x86 pseudo-code “you can create a file on disk”.
    //прототип функции NtCreateFile
    typedef NTSTATUS  (NTAPI * NTCREATEFILE) (OUT PHANDLE FileHandle,
        IN ACCESS_MASK DesiredAccess,
        IN POBJECT_ATTRIBUTES ObjectAttributes,
        OUT PIO_STATUS_BLOCK IoStatusBlock,
        IN PLARGE_INTEGER AllocationSize OPTIONAL,
        IN ULONG FileAttributes,
        IN ULONG ShareAccess,
        IN ULONG CreateDisposition,
        IN ULONG CreateOptions,
        IN PVOID EaBuffer OPTIONAL,
        IN ULONG EaLength);
    #define InitializeObjectAttributes( p, n, a, r, s ) { \
        (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \
        (p)->RootDirectory = r;                             \
        (p)->Attributes = a;                                \
        (p)->ObjectName = n;                                \
        (p)->SecurityDescriptor = s;                        \
        (p)->SecurityQualityOfService = NULL;               \
        }
    typedef VOID (NTAPI * RTLINITUNICODESTRING)(IN OUT PUNICODE_STRING,
                                           IN PCWSTR);
    unsigned char dNtCreateFile[] = 
    {0xb8,0x52,0x00,0x00,0x00,0x33,0xc9,0x8d,0x54,0x24,0x04,0x64,
    0xff,0x15,0xc0,0x00,0x00,0x00,0x83,0xc4,0x04,0xc2,0x2c,0x00};
    ...
    RtlInitUnicodeString
    InitializeObjectAttributes
    ...
    DWORD oldp;    
    VirtualProtect(&dNtCreateFile, sizeof(dNtCreateFile), PAGE_EXECUTE_READ, &oldp); 
    auto func = (NTCREATEFILE) ((void*)dNtCreateFile);
    Ntstatus = (func)(&fileHandle, DesiredAccess, ObjectAttritubes, ioStatusBlock, 0, FileAttributes, ShareAccess, CreateDisposition ,CreateOptions, Optional_Buffer, 0); 
    

    This pseudo-code will work only on windows 7 sp1, however, dNtCreateFile can be generated “dynamically” using the method from point 5, and then the code with minor changes will work on all systems starting with winxp. Any thoughts in the comments are welcome, kernelmode is an extreme case.

    Also popular now: