diff --git a/doc/1.5.9_linux_kernel.md b/doc/1.5.9_linux_kernel.md index f50c16d..5a5c507 100644 --- a/doc/1.5.9_linux_kernel.md +++ b/doc/1.5.9_linux_kernel.md @@ -1 +1,159 @@ # 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 +++ +``` + +在这两个例子中我们直接使用了 `execve`、`write` 和 `exit` 三个系统调用。但一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。例如函数 `printf()` 的调用过程是这样的: +``` +调用printf() ==> C库中的printf() ==> C库中的write() ==> write()系统调用 +``` + + +## 参考资料 +- [The Linux Kernel documentation](https://www.kernel.org/doc/html/latest/) +- [linux-insides](https://legacy.gitbook.com/book/0xax/linux-insides/details) diff --git a/doc/2.1.1_qemu.md b/doc/2.1.1_qemu.md index 649aa58..99b5d46 100644 --- a/doc/2.1.1_qemu.md +++ b/doc/2.1.1_qemu.md @@ -4,6 +4,7 @@ - [安装](#安装) - [参考资料](#参考资料) + ## 简介 QEMU 是一个广泛使用的开源计算机仿真器和虚拟机。当作为仿真器时,可以在一种架构(如PC机)下运行另一种架构(如ARM)下的操作系统和程序,当作为虚拟机时,可以使用 Xen 或 KVM 访问 CPU 的扩展功能(HVM),在主机 CPU 上直接执行虚拟客户端的代码。 diff --git a/doc/4.15_vsyscall_vdso.md b/doc/4.15_vsyscall_vdso.md index 6b1916f..8c9574c 100644 --- a/doc/4.15_vsyscall_vdso.md +++ b/doc/4.15_vsyscall_vdso.md @@ -1,10 +1,15 @@ # 4.15 利用 vsyscall 和 vDSO - +- [vsyscall](#vsyscall) +- [vDSO](#vdso) - [CTF 实例](#ctf-实例) - [参考资料](#参考资料) +## vsyscall + +## vDSO + ## CTF 实例 例如章节 6.1.6 的 ret2vdso。 diff --git a/doc/4.1_linux_kernel_debug.md b/doc/4.1_linux_kernel_debug.md index 59903be..4542bb7 100644 --- a/doc/4.1_linux_kernel_debug.md +++ b/doc/4.1_linux_kernel_debug.md @@ -1,6 +1,10 @@ # 4.1 Linux 内核调试 - [准备工作](#准备工作) +- [printk](#printk) +- [QEMU + gdb](#qemu-gdb) +- [kdb](#kdb) +- [参考资料](#参考资料) ## 准备工作 @@ -102,3 +106,208 @@ block crypto Documentation fs Kbuild MAINTAINERS README sna certs debian drivers include Kconfig Makefile samples sound usr COPYING debian.hwe dropped.txt init kernel mm scripts spl ``` + + +## printk +在用户态程序中,我们常常使用 `printf()` 来打印信息,方便调试,在内核中也可以这样做。内核(v4.16.3)使用函数 `printk()` 来输出信息,在 `include/linux/kern_levels.h` 中定义了 8 个级别: +```c +#define KERN_EMERG KERN_SOH "0" /* system is unusable */ +#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */ +#define KERN_CRIT KERN_SOH "2" /* critical conditions */ +#define KERN_ERR KERN_SOH "3" /* error conditions */ +#define KERN_WARNING KERN_SOH "4" /* warning conditions */ +#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */ +#define KERN_INFO KERN_SOH "6" /* informational */ +#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */ +``` +用法是: +```c +printk(KERN_EMERG "hello world!\n"); // 中间没有逗号 +``` + +而当前控制台的日志级别如下所示: +``` +$ cat /proc/sys/kernel/printk +4 4 1 4 +``` +这 4 个数值在文件定义及默认值在如下所示: +```c +// kernel/printk/printk.c + +int console_printk[4] = { + CONSOLE_LOGLEVEL_DEFAULT, /* console_loglevel */ + MESSAGE_LOGLEVEL_DEFAULT, /* default_message_loglevel */ + CONSOLE_LOGLEVEL_MIN, /* minimum_console_loglevel */ + CONSOLE_LOGLEVEL_DEFAULT, /* default_console_loglevel */ +}; + + +// include/linux/printk.h + +/* printk's without a loglevel use this.. */ +#define MESSAGE_LOGLEVEL_DEFAULT CONFIG_MESSAGE_LOGLEVEL_DEFAULT + +/* We show everything that is MORE important than this.. */ +#define CONSOLE_LOGLEVEL_MIN 1 /* Minimum loglevel we let people use */ + +/* + * Default used to be hard-coded at 7, we're now allowing it to be set from + * kernel config. + */ +#define CONSOLE_LOGLEVEL_DEFAULT CONFIG_CONSOLE_LOGLEVEL_DEFAULT + +#define console_loglevel (console_printk[0]) +#define default_message_loglevel (console_printk[1]) +#define minimum_console_loglevel (console_printk[2]) +#define default_console_loglevel (console_printk[3]) +``` +虽然这些数值控制了当前控制台的日志级别,但使用虚拟文件 `/proc/kmsg` 或者命令 `dmesg` 总是可以查看所有的信息。 + + +## QEMU + gdb +QEMU 是一款开源的虚拟机软件,可以使用它模拟出一个完整的操作系统(参考章节2.1.1)。这里我们介绍怎样使用 QEMU 和 gdb 进行内核调试,关于 Linux 内核的编译可以参考章节 1.5.9。 + +接下来我们需要借助 BusyBox 来创建用户空间: +``` +$ wget -c http://busybox.net/downloads/busybox-1.28.3.tar.bz2 +$ tar -xvjf busybox-1.28.3.tar.bz2 +$ cd busybox-1.28.3/ +``` +生成默认配置文件并修改 `CONFIG_STATIC=y` 让它生成的是一个静态链接的 BusyBox: +``` +$ make defconfig +$ cat .config | grep "CONFIG_STATIC" +CONFIG_STATIC=y +``` +编译安装后会出现在 `_install` 目录下: +``` +$ make +$ sudo make install +$ ls _install +bin linuxrc sbin usr +``` +接下来创建 initramfs 的目录结构: +``` +$ mkdir initramfs +$ cd initramfs +$ cp ../_install/* -rf ./ +$ mkdir dev proc sys +$ sudo cp -a /dev/null /dev/console /dev/tty /dev/tty2 /dev/tty3 /dev/tty4 dev/ +$ rm linuxrc +$ vim init # 创建启动脚本 +$ cat init +#!/bin/busybox sh +mount -t proc none /proc +mount -t sysfs none /sys + +exec /sbin/init +``` +最后把它们打包: +``` +$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz +``` +这样 initramfs 根文件系统就做好了,其中包含了必要的设备驱动和工具,boot loader 会加载 initramfs 到内存,然后内核将其挂载到根目录 `/`,并运行 `init` 脚本,挂载真正的磁盘根文件系统。 + +QEMU 启动! +``` +$ qemu-system-x86_64 -s -S -kernel ~/kernelbuild/linux-4.16.3/arch/x86_64/boot/bzImage -initrd ~/kernelbuild/busybox-1.28.3/initramfs.cpio.gz -nographic -append "console=ttyS0" +``` +- `-s`:`-gdb tcp::1234` 的缩写,QEMU 监听在 TCP 端口 1234,等待 gdb 的连接。 +- `-S`:在启动时冻结 CPU,等待 gdb 输入 c 时继续执行。 +- `-kernel`:指定内核。 +- `-initrd`:指定 initramfs。 +- `nographic`:禁用图形输出并将串行 I/O 重定向到控制台。 +- `-append "console=ttyS0`:所有内核输出到 ttyS0 串行控制台,并打印到终端。 + +在另一个终端里使用打开 gdb,然后尝试在函数 `cmdline_proc_show()` 处下断点: +``` +$ gdb -ex "target remote localhost:1234" ~/kernelbuild/linux-4.16.3/vmlinux +(gdb) b cmdline_proc_show +Breakpoint 1 at 0xffffffff8121ad70: file fs/proc/cmdline.c, line 9. +(gdb) c +Continuing. + +Breakpoint 1, cmdline_proc_show (m=0xffff880006701b00, v=0x1 ) at fs/proc/cmdline.c:9 +9 seq_printf(m, "%s\n", saved_command_line); +``` +可以看到,当我们在内核里执行 `cat /proc/cmdline` 时就被断下来了。 +``` +/ # id +uid=0 gid=0 +/ # echo hello kernel! +hello kernel! +/ # cat /proc/cmdline +console=ttyS0 +``` + +现在我们已经可以对内核代码进行单步调试了。对于内核模块,我们同样可以进行调试,但模块是动态加载的,gdb 不会知道这些模块被加载到哪里,所以需要使用 `add-symbol-file` 命令来告诉它。 + +来看一个 helloworld 的例子: +```c +#include +#include +#include + +static int hello_init(void) +{ + printk(KERN_ALERT "Hello module!\n"); + return 0; +} + +static void hello_exit(void) +{ + printk(KERN_ALERT "Goodbye module!\n"); +} + +module_init(hello_init); +module_exit(hello_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("A simple module."); +``` +Makefile 如下: +```makefile +BUILDPATH := ~/kernelbuild/linux-4.16.3/ +obj-m += hello.o + +all: + make -C $(BUILDPATH) M=$(PWD) modules + +clean: + make -C $(BUILDPATH) M=$(PWD) clean +``` +编译模块并将 `.ko` 文件复制到 initramfs,然后重新打包: +``` +$ make && cp hello.ko ~/kernelbuild/busybox-1.28.3/initramfs +$ cd ~/kernelbuild/busybox-1.28.3/initramfs +$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz +``` + +最后重新启动 QEMU 即可: +``` +/ # insmod hello.ko +[ 7.887392] hello: loading out-of-tree module taints kernel. +[ 7.892630] Hello module! +/ # lsmod +hello 16384 0 - Live 0xffffffffa0000000 (O) +/ # rmmod hello.ko +[ 24.523830] Goodbye module! +``` +三个命令分别用于载入、列出和卸载模块。 + +再回到 gdb 中,`add-symbol-file` 添加模块的 `.text`、`.data` 和 `.bss` 段的地址,这些地址在类似 `/sys/kernel//sections` 位置: +``` +/ # cat /sys/module/hello/sections/.text +0x00000000fa16acc0 +``` +在这个例子中,只有 .text 段: +``` +(gdb) add-symbol-file ~/kernelbuild/busybox-1.28.3/initramfs/hello.ko 0x00000000fa16acc0 +``` +然后就可以对该模块进行调试了。 + + +## kdb + +## 参考资料 +- [KernelDebuggingTricks](https://wiki.ubuntu.com/Kernel/KernelDebuggingTricks) diff --git a/doc/9.1_Linuxtools.md b/doc/9.1_Linuxtools.md index bdc9f35..094f74a 100644 --- a/doc/9.1_Linuxtools.md +++ b/doc/9.1_Linuxtools.md @@ -1,6 +1,7 @@ # 9.1 更多 Linux 工具 - [dd](#dd) +- [dmesg](#dmesg) - [file](#file) - [edb](#edb) - [foremost](#foremost) @@ -48,6 +49,20 @@ dump 运行时的内存镜像: - `dd if=/proc//mem of=/path/a.out skip=xxxx bs= 1 count=xxxx` +## dmesg +**dmesg** 命令用于显示 Linux 内核环形缓冲区(ring buffer)的信息。开机信息和各种错误信息都会放到里面。在调试和故障诊断中非常有用。 + +#### 重要参数 +``` +-c, --read-clear + Clear the ring buffer after first printing its contents. +-s, --buffer-size size + Use a buffer of size to query the kernel ring buffer. This is 16392 by default. +-n, --console-level level + Set the level at which printing of messages is done to the console. +``` + + ## file **file** 命令用来探测给定文件的类型。 diff --git a/doc/9.4_linux_syscall.md b/doc/9.4_linux_syscall.md index 574a324..04c7b1f 100644 --- a/doc/9.4_linux_syscall.md +++ b/doc/9.4_linux_syscall.md @@ -1,3 +1,5 @@ # 9.4 Linux x86-64 系统调用表 -http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ +- [Linux System Call Table for x86 64](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/) +- [syscall_32](https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_32.tbl) +- [syscall_64](https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl) diff --git a/src/Others/1.5.9_linux_kernel/config-fragment b/src/Others/1.5.9_linux_kernel/config-fragment new file mode 100644 index 0000000..b3c1aaa --- /dev/null +++ b/src/Others/1.5.9_linux_kernel/config-fragment @@ -0,0 +1,14 @@ +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_REDUCED=n +CONFIG_GDB_SCRIPTS=y + +CONFIG_STRICT_KERNEL_RWX=n +CONFIG_FRAME_POINTER=y +CONFIG_KGDB=y +CONFIG_KGDB_SERIAL_CONSOLE=y + +CONFIG_KGDB_KDB=y +CONFIG_KDB_KEYBOARD=y + +CONFIG_RANDOMIZE_BASE=n +CONFIG_RANDOMIZE_MEMORY=n diff --git a/src/Others/1.5.9_linux_kernel/hello32.S b/src/Others/1.5.9_linux_kernel/hello32.S new file mode 100644 index 0000000..9e4bfb4 --- /dev/null +++ b/src/Others/1.5.9_linux_kernel/hello32.S @@ -0,0 +1,19 @@ +.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 diff --git a/src/Others/1.5.9_linux_kernel/hello64.S b/src/Others/1.5.9_linux_kernel/hello64.S new file mode 100644 index 0000000..caf47aa --- /dev/null +++ b/src/Others/1.5.9_linux_kernel/hello64.S @@ -0,0 +1,19 @@ +.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 diff --git a/src/Others/4.1_linux_kernel_debug/Makefile b/src/Others/4.1_linux_kernel_debug/Makefile new file mode 100644 index 0000000..5683a91 --- /dev/null +++ b/src/Others/4.1_linux_kernel_debug/Makefile @@ -0,0 +1,8 @@ +BUILDPATH := ~/kernelbuild/linux-4.16.3/ +obj-m += hello.o + +all: + make -C $(BUILDPATH) M=$(PWD) modules + +clean: + make -C $(BUILDPATH) M=$(PWD) clean diff --git a/src/Others/4.1_linux_kernel_debug/create_initramfs.sh b/src/Others/4.1_linux_kernel_debug/create_initramfs.sh new file mode 100644 index 0000000..30c7690 --- /dev/null +++ b/src/Others/4.1_linux_kernel_debug/create_initramfs.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd ./initramfs/ +find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz diff --git a/src/Others/4.1_linux_kernel_debug/hello.c b/src/Others/4.1_linux_kernel_debug/hello.c new file mode 100644 index 0000000..8438a66 --- /dev/null +++ b/src/Others/4.1_linux_kernel_debug/hello.c @@ -0,0 +1,20 @@ +#include +#include +#include + +static int hello_init(void) +{ + printk(KERN_ALERT "Hello module!\n"); + return 0; +} + +static void hello_exit(void) +{ + printk(KERN_ALERT "Goodbye module!\n"); +} + +module_init(hello_init); +module_exit(hello_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("A simple module.");