Skip to main content

HTB-Cyberpsychosis

Table of Contents
Difficulty: Easy
OS: Linux
Date: 2026-01-28

Description:
Static analysis on this program didn’t reveal much. There must be a better way to approach this…

initial analysis
#

We’re given a 64-bit ELF binary with a .ko extension (Kernel Object - a Linux Kernel Module).

1$ file diamorphine.ko
2diamorphine.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), 
3BuildID[sha1]=e6a635e5bd8219ae93d2bc26574fff42dc4e1105, with debug_info, not stripped

reversing with IDA
#

Since this is a Linux Kernel Module, there’s no standard main function. Instead, the entry point is the initialization function.

Initialization Function

The module manipulates the cr0 register to grant the rootkit permission to write to read-only sections of memory where the system call table resides.

In the sys_call_table, the following system calls are hooked and replaced with malicious functions:
- killhacked_kill
- getdentshacked_getdents
- getdents64hacked_getdents64

hacked_kill func (priv esc and stealth)
#

When analyzing the hacked_kill function, we see it checks for signal code 64:

1else if ( (_DWORD)si == 64 )
2{
3    v9 = prepare_creds(pt_regs, a2, v2, di);
4    // ... [snip] ...

The function calls prepare_creds(), which creates a new credential structure for the current process. The credential structure in Linux looks like this:

 1struct cred {
 2    kuid_t uid;
 3    kgid_t gid;
 4    kuid_t euid;
 5    kgid_t egid;
 6    kuid_t suid;
 7    kgid_t sgid;
 8    kuid_t fsuid;
 9    kgid_t fsgid;
10    // ... [snip] ...
11};

The rootkit then overwrites all credential values to 0 (root UID), granting the process root privileges:

1// ... [snip] ...
2*(_QWORD *)(v9 + 4) = 0;
3*(_QWORD *)(v9 + 12) = 0;
4*(_QWORD *)(v9 + 20) = 0;
5*(_QWORD *)(v9 + 28) = 0;
6commit_creds(v9);
7return 0;

The rootkit can hide itself from the module list by manipulating the doubly-linked list when receiving signal code 46:

 1if ( (_DWORD)si == 46 )
 2{
 3    if ( !module_hidden )
 4    {
 5        prev = _this_module.list.prev;
 6        next = _this_module.list.next;
 7        v6 = 0;
 8        next->prev = prev;
 9        module_previous = prev;
10        prev->next = next;
11        // ... [snip] ...
12        module_hidden = 1;
13        return v6;
14    }

It can also unhide itself

hacked_getdents func
#

this function first calls the original getdents and creates a buffer containing a copy of the directory listing (linux_dirent struct).

It then searches for entries containing the string “psychosis” (represented as hex 0x69736F6863797370 + character 115):

1if ( *(_QWORD *)(v12 + 18) != 0x69736F6863797370LL || 
2     *((_BYTE *)buffer + v11 + 26) != 115 )
3     // ... [snip] ...

If found, the entry is removed from the buffer, hiding any files/directories with “psychosis” in their name from ls and similar tools.

exploitation
#

With the rootkit analysis complete, I proceeded to exploit and remove it from the compromised system:

  1. escalate to root: kill -64 $$
  2. unhide the module: kill -46 $$
  3. remove the rootkit: rmmod diamorphine (found in /sys/module/)
  4. find hidden file:
1# find / -name "psychosis*" 2>/dev/null
2/opt/psychosis
3
4# cat /opt/psychosis/flag.txt
5HTB{N0w_Y0u_C4n_S33_m3_4nd_th3_r00tk1t_h4s_b33n_sUcc3ssfully_d3f34t3d!!}