OS
内核态与用户态
CPU指令集:一部分是内核态指令(可以直接控制硬件),一部分是用户态指令
栈:每个进程都有内核栈和用户栈两个栈
内存:每个进程前3G虚拟内存都是自己的,而所有进程的后1G内存都是一样的,都是kernel的内存空间
控制流转移
用户态到内核态
切换GS段寄存器
swapgs指令,把GS段寄存器和某个特定位置的值进行交换,保存GS值,同时交换过来的值作为内核态的GS
保存用户栈帧信息
把用户态栈顶记录在
CPU独占变量区域里
,再把CPU独占区域里记录内核栈顶放入rsp中保存用户态寄存器信息
把寄存器push到栈上形成一个pt_regs结构体
通过汇编指令判断是否为32位,将控制权交给内核
从内核态回到用户态时,依旧用swapgs指令切换GS寄存器,通过syretq或iretq指令让CPU模式回到ring 3,恢复用户态的状态
中断
CPU停下工作,执行中断处理程序。
实模式下,使用中断向量表(interrupt vector table)存放每个中断号对应的处理程序。保护模式引入中断描述符表IDT(Interrupt Descriptor Table)存放门描述符(gate descriptor),而这个IDT的地址存放在IDTR寄存器中。
门是什么
gate会在中断前进行检查
- 中断门(Interrupt Gate)处理硬中断,进入中断门后IF标志位会被清除,防止中断嵌套。并且中断门只能在内核态下访问(Descriptor Priviledge Level = 0)
- 陷阱门(Trap Gate)处理CPU异常,但不会清空IF
- 系统门(System Gate)系统调用(Descriptor Priviledge Level = 3)主要用于系统调用
系统调用
32位的系统调用参数是ebx,ecx,edx,esi,edi,ebp
64位还是和普通函数一样
32位的时候,通过int 0x80触发一个软中断进入kernel mode,但是到了64位的时候有了syscall和sysenter指令,内核启动时会将系统调用函数入口(entry_SYSCALL_64)写入MSR寄存器组中,sycall时进入ring0并跳到MSR寄存器所指定的系统调用入口
因此syscall性能是比int0x80高的
信号
首先,一个进程收到信号,信号会被存储到进程描述符的信号队列中
当进程被重新调度时,内核检查其信号队列,处理信号。内核会将用户态寄存器push到用户态栈上,形成sigcontext结构体,然后push SIGNALINFO以及指向sigreturn的代码。这些内容构成了SigreturnFrame(SROP伪造的就是这个结构体)
接下来控制被返回到用户态进程,跳转到对应的signal handler函数,完成之后会执行sigreturn(也是一个系统调用)
又进入了内核态,恢复原有的用户态上下文信息
控制权返还用户态
虽然信号不是及时处理的,但由于linux进程调度非常频繁,信号能很快被处理
进程权限
在linux kernel源码中,include/linux/sched.h
中定义了taskt_struct结构体来描述进程,结构体中三个Process credentials描述了进程的权限
ptracer_cred
使用ptrace系统调用跟踪该进程的上级进程的cred(gdb的原理)如果提前占用这个位置就可以反调试real_cred
客体凭证(objective cred)通常是一个进程启动时具有的权限cred
主体凭证(subjective cred)该进程的有效cred,kernel以此作为进程权限的凭证
这三个都是一个cred结构体的指针,在cred结构体中
1 |
|
copy过来的,但不是很理解(
1 |
|
进程权限改变
kernel/cred.c中
cred* prepare_kernel_cred(str uct task_struct* daemon)
可以拷贝一个进程的cred进程的结构体,返回一个新的结构体
int commit_creds(struct cred *new)
将一个新的cred结构体应用到进程