Linux Kernel Build
To learn kernel, we should know how to compile it and how it works.
Source code can be found online and all history versions are on there to be downloaded. You can also view source code online on bootlin
Just Compile
First install some essential tools:
1 | $ sudo apt-get update |
Then you can build the kernel. If the kernel is too old, some dependencies will not be satisfied.
1 | $ make menuconfig |
After that we can get bzImage
form linux-x.x.x/arch/x86/boot/bzImage
, and get vmlinux
from linux-x.x.x
root directory.
As for differences between vmLinux
, vmlinuz
, vmlinux.bin
, zImage
& bzImage
…
vmlinux
This is the Linux kernel in an statically linked executable file format. Generally, you don’t have to worry about this file, it’s just a intermediate step in the boot procedure.
The raw vmlinux file may be useful for debugging purposes.vmlinux.bin
The same asvmlinux
, but in a bootable raw binary file format. All symbols and relocation information is discarded. Generated fromvmlinux
by objcopy -O binaryvmlinux
vmlinux.bin
.vmlinuz
Thevmlinux
file usually gets compressed withzlib
. Since 2.6.30 LZMA andbzip2
are also available. By adding further boot and decompression capabilities tovmlinuz
, the image can be used to boot a system with thevmlinux
kernel. The compression ofvmlinux
can occur withzImage
orbzImage
.
The functiondecompress_kernel()
handles the decompression ofvmlinuz
at bootup, a message indicates this:1
2Decompressing Linux... done
Booting the kernel.zImage
(make zImage)
This is the old format for small kernels (compressed, below 512KB). At boot, this image gets loaded low in memory (the first 640KB of the RAM).bzImage
(make bzImage)
The big zImage (this has nothing to do with bzip2), was created while the kernel grew and handles bigger images (compressed, over 512KB). The image gets loaded high in memory (above 1MB RAM). As today’s kernels are way over 512KB, this is usually the preferred way.
More in Here
Busybox and QEMU
Actually to run the customized (built) kernel, we need QEMU
, while QEMU
needs a filesystem image to run, so we first build one image using busybox
.
Download source on the official site, and comiple it with setting Build static binary (no shared libs)
(also check (./_install) Destination path for 'make install'
for not installing to /usr
)
1 | $ make menuconfig |
If you want to cross compile like using arm-linux-gcc
, edit Makefile
1 | ARCH ?= $(SUBARCH) |
After that things are generated in _install
directory, do some extra operations
1 | $ cd _install |
And in etc/init.d/rcS
1 |
|
Finally we build the file system
1 | $ find . | cpio -o --format=newc > ../rootfs.img |
Then run it by QEMU
1 | $ qemu-system-x86_64 \ |
So now you run your os on your own kernel, but all scripts and commands above are just samples, there are more config modifications later.
Add Customized Functions
For example, if we want to implement a personal syscall
, First we need to put things in a directory:
1 | # in root directory of linux kernel |
Then edit the Makefile
(1 line) to include target directory
1 | core-y += x/ xx/ xxx/ ... helloworld/ |
Edit include/linux/syscalls.h
to add function prototype to the tail
1 | asmlinkage long sys_helloworld(void); |
Add customized syscall
number
1 | // arch/x86/entry/syscalls/syscall_32.tbl, for i386 |
Then re-compile the kernel, and write a demo to test:
1 | // compiled: gcc helloworld.c -o helloworld |
Test:
1 | $ id |
Compile Driver
As for driver module, it is apart from linux kernel compilation.
First we also need a driver program written in C
like hello_lkm.c
:
1 |
|
And its Makefile specifies module name, linux kernel path, and intermediate object name:
1 | obj-m := hello_lkm.o |
We put them in one directory, and make
to get one hello_lkm.ko
under the current dir.
To install this module, we can pack it into filesystem image, and add commands in init
to install it:
1 | insmod ./hello_lkm.ko |
Actually this driver reacts when you just when you initialize and exit it, so we can see the results when:
1 | $ insmod hello_lkm.ko |
In gdb
we can load it and set breakpoints to debug step by step:
1 | pwndbg> add-symbol-file /path/to/linux.x.x.x/hello_lkm/hello_lkm.ko 0xffffffffc030b000 |
Linux Kernel Exploit
Normally the pwn challenges give us xxx.ko
, bzImage
, initramfs.img
, and start.sh
xxx.ko
is the module with bugs, can be analyzed by IDAbzImage
is the packed kernel, and we can use extract-vmlinux to extractvmlinux
from it.vmlinux
can be loaded intogdb
to debug.
After that we useobjdump -d vmlinux > gadget
to save gadgets andCtrl+f
to search, or we can useropper
andROPgadget
.initramfs.img
is the filesystem image.
When building image usingbusybox
, wehowever1
2
3
4# produce `rootfs.img`
$ find . | cpio -o --format=newc > ../rootfs.img
# extract image
$ cpio -idmv <rootfs.imginitramfs.img
is always compressed, so we1
2
3
4
5
6
7
8# make initramfs.img
$ find . | cpio -o -H newc | gzip > /boot/initramfs.img.gz
$ mv initramfs.img.gz initramfs.img
# and extract
$ file initramfs.img
$ mv initramfs.img initramfs.img.gz # or 'initramfs.img.xz' according to file type
$ gunzip initramfs.img.gz # or 'xz -d initramfs.img.xz'
$ cpio -idmv < ./initramfs.imgstart.sh
is the script to runQEMU
system mode.
Security Mechanism
Some challenges run on environment with security mechanism (reference), so we normally disable them to obtain more debug info.
Like in init
in extracted initramfs.img
, we can add commands to remove kptr_restrict
, dmesg_restrict
and MMAP_MIN_ADDR
1 | echo 0 > /proc/sys/kernel/dmesg_restrict |
And when stopping KASLR
, SMEP
and SMAP
, we modify QEMU
running parameters (add nokaslr
)
1 | -append "console=ttyS0 nokaslr ..." # replace kaslr with nokaslr |
As for NX
and Canary
(stack protector), these can be done in compilation.
Debug
Using GDBStub
(start gdbserver
port locally) of QEMU
to debug, we add parameter
1 | -s: open port 1234 |
And a script to quickly connect
1 |
|
And some key files & commands to get info:
cat /proc/modules
: see loaded kernel modulescat /proc/kallsyms
: see all kernel symbolscat /sys/module/xxx/sections
: list sections of modulexxx
dmesg
: see kernel logs, whereprintk()
printscat /proc/kallsyms |grep commit_creds
&cat /proc/kallsyms |grep prepare_kernel_cred
: get the two funcs addrs
Script
Here are scripts for packing and unpacking:unpack.py
1 | #!/bin/sh |
repack.py
1 | #!/bin/sh |
If we do experiments locally, we can pack the exploit binary into filesystem image, and see it in the system when booting, while when interacting with remote, we cannot alternate remote filesystem.
Therefore we copy exploit binary to remote via interaction shell, and here is a script to facilitate it:
1 | # -*- coding: utf-8 -*- |