Phalanx 2 Revealed: Using Volatility to Analyze an Advanced Linux Rootkit Archived: 2026-04-02 11:36:58 UTC Month of Volatility Plugins In this blog post I will analyze the Phalanax2 rootkit using both Volatility as well as traditional malware analysis techniques. Phalanx2 Phalanx2 (P2) is the latest version of a private rootkit, whose original source was leaked to PacketStorm back in late 2005. Since then there have been no public leaks of either the source code or the complete set of files for using the rootkit (backdoor, client, config instructions, etc). Instead, the only occurrences of it were from sysadmins and IR teams who found that their systems were infected with it. None of these teams have ever released the files public, so deep analysis of the rootkit is not available in any public forums. With that said, we recently got our hands on a working sample (backdoor and config file only). Although we also are not allowed to release the sample, we can release our analysis of it. The rootkit comes as a statically compiled userland ELF file with stripped symbols. The config file specifies the group ID of processes to hide, the prefix of filenames to hide, and a hash value that we did not determine the purpose of yet. Analysis Setup & Approach Analysis was done on a Debian 6.0.3 (Squeeze) 32 bit VMware virtual machine running the 2.6.32-5-686 #1 SMP kernel. I also compiled a custom kernel, explained later, that let me automate some of the analysis. Our sample included three files, the userland binary that controls all functionality and a .config file that specified the group ID to hide, the hidden directory for files, and a .p2rc file used for when communicating with the backdoor.  When running, P2 produced a file of recorded keystrokes into the hidden directory. The approach during this analysis was a mix of both static and dynamic analysis using Volatility, IDA Pro, custom dynamic monitoring code, and the usual Linux analysis tools (gdb, strace, etc). Analysis with Volatility I will first walk through the analysis of P2 with Volatility.  To determine the changes that the rootkit makes, I booted the VM, took a memory capture with LiME, installed P2, and then took another memory capture. Note that if you run the program without arguments you are greeted with a nice 'HACKED BY CHINESE' message that can be seen below: https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 1 of 22 # insmod ./lime-2.6.32-5-686.ko "format=lime path=before-blog-post.lime" # ./phalanx2 # ./phalanx2 i (_-  phalanx 2.5f -_) ; mmap failed..bypassing /dev/mem restrictions ; locating sys_call_table.. ; sys_call_table_phys = 0x12742b0 ; phys_base = 0x0 ; sys_call_table = 0xc12742b0 ; hooking.. [8=======================D] ; locating &tcp4_seq_show..................... found >>injected # insmod ./lime-2.6.32-5-686.ko "format=lime path=after-blog-post.lime" The output from P2, assuming that it can be trusted, seems like we will need to investigate /dev/mem, the system call table, and the /proc handlers for the TCP protocol. If we tried to install P2 while the hooks are still active we get an error message: # ./phalanx2 i (_-  phalanx 2.5f -_) fatal: already injected? I then ran a number of plugins and diff’ed their output to determine the effects of the rootkit. Hidden Processes # python vol.py --profile=Linuxthisx86 -f before-blog-post.lime linux_pslist > pslist-before https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 2 of 22 Volatile Systems Volatility Framework 2.2 # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_pslist > pslist-after Volatile Systems Volatility Framework 2.2 # diff pslist-before pslist-after 64c64,65 < 0xf6698000 insmod    1292  0  0      Sun, 07 Oct 2012 03:47:59 +0000 --- > 0xf669e600 Xnest     1319  0  42779  Sun, 07 Oct 2012 03:52:58 +0000 > 0xf671aa80 insmod    1353  0  0      Sun, 07 Oct 2012 03:53:33 +0000 In this output we can see that insmod is different, as we should expect since we ran LiME twice and unloaded it in between runs. We also see a process named Xnest with a PID of 1319 and a GID of 42779.   This is the userland process spawned by P2, and 42779 is the GID to hide from userland. If we investigate this process with linux_psaux, which gathers arguments from userland, we see that the process name is different - it is disguised as a kernel thread (because the name is enclosed in brackets).  # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_psaux -p 1319 Volatile Systems Volatility Framework 2.2 Pid    Uid    Gid        Arguments 1319   0      42779  [ata/0]                                                          Although the Xnest process is hidden, it would look very strange on a normal system if it became uncovered, so P2 tries to blend the "Xnest" process name by disguising it as a normal kernel thread.  An experienced investigator or system administrator would even find the new name suspicious though as the PID is very high for a kernel thread, which are all normally started soon after init. Similarly, the process will have memory maps even though the name is in brackets. As final proof that our PID is not really a kernel thread, we can look at the output of the pstree plugin and see that Xnest is indeed not a child of the kernel thread daemon as all kernel threads should be: # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_pstree .Xnest                  1319        0 [kthreadd]              2           0 .[migration/0]          3           0 https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 3 of 22 .[ksoftirqd/0]          4           0 .[watchdog/0]           5           0 .[events/0]             6           0 .[cpuset]               7           0 .[khelper]              8           0 .[netns]                9           0 .[async/mgr]            10          0 .[pm]                   11          0 Memory Maps Investigating the memory maps is pretty straightforward as the binary is statically compiled and does not load any libraries on its own. The memory maps between the binary and the stack may be of interest though, and the “rwx” mapping only adds to this interest. This is because memory mappings normally are either rw, ro, or rx, but not all three. The use of rwx pages is a common malware technique as it allows the malware to write to a memory buffer and then execute it. # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_proc_maps -p 1319 Volatile Systems Volatility Framework 2.2 9c27000 | 9c27000 0x8048000-0x805c000 r-x          0  8: 1       362195 /usr/share/XXXXXXXXXXXX.p2/.p-2.5f 0x805c000-0x805d000 rwx      81920  8: 1  362195 /usr/share/XXXXXXXXXXXX.p2/.p-2.5f 0x805d000-0x805f000 rwx          0  0: 0          0 0xaf891000-0xaf894000 rwx          0  0: 0       0 0xb7894000-0xb78ac000 rwx          0  0: 0     0 0xb78ac000-0xb78ad000 r-x          0  0: 0       0 0xbf874000-0xbf88a000 rwx          0  0: 0        0 [stack] Open Files If we investigate the open files we see that file descriptors 0, 1, and 2 are set to /dev/null, 3 is not present, and 4 and 5 are sockets. This is also fairly strange output as socket file descriptors are normally either dup'ed over the https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 4 of 22 inital 0, 1, and 2 file descriptors.  In the binary analysis portion of this blog post we will see how these file descriptors are set. # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_lsof -p 1319 Volatile Systems Volatility Framework 2.2 Pid      FD       Path -------- -------- ---- 1319        0    /dev/null 1319        1    /dev/null 1319        2    /dev/null 1319        4   socket:[4441] 1319        5   socket:[4442] Netstat Next, we see that the open sockets actually connected over localhost to each other: # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_netstat TCP      127.0.0.1:40719 127.0.0.1:50271 ESTABLISHED             Xnest/1319 TCP      127.0.0.1:50271 127.0.0.1:40719 ESTABLISHED             Xnest/1319 Again, this is quite abnormal… Dmesg # python vol.py --profile=Linuxthisx86 -f before-blog-post.lime linux_dmesg > dmesg_before Volatile Systems Volatility Framework 2.2 # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_dmesg > dmesg_after Volatile Systems Volatility Framework 2.2 # diff dmesg_before dmesg_after 1150a1151,1159 > <6>[ 1573.826831] Program Xnest tried to access /dev/mem between 0->8000000. https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 5 of 22 > <4>[ 1576.063304] Xnest:1297 map pfn RAM range req write-back for 0-8000000, got uncached-minus > <4>[ 1613.438913] [LiME] Parameters > <4>[ 1613.438921] [LiME]   PATH: after-blog-post.lime > <4>[ 1613.438925] [LiME]   DIO: 1 > <4>[ 1613.438928] [LiME]   FORMAT: lime > <4>[ 1613.438931] [LiME] Initilizing Disk... > <4>[ 1613.454205] [LiME] Direct IO may not be supported on this file system. Retrying. > <4>[ 1613.454217] [LiME] Direct IO Disabled In this output, we can see in the entries that the Xnest binary tried to access /dev/mem. The next line shows us the PID of Xnest, which the attentive reader will notice is different than the one we investigated with the previous plugins ;) Loaded Modules Investigating the loaded modules between memory captures produces interesting results.  First, there is no difference between the two. This means that any active kernel module loaded by the rootkit would have to be hidden, but the check_modules plugin also reports no hidden modules. We will see in the binary analysis part why we cannot find any modules related to the rootkit. # python vol.py --profile=Linuxthisx86 -f before-blog-post.lime linux_lsmod > lsmod_before Volatile Systems Volatility Framework 2.2 # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_lsmod > lsmod_after Volatile Systems Volatility Framework 2.2 # diff lsmod_after lsmod_before # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_check_modules Volatile Systems Volatility Framework 2.2 Module Name ----------- Looking for Hooked Kernel Structures As we have seen in previous MoVP rootkits, P2 hooks tcp4_seq_afino. This allows for trivially hiding network connections from userland. https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 6 of 22 # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_check_afinfo Volatile Systems Volatility Framework 2.2 Symbol Name          Member      Address -------------------- ----------- ---------- tcp4_seq_afinfo      show        0xf7c7f000 We then see that P2 hooks a wide range of system calls: # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_check_syscall > p2syscalls Volatile Systems Volatility Framework 2.2 # grep HOOKED p2syscalls 32bit             0x3 0xf7c4f000 HOOKED 32bit             0x4 0xf7c53000 HOOKED 32bit             0x5 0xf7c40000 HOOKED 32bit             0xa 0xf7c57000 HOOKED 32bit             0xc 0xf7c3d000 HOOKED 32bit            0x25 0xf7c4c000 HOOKED 32bit            0x27 0xf7c6a000 HOOKED 32bit            0x53 0xf7c67000 HOOKED 32bit            0x60 0xf7c43000 HOOKED 32bit            0x66 0xf7c7c000 HOOKED 32bit            0x6a 0xf7c73000 HOOKED 32bit            0x6b 0xf7c70000 HOOKED 32bit            0x84 0xf7c5a000 HOOKED 32bit            0x8d 0xf7c3a000 HOOKED 32bit            0xc3 0xf7c79000 HOOKED 32bit            0xc4 0xf7c76000 HOOKED 32bit            0xdc 0xf7c37000 HOOKED https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 7 of 22 32bit           0x127 0xf7c64000 HOOKED 32bit           0x128 0xf7c6d000 HOOKED 32bit           0x12d 0xf7c61000 HOOKED To determine exactly which system calls are hooked, I modified the plugin to print the decimal form of only the hooked system calls. I then used this numbers to grep on unistd: # grep -wf hooked-syscalls /usr/include/asm/unistd_32.h #define __NR_read                 3 #define __NR_write                4 #define __NR_open                 5 #define __NR_unlink              10 #define __NR_chdir               12 #define __NR_kill                37 #define __NR_mkdir               39 #define __NR_symlink             83 #define __NR_getpriority         96 #define __NR_socketcall         102 #define __NR_stat               106 #define __NR_lstat              107 #define __NR_getpgid            132 #define __NR_getdents           141 #define __NR_stat64             195 #define __NR_lstat64            196 #define __NR_getdents64         220 #define __NR_openat             295 #define __NR_mkdirat            296 #define __NR_unlinkat           301 https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 8 of 22 This tells us exactly which system calls are hooked and we can guess some of the rootkit’s functionality based on this. Dynamic & Binary Analysis The Loaded Module I started the analysis process by looking at the strings of the binary in hopes to find something interesting to target analysis on. The first thing I noticed was: rm dummy.ko helper.ko p2.ko From there I grepped for kernel modules and found: # grep \.ko strings UN=`uname -r`;cp `find /lib/modules/$UN -name dummy.ko` . could not find dummy.ko helper.ko ld -r helper.ko dummy.ko -o p2.ko readelf -s p2.ko|grep ' init_module'|awk '{print $1}' readelf -S p2.ko|grep symtab|awk '{print $5}' insmod p2.ko 2>&1 rm dummy.ko helper.ko p2.ko which showed quite a few interesting entries. From the output we can that P2 attempts to find the dummy network interface kernel module, and then links another module helper.ko with dummy.ko to produce p2.ko. This module (p2.ko) is eventually loaded and then the three modules are deleted. At this point I wanted to get helper.ko  and p2.ko so that I could reverse them to determine what in-kernel features P2 had. I was also intrigued as I did not see any calls to rmmod in the strings, although it could just have simply been obfuscated. My first thought was to recover the deleted modules with the Sleuthkit, but unfortunately they were unrecoverable each time I tried. I then decided that I could simply get rid of the call to rm and they would stay on disk. For this, I made a copy of the rootkit and changed rm to aa (a non-existent command) so that the loading would error out. I then loaded the rootkit and was able to get the three kernel modules after the loading process aborted on attempting to execute the aa command. I first examined helper.ko and saw that it only referenced two functions: 5: 00000000    48 FUNC    LOCAL  DEFAULT    1 __memcpy https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 9 of 22 9: 00000000   142 FUNC    GLOBAL DEFAULT    4 module_helper Investigation of module_helper inside of p2.ko (remember they are linked) showed that it was a fairly small function: If you examine helper.ko’s version of this function, the kernel address references 0xCxxxxxxx in p2.ko, are actually stored as 0xcacacaca, and are later fixed up with the addresses from the kernel that is going to be infected. The rootkit relies on determining the address of devmem_is_allowed and set_memory_rw for the current kernel.   After gathering these addresses, the rootkit then marks the page for devmem_is_allowed writeable, and uses memcpy to overwrite the function. This code that is used to overwrite is declared in the beginning of the function starting with mov [ebp+hook_opcodes], 55h. If you disassemble these opcodes you will see: # perl -e 'print "\x55\x89\xE5\xB8\x01\x00\x00\x00\x5d\xc3"' > bin2 https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 10 of 22 # ndisasm -b32 bin2 00000000  55             push ebp 00000001  89E5           mov ebp,esp 00000003  B801000000     mov eax,0x1 00000008  5D             pop ebp 00000009  C3             ret This function simply returns 1, which allows all addresses to be accessed. The outcome of this hooking is that the protections of /dev/mem are disabled and the full range of physical memory can be written to and read.  If you remember from the output of running P2, we saw this line: “; mmap failed..bypassing /dev/mem restrictions” And now we know what it is referring to. You may also remember that the Volatility Linux plugins could not find any loadable kernel modules even though we are clearly seeing p2.ko being loaded into the system. The reason for this is evidence in the last few instructions of init_module: mov  eax,0xfffffffd leave ret Since eax is used as the return value, this is the equivalent of “return -3” in C code. Any negative return value from an init_module function signifies an error to the LKM loader and will force it to unload the beginning pieces of the module and stop processing it. This has the effect of letting P2 disable /dev/mem protections without ever fully loading a kernel module; hence why we cannot find traces of it. Already Injected? When testing what P2 would do if you tried to inject it on an already infected system, I got the output from above about it being already injected. I then wanted to understand how P2 knew the system was already infected. For this, I grepped the strings output for the userland binary for “injected”: # grep -i injected strings [1;40minjected /dev/shm/%s.injected already injected? # grep shm strings https://volatility-labs.blogspot.com/2012/10/phalanx-2-revealed-using-volatility-to.html Page 11 of 22 /dev/shm/.... ; rm /dev/shm/.... and try again /dev/shm/%s.injected The /dev/shm line stood out quite a bit in this output as /dev/shm is always a tmpfs filesystem and rootkits often write to this directory as it does not persist across reboots. To determine what this injected file was, I used the tmpfs plugin to recover /dev/shm. # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_tmpfs -L Volatile Systems Volatility Framework 2.2 1 -> /dev/shm 2 -> /lib/init/rw # python vol.py --profile=Linuxthisx86 -f after-blog-post.lime linux_tmpfs -S 1 -D tmpfs Volatile Systems Volatility Framework 2.2 # ls -lR tmpfs tmpfs: total 0 -rw------- 1 root root 0 Oct  7  2012 XXXXXXXXXXX.injected As can be seen in the output, there is a file named as