聚合国内IT技术精华文章,分享IT技术精华,帮助IT从业人士成长

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

2016-11-08 17:40 浏览: 1765843 次 我要评论(0 条) 字号:

在上一篇文章《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应该是正确的做法!

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


作者:dog250 发表于2016/11/7 21:36:32 原文链接
阅读:68 评论:1 查看评论


网友评论已有0条评论, 我也要评论

发表评论

*

* (保密)

Ctrl+Enter 快捷回复