HTB Link - github.com/hackthebox/business-ctf-2025
Solution
Installation / tools
We are given a .elf file ~2Go, it says it’s a memory capture. We’ll then try to analyse it using volatility3.
python3 vol.py -f mem.elf banners
It returns something like :
0x10399cd0 Linux version 6.1.0-34-amd64 (debian-kernel@lists.debian.org) (gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0, GNU ld (GNU Binutils for Debian) 2.40) #1 SMP PREEMPT_DYNAMIC Debian 6.1.135-1 (2025-04-25)
To be able to analyse this kernel (6.1.0-34-amd64), we have to find the exact matching banner symbol table. A lot of them are available on : https://github.com/Abyss-W4tcher/volatility3-symbols/tree/master
Convert the beginning of the banner in base64 to
Linux version 6.1.0-34-amd64 (debian-kernel@lists.debian.org) (gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0, GNU ld (GNU Binutils for Debian) 2.40) #1 SMP PREEMPT_DYNAMIC Debian 6.1.135-1
->
TGludXggdmVyc2lvbiA2LjEuMC0zNC1hbWQ2NCAoZGViaWFuLWtlcm5lbEBsaXN0cy5kZWJpYW4ub3JnKSAoZ2NjLTEyIChEZWJpYW4gMTIuMi4wLTE0K2RlYjEydTEpIDEyLjIuMCwgR05VIGxkIChHTlUgQmludXRpbHMgZm9yIERlYmlhbikgMi40MCkgIzEgU01QIFBSRUVNUFRfRFlOQU1JQyBEZWJpYW4gNi4xLjEzNS0x

Find a match from banners.json, download it using the link and extract the .gz to the volatility3/volatility3/symbols folder.
We’re now able to use linux plugins with the RAM dump!
Flag 1/10 - What is the name of the backdoor udev Rule (ex:10-name.rules)
Using the linux.pagecache.Files plugin, we can search for rules using regex : \b\d{2}-[a-zA-Z0-9_-]+\.rules\b
We then find a line with a weird rule name : 99-volnaya.rules
0x9a097453c800 / 8:1 652915 0x9a0974b66798 REG 1 1 -rw-r--r-- 2025-05-12 19:19:55.919384 UTC 2025-05-12 19:20:29.372000 UTC 2025-05-12 19:20:29.372000 UTC /etc/udev/rules.d/99-volnaya.rules 77
Flag 2/10 - What is the name of the kernel module ?
A simple volatility3 request using linux.hidden_modules :
0xffffc09804c0 volnaya_xb127 0x4000 OOT_MODULE,UNSIGNED_MODULE N/A
We find volnaya_xb127 as the module name
Flag 3/10 - What is the resolved IP of the attacker ?
Using linux.sockstat plugin:
| |
We find the remote IP address: 16.171.55.6
Flag 4/10 - What is the address of __x64_sys_kill, __x64_sys_getdents64 (ex: kill:getdents64)
Using linux.kallsyms plugin:
| |
Flag: 0xffffb88b6bf0:0xffffb8b7c770
Flag 5/10 - What __SYSCALLS__ are hooked with ftrace, sorted via SYSCALL number (ex: read:write:open)
Using linux.tracing.ftrace.CheckFtrace plugin:
| |
Only __x64_sys_kill and __x64_sys_getdents64 are syscall
Flag: kill:getdents64
Flag 6/10 There is one bash process that is hidden in __USERSPACE__, what is its PID
What is the resolved IP of the attacke
In the __USER_SPACE__ (UID 1000, GID 1000 for the standard user), there’s a lot of bash processes
0x9a0946310000 2957 2957 1750 bash 0 0 0 0 2025-05-12 19:23:34.894489 UTC Disabled
Using linux.pslist, we can see that only 1 bash script runs with UID:0 & GID 0:
2957 1750 bash SUDO_COMMAND /usr/bin/volnaya_usr rsh
We enter PID 2957, flag : 2957
Flag 7/10 - What is the XOR key used (ex: 0011aabb)
In the linux.envars, we can see the line :
2957 1750 bash SUDO_COMMAND /usr/bin/volnaya_usr rsh
We try to recover this binary file to analyse it further using linux.pagecache.RecoverFs plugin:
Using readelf -a volnaya_usr on the recovered binary, we can inspect what it contains:
In the .symtab section, we see interesting entries :
| |
We find that hostname is stored at the virtual address 00000000000040e0 and xor_key at the address 0000000000004100
We search for the data’s offset :
| |
So we can calculate the real addresses of hostname (28 bytes) and xor_key (16 bytes)
The address is calculated with : VirtualAddress - Address Data (0x00000000000040c0) + offset (0x000030c0)
| Name | Virtual Address | Offset | Address |
|---|---|---|---|
| xor_key | 0000000000004100 | 000030c0 | 0000000000003100 |
| hostname | 00000000000040e0 | 000030c0 | 00000000000030e0 |
| We retrieve 16 bytes starting at 0x3100, we find the xor_key : |
| |
Flag : 881ba50d42a430791ca2d9ce0630f5c9
Flag 8/10 - What is the hostname the rootkit connects to
We make the same process than the previous flag :
| |
We see that the data is not in clear text, we try to xor it using the previous found key, we have :

We find a good value :
callback.cnc2811.volnaya.htb
Flag 9/10 - What owner UID and GID membership will make the file hidden (UID:GID)
Extract the hidden kernel module using the linux.module_extract plugin :
python vol.py -f mem.elf linux.module_extract --base 0xffffc09804c0The base address is found using hidden_module plugin.Analyse it using ghidra or
readelf kernel_module.volnaya_xb127.0xffffc09804c0.elf -a
We find interesting 4 bytes variables :
| |
Exploring toggle_hide_proc function in ghidra, we find out, that the function’s goal is to check if the PID is already in the “hide” list.
- If so, it removes it from the list — i.e., un-hides it.
- If not, it adds it to the list — i.e., hides it.
| Element | Purpose |
|---|---|
toggle_hide_proc | Toggle PID in hide/unhide list |
DAT_ffffffffc0a80460 | Head of the hidden PID list |
| List structure | Doubly linked list (prev/next) |
| Hidden if present | In the list |
| Unhidden if removed | From the list |
| By exploring further, we see inside the function patched_getdents64 : |
| |
We can see the followings :
| |
So, it’s reading the UID and GID of the file or process entry returned by getdents64.
Then, they compare them to the values stored at :
| |
DAT_ffffffffc0a80478→ UID to hideDAT_ffffffffc0a80474→ GID to hide If either matches the current entry’s UID or GID:- It skips showing that entry by jumping to a block (
LAB_ffffffffc0a7ebb8) that merges/skips it.

By looking at thoses values, we find : GID -> DAT_ffffffffc0a80474 -> 000007C8h -> 1992 UID -> DAT_ffffffffc0a80478 -> 0000071Dh -> 1821
Flag: 1821:1992
Flag 10/10 - What string must be contained in a file in order to be hidden

I don’t really now how to link it with the program as I found it by chance..
Flag: volnaya