|=-----------------=[ Obtain sys_call_table on amd64(x86_64) ]=---------------=| |=----------------------------------------------------------------------------=| |=-----------------------=[ pouik ]=----------------------=| --[ Summary 1 - Introduction 2 - IDT 3 - System_call 4 - Sys_call_table 5 - Test 6 - Conclusion --[ 1 Introduction Some people remember the article written by sd & devik in phrack 58 [1] which introduce for the first time (or it's silvio?) the way to find the sys_call_table on X86 in userland with /dev/(k)mem and how to use it, in order to create rootkits on Linux. With this article you'll be able to obtain the sys_call_table on archi Amd64 Most Linux distributions have its kernel compiled with option CONFIG_IA32_EMULATION=y which, simply ensure the compatibility between architecture 32 bits and 64 bits. Kernel developers' have inserted the ia32_sys_call_table and this is very interesting... so ladies and gentlemens go to next part ! in order to know why :p --[ 2 IDT On x86, we can obtain IDT (Interrupt Descriptor Table) with asm instruction asm("sidt %0" : "=m" (idtr)); which fill the structure idtr : struct { unsigned short limit; unsigned int base; } __attribute__((packed)) idtr; On amd64, the base needs 8 octects (and not 4 octets), so we use the next structure : struct { unsigned short limit; unsigned long base; } __attribute__((packed)) idtr; --[ 3 System_call Then, the idea of sd & devik was to obtain the IDT corresponding to int $0x80 : readkmem (&idt,idtr.base+8*0x80,sizeof(idt)); sys_call_off = (idt.off2 << 16) | idt.off1; Here that's ok, but we must read 16 bits: readkmem (&idt,idtr.base+16*0x80,sizeof(idt)); sys_call_off = (idt.off2 << 16) | idt.off1; Take a moment, in the introduction, we indicate that kernel developers have inserted a compatibility with software 32 bits, and here, the syscall should be on ia32 : promethee kernel # pwd /usr/src/linux/arch/x86_64/kernel promethee kernel # grep SYSCALL_VECTOR * i8259.c: if (vector != IA32_SYSCALL_VECTOR) io_apic.c: if (current_vector == IA32_SYSCALL_VECTOR) traps.c: set_system_gate(IA32_SYSCALL_VECTOR, ia32_syscall); So bingo, we obtain the system_call which is,in reality, ia32_syscall. :) --[ 4 Sys_call_table Now that we have ia32_syscall, keep a close eye on execution: (gdb) disassemble ia32_syscall Dump of assembler code for function ia32_syscall: 0xffffffff8021f6fc : swapgs 0xffffffff8021f6ff : sti 0xffffffff8021f700 : mov %eax,%eax 0xffffffff8021f702 : push %rax 0xffffffff8021f703 : cld 0xffffffff8021f704 : sub $0x48,%rsp 0xffffffff8021f708 : mov %rdi,0x40(%rsp) 0xffffffff8021f70d : mov %rsi,0x38(%rsp) 0xffffffff8021f712 : mov %rdx,0x30(%rsp) 0xffffffff8021f717 : mov %rcx,0x28(%rsp) 0xffffffff8021f71c : mov %rax,0x20(%rsp) 0xffffffff8021f721 : mov %gs:0x10,%r10 0xffffffff8021f72a : sub $0x1fd8,%r10 0xffffffff8021f731 : orl $0x2,0x14(%r10) 0xffffffff8021f736 : testl $0x181,0x10(%r10) 0xffffffff8021f73e : jne 0xffffffff8021f768 End of assembler dump. (gdb) disassemble ia32_tracesys Dump of assembler code for function ia32_tracesys: 0xffffffff8021f768 : sub $0x30,%rsp 0xffffffff8021f76c : mov %rbx,0x28(%rsp) 0xffffffff8021f771 : mov %rbp,0x20(%rsp) 0xffffffff8021f776 : mov %r12,0x18(%rsp) 0xffffffff8021f77b : mov %r13,0x10(%rsp) 0xffffffff8021f780 : mov %r14,0x8(%rsp) 0xffffffff8021f785 : mov %r15,(%rsp) 0xffffffff8021f789 : movq $0xffffffffffffffda,0x50(%rsp) 0xffffffff8021f792 : mov %rsp,%rdi 0xffffffff8021f795 : callq 0xffffffff8020c684 0xffffffff8021f79a : mov 0x30(%rsp),%r11 0xffffffff8021f79f : mov 0x38(%rsp),%r10 0xffffffff8021f7a4 : mov 0x40(%rsp),%r9 0xffffffff8021f7a9 : mov 0x48(%rsp),%r8 0xffffffff8021f7ae : mov 0x58(%rsp),%rcx 0xffffffff8021f7b3 : mov 0x60(%rsp),%rdx 0xffffffff8021f7b8 : mov 0x68(%rsp),%rsi 0xffffffff8021f7bd : mov 0x70(%rsp),%rdi 0xffffffff8021f7c2 : mov 0x78(%rsp),%rax 0xffffffff8021f7c7 : mov (%rsp),%r15 0xffffffff8021f7cb : mov 0x8(%rsp),%r14 0xffffffff8021f7d0 : mov 0x10(%rsp),%r13 0xffffffff8021f7d5 : mov 0x18(%rsp),%r12 0xffffffff8021f7da : mov 0x20(%rsp),%rbp 0xffffffff8021f7df : mov 0x28(%rsp),%rbx 0xffffffff8021f7e4 : add $0x30,%rsp 0xffffffff8021f7e8 : jmpq 0xffffffff8021f740 End of assembler dump. (gdb) disassemble ia32_do_syscall Dump of assembler code for function ia32_do_syscall: 0xffffffff8021f740 : cmp $0x13c,%eax 0xffffffff8021f745 : ja 0xffffffff8021f7ed 0xffffffff8021f74b : mov %edi,%r8d 0xffffffff8021f74e : mov %ebp,%r9d 0xffffffff8021f751 : xchg %ecx,%esi 0xffffffff8021f753 : mov %ebx,%edi 0xffffffff8021f755 : mov %edx,%edx 0xffffffff8021f757 : callq *0xffffffff804f6110(,%rax,8) End of assembler dump. After visiting ia32_tracesys, we jump on ia32_di_syscall, which makes a call : callq *0xffffffff804f6110(,%rax,8) promethee linux # grep ffffffff804f6110 /boot/System.map ffffffff804f6110 R ia32_sys_call_table And that work, we obtain the ia32_sys_call_table :p In order to obtain it from system_call, sd & devik read 256 characters and search pattern "\xff\x14\x85": readkmem (sc_asm,sys_call_off,CALLOFF); p = (char*)memmem (sc_asm,CALLOFF,"\xff\x14\x85",3); sct = *(unsigned*)(p+3); Why don't apply this method? (gdb) x/xw ia32_do_syscall+23 0xffffffff8021f757 : 0x10c514ff We just search pattern "\xff\x14\xc5" from ia32_syscall. If you have understood until then, you think, perhaps,there are errors. Indeed, we normally have a jump to do with ia32_tracesys in order to arrive in ia32_dosyscall. And it's right... but we can use the fact that these functions are consecutive in memory for jump tracesys. We obtain : readkmem (sc_asm,sys_call_off,CALLOFF); p = (char*)memmem (sc_asm,CALLOFF,"\xff\x14\xc5",3); sct = *(unsigned long*)(p+3); sct = (sct & 0x00000000ffffffff) | 0xffffffff00000000; We apply a mask in order to ignore useless informations. Bonus Track : An other way to find ia32_sys_call_table is using the function walk_krstab writing in phalanx rootkit because : promethee linux # grep ia32_sys_call_table /boot/System.map ffffffff804f6110 R ia32_sys_call_table ffffffff8055feb0 r __ksymtab_ia32_sys_call_table ffffffff8056c9d0 r __kstrtab_ia32_sys_call_table For more informations, please look at the previous article. --[ 5 Test promethee linux # grep ia32_syscall /boot/System.map ffffffff8021f6fc T ia32_syscall ffffffff804f6af8 r ia32_syscall_end promethee linux # grep ia32_sys_call_table /boot/System.map ffffffff804f6110 R ia32_sys_call_table ffffffff8055feb0 r __ksymtab_ia32_sys_call_table ffffffff8056c9d0 r __kstrtab_ia32_sys_call_table Try the first method : promethee linux # zeppoo -s -d /dev/mem Kernel : 2.6.17.11 Kernel : 2.617110 proc : x86_64 KERNEL_START 0xffffffff80000000 KERNEL_END 0xffffffff81000000 PAGE_OFFSET 0xffff810000000000 PAGE_MAX 0xffffffffffffffff Memory : /dev/mem IDTR BASE 0xffffffff8067e000 LIMIT 0xfff idt80: flags = 238 flags=EE sel=10 off1=f6fc off2=8021 SYSTEM_CALL : 0xffffffff8021f6fc Sys Call Table 0xffffffff804f6110 With the second method : promethee linux # zeppoo -s -d /dev/mem -m Kernel : 2.6.17.11 Kernel : 2.617110 proc : x86_64 KERNEL_START 0xffffffff80000000 KERNEL_END 0xffffffff81000000 PAGE_OFFSET 0xffff810000000000 PAGE_MAX 0xffffffffffffffff Memory : /dev/mem IDTR BASE 0xffffffff8067e000 LIMIT 0xfff idt80: flags = 238 flags=EE sel=10 off1=f6fc off2=8021 SYSTEM_CALL : 0xffffffff8021f6fc ????kstrtab = 0xffffffff8056c9d0 ??????Sys Call Table 0xffffffff804f6110 --[ 6 Conclusion So, it's the end of this small article, the source code is avaible on http://www.zeppoo.netand on the svn (repertory libzeppoo) 'svn checkout https://subversion.cru.fr/zeppoo/trunk/ zeppoo'. Now that you have the sys_call_table, techniques which work on i386 can work on amd64...:) Reference : [1] http://phrack.org/archives/58/p58-0x07 Thanks : Yperite, n0name, tbowan et aryliin pour me supporter :p Translated by Yperite (aime pas les chauSettes) "End of your world, the beginning of mine"