Mientras investigo como sobreescribir el firmware de una forma sencilla y emular mips 32 v1, dejaré el avance de lo que estaba haciendo, por si alguien con mas conocimiento que yo quiere probar.
Basado en
éste post, para el firmware
CL_g7.7_100VNP0b43.bin -> 17/03/2023. Pero supongo que funciona con cualquier firmware de vomistar gpon.
Nota: Linux es requerido
Nota 2: Lo siguiente es teórico (para saciar la curiosidad)
Nota 3: No pretende ser un tutorial, mas bien un registro de avance, por lo que los detalles los pasaré de largo.
Advertencia: No intenten recompilar sin testing. Cuando el equipo quede como un pisapapeles no se podrá cargar el firmware de stock vía panel web avanzado. Es posible que el equipo siquiera encienda.
Alerta de chino mandarín
1. Desempaquetar (una vez descargado)
Código:
// binwalk CL_g7.7_100VNP0b43.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
131072 0x20000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 20497354 bytes, 1280 inodes, blocksize: 131072 bytes, created: 2023-03-17 10:00:00
20631564 0x13AD00C LZMA compressed data, properties: 0x6D, dictionary size: 4194304 bytes, uncompressed size: 4588512 bytes
El archivo consta de 3 partes. (Expresado en bytes)
Código:
Header 128kb
// 0 -> 131072
Firmware
// 131072 -> 20497354
Kernel Linux 3.4.11 comprimido con LZMA
// 20631564 -> 4588512
1.1 Header
Extracción
Bash:
dd if=CL_g7.7_100VNP0b43.bin of=header-128kb bs=1 count=131072
Contenido
Código:
6MSTC_a005ver. 2.06838GPT-2541GNAC1220245050032172933122050048032377937921524025100VNP0b6CL_g7.7_100VNP0b43%��a��:�=�B�=�2
El resto es espacio vacío. Me pregunto si contendrá algún tipo de hash para validar el firmware. (Nunca he visto que un fw mod pueda ser cargado así como así).
1.2 Firmware
Extracción
Bash:
// skip=131072 es para no sacar el header junto al firmware
dd if=CL_g7.7_100VNP0b43.bin of=fw.sfs skip=131072 bs=1 count=20497354
mkdir fw && cp fw.sfs fw/fw.sfs && cd fw
// Ingresar como root
sudo su
// Descomprime el firmware
unsquashfs fw.sfs
// avandona root
exit
// Cambia el usuario dueño a la carpeta
sudo chown -R $(whoami).$(whoami) squashfs-root
// mueve la carpeta al directorio padre
mv squashfs-root ../squashfs-root && cd ..
// elimina el directorio temporal fw, usado para realizar la extracción
rm -rf fw
Contenido
Bash:
ll squashfs-root
drwxrwxrwx 18 user user 4096 mar 17 2023 ./
drwxrwxr-x 4 user user 4096 dic 3 20:35 ../
drwxrwxr-x 2 user user 4096 mar 17 2023 app/
drwxr-xr-x 2 user user 4096 mar 17 2023 bin/
drwxrwxr-x 2 user user 4096 mar 17 2023 cfg_upgrade/
drwxrwxr-x 2 user user 4096 mar 17 2023 data/
lrwxrwxrwx 1 user user 16 mar 17 2023 debug -> sys/kernel/debug/
drwxrwxr-x 4 user user 4096 mar 17 2023 dev/
drwxrwxr-x 13 user user 4096 mar 17 2023 etc/
-rw-rw-r-- 1 user user 0 mar 17 2023 .init_enable_core
drwxrwxr-x 8 user user 4096 mar 17 2023 lib/
lrwxrwxrwx 1 user user 11 mar 17 2023 linuxrc -> bin/busybox*
drwxrwxr-x 2 user user 4096 mar 17 2023 mnt/
drwxrwxr-x 5 user user 4096 mar 17 2023 opt/
drwxrwxr-x 2 user user 4096 mar 17 2023 proc/
drwxrwxr-x 2 user user 4096 mar 17 2023 sbin/
drwxrwxr-x 3 user user 4096 mar 17 2023 sys/
lrwxrwxrwx 1 user user 8 mar 17 2023 tmp -> /var/tmp/
drwxrwxr-x 5 user user 4096 mar 17 2023 usr/
drwxrwxr-x 2 user user 4096 mar 17 2023 usrcfg/
drwxrwxr-x 3 user user 4096 mar 17 2023 var/
-rw-rw-r-- 1 user user 1524025 mar 17 2023 vmlinux.lz
drwxrwxr-x 6 user user 12288 mar 17 2023 webs/
En webs/ está el front del panel administrativo.
En etc/ está gran parte de la configuración del equipo
El backend del panel administrativo está compilado en bin/httpd
1.3 Kernel
Extracción
Bash:
dd if=CL_g7.7_100VNP0b43.bin of=kernel.lzma skip=20631564 count=4588512
xz -d kernel.lzma
Contenido
Bash:
binwalk kernel
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
3485796 0x353064 Linux kernel version 3.4.11
3503656 0x357628 gzip compressed data, maximum compression, from Unix, last modified: 1970-01-01 00:00:00 (null date)
3558479 0x364C4F Copyright string: "Copyright 1995-2005 Mark Adler "
3558816 0x364DA0 CRC32 polynomial table, big endian
3562912 0x365DA0 CRC32 polynomial table, little endian
3567023 0x366DAF Copyright string: "Copyright 1995-2005 Jean-loup Gailly "
3581684 0x36A6F4 CRC32 polynomial table, big endian
3901576 0x3B8888 xz compressed data
3995032 0x3CF598 Neighborly text, "NeighborSolicits6InDatagrams"
3995052 0x3CF5AC Neighborly text, "NeighborAdvertisementsorts"
3998783 0x3D043F Neighborly text, "neighbor %.2x%.2x.%pM lostdrType %d is not supported"
4196304 0x4007D0 CRC32 polynomial table, little endian
Con ésta pequeña introducción ya puedo dejar un registro de lo que estaba haciendo...
Había un bug localizado en el panel de diagnóstico que permitía meter un comando directo a la consola, que parcharon.
Código:
// Login al panel avanzado (usuario Support)
https://192.168.1.1:8000/main.html
En esa pantalla se podia ingresar un comando, por ejemplo (con trace route)
Se hicieron 2 parches a nivel de side. En el front cargaron validaciones
JavaScript:
function btnApply(type) {
//var loc = 'disagnostic-general.cgi?';
/* Desactivar ésto
if (document.forms[0].diagAddr.value == '') {
alert('Address should not be empty!');
return;
}
if (!isValidHostName(document.forms[0].diagAddr.value)) {
alert('Address is invalid!');
return;
}
*/
buttonControl(1);
//loc += 'diagAddr=' + document.forms[0].diagAddr.value;
//loc += '&diagTestType=' + type;
//loc += '&sessionKey=' + sessionKey;
//var code = 'location.assign("' + loc + '")';
//eval(code);
document.port_pingTest.diagAddr.value = document.forms[0].diagAddr.value;
document.port_pingTest.diagTestType.value = type;
document.port_pingTest.sessionKey.value = sessionKey;
document.port_pingTest.submit();
}
Don dicha desactivación ya se puede cargar otra cosa que no sea una url o una ip. Para hacer permanente el cambio se puede modificar el archivo webs/diagnostic.html a partir de la línea 242.
A nivel de backend, lo siguiente es mas complejo, requiere
instalar ghidra (usando snap por ej), (otros decompiladores eran de pago y no pude desensamblar para MIPS 32 V1)(con objdump para mips 32 tampoco pude).
Según lo que pude leer, éstos equipos usan
micro_httpd como servidor web debido al poco uso que tienen sus paneles administrativos (porque acme advierte sobre lo mal que funciona el software bajo alta demanda).
Ahora que ya lo hemos instalado, creamos un nuevo proyecto e importamos bin/httpd.
Damos doble clic en httpd y se abrirá una ventana nueva. Nos preguntará si queremos realizar un análisis, le decimos que si y dejamos la configuración del análisis tal cual viene. Esperamos... (en la parte derecha inferior de la pantalla aparece el status del análisis). El CPU irá a todo vapor, es normal. Luego nos preguntará si queremos ir a la función main (es indiferente).
En la ventana izquierda llamada Symbol Tree, tenemos que buscar la función "cgiSetDiagnostic".
En la pestaña de la derecha tendremos el resultado del análisis para la función.
C:
void cgiSetDiagnostic(char *param_1,int param_2)
{
char *pcVar1;
int iVar2;
char acStack_90 [128];
memset(acStack_90,0,0x80);
iVar2 = param_2;
log_log(7,"cgiSetDiagnostic",0x7ac,"dstAddr=%s type=%d",param_1,param_2);
if (param_1 != (char *)0x0) {
if (*param_1 == '\0') {
DAT_004c4560 = 0;
}
else {
pcVar1 = strpbrk(param_1,"\"<>%\\^[]`+$=\'#&\t(){}|/;-_~!@*?,");
if (pcVar1 == (char *)0x0) {
if (param_2 == 2) {
pcVar1 = "traceroute -o %s >%s&";
}
else if (param_2 == 3) {
pcVar1 = "nslookup -h %s >%s&";
}
else {
if (param_2 != 1) {
log_log(7,"cgiSetDiagnostic",0x7c3,"not start to diag (type=%d)",param_2,iVar2);
return;
}
pcVar1 = "ping %s >%s&";
}
snprintf(acStack_90,0x80,pcVar1,param_1,"/var/diagResult");
prctl_runCommandInShellBlocking(acStack_90);
DAT_004c4560 = 1;
}
}
}
return;
}
Antes de proseguir, hay que analizar la pantalla de diagnóstico en la interfáz avanzada, procedamos.
Con el dev tools abierto (F12) hacemos un traceroute a 1.1.1.1 como en la foto.
En la pestaña networks veremos lo siguiente al cabo de un rato.
Para el endpoint disagnostic-general.cgi veremos 3 parámetros que viajan vía HTTP POST. El sessionKey es recalculado en cada petición. diagTestType es el tipo de operación a realizar (1: ping; 2: traceroute; 3: nslookup). diagAddr es el input que nos interesa.
Para construir una herramienta que permita automatizar todo, se necesita de sessionKey, por lo que si miran el response de disagnostic-general.cgi, verán que sessionKey es reescrito siempre.
Ahora podemos volver a lo interesante.
C:
pcVar1 = strpbrk(param_1,"\"<>%\\^[]`+$=\'#&\t(){}|/;-_~!@*?,");
if (pcVar1 == (char *)0x0) {
if (param_2 == 2) {
pcVar1 = "traceroute -o %s >%s&";
}
else if (param_2 == 3) {
pcVar1 = "nslookup -h %s >%s&";
}
else {
if (param_2 != 1) {
log_log(7,"cgiSetDiagnostic",0x7c3,"not start to diag (type=%d)",param_2,iVar2);
return;
}
pcVar1 = "ping %s >%s&";
}
snprintf(acStack_90,0x80,pcVar1,param_1,"/var/diagResult");
prctl_runCommandInShellBlocking(acStack_90);
DAT_004c4560 = 1;
}
Código:
\"<>%\\^[]`+$=\'#&\t(){}|/;-_~!@*?,
Si se fijan en el código, todo lo de arriba es lo que no puede ir en el parámetro diagAddr, de lo contrario el if principal de mas arriba no se ejecutará (a grandes rasgos).
Los siguientes son los templates de ejecución, en el primer %s es en donde se insertaría diagAddr. En el segundo %s iría "/var/diagResult" (donde se guardaría el resultado de la ejecución).
traceroute
nslookup
ping
Lo que nos lleva a
pingTestResult.html siempre nos va a devolver el contenido de /var/diagResult.
Si pudieramos convertir el código en
C:
pcVar1 = strpbrk(param_1,"\"<>%\\^[]`+$=\'#&\t(){}|/;-_~!@*?,");
// siempre en True
if (1) { // se ejecuta siempre
if (param_2 == 2) { // Punto de ingreso
pcVar1 = "traceroute -o %s >%s&";
}
else if (param_2 == 3) {
pcVar1 = "nslookup -h %s >%s&";
}
else {
if (param_2 != 1) {
log_log(7,"cgiSetDiagnostic",0x7c3,"not start to diag (type=%d)",param_2,iVar2);
return;
}
pcVar1 = "ping %s >%s&";
}
snprintf(acStack_90,0x80,pcVar1,param_1,"/var/diagResult");
prctl_runCommandInShellBlocking(acStack_90);
DAT_004c4560 = 1;
}
Por lo que nuestro traceroute se convertiría en
Código:
traceroute -o 0\\\;echo$IFS"test!" >/var/diagResult&
Tendríamos una forma de ejecución remota de comandos en el router. Para que eso suceda hay que trabajar en ghidra.
La instrucción bne debe ser reemplazada por un j a la dirección 00419508, de forma que siempre se ingrese a "// Punto de ingreso" (mirar el código de mas arriba).
El bloque que deberíamos conseguir ejecutar es el siguiente:
Hasta aquí por ahora.
Para leer mas sobre la arquitectura mips
acá, y
acá.