中断是整个计算机体系最核心的功能之一,关于中断硬件原理可以参考文章末尾的链接1(https://www.cnblogs.com/theseventhson/p/13068709.html),这里不再赘述;中断常见的种类如下:
1、(1)本人刚开始学习的时候,看到很多资料把中断、异常、陷阱放在一起介绍,很容易混淆这3个概念,这里详细列举一下各个概念之间的关系如下:
(2)上述所有的操作,统称为中断!再说直白一点,中断的本质就是被打断!举个例子:cpu正在执行A进程的代码,突然用户敲了一下键盘,或者移动了鼠标,这时候就要马上接受用户的输入,然后采取相应的措施处理用户的输入;
2、操作系统关于中断的开发,最核心的部分就是填充IDT了,本质就是先写好不同中断号的handler,再把handler函数的入口地址填写到正确的IDT表项(当然格式要符合中断描述符的要求)!接下来看看linux 4.9版本是怎样一步一步填充IDT和使用中断的!
(1)填充IDT,也就是中断初始化,在arch\x86\kernel\traps.c种的trap_init函数中:
void __init trap_init(void) { int i; #ifdef CONFIG_EISA void __iomem *p = early_ioremap(0x0FFFD9, 4); if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24)) EISA_bus = 1; early_iounmap(p, 4); #endif set_intr_gate(X86_TRAP_DE, divide_error); set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK); /* int4 can be called from all */ set_system_intr_gate(X86_TRAP_OF, &overflow); set_intr_gate(X86_TRAP_BR, bounds); set_intr_gate(X86_TRAP_UD, invalid_op); set_intr_gate(X86_TRAP_NM, device_not_available); #ifdef CONFIG_X86_32 set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS); #else set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK); #endif set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun); set_intr_gate(X86_TRAP_TS, invalid_TSS); set_intr_gate(X86_TRAP_NP, segment_not_present); set_intr_gate(X86_TRAP_SS, stack_segment); set_intr_gate(X86_TRAP_GP, general_protection); set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug); set_intr_gate(X86_TRAP_MF, coprocessor_error); set_intr_gate(X86_TRAP_AC, alignment_check); #ifdef CONFIG_X86_MCE set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK); #endif set_intr_gate(X86_TRAP_XF, simd_coprocessor_error); /* Reserve all the builtin and the syscall vector: 将前32个中断号都设置为已使用状态 */ for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++) set_bit(i, used_vectors); //设置0x80系统调用的系统中断门 #ifdef CONFIG_IA32_EMULATION set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_compat); set_bit(IA32_SYSCALL_VECTOR, used_vectors); #endif #ifdef CONFIG_X86_32 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32); set_bit(IA32_SYSCALL_VECTOR, used_vectors); #endif /* * Set the IDT descriptor to a fixed read-only location, so that the * "sidt" instruction will not leak the location of the kernel, and * to defend the IDT against arbitrary memory write vulnerabilities. * It will be reloaded in cpu_init() */ __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO); idt_descr.address = fix_to_virt(FIX_RO_IDT); /* * Should be a barrier for any external CPU state: 执行CPU的初始化,对于中断而言,在 cpu_init() 中主要是将 idt_descr 放入idtr寄存器中 */ cpu_init(); /* * X86_TRAP_DB and X86_TRAP_BP have been set * in early_trap_init(). However, ITS works only after * cpu_init() loads TSS. See comments in early_trap_init(). */ set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK); /* int3 can be called from all */ set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK); x86_init.irqs.trap_init(); #ifdef CONFIG_X86_64 memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16); set_nmi_gate(X86_TRAP_DB, &debug); set_nmi_gate(X86_TRAP_BP, &int3); #endif }
used_vectors变量是一个bitmap,它用于记录中断向量表中哪些中断已经被系统注册和使用,哪些未被注册使用;
(2)trap_init()已经完成了异常和陷阱的初始化。对于linux而言,中断号0~19是专门用于陷阱和故障使用的,20~31一般是intel用于保留的;而外部IRQ线使用的中断为32~255(代码中32号中断被用作汇编指令异常中断)。所以,在trap_init()代码中,专门对0~19号中断的门描述符进行了初始化,最后将新的中断向量表起始地址放入idtr寄存器中;相应的handler定义和实现在arch\x86\kernel\traps.c中,举个大家都熟悉的int 3为例,实现如下:
/* May run on IST stack. */ dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code) { #ifdef CONFIG_DYNAMIC_FTRACE /* * ftrace must be first, everything else may cause a recursive crash. * See note by declaration of modifying_ftrace_code in ftrace.c */ if (unlikely(atomic_read(&modifying_ftrace_code)) && ftrace_int3_handler(regs)) return; #endif if (poke_int3_handler(regs)) return; ist_enter(regs); RCU_LOCKDEP_WARN(!rcu_is_watching(), "entry code didn't wake RCU"); #ifdef CONFIG_KGDB_LOW_LEVEL_TRAP if (kgdb_ll_trap(DIE_INT3, "int3", regs, error_code, X86_TRAP_BP, SIGTRAP) == NOTIFY_STOP) goto exit; #endif /* CONFIG_KGDB_LOW_LEVEL_TRAP */ #ifdef CONFIG_KPROBES if (kprobe_int3_handler(regs)) goto exit; #endif if (notify_die(DIE_INT3, "int3", regs, error_code, X86_TRAP_BP, SIGTRAP) == NOTIFY_STOP) goto exit; /* * Let others (NMI) know that the debug stack is in use * as we may switch to the interrupt stack. */ debug_stack_usage_inc(); preempt_disable(); cond_local_irq_enable(regs); do_trap(X86_TRAP_BP, SIGTRAP, "int3", regs, error_code, NULL);//核心代码 cond_local_irq_disable(regs); preempt_enable_no_resched(); debug_stack_usage_dec(); exit: ist_exit(regs); }
(3)部分中断比如网卡接受到数据后,通过中断通知cpu来读取;如果数据量很大,cpu读取和处理数据的时候一直关闭中断,可能导致其他中断被延迟甚至忽略(大家肯定都遇到过电脑“卡死”的情况:敲击键盘、移动鼠标都没反应,很有可能是cpu还在处理旧中断,来不及响应新的中断);为了在处理上一个中断的同时避免耽误下一个中断,linux把中断分成了上中断和下中断两部分(类似windows的DPC机制)。上部分代码优先级高,但是代码量较少,耗时不多;下半段执行优先级低但是耗时的代码;上半段执行时依然关闭中断,下半段就可以开中断了;此过程称之为softtirq,图示如下:
arch\x86\entry\entry_64.S中的调用代码:
/* Call softirq on interrupt stack. Interrupts are off. */ ENTRY(do_softirq_own_stack) pushq %rbp mov %rsp, %rbp incl PER_CPU_VAR(irq_count) cmove PER_CPU_VAR(irq_stack_ptr), %rsp push %rbp /* frame pointer backlink */ call __do_softirq leaveq decl PER_CPU_VAR(irq_count) ret END(do_softirq_own_stack)
3、系统调用的核心意义:
4、 系统调用在逆向/安全防护的应用:以字节跳动HIDS为例
早期在windows下无论是杀毒软件,还是逆向破解的程序,都喜欢hook SSDT来监控3环的app在干啥,比如hook openProcess就知道3环有没有app在调试自己;在linux平台上原理类似,也可以通过hook 系统调用来做防护,拿字节跳动的HIDS举例(末尾参考5、6两个链接):
(1)mprotect 函数挂钩:函数本是用来设置物理内存页的rwx属性的,利用这个功能可以用来调试和反调试
(2)open函数挂钩:函数本来是用来打开文件、获取文件句柄的,利用这个可以用来:
(3)prctl函数挂钩:函数原本是用来设置进程属性的,利用这个可以用来:
(4)ptrace函数挂钩:这可能是逆向最有用的系统调用了,frida底层貌似就用了这个函数;HIDS hook这个函数记录了关键信息有:
POKETEXT
/POKEDATA
这样就很容易检测自己的进程是不是正在被调试了!
还有很多重要的系统调用如execve、init_module等都被hook了,这里不再赘述!
参考:
1、https://www.cnblogs.com/theseventhson/p/13068709.html 实模式中断原理
2、https://www.cnblogs.com/jiading/p/12606978.html linux中断和系统调用解析
3、https://www.cnblogs.com/LittleHann/p/4111692.html?utm_source=tuicool&utm_medium=referral Linux Systemcall Int0x80方式、Sysenter/Sysexit Difference Comparation
4、https://bbs.pediy.com/thread-226254.htm syscall/sysenter具体过程
5、https://mp.weixin.qq.com/s/rm_hXHb_YBWQqmifgAqfaw 最后防线:字节跳动HIDS分析
6、https://github.com/EBWi11/AgentSmith-HIDS https://github.com/bytedance/Elkeid/blob/main/README-zh_CN.md
7、https://blog.csdn.net/hunter___/article/details/83063131 prctl()函数详解
8、https://www.jianshu.com/p/b1f9d6911c90 ptrace使用介绍
9、https://www.cnblogs.com/tolimit/p/4415348.html linux中断源码分析-初始化