has_error_code被设置
.if \has_error_code
    movq    ORIG_RAX(%rsp), %rsi
    movq    $-1, ORIG_RAX(%rsp)
.else
    xorl    %esi, %esi
.endif

作用是将错误代码传递给RSI寄存器,这将作为将是异常处理程序的第二个参数,在那之后将其设置-1以防止再次启动系统调用,另外,如果异常不提供错误代码,将会清空ESI寄存器。

收尾逻辑分析

最后一定会执行的逻辑是:

.if \paranoid == 0
    testb   $3, CS(%rsp)
    jz  .Lfrom_kernel_no_context_tracking_\@
    CALL_enter_from_user_mode
.Lfrom_kernel_no_context_tracking_\@:
.endif

movq    %rsp, %rdi          /* pt_regs pointer */

首先再次检查CPL以确保异常来自用户控件,然后将pt_regs(存储了保存的”现场”)赋值给RDI,这将作为中断服务程序的第一个参数,最后调用辅助异常处理程序

call  \do_sym

若是debug异常,则调用:

dotraplinkage void do_debug(struct pt_regs *regs, long error_code);

若是int3异常,则调用:

dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code);

当内核空间中发生异常时

当内核空间中发生异常且paranoid > 0时,内核将进入paranoid_entry进行处理

paranoid_entry处理分析

paranoid_entry的处理逻辑在/source/arch/x86/entry/entry_64.S#L1218处实现:

/*
 * Save all registers in pt_regs, and switch gs if needed.
 * Use slow, but surefire "are we in kernel?" check.
 * Return: ebx=0: need swapgs on exit, ebx=1: otherwise
 */
SYM_CODE_START_LOCAL(paranoid_entry)
    UNWIND_HINT_FUNC
    cld
    PUSH_AND_CLEAR_REGS save_ret=1
    ENCODE_FRAME_POINTER 8
    movl    $1, %ebx
    movl    $MSR_GS_BASE, %ecx
    rdmsr
    testl   %edx, %edx
    js  1f              /* negative -> in kernel */
    SWAPGS
    xorl    %ebx, %ebx

1:
    /*
     * Always stash CR3 in %r14.  This value will be restored,
     * verbatim, at exit.  Needed if paranoid_entry interrupted
     * another entry that already switched to the user CR3 value
     * but has not yet returned to userspace.
     *
     * This is also why CS (stashed in the "iret frame" by the
     * hardware at entry) can not be used: this may be a return
     * to kernel code, but with a user CR3 value.
     */
    SAVE_AND_SWITCH_TO_KERNEL_CR3 scratch_reg=%rax save_reg=%r14

    /*
     * The above SAVE_AND_SWITCH_TO_KERNEL_CR3 macro doesn't do an
     * unconditional CR3 write, even in the PTI case.  So do an lfence
     * to prevent GS speculation, regardless of whether PTI is enabled.
     */
    FENCE_SWAPGS_KERNEL_ENTRY

    ret
SYM_CODE_END(paranoid_entry)

正如之前所说明的那样,这个入口将会以较慢的方式来获取有关被中断任务的先前状态以检查异常是否真的来自内核空间,可以看到我们首先执行的操作和error_entry逻辑相同,首先保存现场,然后使用较慢的方式检查异常的来源,随即返回到上级函数。

0x04 一个简单内核模块的编写

事实上,本篇文章的内容到0x03就已经结束了,我们将在下一篇文章介绍具体的中断服务函数的实现。

但是在这里我想添加一点内容,就是如何去编译一个简易的内核模块并运行。

编译Linux Kernel

这个部分已经在Kernel Pwn 学习之路(一)给予了说明故此处不再赘述

这里需要注意一点,因为我们想要在使用QEMU启动时使其支持9p协议,因此我们需要需要修改.config文件,需要将文件里的

CONFIG_NET_9P=m
CONFIG_NET_9P_VIRTIO=m
CONFIG_NET_9P_XEN=m
CONFIG_NET_9P_RDMA=m
# CONFIG_NET_9P_DEBUG is not set
......
CONFIG_9P_FS=m
CONFIG_9P_FSCACHE=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y

替换为

CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_NET_9P_XEN=m
CONFIG_NET_9P_RDMA=m
CONFIG_NET_9P_DEBUG=y (Optional)
......
CONFIG_9P_FS=y
CONFIG_9P_FSCACHE=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y

⚠️:如果执行make编译后无法在/arch/x86/boot中找到bzImage,请尝试执行make -jx bzImage(x是你期望使用的核数)直至看到以下提示:

image-20200417214309083

构建文件系统

首先找一个已经构建好的文件系统解包(可以直接利用Busybox生成),重点是binsbinusr这三个文件夹以及根目录下的linuxrc文件,其他文件夹均可暂时置空,然后在/etc下建立passwd文件以建立用户,内容如下:

root:x:0:0:root:/root:/bin/sh
error404:x:1000:1000:error404:/home/error404:/bin/sh
分类: CTF

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注