This can be done using several methods including overwriting the first several bytes of the syscall with a jump to other code, or modifying the function pointers. The method we will be discussing involves the IDT though. First it is important to have a basic understanding of the process involved.
Brief explanation on how syscalls are invoked:
syscalls are generally called from libc wrappers, i.e write() is a wrapper for sys_write(). The user space app calls write() which ultimately signals the kernel that a syscall needs to be executed. The primary action that transpires between the user space application and the kernel is a software interrupt. Software interrupts incur an exception, which causes the system to switch to kernel mode and execute the exception handler. In the event of a syscall, the handler is the system_call() function (system call handler). The interrupt for this on x86 is the 0x80 instruction (exception vector 128). Modern x86 processors now include a feature called sysenter, which is a faster way to enter the kernel and execute a syscall, but we will not be discussing that in this paper.
In order to know which syscall is being invoked, the syscall number is stored in %eax, this is a standard convention we all follow. The system_call() function verifies the syscall number in %eax is valid, if it proves to be so, it invokes the syscall through:
call *sys_call_table(,%eax,4)
Each syscall table element is 4 bytes (keep in mind the syscall table is an array of function pointers), and therefore the kernel multiplies %eax by four to arrive at its destination within the table. All parameters are passed through registers. As already stated, %eax holds the syscall number and for all the syscall parameters the following registers are used: ebx, ecx, edx, esi, edi. There are certain syscalls i.e mmap() where there will be too many arguments, in this case you can simply use the stack and store a pointer to it in ebx. How does one obtain the sys_call_table? Here is a common method. This code will work on 2.6 linux systems.
#include < stdio.h >
#include < sys/types.h >
#include < fcntl.h >
#include < stdlib.h >
int kfd;
struct
{
unsigned short limit;
unsigned int base;
} __attribute__ ((packed)) idtr;
struct
{
unsigned short off1;
unsigned short sel;
unsigned char none, flags;
unsigned short off2;
} __attribute__ ((packed)) idt;
int readkmem (unsigned char *mem,
unsigned off,
int bytes)
{
if (lseek64 (kfd, (unsigned long long) off,
SEEK_SET) != off)
{
return -1;
}
if (read (kfd, mem, bytes) != bytes)
{
return -1;
}
}
int main (void)
{
unsigned long sct_off;
unsigned long sct;
unsigned char *p, code[255];
int i;
/* request IDT and fill struct */
asm ("sidt %0":"=m" (idtr));
if ((kfd = open ("/dev/kmem", O_RDONLY)) == -1)
{
perror("open");
exit(-1);
}
if (readkmem ((unsigned char *)&idt,
idtr.base + 8 * 0x80, sizeof (idt)) == -1)
{
printf("Failed to read from /dev/kmem\n");
exit(-1);
}
sct_off = (idt.off2 < < 16) | idt.off1;
if (readkmem (code, sct_off, 0x100) == -1)
{
printf("Failed to read from /dev/kmem\n");
exit(-1);
}
/* find the code sequence that calls SCT */
sct = 0;
for (i = 0; i < 255; i++)
{
if (code[i] == 0xff && code[i+1] == 0x14 &&
code[i+2] == 0x85)
sct = code[i+3] + (code[i+4] < < 8) +
(code[i+5] < < 16) + (code[i+6] < < 24);
}
if (sct)
printf ("sys_call_table: 0x%x\n", sct);
close (kfd);
}
The general idea from here is that once we have the address of the syscall table, we can make a copy of it in memory then modify certain values to point to replacement code. i.e modify sys_write to point to a replacement write. We will also modify the system_call() interrupt handler by replacing the call *sys_call_table_address with the address of our new table. This will leave the original sys_call_table intouched, but also no longer in use by means of int $0x80. Our new sys_call_table will be the one in use.
The technical details of writing a rootkit of such a nature are not significant to this paper, and are heavily documented in other papers. The important thing here is that we understand what is necessary to detect such a kernel rootkit, and the obvious answer is that the consistency of the interrupt handler must be checked. A snapshot should be taken after a fresh kernel compilation. Where do we want to look?
Within kernel memory we are interested in the first N bytes of (idt.off2 < < 16) | idt.off1 as shown in the code above. The modification that should be noticed is the 4 bytes after \xff\x14\x85, the memory address to the sys_call_table will no longer be the same.
There will be a new tool to perform this simple analysis available soon on this site [www.RootkitAnalytics.com].
Hidden Process Detection
Hidden Service Detection
Tools-Elfstat
Tools-KsiD
Hidden Registry Detection
ToolsCount
~~~~~~~~~~~~~~~~~~~
Elfstat2995
dwtf2194
KsiD1934
SHC1435
NOTE: Our tools are listed in many sites and torrents, which makes it hard for us to track all downloads. Hence, we are listing only the total installations from our website.