You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: main/content/post/how-to-kernel-pwn.md
+82-10Lines changed: 82 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,8 +14,11 @@ This guide will
14
14
- Adding custom components to the stock image (namely custom kernel modules and init scripts)
15
15
- Discuss enabling / disabling specific kernel protections
16
16
- Demonstrate the use of containerization to deploy as a CTF challenge
17
+
- Show a prototype of a fully reproducible kernel challenge build system
17
18
18
-
## Building your image and running it with qemu
19
+
## Getting Started
20
+
21
+
### Building your first image
19
22
20
23
To start, go to the the [buildroot download's page](https://buildroot.org/download.html) to download an LTS version of buildroot. From here, you can unzip it with `tar -xvf /path/to/my/file.tar.gz`.
21
24
@@ -35,21 +38,79 @@ make -j<max logical core number>`
35
38
```
36
39
The output will be in`output/images/`.
37
40
41
+
### Running your image
42
+
38
43
This output image will, by default, contain a `bzImage` file, which holds the kernel itself, an `rootfs.ext4` file, which holds the root filesystem, and a `start-qemu.sh` script which will boot qemu for you.
39
44
40
45
Now, when you launch you'll see a login screen. From here, I'd recommend setting up a custom user for the challenge. In this case, you can change the shell for the `root` user to `/bin/false`, and add a new user to `/etc/passwd` and `/etc/shadow`.
41
46
42
-
## Adding custom components on startup
47
+
## Building a challenge
43
48
44
-
This can be done with a custom `init` script. In order to pass a custom `init` script, we need a custom `initramfs`. You can either build this into the kernel, or you can build this and run qemu with flags for a custom initramfs. The latter is the approach I'd recommend taking for CTFs, because you'll often want an easy and quick way of updating init scripts, and waiting for a full recompile takes a lot of time.
49
+
### Writing your kernel module
45
50
46
-
### Adding your custom kernel module
51
+
Writing kernel modules is often _hard_ for beginners, because it's fundamentally working within a much larger codebase that's sparsely documented and can have disasterous consequences. For simpler CTF challenges, you really don't need much more than the basics. As a starting point, I've included the example below, which allows you to write directly into a buffer with no size check.
I'd recommend placing most of your custom code in the same place. So, you'll want to copy your compiled `chall.ko` into something like `/challenge/chall.ko`.
printk(KERN_INFO "[challenge] Try to connect to me at /proc/challenge/kboff.\n");
99
+
return 0;
100
+
}
101
+
102
+
void challenge_exit(void)
103
+
{
104
+
printk(KERN_INFO "[challenge] Goodbye!\n");
105
+
}
106
+
107
+
module_init(challenge_init);
108
+
module_exit(challenge_exit);
109
+
```
49
110
50
111
### Compiling your kernel module
51
112
52
-
This is a more complex issue, but in short, you can use the Kernel's build system to build your challenge against the Kernel's specific headers. An example `Makefile` can be seen below:
113
+
You'll need to use use the kernels build system to build your challenge against the kernel's specific headers. An example `Makefile` can be seen below:
53
114
54
115
```Makefile
55
116
obj-m += challenge.o
@@ -67,6 +128,17 @@ clean:
67
128
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
68
129
```
69
130
131
+
This can be run like `make KERNELDIR=/path/to/kernel/headers/`, and will output a `challenge.ko` file.
132
+
133
+
Importantly, this is both architecture and version dependent. In effect, you'll either want to compile on the target itself (feasible if it's a larger VM forresearching) or cross-compile with headers from that VM. In the below examples, I mostly do the latter, predominantly because it's significantly faster and avoids needing to run our compilation stagein the VM itself.
134
+
135
+
### Adding custom components on startup
136
+
137
+
To add custom components on startup, use a custom `init` script. In order to pass a custom `init` script, we need put the script in the filesystem itself. You can either build this with the kernel or patch the filesystem after the fact. The latter is the approach I'd recommend taking for CTFs, because you'll often want an easy and quick way of updating `init`, modules, etc., and waiting for a full recompile takes a lot of time.
138
+
139
+
### Adding your custom kernel module
140
+
141
+
I'd recommend placing most of your custom code in the same place. So, you'll want to copy your compiled `chall.ko` into something like `/challenge/chall.ko`. Anecdotally people tend to dislike it when they have to tear apart a filesystem looking forthe module. In my opinion it's quite a useful skill (and quite easy, a simple `grep` for the file extension would do, but I tend to include the specific `chall.ko` as a filein the distributable seperately).
70
142
71
143
### Writing an init script
72
144
@@ -85,11 +157,11 @@ exec busybox init
85
157
86
158
Notice in the above script that the actual script switches to a login shell at the final step in the `busybox init`. If you wanted, you could simply rebuild the init script from scratch to launch directly into your binary. This would allow you to give a userspace pwn into kernel pwn if you wanted to (for instance, shellcoding into kernel pwn). Alternatively, you can set a users shell to your custom binary, which would launch them directly into the userspace program.
87
159
88
-
Alternatively, if you wanted to build a jail breakout challenge, this same process could be used to provide kernel isolation.
160
+
If you wanted to build a jail breakout challenge, this same process could be used to provide kernel isolation. A good example of this would be a seccomp restricted shellcoding challenge, where you need to disable seccomp by exploiting a kernel module. Some examples of this format are available on [pwn.college](https://pwn.college/system-security/kernel-security/).
89
161
90
-
## How do I disable \[X\] protection?
162
+
### How do I disable \[X\] protection?
91
163
92
-
Some protections can be enabled or disabled at runtime (assuming a build with support for it). These include KASLR, SMAP, SMEP, NX, and PTI. These can be disabled by adding flags in qemu. For instance, to disable KASLR, you can use `-append "nokaslr"` (multiple options are delimited with a space), which will disable KASLR at runtime.
164
+
Some protections can be enabled or disabled at runtime (assuming a build with support for it). These include KASLR, SMAP, SMEP, NX, and PTI (though, this can change depending on the architecture --- this is generally the case for x86/x86-64 though). These can be disabled by adding flags in qemu. For instance, to disable KASLR, you can use `-append "nokaslr"` (multiple options are delimited with a space), which will disable KASLR at runtime.
93
165
94
166
Some protections can only be changed at compile time, namely canaries and SLUB/SLAB hardening. These can be changed by editing the `CONFIG_STACKPROTECTOR`, `CONFIG_SLAB_FREELIST_HARDENED`, etc. options in your `kconfig`. The easiest way to use a custom `kconfig` with buildroot is to use the menu build option (`make menuconfig`).
95
167
@@ -113,7 +185,7 @@ At this point, depending on your build configurations, it may be necessary to tw
113
185
114
186
## Running with docker (but unhinged)!
115
187
116
-
To take this a step further, we can completely automate the build process, so that the entire thing is done in Docker. An example of this can be seen below:
188
+
To take this a step further, we can completely automate the build process, so that the entire thing is done in Docker. The major benefit of this is completely reproducable builds, but the major downside being the fact that it relies on docker caching layers to stop the kernel from fully rebubilding itself every time. In production, I recommend splitting this into two (one for building the filesystem / kernel, and the other for running VM). An example of this can be seen below:
117
189
118
190
```Dockerfile
119
191
FROM debian:unstable@sha256:cc1675ddb1073d19ba9ef6fe9b9c625eceb02fccb9c0f7afbb4e60f16325c91d AS fs_builder
0 commit comments