Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Tenda-Router-VR-and-Exploit — Tenda AC15 라우터 펌웨어 재호스팅 및 원격 명령 실행 (CVE-2020-10987) 익스플로잇 재현에 대한 분석 보고서 | Kitploit
도구/GitHubGitHub/jaden-bowers/tenda-router-vr-and-exploit
Embedded Systems SecurityIoT SecurityVulnerability AnalysisExploitationReverse EngineeringPenetration TestingHardware SecurityLearning & EducationFirmware Analysis

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
Binary Exploitation
Labs & Practice
GitHubjaden-bowers/tenda-router-vr-and-exploit

Tenda-Router-VR-and-Exploit

Tenda AC15 라우터 펌웨어 재호스팅 및 원격 명령 실행 (CVE-2020-10987) 익스플로잇 재현에 대한 분석 보고서

저장소 보기
11910개월 전아직 검토되지 않음

Tenda-Router-VR-and-Exploit

이 글은 AC15 (V15.03.05.19) 펌웨어의 웹서버를 QEMU로 에뮬레이션하고, 호스트 브라우저에서 접근 가능하게 만든 후, 취약한 /goform/setUsbUnload 핸들러(CVE-2020-10987)를 실행하여 에뮬레이션된 rootfs 내에서 명령어 실행을 얻는 과정을 설명합니다.


개요

  • 펌웨어 이미지에서 squashfs 파일 시스템을 추출(또는 획득)하고 리버싱 시작
  • qemu-system-arm에서 실행되는 Debian armhf 게스트 구축
  • 게스트 네트워킹 및 에뮬레이션된 라우터로의 포트 포워딩 경로 설정
  • 부트 스크립트 작성
  • 에뮬레이션된 라우터 익스플로잇

파일 시스템 추출

먼저 펌웨어 이미지를 가져와야 합니다. AC15 V15.03.05.19에 대한 이미지 다운로드를 찾을 수 없었지만, 이미 추출된 squashfs 파일 시스템이 있는 GitHub 저장소를 찾을 수 있었습니다. 올바른 이미지 파일이 있었다면 binwalk를 사용하여 다음과 같이 추출할 수 있었을 것입니다.``` user@computer $ binwalk -e AC15_V15.03.05.19.bin

DECIMAL HEXADECIMAL DESCRIPTION

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 펌웨어를 확보했습니다. 리버싱을 통해 내부를 살펴보겠습니다.

Reverse Engineering

리버싱에는 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에서 사용되는 몇몇 함수들이 존재하지 않는 하드웨어와 상호작용하려고 시도합니다. 다음은 프로그램이 시작할 때 가정하는 사항입니다.

  • 플래시 + MTD 파티션이 존재하고 마운트 가능합니다.
  • NVRAM 장치(/dev/nvram)가 존재하며 적절한 기본값을 반환합니다.
  • 기타 플랫폼 루틴이 성공합니다 (Layer-7 설정 로더, RF 전원 복원 등).

다음 함수들이 문제가 되므로 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)는 설정 블록 크기 계산에 사용됩니다.
  • Broadcom NVRAM shim(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`에 만들었습니다.
도구 다운로드