CONFIG_IA32_EMULATION
). In this regard, there are 2 non-exportable tables in the kernel - sys_call_table
and ia32_sys_call_table
(available only for emulation mode), containing the addresses of functions that process system calls. 0xffffffff81731670 <+0>: swapgs 0xffffffff81731673 <+3>: mov %rsp,%gs:0xc000 0xffffffff8173167c <+12>: mov %gs:0xc830,%rsp 0xffffffff81731685 <+21>: sti 0xffffffff81731686 <+22>: data32 data32 xchg %ax,%ax 0xffffffff8173168a <+26>: data32 xchg %ax,%ax 0xffffffff8173168d <+29>: sub $0x50,%rsp 0xffffffff81731691 <+33>: mov %rdi,0x40(%rsp) 0xffffffff81731696 <+38>: mov %rsi,0x38(%rsp) 0xffffffff8173169b <+43>: mov %rdx,0x30(%rsp) 0xffffffff817316a0 <+48>: mov %rax,0x20(%rsp) 0xffffffff817316a5 <+53>: mov %r8,0x18(%rsp) 0xffffffff817316aa <+58>: mov %r9,0x10(%rsp) 0xffffffff817316af <+63>: mov %r10,0x8(%rsp) 0xffffffff817316b4 <+68>: mov %r11,(%rsp) 0xffffffff817316b8 <+72>: mov %rax,0x48(%rsp) 0xffffffff817316bd <+77>: mov %rcx,0x50(%rsp) 0xffffffff817316c2 <+82>: testl $0x100801d1,-0x1f78(%rsp) 0xffffffff817316cd <+93>: jne 0xffffffff8173181e <tracesys> 0xffffffff817316d3 <+0>: and $0xbfffffff,%eax 0xffffffff817316d8 <+5>: cmp $0x220,%eax /* <-------- cmp $__NR_syscall_max,%eax */ 0xffffffff817316dd <+10>: ja 0xffffffff817317a5 <badsys> 0xffffffff817316e3 <+16>: mov %r10,%rcx 0xffffffff817316e6 <+19>: callq *-0x7e7fec00(,%rax,8) /* <-------- call *sys_call_table(,%rax,8) */ 0xffffffff817316ed <+26>: mov %rax,0x20(%rsp) 0xffffffff817316f2 <+0>: mov $0x1008feff,%edi 0xffffffff817316f7 <+0>: cli 0xffffffff817316f8 <+1>: data32 data32 xchg %ax,%ax 0xffffffff817316fc <+5>: data32 xchg %ax,%ax 0xffffffff817316ff <+8>: mov -0x1f78(%rsp),%edx 0xffffffff81731706 <+15>: and %edi,%edx 0xffffffff81731708 <+17>: jne 0xffffffff81731745 <sysret_careful> 0xffffffff8173170a <+19>: mov 0x50(%rsp),%rcx 0xffffffff8173170f <+24>: mov (%rsp),%r11 0xffffffff81731713 <+28>: mov 0x8(%rsp),%r10 0xffffffff81731718 <+33>: mov 0x10(%rsp),%r9 0xffffffff8173171d <+38>: mov 0x18(%rsp),%r8 0xffffffff81731722 <+43>: mov 0x20(%rsp),%rax 0xffffffff81731727 <+48>: mov 0x30(%rsp),%rdx 0xffffffff8173172c <+53>: mov 0x38(%rsp),%rsi 0xffffffff81731731 <+58>: mov 0x40(%rsp),%rdi 0xffffffff81731736 <+63>: mov %gs:0xc000,%rsp 0xffffffff8173173f <+72>: swapgs 0xffffffff81731742 <+75>: sysretq
swapgs
) switches data structures (from user to nuclear). Next, the stack is configured, interrupts are enabled, and the register context of the flow ( pt_regs
structure) required during processing is formed on the stack. Returning to the listing presented above, special attention should be paid to the following commands: 0xffffffff817316d8 <+5>: cmp $0x220,%eax /* <-------- cmp $__NR_syscall_max,%eax */ 0xffffffff817316dd <+10>: ja 0xffffffff817317a5 <badsys> 0xffffffff817316e3 <+16>: mov %r10,%rcx 0xffffffff817316e6 <+19>: callq *-0x7e7fec00(,%rax,8) /* <-------- call *sys_call_table(,%rax,8) */ 0xffffffff817316ed <+26>: mov %rax,0x20(%rsp)
%rax
) %rax
maximum allowable value ( __NR_syscall_max
). In the event that the check is successful, the system call will be dispatched, namely, the control will transfer to a function that implements the appropriate logic.call *sys_call_table(,%rax,8)
( call *sys_call_table(,%rax,8)
). Further embedding will be carried out by modifying this command.call *sys_call_table(,%rax,8)
), and write an unconditional branch command ( JMP REL32
) on it to the service_stub
handler. In this case, the general structure of such a handler will be as follows (hereinafter pseudocode): system_call: swapgs .. jmp service_stub /* <-------- call *sys_call_table(,%rax,8) */ mov %rax,0x20(%rsp) /* <-------- service_stub */ ... swapgs sysretq service_stub: ... call ServiceTraceEnter /* void ServiceTraceEnter(struct pt_regs *) */ ... call sys_call_table[N](args) ... call ServiceTraceLeave(regs) /* void ServiceTraceLeave(struct pt_regs *) */ ... jmp back
ServiceTraceEnter()
and ServiceTraceLeave()
are the pre- and post-processing functions, respectively. Their parameters are a pointer to a pt_regs
- a register structure representing the stream context. The final instruction is the command to transfer control to the system call manager code, from where the call to this handler was previously made.system_call
interception (SYSCALL instruction): .global service_syscall64 service_syscall64: SAVE_REST movq %rsp, %rdi call ServiceTraceEnter RESTORE_REST LOAD_ARGS 0 movq %r10, %rcx movq ORIG_RAX - ARGOFFSET(%rsp), %rax call *0x00000000(,%rax,8) // origin call movq %rax, RAX - ARGOFFSET(%rsp) SAVE_REST movq %rsp, %rdi call ServiceTraceLeave RESTORE_REST movq RAX - ARGOFFSET(%rsp), %rax jmp 0x00000000
SAVE_REST
, RESTORE_REST
, LOAD_ARGS
), the purpose of which is mainly to form a stream context ( pt_regs
) before calling the ServiceTraceEnter
and ServiceTraceLeave
.sys_call_table
and ia32_sys_call_table
not exported. There are different ways to determine their addresses, but by defining the addresses of dispatchers in the previous step, the addresses of the tables are also determined simply by searching for a dispatching instruction, which has the form call sys_call_table[N]
.__NR_open
expected table is equal to the address of the sys_open function. However, in this example, such additional checks are not performed.stop_machine
.CALL MEM32
dispatch CALL MEM32
is replaced with the 5-byte unconditional branch command with the corresponding JMP REL32
handler. As a result, certain restrictions are imposed on the distance of the transition. The handler must be located no further than ± 2 GB from the location of the JMP REL32
. typedef struct scentry { const char *name; const void *entry; const void *table; const void *pcall; void *pcall_map; void *stub; const void *handler; void (*prepare)(struct scentry *); void (*implant)(struct scentry *); void (*restore)(struct scentry *); void (*cleanup)(struct scentry *); } scentry_t;
scentry_t elist[] = { ... { .name = "system_call", /* SYSCALL: MSR(LSTAR), kernel/entry_64.S (1) */ .handler = service_syscall64, .prepare = prepare_syscall64_1 }, { .name = "system_call", /* SYSCALL: MSR(LSTAR), kernel/entry_64.S (2) */ .handler = service_syscall64, .prepare = prepare_syscall64_2 }, ... };
prepare
function is responsible for this. Below is an example of the implementation of the function to prepare for embedding a SYSCALL command in the manager: extern void service_syscall64(void); static void prepare_syscall64_1(scentry_t *se) { /* * searching for -- 'call *sys_call_table(,%rax,8)' * http://lxr.free-electrons.com/source/arch/x86/kernel/entry_64.S?v=3.13#L629 */ se->entry = get_symbol_address(se->name); se->entry = se->entry ? se->entry : to_ptr(x86_get_msr(MSR_LSTAR)); if (!se->entry) return; se->pcall = ud_find_insn(se->entry, 512, UD_Icall, 7); if (!se->pcall) return; se->table = to_ptr(*(int *)(se->pcall + 3)); }
se->entry
). If it is not possible to determine the address in this way, the mechanisms specific to each dispatcher come into play (in this case, reading the MSR register with the number MSR_LSTAR).se->pcall
) is se->pcall
and, if successful, the address used by the system call table manager is determined. static void fixup_stub(scentry_t *se) { ud_t ud; memset(se->stub, 0x90, STUB_SIZE); ud_initialize(&ud, BITS_PER_LONG, \ UD_VENDOR_ANY, se->handler, STUB_SIZE); while (ud_disassemble(&ud)) { void *insn = se->stub + ud_insn_off(&ud); const void *orig_insn = se->handler + ud_insn_off(&ud); memcpy(insn, orig_insn, ud_insn_len(&ud)); /* fixup sys_call_table dispatcher calls (FF.14.x5.xx.xx.xx.xx) */ if (ud.mnemonic == UD_Icall && ud_insn_len(&ud) == 7) { x86_insert_call(insn, NULL, se->table, 7); continue; } /* fixup ServiceTraceEnter/Leave calls (E8.xx.xx.xx.xx) */ if (ud.mnemonic == UD_Icall && ud_insn_len(&ud) == 5) { x86_insert_call(insn, insn, orig_insn + (long)(*(int *)(orig_insn + 1)) + 5, 5); continue; } /* fixup jump back (E9.xx.xx.xx.xx) */ if (ud.mnemonic == UD_Ijmp && ud_insn_len(&ud) == 5) { x86_insert_jmp(insn, insn, se->pcall + 7); break; } } se->pcall_map = map_writable(se->pcall, 64); }
JMP REL32
, .JMP REL32
) . static void generic_restore(scentry_t *se) { ud_t ud; if (!se->pcall_map) return; ud_initialize(&ud, BITS_PER_LONG, \ UD_VENDOR_ANY, se->stub, STUB_SIZE); while (ud_disassemble(&ud)) { if (ud.mnemonic == UD_Icall && ud_insn_len(&ud) == 5) { memset(se->stub + ud_insn_off(&ud), 0x90, ud_insn_len(&ud)); continue; } if (ud.mnemonic == UD_Ijmp) break; } debug(" [o] restoring original call instruction %p (%s)\n", se->pcall, se->name); x86_insert_call(se->pcall_map, NULL, se->table, 7); }
stop_machine
, .pcall_map
).open(2)
. Below is the trace_syscall_entry function that implements this interception using the ServiceTraceEnter handler : static void trace_syscall_entry(int arch, unsigned long major, \ unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3) { char *filename = NULL; if (major == __NR_open || major == __NR_ia32_open) { filename = kmalloc(PATH_MAX, GFP_KERNEL); if (!filename || strncpy_from_user(filename, (const void __user *)a0, PATH_MAX) < 0) goto out; printk("%s open(%s) [%s]\n", arch ? "X86_64" : "I386", filename, current->comm); } out: if (filename) kfree(filename); } void ServiceTraceEnter(struct pt_regs *regs) { if (IS_IA32) trace_syscall_entry(0, regs->orig_ax, \ regs->bx, regs->cx, regs->dx, regs->si); #ifdef CONFIG_X86_64 else trace_syscall_entry(1, regs->orig_ax, \ regs->di, regs->si, regs->dx, regs->r10); #endif }
$ git clone https://github.com/milabs/kmod_hooking_sct $ cd kmod_hooking_sct $ make $ sudo insmod scthook.ko
dmesg
, the following information should appear in the kernel log (command ): [ 5217.779766] [scthook] # SYSCALL hooking module [ 5217.780132] [scthook] # prepare [ 5217.785853] [scthook] [o] prepared stub ffffffffa000c000 (ia32_syscall) [ 5217.785856] [scthook] entry:ffffffff81731e30 pcall:ffffffff81731e92 table:ffffffff81809cc0 [ 5217.790482] [scthook] [o] prepared stub ffffffffa000c200 (ia32_sysenter_target) [ 5217.790484] [scthook] entry:ffffffff817319a0 pcall:ffffffff81731a36 table:ffffffff81809cc0 [ 5217.794931] [scthook] [o] prepared stub ffffffffa000c400 (ia32_cstar_target) [ 5217.794933] [scthook] entry:ffffffff81731be0 pcall:ffffffff81731c75 table:ffffffff81809cc0 [ 5217.797517] [scthook] [o] prepared stub ffffffffa000c600 (system_call) [ 5217.797518] [scthook] entry:ffffffff8172fcb0 pcall:ffffffff8172fd26 table:ffffffff81801400 [ 5217.800013] [scthook] [o] prepared stub ffffffffa000c800 (system_call) [ 5217.800014] [scthook] entry:ffffffff8172fcb0 pcall:ffffffff8172ff38 table:ffffffff81801400 [ 5217.800014] [scthook] # prepare OK [ 5217.800015] [scthook] # implant [ 5217.800052] [scthook] [o] implanting jump to stub handler ffffffffa000c000 (ia32_syscall) [ 5217.800054] [scthook] [o] implanting jump to stub handler ffffffffa000c200 (ia32_sysenter_target) [ 5217.800054] [scthook] [o] implanting jump to stub handler ffffffffa000c400 (ia32_cstar_target) [ 5217.800055] [scthook] [o] implanting jump to stub handler ffffffffa000c600 (system_call) [ 5217.800056] [scthook] [o] implanting jump to stub handler ffffffffa000c800 (system_call) [ 5217.800058] [scthook] # implant OK
open(2)
will result in the following messages in the same log: [ 5370.999929] X86_64 open(/usr/share/locale-langpack/en_US.utf8/LC_MESSAGES/libc.mo) [perl] [ 5370.999930] X86_64 open(/usr/share/locale-langpack/en_US/LC_MESSAGES/libc.mo) [perl] [ 5370.999932] X86_64 open(/usr/share/locale-langpack/en.UTF-8/LC_MESSAGES/libc.mo) [perl] [ 5370.999934] X86_64 open(/usr/share/locale-langpack/en.utf8/LC_MESSAGES/libc.mo) [perl] [ 5370.999936] X86_64 open(/usr/share/locale-langpack/en/LC_MESSAGES/libc.mo) [perl] [ 5371.001308] X86_64 open(/etc/login.defs) [cron] [ 5372.422399] X86_64 open(/home/ilya/.cache/awesome/history) [awesome] [ 5372.424013] X86_64 open(/dev/null) [awesome] [ 5372.424682] I386 open(/etc/ld.so.cache) [skype] [ 5372.424714] I386 open(/usr/lib/i386-linux-gnu/libXv.so.1) [skype] [ 5372.424753] I386 open(/usr/lib/i386-linux-gnu/libXss.so.1) [skype] [ 5372.424789] I386 open(/lib/i386-linux-gnu/librt.so.1) [skype] [ 5372.424827] I386 open(/lib/i386-linux-gnu/libdl.so.2) [skype] [ 5372.424856] I386 open(/usr/lib/i386-linux-gnu/libX11.so.6) [skype] [ 5372.424896] I386 open(/usr/lib/i386-linux-gnu/libXext.so.6) [skype] [ 5372.424929] I386 open(/usr/lib/i386-linux-gnu/libQtDBus.so.4) [skype] [ 5372.424961] I386 open(/usr/lib/i386-linux-gnu/libQtWebKit.so.4) [skype] [ 5372.425003] I386 open(/usr/lib/i386-linux-gnu/libQtXml.so.4) [skype] [ 5372.425035] I386 open(/usr/lib/i386-linux-gnu/libQtGui.so.4) [skype] [ 5372.425072] I386 open(/usr/lib/i386-linux-gnu/libQtNetwork.so.4) [skype] [ 5372.425103] I386 open(/usr/lib/i386-linux-gnu/libQtCore.so.4) [skype] [ 5372.425151] I386 open(/lib/i386-linux-gnu/libpthread.so.0) [skype] [ 5372.425191] I386 open(/usr/lib/i386-linux-gnu/libstdc++.so.6) [skype] [ 5372.425233] I386 open(/lib/i386-linux-gnu/libm.so.6) [skype] [ 5372.425265] I386 open(/lib/i386-linux-gnu/libgcc_s.so.1) [skype] [ 5372.425292] I386 open(/lib/i386-linux-gnu/libc.so.6) [skype] [ 5372.425338] I386 open(/usr/lib/i386-linux-gnu/libxcb.so.1) [skype] [ 5372.425380] I386 open(/lib/i386-linux-gnu/libdbus-1.so.3) [skype] [ 5372.425416] I386 open(/lib/i386-linux-gnu/libz.so.1) [skype] [ 5372.425444] I386 open(/usr/lib/i386-linux-gnu/libXrender.so.1) [skype] [ 5372.425475] I386 open(/usr/lib/i386-linux-gnu/libjpeg.so.8) [skype] [ 5372.425510] I386 open(/lib/i386-linux-gnu/libpng12.so.0) [skype] [ 5372.425546] I386 open(/usr/lib/i386-linux-gnu/libxslt.so.1) [skype] [ 5372.425579] I386 open(/usr/lib/i386-linux-gnu/libxml2.so.2) [skype]
open(2)
.Source: https://habr.com/ru/post/245539/
All Articles