Skip to content
KitploitKITPLOIT
HerramientasBlog
Log in
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Tenda-Router-VR-and-Exploit — Informe sobre el re-hosting del firmware del router Tenda AC15 y la replicación del exploit de ejecución remota de comandos (CVE-2020-10987). | Kitploit
Herramientas/GitHubGitHub/jaden-bowers/tenda-router-vr-and-exploit
Seguridad de Sistemas EmbebidosSeguridad IoTAnálisis de VulnerabilidadesExplotaciónIngeniería InversaPruebas de PenetraciónSeguridad de HardwareAprendizaje y EducaciónAnálisis de Firmware

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Explotación de Binarios
Labs y Práctica
GitHubjaden-bowers/tenda-router-vr-and-exploit

Tenda-Router-VR-and-Exploit

Informe sobre el re-hosting del firmware del router Tenda AC15 y la replicación del exploit de ejecución remota de comandos (CVE-2020-10987).

Ver Repositorio
121hace 10 mesesAún no revisado

Tenda-Router-VR-and-Exploit

Esta publicación muestra exactamente cómo emulé el servidor web del firmware AC15 (V15.03.05.19) con QEMU, lo hice accesible desde el navegador del host y ejercité el manejador vulnerable /goform/setUsbUnload (CVE-2020-10987) para obtener ejecución de comandos dentro del rootfs emulado.


Overview

  • Extraer (o en nuestro caso obtener) el sistema de archivos squashfs de la imagen del firmware y empezar a hacer ingeniería inversa
  • Construir una invitada Debian armhf que se ejecute bajo qemu-system-arm
  • Configurar la red de la invitada y la ruta de reenvío de puertos hacia el router emulado
  • Crear nuestro script de arranque
  • Explotar el router emulado

Extracción del sistema de archivos

Primero necesitamos obtener nuestra imagen de firmware. No pude encontrar la descarga de la imagen para AC15 V15.03.05.19, sin embargo pude encontrar un repositorio de GitHub con el sistema de archivos squashfs ya extraído. Si tuviéramos el archivo de imagen correcto, podríamos extraerlo usando binwalk de la siguiente manera,``` 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

Como no tenemos el archivo de imagen correcto, pero sí un repositorio con el sistema de archivos ya extraído, podemos simplemente clonar el repositorio.```
git clone https://github.com/lapinpt/Tenda-AC15-Firmware-V15.03.05.19-9061

He creado un directorio llamado VR y clonado este repo dentro de él. Mi rootfs está en $HOME/VR/Tenda-AC15-Firmware-V15.03.05.19-9061/rootfs

Genial, ahora tenemos nuestro firmware AC15 V15.03.05.19. Pasemos a algo de reversing para ver qué está pasando.

Ingeniería inversa

Usaré Ghidra 11.4.2 para mi reversing. Naveguemos hasta nuestro binario objetivo en rootfs/bin/httpd y carguémoslo en Ghidra. Si vamos a la función formsetUsbUnload podemos ver,```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;

Esta es la vulnerabilidad. El parámetro `deviceName` se pasa directamente a `doSystemCmd`, lo que nos permite enviar los comandos que queramos.

Ahora, dado que vamos a rehospedar este firmware usando qemu y no el hardware original del router, algunos programas van a intentar alcanzar dispositivos que no existen y provocarán un fallo en nuestro arranque.
Como nuestro objetivo es explotar el servidor web (`httpd`), me centré únicamente en rehospedar ese binario y no todo el arranque (`/rootfs/etc_ro/init.d/rcS`). _En retrospectiva, no estoy seguro de que esa fuera la decisión correcta_

Así que al revisar `rcS`, quería encontrar cualquier cosa que `rcS` pudiera hacer y que `httpd` necesitara. Hacia el final del archivo podemos ver:```bash
cfmd &
echo '' > /proc/sys/kernel/hotplug
udevd &
logserver &

rcS inicia cfmd en segundo plano justo antes de que el resto de la pila se ponga en marcha. Tras investigar un poco más y observar cómo la función vulnerable envía el comando, llegué a la conclusión de que:

  • httpd construye un cfm post
  • Luego el cliente cfm se comunica con cfmd a través de un socket de dominio UNIX (p. ej. /var/cfm_socket)

En la rutina InitServer de cfmd podemos ver```C unlink("/var/cfm_socket"); strncpy(sa_unix.sun_path, "/var/cfm_socket", ...); bind(fd, (sockaddr*)&sa_unix, 0x6e); listen(fd, 5);

Crea un socket UIX y escucha.
En general, cómo funciona es que el handler lee una trama fija de 0x7e0 bytes de cada cliente (`RecvMsg`/`SendMsg`). Los primeros 4 bytes son un código de comando; luego hay un buffer de clave de 512 bytes y un buffer de valor de 1500 bytes (puedes ver los tamaños de los objetos de pila en el handler). Cambia según el opcode y responde con un código ACK:
- `2` -> Get: `GetCfmValue(key, value)` y luego responde con código `3`
- `0` -> Set: `SetCfmValue(key, value)` y luego responde con código `1`
- `0x11` -> Unset: `UnSetCfmValue(key)` y luego responde con código `0x12`
- `10` -> Commit: `SaveCfm2Flash()` y luego responde `0x10` (OK) o `0xB` (error)

Así que para emular `cfmd` creé un script corto `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;
}

Mostraré cómo compilar esto después de la siguiente parte.

El siguiente archivo auxiliar es hooks.so. Hay algunas funciones que se usan en httpd y cfm que intentan interactuar con hardware inexistente. Lo siguiente es lo que nuestro programa asume al iniciar:

  • Existen particiones Flash + MTD y son montables.
  • Existe un dispositivo NVRAM (/dev/nvram) y devuelve valores predeterminados razonables.
  • Varias rutinas de plataforma tienen éxito (cargador de ajustes de capa 7, restauración de potencia de RF, etc.).

Las siguientes funciones se convierten en un problema y por lo tanto tenemos que parchearlas con LD_PRELOAD=/hooks.so.

  • get_flash_type() -> si devuelve 4, el código toma una ruta basada en archivos (cfm_file_init); de lo contrario, intenta hablar con MTD (que no tenemos).
  • get_cfm_blk_size_from_cache() (y/o la variante j_get_cfm_blk_size_from_cache) se consulta para el tamaño de bloques de configuración.
  • Las llamadas a los shims de NVRAM de Broadcom (bcm_nvram_get) no deben fallar o la pila asume “NVRAM destruida” y entra en la lógica de restauración/reinicio.
  • Se espera que rutinas adicionales como load_l7setting_file() y restore_power() tengan éxito, pero tocan hardware/archivos inexistentes.

Aquí está nuestro hooks.c. Crédito a azeria-labs por el 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 }

Descargar herramienta