Linux x86_64内核终止D状态的进程

时间:2021-05-20 03:15:15
在上一篇文章《 Linux x86内核终止D状态的进程》中,我展示了32位x86系统中如何编码杀死D进程。本文我将展示一种64位x86系统上的方法。
        说实话,64位系统上做这样的事是比较难的,因为你无法通过修改p->thread.ip来到达将进程拽出死循环的目的。要想知道64位系统上到底该怎么把进程执行绪引出,我们得先看看”标准“的做法是什么。

        标准的做法就是fork时的行为,一个新进程刚刚被创建,它第一次进入运行状态之前,并不是通过switch_to切出的,为了让它”看起来像“是被切出而后被切入,就需要ret_from_fork来制造现场。问题是既然无法修改p->thread.ip,那又如何把执行绪引导到ret_from_fork里。

        答案在于,64位(这里特指x86_64)系统是在switch_to中直接通过标志位判断跳转的,其过程如下:

1.在copy_thread中设置TIF_FORK标志

2.在switch_to中判断TIF_FORK标志是否存在,若存在则直接跳转到ret_from_fork

因此ret_from_fork在64位系统中是硬编码到switch_to中的,不像32位系统中那样是可以随意修改的。

        到这里,想通过修改堆栈上保存的PC寄存器来达到跳出循环的这条心也该死了。一个进程被切入,要么循着被切出之前的路径走,要么进入ret_from_fork,只有这两条路。如果循着之前的路,那还是在死循环里面,那么只能给D进程设置TIF_FORK标记,引导它进入ret_from_fork!
        然而我们并不是真的希望它return from fork,而是因为这是不得已的办法,它只能到ret_from_fork里面。接下来怎么办?
        接下来的技术涉及到inline hook,我们希望hook掉ret_from_fork这个entry!具体如何inline hook,本文不讲,不然本文又要很长很长了。本文仅仅给出ret_from_fork被hook后的样子:
ENTRY(ret_from_fork)        DEFAULT_FRAME

LOCK ; btr $TIF_FORK,TI_flags(%r8)

push kernel_eflags(%rip)
CFI_ADJUST_CFA_OFFSET 8
popf # reset kernel eflags
CFI_ADJUST_CFA_OFFSET -8

call schedule_tail # rdi: 'prev' task parameter

GET_THREAD_INFO(%rcx)

testl $_TIF_D, TI_flags(%rcx) # 这里判断是不是有新增的TIF_D标识,如果有,就直接do_exit
jnz do_exit

RESTORE_REST


testl $3, CS-ARGOFFSET(%rsp) # from kernel_thread?
je int_ret_from_sys_call

testl $_TIF_IA32, TI_flags(%rcx) # 32-bit compat task needs IRET
jnz int_ret_from_sys_call

RESTORE_TOP_OF_STACK %rdi, -ARGOFFSET
jmp ret_from_sys_call # go to the SYSRET fastpath

CFI_ENDPROC
END(ret_from_fork)

然后,模块里非常简单的设置TIF_FORK和TIF_D即可:
if (pid > 0) {    for_each_process(p) {        if (task_pid_vnr(p) == pid) {            set_task_state(p, TASK_INTERRUPTIBLE);            // 设置TIF_FORK,目的是执行流导入ret_from_fork            set_tsk_thread_flag(p, TIF_FORK);            // 设置TIF_D,目的是将执行流在hook后的ret_from_fork里进行区分            set_tsk_thread_flag(p, TIF_D);            wake_up_process(p);            break;        }    }}

和32位系统的实验方法一样,D进程将被拉出死循环,然后死掉!

        注意,用kprobe/jprobe技术进行hook事实上就是一种inline hook的应用,然而我们不方便用它来hook ret_from_fork,因为你既不能在ret_from_fork之前执行hook,也不能之后执行hook,而必须在其中间,即调用完schedule_tail之后去执行do_exit,因此,正确的做法是hook别的函数而不是hook ret_from_fork这个函数。仔细观察ret_from_fork的汇编码,就会发现在int_ret_from_sys_call,ret_from_sys_call的pre handler中进行TIF_D的判断并且执行do_exit应该是正确的做法!

        温州皮鞋,下雨进水不会胖!