← Back to Writeups
CUSTOMMediumReverse

Kernel Monarch

XESXOR9/2/20265 min read
#reverse#custom#medium#alternate_device_node_creation#dentry_name_bypass#ftrace_hook_analysis#kernel_module_symbol_analysis#rodata_string_extraction#syscall_hook_identification

Kernel Monarch

Platform: CUSTOM | Category: Reverse | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: alternate_device_node_creation, dentry_name_bypass, ftrace_hook_analysis, kernel_module_symbol_analysis, rodata_string_extraction, syscall_hook_identification

Summary

Walkthrough of the CUSTOM Reverse Kernel Monarch. Task: reverse a Linux kernel rootkit (malware.ko) that uses ftrace to hook syscalls and filter /dev/kmsg output. Solution: identify dentry-based filename check in hook_read, bypass by creating alterna

Description

A kernel rootkit intercepts system calls to hide sensitive information. Your mission is to reversing the kernel module, understand its hooking mechanism, and reveal what's hidden.

A ZIP archive (password: hackthebox) contains malware.ko, a Linux kernel module (rootkit). A remote QEMU VM instance is provided via netcat where the rootkit is already loaded. The goal is to reverse-engineer the hooking mechanism and recover the hidden flag from kernel logs. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

File Identification

$ file malware.ko
malware.ko: ELF 64-bit LSB relocatable, x86-64, not stripped, with debug_info

The module targets kernel 5.10.0-41-amd64, authored by "matheuz". Being not stripped with debug_info makes static analysis straightforward.

Symbol Table Reconnaissance

Key symbols discovered via nm:

SymbolRole
poop_initinit_module — rootkit entry point
poop_exitcleanup_module — rootkit cleanup
hideSelf-hides module from lsmod/sysfs
fh_install_hooksInstalls ftrace-based syscall hooks
fh_remove_hooksRemoves hooks on unload
fh_ftrace_thunkftrace callback that redirects execution
hook_readFilters content read from specific files
hook_writeProtects ftrace control files from writes
hook_syslog / hook_do_syslogBlocks dmesg/syslog access
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Decoy Flag

A fake flag exists in plaintext in .rodata: HTB{f4k3_fl4g_f0r_r3v3rs1ng_0nly} — this is a red herring to mislead reversers who rely solely on strings.

Ftrace-Based Hooking Mechanism

The rootkit uses ftrace (Linux kernel's function tracing framework) to hook 6 system calls:

Original SyscallHook FunctionPurpose
__x64_sys_readhook_readFilters lines from read output
__x64_sys_writehook_writeBlocks writes to ftrace control files
__x64_sys_sysloghook_syslogReturns -1, blocks dmesg
__ia32_sys_sysloghook_ia32_syslogReturns -1, blocks dmesg (32-bit compat)
__ia32_sys_writehook_ia32_writeBlocks writes to ftrace (32-bit compat)
do_sysloghook_do_syslogReturns -1, blocks syslog internally
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

hook_read — The Core Filtering Logic

This is the most important hook and the one that hides the flag:

  1. File identification: Gets the file descriptor via fget(), extracts the filename from file->f_path.dentry->d_name.name
  2. Filename check: Compares against a whitelist of monitored files: kmsg, kallsyms, enabled_functions, touched_functions
  3. If filename matches: Calls the original sys_read, then copies the buffer from userspace to kernel space
  4. Line-by-line filtering: Splits output on \n delimiters, then for each line calls strstr(line, "taint") and strstr(line, "malware")
  5. Censorship: Lines containing either keyword are dropped entirely; clean lines are reassembled and copied back to userspace
  6. If filename doesn't match: Passes through to original sys_read unmodified ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

hook_write — Ftrace Self-Protection

  • Checks if the target filename matches ftrace_enabled (15 bytes) or tracing_on (11 bytes)
  • If matched: silently intercepts the write, preventing anyone from disabling ftrace (which would break all hooks)
  • Otherwise: passes through to original write syscall

hook_syslog / hook_do_syslog — Complete Log Blockade

Both hooks simply return -1, completely blocking dmesg and any syslog-based kernel log access. This is a blunt but effective defense layer.

Module Self-Hiding

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The hide() function, called during poop_init:

  • Uses kobject_del to remove the module from sysfs (invisible to lsmod)
  • Sets magic value 0xDEAD000000000100 in module list pointers
  • The /init script also deletes malware.ko from disk after loading

Remote VM Environment

The QEMU VM's /init script reveals the loading sequence:

sh
echo 1 > /proc/sys/kernel/printk    # suppress console output
insmod /lib/modules/malware.ko       # load rootkit
rm -f /lib/modules/malware.ko        # delete module file from disk
setsid /bin/busybox cttyhack /bin/sh  # drop to interactive shell

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

The Vulnerability: Dentry-Based Filename Check

The critical weakness in hook_read is that it checks the dentry filename (the name of the file as it appears in the directory entry) of the opened file descriptor. It does NOT check the device major:minor numbers or inode.

/dev/kmsg is a character device with major 1, minor 11. The hook filters reads from any file named kmsg — but if we create a new device node pointing to the same device with a different name, the filename check fails to match, and the read passes through completely unfiltered. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The Bypass

bash
# Create alternate device node for /dev/kmsg (char device major 1, minor 11)
mknod /tmp/mylog c 1 11

# Read kernel messages through the unfiltered path
cat /tmp/mylog

Since the filename is "mylog" (not "kmsg"), hook_read's strcmp against the monitored filenames returns false, and the original sys_read result is returned without any line filtering.

Revealed Output

The unfiltered kernel log shows three lines that were previously hidden (they contain "malware" and/or "taint"): ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

4,350,...,-;malware: loading out-of-tree module taints kernel.
5,351,...,-;malware: module verification failed: signature and/or required key missing - tainting kernel
6,352,...,-;HTB{REDACTED}

Line 352 contains the real flag, printed by the rootkit module during initialization via printk. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering alternate_device_node_creation, dentry_name_bypass, ftrace_hook_analysis, kernel_module_symbol_analysis, rodata_string_extraction, syscall_hook_identification was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-11 • CUSTOM • Kernel Monarch • Difficulty: Medium