📜 ⬆️ ⬇️

Antihucking 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 which 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 the theoretical part, the examples in the article will be accompanied by pseudocode.

The following interception methods are commonly used:
  1. Inserting an unconditional jump in place of a function (x86 - 5 bytes jmp addr, x64 - 12 bytes, mov rax addr, jmp rax)
  2. Inserting a call instruction at the place where the original function was called (hooked_function_entry: call my_function)
  3. Modification of the application import table, the introduction of a proxy DLL.
  4. Interception without modification of the code by means of hardware breakpoints (hardware breakpoints).
  5. Interception through the kernel driver Zw / Nt functions.

You can list the following methods for detecting interception from usermode:
1. Comparison of the beginning of a function before calling it with the 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 bypassed, with smarter transitions.
2. The listed interception methods are a modification of memory, and they can be detected by 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 oneself), with the restoration of important api.
4. Searching the api, which are important for us, using the export table in the system libraries, compare the disassembled length of the function with the original one.
5. Restoration of modified api by copying bytes from the library, an approximate scheme is as follows:
- getting the base of a specific module
- export iteration
- obtaining RVA necessary function.
- RVA-> FileOffset conversion.
- reading the original and writing to memory.
- in some cases, do not forget about relocs.
6. View the library import table and for each imported function from another system library, make a check for consistency, if it does not match, we also read from the disk and write to memory.
7. Simulating the Nt / Zw system functions using syscalls, int2e in (older windows versions), syscalls table for all systems, is easily located in google.

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

the following happens:
- in eah is the number of syscall-a.
- reset to ecx, (wow64 index?).
- edx contains a pointer to the parameters.
- further call, the adjustment of the stack 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 pseudocode will only work on windows 7 sp1, however dNtCreateFile can be formed “dynamically” using the method from clause 5, and then the code with minor edits will work on all systems starting with winxp. Any thoughts in the comments are welcome, the kernelmode is an extreme case.

Source: https://habr.com/ru/post/186334/


All Articles