i'm a reversing challenge, the sccenario is: Chall...
बनाया गया: 8 मई 2026
का उपयोग करके उत्तर दिया गया GPT-5.5 Thinking द्वारा Chat01
बनाया गया: 8 मई 2026
का उपयोग करके उत्तर दिया गया GPT-5.5 Thinking द्वारा Chat01
i'm a reversing challenge, the sccenario is:
Challenge Scenario
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.
the challenge have a zip with a binary, zip password: hackthebox.
I unpacked and reversed the module. The zip contains one kernel object:
textrev_kernel_monarch/malware.ko ELF 64-bit LSB relocatable, x86-64, Linux kernel module, not stripped, with debug_info
The plain strings flag is a decoy:
textHTB{f4k3_fl4g_f0r_r3v3rs1ng_0nly}
I do not see another real HTB{...} flag embedded in the module or zip, so the real hidden value is likely meant to be recovered from the live challenge VM’s kernel state/logs after understanding the hooks.
It hides itself first:
clist_del(&THIS_MODULE->list); kobject_del(&THIS_MODULE->mkobj.kobj);
So it disappears from things like lsmod, /proc/modules, and /sys/module/malware.
Then it installs 6 ftrace hooks:
| Original symbol | Hook function | Purpose |
|---|---|---|
__x64_sys_read | hook_read | Filters reads from selected kernel/proc/debugfs files |
__x64_sys_write | hook_write | Silently swallows writes to tracing controls |
__ia32_sys_write | hook_ia32_write | Same for 32-bit compat writes |
__x64_sys_syslog | hook_syslog | Blocks dmesg/syslog |
__ia32_sys_syslog | hook_ia32_syslog | Same for 32-bit compat |
do_syslog | hook_do_syslog | Blocks lower-level kernel log access |
It resolves kallsyms_lookup_name by temporarily registering a kprobe on:
textkallsyms_lookup_name
Then it uses that resolver to find syscall addresses and redirects execution with ftrace_set_filter_ip() + register_ftrace_function().
hook_read only filters reads from files whose dentry name is one of:
textkmsg kallsyms enabled_functions touched_functions
For those files, it removes any line containing either:
texttaint malware
Clean pseudocode:
cif (fd_name in ["kmsg", "kallsyms", "enabled_functions", "touched_functions"]) { bytes = orig_read(regs); copy_from_user(kernel_buf, user_buf, bytes); for each line in kernel_buf: if strstr(line, "taint") continue; if strstr(line, "malware") continue; copy line to filtered_buf; copy_to_user(user_buf, filtered_buf, filtered_len); return filtered_len; } return orig_read(regs);
So it hides evidence of the module from /proc/kallsyms, ftrace state, and kernel log taint messages.
dmesg failsThe syslog hooks are very simple:
asmmov rax, -1 ret
So dmesg / syslog(2) is blocked with -EPERM.
But /proc/kmsg goes through read, not the syslog syscall. The read hook filters only lines containing taint or malware, not HTB.
Try reading the kernel message buffer directly instead of using dmesg:
bashsudo timeout 3 cat /proc/kmsg | grep -a 'HTB'
or:
bashsudo timeout 3 cat /proc/kmsg
/proc/kmsg may block, so use timeout.
The module is hiding:
textlines containing "malware" or "taint"
from:
text/proc/kmsg /proc/kallsyms /sys/kernel/debug/tracing/enabled_functions /sys/kernel/debug/tracing/touched_functions
and it blocks normal dmesg access via syslog hooks. The visible flag in the .ko is intentionally fake; the real secret is probably in the live kernel log and should be reachable through /proc/kmsg because the filter does not remove HTB lines.
also have this ip: 154.57.164.66:30242 extract me the real flag
can you give me a exploit for get a flag?
Yes. For this challenge, the “exploit” is a syscall bypass: the rootkit hooks read(2), but not readv(2). So don’t use cat, grep, dmesg, etc. Dump /proc/kmsg using readv.
Run this on the challenge VM as root:
bashcat > exploit.py <<'PY' #!/usr/bin/env python3 import os import re import errno FAKE = b"HTB{f4k3_fl4g_f0r_r3v3rs1ng_0nly}" targets = [ "/proc/kmsg", "/dev/kmsg", "/proc/kallsyms", "/sys/kernel/debug/tracing/enabled_functions", "/sys/kernel/debug/tracing/touched_functions", "/sys/kernel/tracing/enabled_functions", "/sys/kernel/tracing/touched_functions", ] def read_with_readv(path, max_reads=256): data = b"" try: fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK) except PermissionError: print(f"[-] Permission denied: {path}") return b"" except FileNotFoundError: return b"" except OSError as e: print(f"[-] Cannot open {path}: {e}") return b"" try: for _ in range(max_reads): buf = bytearray(65536) try: n = os.readv(fd, [buf]) except BlockingIOError: break except OSError as e: if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK): break print(f"[-] readv failed on {path}: {e}") break if n <= 0: break data += bytes(buf[:n]) if n < len(buf): break finally: os.close(fd) return data print("[+] Bypassing hooked read(2) with readv(2)...") for path in targets: blob = read_with_readv(path) if not blob: continue print(f"\n===== {path} =====") found = False # Print real-looking flags, excluding the embedded fake flag. for m in re.findall(rb"HTB\{[^}\r\n]+\}", blob): found = True if m == FAKE: print(f"[fake] {m.decode(errors='ignore')}") else: print(f"[FLAG] {m.decode(errors='ignore')}") # Also show the lines the rootkit normally hides. for line in blob.splitlines(): low = line.lower() if b"malware" in low or b"taint" in low: found = True print(line.decode(errors="ignore")) if not found: print("[*] No obvious flag/hidden rootkit line in this file.") PY chmod +x exploit.py sudo ./exploit.py
The important part is this line:
pythonos.readv(fd, [buf])
That calls readv(2), while the rootkit only hooks:
text__x64_sys_read __x64_sys_write __ia32_sys_write __x64_sys_syslog __ia32_sys_syslog do_syslog
So readv bypasses its filtering and should reveal the hidden /proc/kmsg or /proc/kallsyms lines.
If the script only prints the fake flag, try forcing fresh kernel messages, then rerun it:
bashsudo dmesg 2>/dev/null sudo ./exploit.py
or directly:
bashsudo python3 - <<'PY' import os, re fd = os.open("/proc/kmsg", os.O_RDONLY | os.O_NONBLOCK) buf = bytearray(1024 * 1024) try: n = os.readv(fd, [buf]) print(buf[:n].decode(errors="ignore")) finally: os.close(fd) PY
Expected result: a line containing the real HTB{...} flag, not:
textHTB{f4k3_fl4g_f0r_r3v3rs1ng_0nly}
but adapt to get a flag on 154.57.164.66:30242
I tried connecting from here, but the endpoint is currently refusing connections:
textnc: connect to 154.57.164.66 port 30242 (tcp) failed: Connection refused
So first make sure your HTB instance is still alive / restarted. When it is reachable, use this adapted remote solve script.
It assumes the port drops you into a Linux shell or serial console, which is the usual setup for this kind of kernel challenge. It uploads a tiny readv(2) dumper, runs it remotely, and extracts the real HTB{...} flag.
python#!/usr/bin/env python3 import base64 import os import re import socket import subprocess import textwrap import time HOST = "154.57.164.66" PORT = 30242 FAKE = "HTB{f4k3_fl4g_f0r_r3v3rs1ng_0nly}" C_CODE = r''' #define _GNU_SOURCE #include <fcntl.h> #include <unistd.h> #include <sys/uio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> static const char *targets[] = { "/proc/kmsg", "/dev/kmsg", "/proc/kallsyms", "/sys/kernel/debug/tracing/enabled_functions", "/sys/kernel/debug/tracing/touched_functions", "/sys/kernel/tracing/enabled_functions", "/sys/kernel/tracing/touched_functions", NULL }; static int has_ci(const char *s, const char *needle) { size_t n = strlen(needle); for (; *s; s++) { size_t i = 0; while (i < n && s[i]) { char a = s[i]; char b = needle[i]; if (a >= 'A' && a <= 'Z') a += 32; if (b >= 'A' && b <= 'Z') b += 32; if (a != b) break; i++; } if (i == n) return 1; } return 0; } static void print_flags(const char *data, size_t len) { for (size_t i = 0; i + 4 < len; i++) { if (memcmp(data + i, "HTB{", 4) == 0) { size_t j = i; while (j < len && data[j] != '}') j++; if (j < len) { fwrite(data + i, 1, j - i + 1, stdout); putchar('\n'); i = j; } } } } static void print_hidden_lines(const char *data, size_t len) { size_t start = 0; for (size_t i = 0; i <= len; i++) { if (i == len || data[i] == '\n') { size_t l = i - start; if (l > 0 && l < 8192) { char line[8192]; memcpy(line, data + start, l); line[l] = 0; if (has_ci(line, "HTB{") || has_ci(line, "malware") || has_ci(line, "taint")) { puts(line); } } start = i + 1; } } } static void dump_one(const char *path) { int fd = open(path, O_RDONLY | O_NONBLOCK); if (fd < 0) return; size_t cap = 8 * 1024 * 1024; char *data = malloc(cap); if (!data) { close(fd); return; } size_t len = 0; for (int iter = 0; iter < 512 && len < cap; iter++) { struct iovec iov; iov.iov_base = data + len; iov.iov_len = cap - len > 65536 ? 65536 : cap - len; ssize_t n = readv(fd, &iov, 1); if (n <= 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; break; } len += (size_t)n; if (n < 65536) break; } close(fd); if (len > 0) { printf("\n===== %s =====\n", path); print_flags(data, len); print_hidden_lines(data, len); } free(data); } int main(void) { for (int i = 0; targets[i]; i++) { dump_one(targets[i]); } return 0; } ''' def build_binary(): open("/tmp/rk_bypass.c", "w").write(C_CODE) compilers = [ ["musl-gcc", "-static", "-Os", "-s", "/tmp/rk_bypass.c", "-o", "/tmp/rk_bypass"], ["gcc", "-static", "-Os", "-s", "/tmp/rk_bypass.c", "-o", "/tmp/rk_bypass"], ["gcc", "-Os", "-s", "/tmp/rk_bypass.c", "-o", "/tmp/rk_bypass"], ] for cmd in compilers: try: subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return open("/tmp/rk_bypass", "rb").read() except Exception: pass raise SystemExit("[-] Could not compile. Install gcc or musl-gcc locally.") def recv_some(sock, seconds=1.0): sock.setblocking(False) end = time.time() + seconds out = b"" while time.time() < end: try: chunk = sock.recv(65536) if chunk: out += chunk else: break except BlockingIOError: time.sleep(0.05) return out def sendline(sock, s): if isinstance(s, str): s = s.encode() sock.sendall(s + b"\n") def main(): elf = build_binary() b64 = base64.b64encode(elf).decode() print(f"[+] Built payload: {len(elf)} bytes") print(f"[+] Connecting to {HOST}:{PORT}") sock = socket.create_connection((HOST, PORT), timeout=10) banner = recv_some(sock, 2) if banner: print(banner.decode(errors="ignore"), end="") # Wake shell/console. sendline(sock, "") sendline(sock, "id; uname -a") print(recv_some(sock, 2).decode(errors="ignore"), end="") print("[+] Uploading readv bypass binary...") sendline(sock, "cat > /tmp/rk_bypass.b64 <<'EOF_B64'") for chunk in textwrap.wrap(b64, 4000): sendline(sock, chunk) time.sleep(0.01) sendline(sock, "EOF_B64") decode_cmd = ( "base64 -d /tmp/rk_bypass.b64 > /tmp/rk_bypass " "|| busybox base64 -d /tmp/rk_bypass.b64 > /tmp/rk_bypass " "|| openssl enc -d -base64 -A -in /tmp/rk_bypass.b64 -out /tmp/rk_bypass" ) sendline(sock, decode_cmd) sendline(sock, "chmod +x /tmp/rk_bypass") sendline(sock, "/tmp/rk_bypass") sendline(sock, "echo __DONE__") out = b"" for _ in range(30): out += recv_some(sock, 1) if b"__DONE__" in out: break text = out.decode(errors="ignore") print(text) flags = re.findall(r"HTB\{[^}\r\n]+\}", text) real = [f for f in flags if f != FAKE] if real: print(f"\n[+] REAL FLAG: {real[0]}") elif flags: print(f"\n[-] Only fake flag found: {flags[0]}") else: print("\n[-] No HTB flag found in output.") print(" The service may not be a shell, or the instance may not be fully booted/root.") if __name__ == "__main__": main()
Run it:
bashpython3 solve.py
Why this works: the rootkit filters read(2) output from /proc/kmsg, /proc/kallsyms, and ftrace files, but it does not hook readv(2). The uploaded binary reads those same files with readv, bypassing the filter and printing hidden HTB{...}, malware, or taint lines.
If the connection still says Connection refused, restart the HTB spawned instance and update HOST/PORT if HTB gives you a new pair.
raceback (most recent call last):
File "/home/cibaism/ctf/reversing/Kernel Monarch/rev_kernel_monarch/solve.py", line 236, in <module>
main()
~~~~^^
File "/home/cibaism/ctf/reversing/Kernel Monarch/rev_kernel_monarch/solve.py", line 200, in main
sendline(sock, chunk)
~~~~~~~~^^^^^^^^^^^^^
File "/home/cibaism/ctf/reversing/Kernel Monarch/rev_kernel_monarch/solve.py", line 176, in sendline
sock.sendall(s + b"\n")
~~~~~~~~~~~~^^^^^^^^^^^
BlockingIOError: [Errno 11] Resource temporarily unavailable
The bug is in my script: recv_some() switches the socket to non-blocking mode and never restores it. Then sendall() hits EAGAIN:
textBlockingIOError: [Errno 11] Resource temporarily unavailable
Patch these two functions.
Replace your current recv_some() and sendline() with this:
pythonimport select import time def recv_some(sock, seconds=1.0): old_timeout = sock.gettimeout() sock.setblocking(False) end = time.time() + seconds out = b"" try: while time.time() < end: try: chunk = sock.recv(65536) if chunk: out += chunk else: break except BlockingIOError: time.sleep(0.05) finally: sock.settimeout(old_timeout) return out def sendline(sock, s): if isinstance(s, str): s = s.encode() data = s + b"\n" # Put socket back into blocking/timeout mode before sending. sock.settimeout(10.0) total = 0 while total < len(data): try: sent = sock.send(data[total:]) if sent == 0: raise RuntimeError("socket connection broken") total += sent except BlockingIOError: select.select([], [sock], [], 10.0)
Also slow the upload down a bit. Change this part:
pythonfor chunk in textwrap.wrap(b64, 4000): sendline(sock, chunk) time.sleep(0.01)
to:
pythonfor chunk in textwrap.wrap(b64, 512): sendline(sock, chunk) time.sleep(0.03)
Then rerun:
bashpython3 solve.py
The root cause was not the exploit logic; it was just socket mode handling. After recv_some() made the socket non-blocking, every later sendline() inherited that mode. Restoring the timeout/blocking state fixes the traceback.
give me a new code
Here’s a fresh corrected solve.py with the socket bug fixed and a slower upload.
python#!/usr/bin/env python3 import base64 import errno import os import re import select import socket import subprocess import textwrap import time HOST = "154.57.164.66" PORT = 30242 FAKE_FLAG = "HTB{f4k3_fl4g_f0r_r3v3rs1ng_0nly}" C_CODE = r''' #define _GNU_SOURCE #include <fcntl.h> #include <unistd.h> #include <sys/uio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> static const char *targets[] = { "/proc/kmsg", "/dev/kmsg", "/proc/kallsyms", "/sys/kernel/debug/tracing/enabled_functions", "/sys/kernel/debug/tracing/touched_functions", "/sys/kernel/tracing/enabled_functions", "/sys/kernel/tracing/touched_functions", NULL }; static int contains_ci(const char *s, const char *needle) { size_t n = strlen(needle); for (; *s; s++) { size_t i = 0; while (i < n && s[i]) { char a = s[i]; char b = needle[i]; if (a >= 'A' && a <= 'Z') a += 32; if (b >= 'A' && b <= 'Z') b += 32; if (a != b) break; i++; } if (i == n) return 1; } return 0; } static void print_interesting_lines(const char *data, size_t len) { size_t start = 0; for (size_t i = 0; i <= len; i++) { if (i == len || data[i] == '\n') { size_t l = i - start; if (l > 0 && l < 8192) { char line[8192]; memcpy(line, data + start, l); line[l] = 0; if ( contains_ci(line, "HTB{") || contains_ci(line, "malware") || contains_ci(line, "taint") || contains_ci(line, "flag") ) { puts(line); } } start = i + 1; } } } static void dump_file_readv(const char *path) { int fd = open(path, O_RDONLY | O_NONBLOCK); if (fd < 0) return; size_t cap = 8 * 1024 * 1024; char *data = malloc(cap); if (!data) { close(fd); return; } size_t total = 0; for (int i = 0; i < 512 && total < cap; i++) { struct iovec iov; iov.iov_base = data + total; iov.iov_len = cap - total; if (iov.iov_len > 65536) iov.iov_len = 65536; ssize_t n = readv(fd, &iov, 1); if (n <= 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; break; } total += (size_t)n; if (n < 65536) break; } close(fd); if (total > 0) { printf("\n===== %s =====\n", path); print_interesting_lines(data, total); } free(data); } int main(void) { printf("[+] running readv syscall bypass\n"); for (int i = 0; targets[i]; i++) { dump_file_readv(targets[i]); } printf("[+] done\n"); return 0; } ''' def build_payload(): src = "/tmp/rk_readv_bypass.c" out = "/tmp/rk_readv_bypass" with open(src, "w") as f: f.write(C_CODE) compilers = [ ["musl-gcc", "-static", "-Os", "-s", src, "-o", out], ["gcc", "-static", "-Os", "-s", src, "-o", out], ["gcc", "-Os", "-s", src, "-o", out], ] for cmd in compilers: try: subprocess.check_call( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) with open(out, "rb") as f: return f.read() except Exception: continue raise SystemExit("[-] Could not compile payload. Install gcc or musl-gcc.") def recv_some(sock, seconds=1.0): old_timeout = sock.gettimeout() sock.setblocking(False) end = time.time() + seconds out = b"" try: while time.time() < end: try: chunk = sock.recv(65536) if not chunk: break out += chunk except BlockingIOError: time.sleep(0.05) finally: sock.settimeout(old_timeout) return out def send_all(sock, data): sock.settimeout(10.0) sent_total = 0 while sent_total < len(data): try: sent = sock.send(data[sent_total:]) if sent == 0: raise RuntimeError("socket connection broken") sent_total += sent except BlockingIOError: select.select([], [sock], [], 10.0) def sendline(sock, s): if isinstance(s, str): s = s.encode() send_all(sock, s + b"\n") def run_cmd(sock, cmd, wait=0.5): sendline(sock, cmd) time.sleep(wait) out = recv_some(sock, wait) if out: print(out.decode(errors="ignore"), end="") return out def main(): print("[+] Building local readv payload...") elf = build_payload() b64 = base64.b64encode(elf).decode() print(f"[+] Payload size: {len(elf)} bytes") print(f"[+] Connecting to {HOST}:{PORT}...") sock = socket.create_connection((HOST, PORT), timeout=15) sock.settimeout(10.0) banner = recv_some(sock, 2) if banner: print(banner.decode(errors="ignore"), end="") print("[+] Checking remote shell...") sendline(sock, "") run_cmd(sock, "id; uname -a", wait=1.5) print("[+] Uploading payload...") sendline(sock, "rm -f /tmp/rk_bypass /tmp/rk_bypass.b64") sendline(sock, "cat > /tmp/rk_bypass.b64 <<'EOF_B64'") for chunk in textwrap.wrap(b64, 512): sendline(sock, chunk) time.sleep(0.03) sendline(sock, "EOF_B64") print("[+] Decoding payload...") decode_cmd = ( "base64 -d /tmp/rk_bypass.b64 > /tmp/rk_bypass " "|| busybox base64 -d /tmp/rk_bypass.b64 > /tmp/rk_bypass " "|| openssl enc -d -base64 -A -in /tmp/rk_bypass.b64 -out /tmp/rk_bypass" ) run_cmd(sock, decode_cmd, wait=1.0) run_cmd(sock, "chmod +x /tmp/rk_bypass", wait=0.5) print("[+] Running readv bypass...") sendline(sock, "/tmp/rk_bypass") sendline(sock, "echo __RK_DONE__") output = b"" for _ in range(60): chunk = recv_some(sock, 1) if chunk: output += chunk print(chunk.decode(errors="ignore"), end="") if b"__RK_DONE__" in output: break text = output.decode(errors="ignore") flags = re.findall(r"HTB\{[^}\r\n]+\}", text) real_flags = [f for f in flags if f != FAKE_FLAG] print("\n") if real_flags: print(f"[+] REAL FLAG: {real_flags[0]}") elif flags: print(f"[-] Only fake flag found: {flags[0]}") else: print("[-] No HTB flag found.") print("[*] Check whether the remote service gives a shell and whether the instance is fully booted.") sock.close() if __name__ == "__main__": main()
Run it like this:
bashpython3 solve.py
This version fixes the BlockingIOError by restoring the socket timeout after non-blocking reads and by sending data in a retry loop.