CTF-All-In-One/doc/1.5.9_linux_kernel.md
2018-04-20 21:00:41 +08:00

4.5 KiB
Raw Blame History

1.5.9 Linux 内核

编译安装

我的编译环境是如下。首先安装必要的软件:

$ uname -a
Linux firmy-pc 4.14.34-1-MANJARO #1 SMP PREEMPT Thu Apr 12 17:26:43 UTC 2018 x86_64 GNU/Linux
$ yaourt -S base-devel

为了方便学习,选择一个稳定版本,比如最新的 4.16.3。

$ mkdir ~/kernelbuild && cd ~/kernelbuild
$ wget -c https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.16.3.tar.xz
$ tar -xvJf linux-4.16.3.tar.xz
$ cd linux-4.16.3/
$ make clean && make mrproper

内核的配置选项在 .config 文件中,有两种方法可以设置这些选项,一种是从当前内核中获得一份默认配置:

$ zcat /proc/config.gz > .config
$ make oldconfig

另一种是自己生成一份配置:

$ make localmodconfig   # 使用当前内核配置生成
$ # OR
$ make defconfig        # 根据当前架构默认的配置生成

为了能够对内核进行调试,需要设置下面的参数:

CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_REDUCED=n
CONFIG_GDB_SCRIPTS=y

如果需要使用 kgdb还需要开启下面的参数

CONFIG_STRICT_KERNEL_RWX=n
CONFIG_FRAME_POINTER=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y

CONFIG_STRICT_KERNEL_RWX 会将特定的内核内存空间标记为只读,这将阻止你使用软件断点,最好将它关掉。 如果希望使用 kdb在上面的基础上再加上

CONFIG_KGDB_KDB=y
CONFIG_KDB_KEYBOARD=y

另外如果你在调试时不希望被 KASLR 干扰,可以在编译时关掉它:

CONFIG_RANDOMIZE_BASE=n
CONFIG_RANDOMIZE_MEMORY=n

将上面的参数写到文件 .config-fragment,然后合并进 .config

$ ./scripts/kconfig/merge_config.sh .config .config-fragment

最后因为内核编译默认开启了 -O2 优化,可以修改 Makefile 为 -O0

KBUILD_CFLAGS   += -O0

编译内核:

$ make

完成后当然就是安装,但我们这里并不是真的要将本机的内核换掉,接下来的过程就交给 QEMU 了。参考章节4.1

系统调用

在 Linux 中,系统调用是一些内核空间函数,是用户空间访问内核的唯一手段。这些函数与 CPU 架构有关x86-64 架构提供了 322 个系统调用x86 提供了 358 个系统调用参考附录9.4)。

下面是一个用 32 位汇编写的例子:

.data

msg:
    .ascii "hello 32-bit!\n"
    len = . - msg

.text
    .global _start

_start:
    movl $len, %edx
    movl $msg, %ecx
    movl $1, %ebx
    movl $4, %eax
    int $0x80

    movl $0, %ebx
    movl $1, %eax
    int $0x80

编译执行可以编译成64位程序的

$ gcc -m32 -c hello32.S 
$ ld -m elf_i386 -o hello32 hello32.o 
$ strace ./hello32 
execve("./hello32", ["./hello32"], 0x7ffff990f830 /* 68 vars */) = 0
strace: [ Process PID=19355 runs in 32 bit mode. ]
write(1, "hello 32-bit!\n", 14hello 32-bit!
)         = 14
exit(0)                                 = ?
+++ exited with 0 +++

可以看到程序将调用号保存到 eax,并通过 int $0x80 来使用系统调用。

虽然软中断 int 0x80 非常经典,早期 2.6 及以前版本的内核都使用这种机制进行系统调用。但因其性能较差在往后的内核中使用了快速系统调用指令来替代32 位系统使用 sysenter 指令,而 64 位系统使用 syscall 指令。

下面是一个 64 位的例子:

.data

msg:
    .ascii "Hello 64-bit!\n"
    len = . - msg

.text
    .global _start

_start:
    movq  $1, %rdi
    movq  $msg, %rsi
    movq  $len, %rdx
    movq  $1, %rax
    syscall

    xorq  %rdi, %rdi
    movq  $60, %rax
    syscall

编译执行不能编译成32位程序

$ gcc -c hello64.S                 
$ ld -o hello64 hello64.o 
$ strace ./hello64 
execve("./hello64", ["./hello64"], 0x7ffe11485290 /* 68 vars */) = 0
write(1, "Hello 64-bit!\n", 14Hello 64-bit!
)         = 14
exit(0)                                 = ?
+++ exited with 0 +++

在这两个例子中我们直接使用了 execvewriteexit 三个系统调用。但一般情况下应用程序通过在用户空间实现的应用编程接口API而不是直接通过系统调用来编程。例如函数 printf() 的调用过程是这样的:

调用printf() ==> C库中的printf() ==> C库中的write() ==> write()系统调用

参考资料