Introduction
Many articles on intercepting x32 system calls have been published on the Internet. As part of solving one task, it became necessary to intercept system calls under the x86-64 architecture using a loadable kernel module. Let's start:
Intercept system calls
Algorithm:
- Finding the address of the system call table
- Substitution for addresses of new system calls
Finding the address of the system call table
The first option: can be found through the table of interrupt descriptors (IDT), IDT - is used to link the interrupt handler with the interrupt number. In protected mode, the address in physical memory and the size of the interrupt table is determined by the 80-bit IDTR register. In protected mode, the IDT element is the 10-byte interrupt gateway containing the segment (logical) address of the interrupt handler, access rights, etc. This method is not interesting to us because we get the address of the handler, which is made for compatibility with x32
')
The second option is more interesting.
For a start, not a big digression:
MSR - machine state register is a set of registers of Intel processors used in the x86 and x86-64 processors. These registers provide the ability to monitor and obtain information about the state of the processor. All MSR registers are available only for system functions and are not accessible from user programs. We are particularly interested in the following register: MSR_LSTAR - 0xc0000082 (long mode SYSCALL target)
(the full list can be found in /usr/include/asm/msr-index.h).
This register stores the address of the interrupt handler for x86-64.
You can get the address as follows:
int i, lo, hi;
asm volatile("rdmsr" : "=a" (lo), "=d" (hi) : "c" (MSR_LSTAR));
system_call = (void*)(((long)hi<<32) | lo);
Next, find the address of the table itself. Let's go to the address we just received and find the sequence \ xff \ x14 \ xc5 in memory (these magic numbers are taken if you look at the kernel code, in particular, the system_call function code, in which the handler is called from the required one). Considering the following 4 bytes, we get the address of the syscall_table system call table. Knowing its address, we can get the contents of this table (addresses of all system functions) and change the address of any system call, intercepting it.
code to find the address of the system call table:
unsigned char *ptr;
for (ptr=system_call, i=0; i<500; i++) {
if (ptr[0] == 0xff && ptr[1] == 0x14 && ptr[2] == 0xc5)
return (void*)(0xffffffff00000000 | *((unsigned int*)(ptr+3)));
ptr++;
}
Substitution for addresses of new system calls
Here, too, there are certain nuances, if you just try to change anything in the table, then an error will be generated. Fortunately, this is easy enough:
- Disable memory protection
- We rewrite the address to the address of our handler.
- Turn on memory protection
To remove and install protection, you need to know the following: Register CR0 - contains system control flags that control the behavior and state of the processor. WP flag - write protection (Write Protect), 48th bit of CR0. When set, prevents system procedures from writing to user pages with read-only access (when the WP flag is cleared, it allows). Unlike x32, only the size of the register and the flag number changed
security removal code:
asm("pushq %rax");
asm("movq %cr0, %rax");
asm("andq $0xfffffffffffeffff, %rax");
asm("movq %rax, %cr0");
asm("popq %rax");
security enable code:
asm("pushq %rax");
asm("movq %cr0, %rax");
asm("xorq $0x0000000000001000, %rax");
asm("movq %rax, %cr0");
asm("popq %rax");
This knowledge is enough to replace the system calls in Linux x86-64. I hope someone this will be useful.
Thanks for attention.
UPD: