- Discord: https://discord.gg/wkWr6Dj46D
- Telegram: https://t.me/hardwarehackinges
- Fundamentos Teóricos
- Hacking EEPROM AT24C256 I2C 5V
- Hacking FLASH SPI Winbond 25Q64FVSIG
- Hacking Router TP-Link TL-WR841N
- Hacking SD Transcend TS2GUSD
- Emulación de una firmware con Emux
- CTF Hardware Hacking RootedCon 2024
- Otra información a tener en cuenta
- Webs, libros, recursos, a quien seguir...
Básicamente, y en fácil, intentar modificar el comportamiento de un cacharro para que haga algo que yo quiera que haga, esté dentro de su diseño inicial o no.
Para conseguir tal objetivo tendremos que acceder hasta su hardware de alguna forma, entender qué tiene y cómo está montado, para luego conseguir extraer su firmware y modificarlo.
La modificación que quiera hacer puede ser tan sencilla como cambiar algo de su firmware hasta implicar una modificación del hardware físico (desoldar y soldar otros componentes) para cambiar sus capacidades.
En este repositorio vamos a intentar cubrir algunos casos prácticos para irnos introduciendo en esto del hardware hacking y sus técnicas, más como ejemplos sencillitos para ir aprendiendo las técnicas que con ningún otro objetivo.
Un disclaimer importante es que no se van a realizar técnicas ilegales o vinculadas con el imaginario popular de lo que se cree que es el hacking: no vamos a hackear el whatsapp de tu novia ni vamos a atacar a ninguna entidad gubernamental. Ésto va más en la línea de entender cacharros para poderles sacar capacidades.
-
Ganas de estudiar y aprender: No hay una forma directa y sencilla de adquirir conocimiento sobre esto, normalmente necesitarás estudiar esquemáticos, leer documentación técnica y pasar muchas horas intentando entender qué carajos estás haciendo. Esto es parte de la diversión. Aún así, partiremos de que tienes un nivel básico de conocimientos teóricos de electricidad y electrónica. Más o menos lo que te enseñan en el bachillerato tecnológico bastaría (sistema educativo español).
-
Material de soldadura: No te tienes que gastar una millonada, pero sí hay una serie de cosas que son básicas y que vas a necesitar. Hay miles de guías y listas de materiales que podrías seguir, pero todo se resume en:
- Soldador de estaño y desoldador. Con el desoldador en un principio te basta con una pipeta desoldadora, en un futuro ya podrás pensar si invertir en un desoldador de verdad
- Estaño (que sea de buena calidad)
- Flux (lo mismo que el estaño, gástate algo más en algo de buena calidad)
- Pines macho y hembra
- Cables de electrónica (los reconocerás porque son muy finitos y de colorines)
Un añadido opcional pero interesantísimo es un microscopio para electrónica. En AliExpress se pueden encontrar algunos bastante decentes por un presupuesto bastante ajustado. No te precipites y compres algo de menos de 50€, es mejor esperar, ahorrar ese poquito y gastarte entre 50 y 70€ en un microscopio que te va a servir durante muchísimo tiempo.
-
Cacharrada: Llamaremos cacharrada a todo aquello que nos ayudará a extraer el firmware y a manipularlo. Para conseguir el objetivo de llegar hasta el firmware de casi cualquier cosa nos bastará con:
Cuando empecemos a trabajar con cacharretes a los que les queramos sacar los firmwares, tenemos que entender cuál es su forma de comunicación.
Un genérico en la comprensión de cómo funcionan los protocolos de datos es que siempre tendremos un orquestrador (Master) y un esclavo (Slave). Ambos pueden estar emitiendo y recibiendo datos, pero habrá una relación de control del Master sobre el Slave.
A medida que vayamos trabajando con más y más cacharros en las demostraciones prácticas iremos añadiendo las especificaciones de cada uno de los protocolos.
- SPI
- JTAG
- 1-wire
- I2C
- KB
- UART
En I2C tenemos, como particularidad, que podemos conectar múltiples Esclavos a múltiples Masters y viceversa. Esto nos da la flexibilidad de poder tener múltiples microcontroladores escribiendo en una misma SD o mostrando los datos a una misma salida.
I2C solamente usa dos cables para los datos:
- SDA (Serial Data): línea para el intercambio bidireccional de datos entre Esclavo y Master
- SCL (Serial Clock): línea para la señal del reloj
I2C forma parte de la familia de los protocolos seriales, con lo que la información se transmite a través de un cable bit a bit.
En la siguiente imagen podemos ver las especificaciones técnicas del protocolo I2C:
- Start Condition: La línea SDA pasa de un alto voltaje a un bajo voltaje, y a continuación la línea SCL hace lo mismo
- Address Frame: Una secuencia de 7 o 10 bits única para cada esclavo que le identifica al máster
- Read/Write bit: Un único bit que indica si el máster envía o pide datos al esclavo
- ACK/NACK: Cada mensaje está finalizado con una señal de acknowledge/no-acknowledge, que confirma que se ha recibido o no correctamente la información. El ACK se devuelve de vuelta hacia el emisor para confirmar que el receptor ha recibido el mensaje
- Stop Condition: La línea SDA pasa de un bajo voltaje a un alto voltaje, y a continuación la línea SCL hace lo mismo
Un Analizador Lógico nos permite extraer las señales digitales que emita el sistema que estamos analizando a través de un osciloscopio. El análisis que haremos con un analizador lógico lo podríamos hacer a través de un osciloscopio clásico, pero hoy existen cacharritos como este que nos permiten analizarlo con nuestro ordenador a través de su interfaz. La información que extraigamos de este análisis nos permitirá posteriormente volcarnos el firmware del sistema analizado.
La documentación oficial del producto que recomendamos está aquí, pero vamos a intentar hacer un resumen de lo más importante.
El Analizador que usamos, proporcionado por AZ-Delivery, está basado en Arduino, lo que hace que ya partamos de un sistema con muchísima comunidad, que como iréis viendo, es algo que nos agrada especialmente.
Para iniciar con el cacharrito, accedemos a la página de descargas del producto y nos descargamos su software. Este paso no tiene demasiado interés, download & install as usual. El software no tiene más configuraciones que hacer.
Los protocolos más habituales para Arduino son conexión serial, SPI e I2C; pero el analizador que tenemos entre manos admite todos estos protocolos: SPI, IIC, UART, SMBus, I2S, CAN, Parallel, Custom, Search, Async, 1-Wite, PS/2. Para nuestro nivel, básicamente lo admite todo.
Nos centramos en el BusPirate v3 en concreto, a pesar de que hay actualizaciones posteriores, por varios motivos:
- Es económico. Hoy en día (finales del 2023) no cuesta más allá de 40€ el propio cacharro y sus pinzas.
- Lleva mucho tiempo en el mercado, hay mucha gente usándolo, con lo que está mantenido y hay mucha documentación al respecto (incluída esta que estás leyendo). A más comunidad alrededor de un cacharrete, más mantenible será en el futuro, y más pequeña es su curva de entrada.
- Tiene muchísimas funcionalidades. Esto es consecuencia directa del punto 2, porque se ha ido evolucionando a medida que más gente ha ido usándolo y ampliando su uso.
Para saber exactamente qué tenemos entre manos, puedes acceder a su documentación aquí, o puedes seguir leyendo. Haremos un resumen de sus funcionalidades centrado en los casos de uso que vamos a ver.
ADVERTENCIA: Las máquinas virtuales (VM) y/o USB HUBs pueden causar problemas. Es recomendable utilizar SIEMPRE un sistema operativo nativo y conectar el Bus Pirate directamente a un puerto USB. Si la consola con Bus Pirate / flashrom / asprogrammer dregmod... se congela (~2 minutos sin salida)/bloquea: cierra este programa, vuelve a conectar el puerto USB y vuelve a intentarlo.
ADVERTENCIA: Recomiendo utilizar como máximo una velocidad de 100kHz aproximadamente para cada protocolo, ya que la calidad de los cables es importante y no me fío de que la longitud del tuyo sea corta, sobre todo si hay un adaptador a clips, etc. Cuanto más largo sea el cable y más adaptadores haya, junto con un voltaje más bajo, necesitarás usar una velocidad más baja. Así que configura el flashrom o cualquier otro software que utilices para usar la velocidad que sea más conveniente.
ADVERTENCIA: Algunos firmwares tienen un error en el modo SPI binario (como el último firmware comunitario) y causan problemas con flashrom y otras herramientas. Recomiendo usar el último firmware de Buzzpirat para Bus Pirate v3: https://buzzpirat.com/docs/firmware-update/
ADVERTENCIA: USA UN CABLE USB CORTO Y DE ALTA CALIDAD.
Recuerda que como lector puedes contribuir a seguir mejorando esta documentación haciendo un Pull Request a la documentación añadiendo todo lo que te gustaría leer.
El esquema genérico de colores para los cables es el siguiente:
La explicación de cada uno de los pines (y cables, con sus respectivos colores es):
Nombre del pin | Color del cable | Descripción genérica | SPI | JTAG | 1-Wire | I2C | KB | UART |
---|---|---|---|---|---|---|---|---|
MOSI | Gris | Master de datos de salida | Esclavo de entrada | Esclavo de entrada | Datos de serie | Datos de serie | Datos de serie | TX |
CLK | Morado | Señal de reloj | Señal de reloj | Señal de reloj | Señal de reloj | Señal de reloj | ||
MISO | Negro | Master de datos de entrada | Esclavo de salida | Esclavo de salida | RX | |||
CS | Blanco | Selector de chip | TMS | |||||
AUX | Azul | Auxiliar IO, medidor de frecuencia, modulador del ancho de pulso | ||||||
ADC | Amarillo | Medidor de voltaje (máximo 6V) | ||||||
VPU | Verde | Entrada de voltaje para las resistencias pull-up (0-5V) | ||||||
+3.3v | Rojo | Fuente de 3.3V | ||||||
+5.0v | Naranja | Fuente de 5V | ||||||
GND | Marrón | Tierra, conectar a la tierra del circuito test |
Hemos creado una serie de Modelos 3D: el propio Bus Pirate v3 y una caja para protejerlo.
También hemos dejado los ficheros stl de ambos modelos para que te los puedas imprimir en el directorio modelos3D de este repositorio (hay varias iteraciones de diseño de caja, la recomendada es la iteración 3).
Aquí encontrarás explicada, de manera sencilla, la justificación, finalidad, funcionamiento y detalles de las resistencias Pull-up y Pull-down.
Te han puesto una tarea. Debes apuntar la posición de una ficha blanca, esta ficha solo puede acabar en dos casillas, en la Posición 1, y en la Posición 0.
Es evidente que la posición de la ficha es 1. Vamos a ver el siguiente caso:
Ahora se encuentra en el 0, pero, ¿cómo ha llegado hasta ahí? La ficha ha tenido que desplazarse, eso seguro. La pregunta es, ¿qué posición le asignamos hasta que llegue a una de las casillas?
Este es uno de los problemas que se encuentras a la hora de transmitir información en la electrónica. Como solo podemos interpretar 2 estados, ¡lo que pase en la transición no lo entenderemos!
En los estados intermedios entre el 1 y el 0 de esta viñeta, la pata que se conecta a uno de los estados está en el aire, actuando como antena y captando interferencias indeseadas. A esto se le llama dejar una pata "flotando", algo que debemos evitar en las entradas de información.
Para evitar este problema, y mantener una comunicación estable, se usan las Resistencias Pull-up y Pull-down.
Bueno... Espero que no te asustes. Lo que ves ahora es el esquema de una resistencia Pull-up. Tranquilo, porque vas a entenderlo después de algunos conceptos. Primero vamos a etiquetar cada elemento:
Vamos a suponer que el voltaje o tensión es la "velocidad" de la electricidad que recorre el circuito y el amperaje o intensidad de la corriente es el caudal.
- GND: es una abreviación del término ground. Se denomina GND al punto de referencia que tomaremos como "velocidad" 0, es decir, el aparcamiento. Si te fijas, todos los extremos del circuito acaban en el GND, es decir, que antes de llegar a GND tendrán que frenar un poco...
- VCC (Voltage Common Colector): es la fuente de voltaje que genera la diferencia de tensión más alta respecto al GND. Conceptualmente, se encarga de dar "velocidad" a la electricidad.
- Resistencia: la resistencia reduce la "velocidad" de la electricidad. Cuanta más corriente pase por el circuito, la caída de "velocidad" será mayor. Si esta supuesta velocidad fuese muy alta y "chocase" con GND algo podría salir mal. Se produciría un cortocircuito.
- Interruptor: seguro que ya lo conoces, alguno debes apretar para poder leer esto... Pero si aún así no te suena lo que hace: interrumpe o deja pasar la electricidad.
- Cables: son las líneas rojas que unen cada elemento y permite que pase por él el "caudal" que requiera el circuito.
- Sentido de la corriente: es importante conocer el sentido de la corriente. Usaremos el sentido convencional, que irá del punto negativo al positivo, como muestra la flecha naranja del esquema.
Para entender los Pull-up/Pull-down no necesitarás realizar cálculos, pero es útil poder interpretar las siguientes expresiones:
-
$Voltaje=Intensidad*Resistencia$ ;$V= I * R$ -
$Potencia=Voltaje*Intensidad$ ;$P= V * I$
En la primera, nos permite calcular cuánto voltaje es capaz de retener una resistencia según la corriente que pasa por él. Es decir, la "velocidad" que es capaz de robar al flujo.
Si conociésemos el voltaje que una resistencia va a restar al circuito podríamos calcular, entonces, la intensidad de corriente que pasa por esta.
La segunda expresión nos permitirá estimar el consumo del circuito, usando el voltaje proporcionado por la fuente y la corriente requerida por los elementos que conectemos.
No olvidemos que el objetivo de todo esto es enviar información, evitando las confusiones. La información viajará de un origen, que llamamos Maestro, a un destinatario, un Esclavo.
Por suerte solo tenemos que diferenciar, como hemos dicho, dos mensajes: el 0 y el 1.
Digamos que nosotros somos el maestro y queremos enviar un 1 al esclavo. Esto podría funcionar:
Si dejamos el interruptor abierto, el 1 solo tiene un camino. Ahora vamos a intentar cerrar el interruptor.
Si no te has saltado los conceptos previos verás que aquí algo ha ido bastante mal. Como el voltaje no ha bajado antes de llegar a GND el cable empieza a actuar como resistencia para disipar la energía en forma de calor. El cable no está preparado para eso y puede alcanzar altas temperaturas. Por si te has perdido, la velocidad del 1 era muy alta antes de llegar al aparcamiento y se ha estampado contra GND. Ahora nuestro esclavo podría irse a jugar al parchís, si no se ha roto nada en el proceso.
Quizá te preguntes porqué el 1 no se ha ido directamente hacia nuestro esclavo... Bien, hay algo que no te he explicado, y es que, para llegar a nuestro esclavo hay un espacio que lo separa. La información le cuesta mucho pasar por ahí, así que prefiere pasar por el camino más fácil.
En el entorno real, las entradas de los dispositivos cuentan con una impedancia muy alta (que se opone al paso de la corriente), lo que representaremos en los esquemáticos como resistencias del valor que ronda los MΩ. El objetivo de esta impedancia es afectar en la menor medida posible al circuito.
Aclarado todo esto, ahora necesitaremos algo para reducir la velocidad. ¡Seguro que lo estás pensando! Nuestras amigas las resistencias pueden hacernos el trabajo.
Ahora, la resistencia ha conseguido frenar por completo el 1, así que como no llega nada, al esclavo se le carga un 0.
Pero, ¿qué pasará si vuelvo a abrir el interruptor? ¿se frenará el 1 sin poder llegar al esclavo? Bueno, eso es una buena pregunta...
Cuando el interruptor no deja pasar la corriente hasta GND, el único elemento que requiere de voltaje es el esclavo. El esclavo, que hemos dicho que estaba un poco separado de la línea de comunicación, produce una impedancia muy alta.
Recuerda ahora la formulita:
Si suponemos un voltaje típico de 5V y una resistencia producida por el esclavo de unos 10MΩ, la intensidad de corriente que pasa por TODO el cable es 0,0000005 A, ¡Casi nada!
A consecuencia de esta baja corriente, la caída de voltaje o "velocidad", si así lo prefieres, en la resistencia que hemos colocado al principio es muy pequeña. En el ejemplo puesto habría una caída de: $V=IR=0,000000510kΩ= 0,005 V$
El esclavo recibiría un voltaje de 4.995V, lo que interpretaría como un 1.
¡Ya entiendes como funciona el Pull-up!, pero no cantes victoria, aun queda que te explique el Pull-down.
Vamos a coger la misma estructura del Pull-up y intercambiamos las posiciones del interruptor y la resistencia de este modo:
Como podemos ver, cuando el interruptor está abierto, el esclavo solo puede interpretar un 0, mientras que si cerramos el interruptor, conseguiremos pasarle al esclavo el 1.
Los Pull-up y Pull-down sirven para guardar estados por defecto, incluso cuando no se recibe energía de ninguna fuente. Los estados por defecto son aquellos en los que se queda cuando no se recibe energía.
Tipo | Estado por defecto | Estado Activo |
---|---|---|
Pull-Up | 1 | 0 |
Pull-Down | 0 | 1 |
Para ello, miramos el esquemático del fabricante, en mi caso, la v3.6a.
Mirando el esquemático del BusPirate, vemos que en la parte dedicada a los Pull-Up hay un circuito adicional. Este nos permite habilitar o desabilitar el uso de los Pull-Up. Veamos como funciona cada una de las partes:
1. Esta parte del circuito representada con el cable naranja permite controlar a través de la linea de comandos, con el comando "P", activar los Pull-Up del circuito del color azul. En el caso de la imagen de arriba, el interruptor integrado dentro del chip del BusPirate, que usaremos de forma conceptual, está cerrado y conectado a GND, lo que garantiza un voltaje nulo y, por tanto, el interruptor entre los puntos A y B quedará abierto. Eso significa que no puede llegar de ningún modo voltaje desde VEXTERN. En caso de que el interruptor interno del BusPirate se dejase flotando, llegarían 5V al punto C, lo que cerraría el circuito azul, permitiendo el paso de la corriente que proviene de VEXTERN.
2. Conectada a una fuente externa, en este caso, de 5V, el circuito de color azul es el que nos permitirá dar una señal alta (dar un 1) o, conectando el extremo final a GND, una señal baja (dar un 0).
Ahora veamos como sería el circuito y sus conexiones con el Chip Target y el Chip del Bus Pirate cuando el interruptor de la linea naranja está abierto:
3. El cable amarillo representa a los canales de la PCB del BusPirate que conectan el Pull-Up con el pin de Chip Select (CS). En este caso, el Chip ha conectado el pin directamente a GND, lo que da un voltaje nulo (0V) en toda la linea.
4. El cable verde es la pinza que colocaremos en la pata CS del BusPirate hasta el pin CS del Chip Objetivo. En la imagen de arriba, el pin está conectado a la linea amarilla, por la que no pasa corriente. Como consecuencia, recibe un 0.
3. Ahora el BusPirate ha abierto el interruptor y la linea amarilla ya no está conectada a GND. De este modo los 5V de VEXTERN llegan desde el cable azul hasta el final.
4. Como ahora el cable amarillo tiene 5V, y el verde sigue conectado al amarillo, los 5V llegan hasta el Chip Objetivo, lo que interpretará como un 1.
Resumiendo, si el interruptor del cable amarillo está cerrado (conectado a GND): Chip Target recibe un 0.
Si el interruptor se queda flotando (no se conecta a GND ni a ningún otro Voltaje): Chip Target recibe un 1.
A este modo de funcionamiento del PIN se le denomina OPEN DRAIN.
Dato: Los protocolos I2C necesitan siempre los Pull-Up para funcionar, ya que siempre reciben 0 de GND y el 1 con el PIN flotando.
Te dejo los esquemáticos con medidas del circuito.
Importante hacer estos pasos antes de conectar el Bus Pirate
La manera más rápida y eficaz es abrir el Administrador de Dispositivos (device manager) buscándolo en el sistema.
También podemos usar la secuencia win+r escribiendo devmgmt.msc
Una vez se nos abra el administrador de dispositivos nos interesa la parte Puertos (COM y LPT).
Al desplegarlo obtendremos los puertos previos a conectar el Bus Pirate
Al conectar el Bus Pirate aparece el nuevo puerto COM que tendremos que utilizar. En el caso de la imagen USB Serial Port (COM4).
Material Requerido
Usamos este esquema para conectarnos:
schema by David Sánchez
Tiene que quedar asi:
Ahora nos conectaremos usando algun software como TeraTerm o Putty, en mi caso usare TeraTerm.
-
Lo abrimos y seleccionamos la interfaz COM correspondiente del BusPirate
-
Ahora configuramos la interfaz serial dentro de Setup->Serial Port
-
Y lo dejamos con esta configuracion de 115200 baudios de velocidad, 8 bits de datos, ninguno de paridad y uno de stop.
-
Ahora para no quedarnos ciegos vamos a la configuración de fuente y aumentamos el tamaño y lo dejamos en 14.
-
Abrimos el menu del buspirate presionando m+enter
-
Presionamos el 4 y damos enter, dos veces
-
Presionamos W mayúscula y P mayúscula y ya podriamos leer la memoria y escribir
Usamos la macro de búsqueda de direcciones de 7bit para obtener la dirección de escritura y de lectura.
Escribimos (1) y damos a enter
Con los corchetes indicamos el principio y el final de cada comando I2C.
El primer byte en hexadecimal es la dirección que indica si escribimos o leemos.
Los dos siguientes bytes son la direccion donde se va a escribir.
Los bytes 0x41 0x41 0x41 son el contenido que se escribira en la dirección selecionada de manera consecutiva.
Para leer una dirección tenemos que usar la direccion de escritura de la eeprom.
Hay que escribir la direccion para luego poder leer el contenido aunque no sobreescribamos nada.
Se sigue el mismo patrón que escribiendo pero sin Bytes de contenido.
[0xA0 0x00 0x69]
Después de seleccionar la direccion 0x69 escribimos el byte de lectura y una r para leer un byte, en el caso que queramos leer varios bytes escribimos 'r:(numero de veces)' para que sea una lectura secuencial.
[0xa1 r:20]
Tenemos flasheado el CTF de Dreg, para leer la flag de la EEPROM introducimos estos comandos:
[0xA0 0x00 0x69]
[0xa1 r:20]
Para saber mas sobre el protocolo I2C con el bus pirate lee esto.
Usamos un conversor de hexadecimal y obtenemos este resultado.
Tras comprender como se lee la memoria EEPROM, se puede automatizar todo el proceso montando un script que permita disponer de una copia coa través de una imagen o backup con los datos que contiene la misma.
Lo primero que se debe tener en cuenta a la hora de leer la información es la capacidad total que tiene, en este caso: - Un kilobyte son 1024 bytes, por lo que al ser 32 kilobytes se tendrán que extraer 32768 bytes totales.
Una forma sencilla es guardar todo el output y luego "parsear" lo que nos interesa en bruto a un fichero de tipo imagen. Abriendo dicha imagen con un editor hexadecimal se puede ver el contenido en bruto:
A continuación, adjunto el script que use:
import time
import serial
import os
# Configura el puerto COM y la velocidad de comunicación según tu configuración
COM_PORT = 'COM3'
BAUD_RATE = 115200 # Esta velocidad puede variar según tu configuración
# Dirección del dispositivo AT24C256 para lectura
EEPROM_READ_ADDRESS = 0xA1
# Número total de bloques a leer
NUM_BLOCKS = 32 # 32 bytes x 1024 = 32768
# Nombre del archivo para guardar el dump con todo el output
DUMP_FILE = 'eeprom_dump.log'
def configure_bus_pirate(ser):
# Configura el Bus Pirate para el modo I2C y la velocidad deseada (~100kHz)
ser.write(b'm\n') # Selecciona el modo I2C
time.sleep(0.1)
ser.write(b'4\n') # Modo I2C
time.sleep(0.1)
ser.write(b'1\n') # Selección de modo software
time.sleep(0.1)
ser.write(b'2\n') # Establecer velocidad ~50kHz
time.sleep(0.1)
ser.write(b'W\n') # Encender las fuentes de alimentación
time.sleep(0.1)
ser.write(b'[0xA0 0x00 0x00]\n') # Direccion de inicio para leer
def read_eeprom_dump():
try:
# Abre la conexión con el puerto COM
ser = serial.Serial(port=COM_PORT, baudrate=BAUD_RATE, timeout=1)
# Espera a que se inicialice la comunicación
time.sleep(2)
# Configura el Bus Pirate para I2C
configure_bus_pirate(ser)
# Realiza la lectura de la EEPROM y guarda los datos en el archivo
with open(DUMP_FILE, 'wb') as dump_file:
# Realiza la lectura de los datos en bloques de 64 bytes
data = ser.read(1200)
dump_file.write(data)
for _ in range(NUM_BLOCKS):
ser.write(b'[0xA1 r:1024]\n') # Envía el comando para leer 64 bytes
time.sleep(0.1)
data = ser.read(12800)
dump_file.write(data)
print('Dump guardado en:', DUMP_FILE)
except Exception as e:
print('Error:', str(e))
finally:
# Cierra la conexión
ser.close()
def parse_log_and_extract_ascii(filename, output_filename):
ascii_values = []
with open(filename, 'r') as file:
lines = file.readlines()
read_block = False # Flag para indicar que estamos dentro de un bloque de lectura
for line in lines:
if "READ:" in line:
# Si encontramos un bloque de lectura, marcamos el flag y lo procesamos
read_block = True
hex_values = line.split(" ")[1:] # Extraer los valores hexadecimales
# Filtrar los valores hexadecimales válidos y convertir a ASCII
ascii_values.append(''.join([chr(int(value, 16)) for value in hex_values if is_hex(value)]))
# Filtramos las líneas no deseadas y escribimos los valores ASCII en el archivo de salida
formatted_values = ''.join(ascii_values) # Unimos los valores sin saltos de línea
with open(output_filename, 'w') as output_file:
output_file.write(formatted_values)
print('Valores ASCII guardados en:', output_filename)
# Función para verificar si una cadena es un valor hexadecimal
def is_hex(value):
try:
int(value, 16)
return True
except ValueError:
return False
if __name__ == "__main__":
read_eeprom_dump()
parse_log_and_extract_ascii(DUMP_FILE, 'ascii_values.img')
Una vez se tiene una imagen que cuadra perfectamente con la capacidad de la memoria EEPROM (en este caso 32 KB), se puede automatizar el proceso de flasheo o escritura lo cual nos quitará dolores de cabeza si tenemos que flashear 500 :)
Conociendo el procedimiento de escritura, se pueden escribir todos los bytes iterándolos en bloques. En este caso seleccioné 21 bytes por bloque para no tener problemas con bloques mas grandes, y realicé 1560 iteraciones:
A continuación, adjunto el script que use:
import time
import serial
import os
# Configura el puerto COM y la velocidad de comunicación según tu configuración
COM_PORT = 'COM3'
BAUD_RATE = 115200 # Esta velocidad puede variar según tu configuración
# Dirección del dispositivo AT24C256 para escritura
EEPROM_WRITE_ADDRESS = 0xA0
# Número total de bloques a escribir (calculo realizado en base a bloques de 21 bytes)
NUM_BLOCKS = 1560 # 32 bytes x 1024 = 32768
# Nombre del archivo de imagen
IMG_FILE = 'eeprom.img'
def configure_bus_pirate(ser):
# Configura el Bus Pirate para el modo I2C y la velocidad deseada (~100kHz)
ser.write(b'm\n') # Selecciona el modo I2C
time.sleep(0.1)
ser.write(b'4\n') # Modo I2C
time.sleep(0.1)
ser.write(b'1\n') # Selección de modo software
time.sleep(0.1)
ser.write(b'2\n') # Establecer velocidad ~50kHz
time.sleep(0.1)
ser.write(b'W\n') # Encender las fuentes de alimentación
time.sleep(0.1)
def generate_write_commands(img_filename):
commands = []
with open(img_filename, 'rb') as img_file:
img_data = img_file.read()
# Divide los datos de la imagen en bloques de 21 bytes
blocks = [img_data[i:i + 21] for i in range(0, len(img_data), 21)]
# Genera los comandos de escritura para cada bloque
for i, block in enumerate(blocks, start=0):
# Dirección de memoria para escribir
address_high = (i * 21) >> 8 # Segundo byte de la dirección
address_low = (i * 21) & 0xFF # Tercer byte de la dirección
command = f'[0xA0 0x{address_high:02X} 0x{address_low:02X} ' + ' '.join([f'0x{byte:02X}' for byte in block]) + ']\n'
commands.append(command)
return commands
def send_commands(ser, commands):
for i, command in enumerate(commands, start=0):
print(f"Escribiendo bloque {i}: {command.strip()}")
ser.write(command.encode())
time.sleep(0.1) # Espera para que el comando se ejecute correctamente
if __name__ == "__main__":
try:
# Abre la conexión con el puerto COM
ser = serial.Serial(port=COM_PORT, baudrate=BAUD_RATE, timeout=1)
# Espera a que se inicialice la comunicación
time.sleep(2)
# Configura el Bus Pirate para I2C
configure_bus_pirate(ser)
# Genera los comandos de escritura
commands = generate_write_commands(IMG_FILE)
# Envía los comandos de escritura
send_commands(ser, commands)
except Exception as e:
print('Error:', str(e))
finally:
# Cierra la conexión
ser.close()
Material Requerido:
Usamos este esquema para conectarnos:
schema by David Sánchez
Tiene que quedar asi:
El modelo es W25Q64FVSIG aunque en la herramienta del flashrom indicaremos que es la W25Q64JV-.Q y funcionara correctamente.
Descargamos el ultimo release del flashrom desde este repositorio.
Conectamos el bus pirate y revisamos el numero del puerto COM.
Descomprimimos y abrimos una terminal en el mismo directorio y ejecutamos este comando escribiendo el puerto COM correspondiente.
flashrom.exe --progress -V -c "W25Q64JV-.Q" -p buspirate_spi:dev=COM6 -r flash_contenido.img
Si no funciona reconecta el buspirate y ejecuta este comando:
flashrom.exe --progress -V -c "W25Q64BV/W25Q64CV/W25Q64FV" -p buspirate_spi:dev=COM6,spispeed=250k,serialspeed=115200 -r flash_contenido.img
Obtendremos este archivo:
Así es como se conectaría el analizador lógico a la memoria flash y al bus pirate:
Primero configuramos el buspirate.
Ahora ya estamos listos, momento de activar la captura del analizador lógico.
Pero primero hay que entender como leer las capturas
Los comandos del datasheet resaltados serán los que usaremos:
Esto sera el orden de introducción.
[0x06]
[0x05 r:1]
[0x20 0x00 0x00 0x00]
[0x03 0x00 0x00 0x00 r:256]
[0x06]
[0x05 r:1]
[0x02 0x00 0x00 0x00 0x41 0x42 0x43]
[0x03 0x00 0x00 0x00 r:256]
[0x06]
[0x05 r:1]
[0x02 0x00 0x00 0x00 0x41:255]
[0x03 0x00 0x00 0x00 r:256]
Ejecutamos [0x06] para habilitar la escritura y le ponemos los corchetes entremedias para que con el que abre ponga el CS en activo es decir en estado bajo y que cuando introduzca el 0x06 lo vuelva a poner en alto para deshabilitarlo.
En la imagen se puede ver la correspondecia de los pulsos de reloj(CLK) con el de los bits.
Si mandamos un 0xAA se mandara 10101010:
Si mandamos un byte 11111111 el MOSI estara a 3,3V todos los ciclos de reloj:
Si mandamos un 0xF0 pondra los primeros 4 pulsos de reloj a 1 y los otros 4 a 0:
Si mandamos un 0x00 en los 8 pulsos de reloj el bit de MOSI estara puesto en 0:
Estos serian los bits del Registro de Estado 1, hay que comprobar si el S1 esta activo.
Para comprobar que la escritura esta habilitada y todo ha ido correctamente ejecutamos [0x05 r:1] para leerlo.
Esta seria la instrucción a nivel lógico:
Y nos devuelve un 2 que quiere decir que esta habilitada:
Ahora procedemos a borrar la pagina 0x000000 con el comando [0x20 0x00 0x00 0x00]
El primer byte es la instruccion y los otros 3 la dirección.
Primero el corchete abierto indica bajar el cs y seleccionarlo, después se manda por el MOSI el comando y la dirección , después se manda el corchete cerrado para levantar el CS y desactivarlo.
Ahora leemos la dirección 0x000000 con el comando [0x03 0x00 0x00 0x00 r:256]
El uso de los corchetes es igual siempre, el 0x03 marca la instruccion y los tres bytes de 00 la dirección, después el r:256 es para repetir la acción de lectura 256 veces.
Asi se vería el comando y la respuesta:
Al haber borrado previamente todo este sector hemos puesto todos los bytes en FF.
Ahora volvemos a hablitar la escritura con la orden [0x06]
Y releemos el registro de estado para comprobar que esta hablitada la escritura, recordad hacerlo siempre para evitar errores.
Y efectivamente la escritura esta habilitada.
Ahora usaremos la instrucción Page Program (0x02) para escribir en la pagina 0x000000 y escribiremos los 3 primeros bytes con la cadena en ASCII "ABC", el comando es [0x02 0x00 0x00 0x00 0x41 0x42 0x43].
El primer byte es la instrucción, los 3 siguientes la dirección y el resto los bytes a escribir.
Solo podemos escribir desde 1 byte hasta 256 que es el tamaño de la pagina de esta flash. Si escribimos mas, sobrescribirá el principio de la pagina.
Ahora leemos la pagina 0x000000 con el comando [0x03 0x00 0x00 0x00 r:256]
Tras leer podemos ver como los 3 primeros bytes de la pagina 0x000000 estan escritos con 0x41 0x42 0x43 y el resto de bytes están a 0xFF es decir vacíos.
Ahora llenaremos toda la pagina con 0x41(A).
Se repite el proceso, primero habilitamos la escritura, después leemos el Registro de estado para comprobar que la escritura esta bien, después escribimos las As con la instrucción de Page Program y luego leemos la pagina.
Estos serian los comandos:
[0x06] (Habilitar escritura)
[0x05 r:1] (Leer Registro de Estado)
[0x02 0x00 0x00 0x00 0x41:255] (LLenar la pagina de 0x41)
[0x03 0x00 0x00 0x00 r:256] (Leer la pagina entera)
Asi se vería la instrucción de escribir en el analizador lógico:
Y aquí vemos como la instrucción de lectura nos devuelve por el MISO todas las A:
Podéis ver detalladamente la captura con el programa Logic 2 y la captura de la carpeta assets.
La abrimos con binwalk y la descomprimimos con este comando.
binwalk -eM flash_contenido.img
Ahora revisamos lo extraído y buscamos la flag.
Material Requerido
- TP-Link TL-WR841N
- Soldador Estaño
- FLux
- USB a TTL o Buspirate
- Multimetro
- Cable Ethernet o Wifi
- Pines para UART
- Ordenador
El modelo que vamos a analizar es el TP-Link TL-WR841N V14
Lo primero que debemos hacer es quitar los dos tornillos que hay en la parte inferior y después haremos palanca con algún trozo de plástico, yo he usado una púa de guitarra.
Y ya tendríamos acceso directo a la PCB del router.
Una vez abierto el router hay que identificar todos los componentes posibles.
Cada circulo es una parte interesante de la pcb para nosotros:
- Circulo Rojo: Es la memoria RAM. Mirando la ficha técnica de la Versión 14 en OpenWRT podemos ver que tiene 32 MiB.
- Circulo Amarillo: Es el SOC (System-On-Chip) MT7628NN.
- Circulo Azul: Es la memoria flash EN25Q32(A/B). Aquí es donde se guarda el bootloader y el sistema de archivos etc.
- Circulo Rosa: Es la Interfaz UART que usaremos para obtener terminal de root y obtener el sistema de archivos. La resistencia R18 bloquea la escritura UART, luego habrá que quitarla.
Soldar pines a UART no es complicado simplemente necesitaremos:
- Soldador de estaño
- Estaño a poder ser de calidad (se notan mucho los acabados.)
- Pines
Aqui vemos como se ha eliminado la R18 que impide la escritura a través de UART y los pines listos para conectarnos.
Para eliminar la R18 vas a necesitar desoldarlo, no lo hagas arrancándolo porque te puedes cargar la placa. En la siguiente imagen puedes ver como una contribuidora (CristinaCTGN) eliminó a las bravas la resistencia creando un surco en la PCB que en este caso no provocó daños mayores, pero pudo haberse cargado alguna pista (ignora la mala calidad de la foto y si puedes ver la mala calidad de la soldadura, también):
En este caso hemos conectado el RX, el TX y el GND. El VCC no es necesario porque la alimentación la daremos usando el adaptador de corriente del router.
Si tenemos el UART a la vista puede que tenga escrito cerca GND. Otra opción es identificar la flash o otro componente y buscar que pata según el datasheet es el GND, después buscaremos continuidad en otros componentes de la placa donde poder engancharnos.
Aqui se ve el modo continuidad del multimetro:
Mirando el datasheet vemos cual es el GND.
Ahora que ya sabemos cual de los pines es el GND es momento de conectar el analizador lógico.
Conectamos el GND del router al del analizador lógico y los pines que pueden ser el UART del router a 2 pines del analizador.
Aquí vemos los diferentes canales, yo conectare el GND, 5 y el 7 que en el Logic Pro son el 4 y el 6.
Aquí vemos como conecte el GND.
Abrimos el Saleae Logic y le damos al boton de play, despues enchufamos el router y esperamos a recibir señales.
Asi se vera la imagen del Logic tras darle al pause.
Ahora tenemos que añadir la extension de Baud Rate estimate.
Vamos a la pestaña de extensiones de la derecha, la buscamos y le damos click a install.
Vamos a la pestaña de marcadores de tiempo medidas y notas.
Mantenemos shift y selecionamos una parte grande del canal donde hayamos recibido datos.
Tras esto nos saldrá un recuadro con un baud rate aproximado de la zona seleccionada.
Ahora creamos un analizador dentro de saleae logic tipo serial asíncrono con un baud rate de 115200 baudios que es lo más aproximado a la cifra que nos sale.
Solo cambiamos los baudios y el canal que hayamos usado, el resto lo dejamos así.
Tras esto seleccionamos la terminal y podremos ver todo el contenido de la señal en texto por pantalla.
Ahora veremos lo que recibimos del puerto UART seleccionando el modo terminal en vez de tabla de datos.
Aqui tenemos el arranque del u-boot:
Aqui podemos ver como he conectado todos los pines desde el USB_UART-to-TTL, una cosa IMPORTANTE es que para que funcione la escritura necesitamos que el cable de datos TX vaya a 5V, porque si lo ponemos a 3,3V no funcionara correctamente.
Ahora para conectarnos correctamente lo primero es entender el protocolo SERIAL ,lo mas importante es que el pin RX del adaptador TTL-USB tiene que ir al TX del router y el TX del adaptador TTL-USB al RX del router además de conectar los dos GNDs.
Ahora nos conectaremos usando algun software como TeraTerm o Putty, en mi caso usare TeraTerm.
-
Lo abrimos y seleccionamos la interfaz COM correspondiente del TTL-USB
-
Ahora configuramos la interfaz serial dentro de Setup->Serial Port
-
Y lo dejamos con esta configuracion de 115200 baudios de velocidad, 8 bits de datos, ninguno de paridad y uno de stop.
-
Ahora para no quedarnos ciegos vamos a la configuración de fuente y aumentamos el tamaño y lo dejamos en 14.
-
Tras esto enchufamos el router a la corriente y veremos el bootloader cargando.
Tras esto ya podemos escribir comandos pero antes vamos a ver como se haría con el bus pirate.
Ahora explicaremos paso a paso con el bus pirate:
La configuración del emulador de terminal es la misma que en el USB-to-TTL ahora lo importante es como conectemos los cables, aqui tenemos un esquema de que es cada color:
-
Ahora debemos conectar el MOSI(GRIS) al RX y el MISO(NEGRO) al TX, el GND(MARRON) al GND del router además de conectar el VPU(VERDE) al 5V(NARANJA) del propio bus pirate
-
Lo siguiente es conectarse usando TeraTerm y con la misma configuración de antes e interactuar con el menu del bus pirate.
Escribimos 'm' y pulsamos intro
Elegimos el modo 3 (UART)
Pulsamos enter 4 veces para elegir todo por defecto es decir: Sin bit de paridad, 1 bit de parada , y con el colector abierto (Open Drain) para meterle por VPU esos 5V que necesita.
Pulsamos 'W' mayúscula para dar corriente, pulsamos 'P' mayúscula para activar las resistencias pull-up y solo nos falta activar el live monitor para recibir la shell de UART.
Esto lo haremos usando la macro (1) que es el Transparent bridge aunque si ponemos (0) nos saldra la lista de macros.
Ahora conectamos el router a la corriente y obtendremos shell.
Lo primero que haremos para extraer el sistema de archivos es instalar un servidor TFTP (Trivial FIle Transfer Protocol), en mi caso instale la version Tftpd64-4.62-setup.exe una vez instalada hay que abrirlo y dejar la configuración de esta manera:
Tras configurarlo nos conectamos al router por wifi o por cable y revisamos que ip nos ha dado:
Con esto ya sabemos que desde el terminal del router tenemos transferir por TFTP a 192.168.0.100
Para comprobar que estamos conectados al router por red introducimos la puerta de enlace en el navegador:
Una vez conectados es el momento de extraer el sistema de archivos y el firmware completo.
Tenemos una terminal muy limitada, para tener mas herramientas hay que subir una copia de busybox-mipsel.
Este router utiliza el subsistema MTD, que se divide en bloques. Concatenamos todos los bloques en orden para obtener el contenido de la flash, para más información de MTD.
Los bloques se encuentran en el directorio /dev y empiezan por la palabra mtd:
Copiamos todos los archivos de mtd con el script transfer.sh.
Metemos los archivos en kali y los concatenamos siguiendo estos comandos:
cat mtdblock0 > firmware
cat mtdblock1 >> firmware
cat mtdblock2 >> firmware
cat mtdblock3 >> firmware
cat mtdblock4 >> firmware
Descomprimimos y extraemos con binwalk para después poder analizarlo:
binwalk -eM firmware
En este taller se usará el Bus Pirate para leer una tarjeta SD en modo SPI.
Materiales:
Usamos este esquema para conectarnos (concretament, habrá que mirar la fila SPI prestando atención a los colores de los cables, no de las pinzas):
Nota: Si teneis algún problema durante la lectura de la SD, tened en cuenta que las pinzas a veces funcionan un poco mal. Probad usando unos cables dupont hembra-hembra para descartar.
Para interactuar con el Bus Pirate, el primer paso es conectarlo a alguno de los puertos USB de nuestro ordenador. Se aconseja lo siguiente:
- No usar hub USB.
- El puerto, cuanto más antiguo mejor (p.e. USB2.0 en lugar de 3.0)
- Hacerlo todo en un sistema operativo nativo, ya sea Linux o Windows, pero nada de máquinas virtuales.
No significa que si no cumples algo de lo de arriba no vaya a funcionar, pero son cosas que suelen dar problemas y así minimizamos el número de cosas que pueden salir mal...
El siguiente paso es conectarse al Bus Pirate con un software como Tera Term o Putty. Como ya hay suficientes ejemplos más arriba usando Tera Term en Windows, esta vez lo haremos con tio (Linux).
Una vez instalado el software (fuera del alcance de esta guía), ejecutar el siguiente comando:
sudo tio -b 115200 /dev/ttyUSBX
Si no sabes el ttyUSB del Bus Pirate, puedes comprobarlo rápidamente de la siguiente forma:
- Desconecta el USB del Bus Pirate.
- Ejecuta el siguiente comando:
sudo dmesg -w
- Vuelve a conectar el Bus Pirate.
- Comprueba el dispositivo nuevo que se ha conectado:
Tras ejecutar tio
, envía una i
(+ ENTER) para obtener la versión de Bus Pirate.
Para esta guía estamos usando Bus Pirate v3.5, con el Bootloader v4.5 y el Community Firmware v7.1. Si no cuentas con la misma versión y algo no te funciona (o no te salen igual los menús), puede que te convenga actualizar (o bajar) a esta versión.
Lo primero que haremos será indicarle a Bus Pirate que queremos que hable con la tarjeta SD a través del protocolo SPI. Para ello, tenemos que cambiar el modo con el comando m
:
En cuanto a la velocidad, estableceremos la más pequeña para estar seguros (30kHz, despacito y con buena letra):
El resto de opciones hasta llegar al CS lo dejaremos por defecto:
Para la última opción, cómo alimentar al chip, usaremos el normal, ya que con 3v3 deberíamos de tener de sobra para alimentar la SD.
Llegados a este punto, la configuración del Bus Pirate estaría lista, solo faltaría enviar W
para darle corriente a la SD:
Podemos comprobar además que estamos dando voltajes correctos haciendo uso de v
:
Para la prueba que estamos haciendo, el voltaje que nos interesa es el marcado en rojo. Cuanto más cerca estemos de 3.3V mejor. En mi caso el USB llega a darme 3.26V y me funciona todo, así que entiendo que podeis usarlo como referencia de "voltaje normal".
Para inicializar la tarjeta SD en modo SPI, debemos enviarle una serie de comandos. Lo primero que haremos será ver el formato de los comandos:
CMD | ARGUMENT | CRC |
---|---|---|
1 byte | 4 bytes | 1 byte |
Un comando podemos dividirlo en 3 partes:
- CMD: Indica el comando que tiene que ejecutar la tarjeta SD. Hay que tener cuidado cuando queremos indicar el comando que queremos ejecutar ya que si por ejemplo queremos ejecutar CMD0 (GO_IDLE_STATE), no mandamos un 0 (0x00) y ya está. En realidad, de los 8 bits que se mandan para indicar a la tarjeta SD el comando (1 byte), solo los 6 últimos bits indican el comando, los 2 primeros bits están fijados a 01. Por tanto, el primer byte, el comando que se ejecuta, siempre se verá así:
Bit 1 | Bit 2 | Bit 3 | Bit 4 | Bit 5 | Bit 6 | Bit 7 | Bit 8 |
---|---|---|---|---|---|---|---|
0 | 1 | X | X | X | X | X | X |
Truco: Como siempre tiene que empezar por 01, podemos entender que para CMDX, CMD = 01000000 + X. O si queremos verlo en hexadecimal: CMDX = 0x40 + X. Por ejemplo: Para CMD0, CMD = 0x40 + 0 = 0x40; Para CMD17, CMD = 0x40 + 17 = 0x40 + 0x11 = 0x51.
-
ARGUEMENT: Este es más sencillo de entender. Hay ciertos comandos que necesitan más información detrás. Por ejemplo, hay un comando que te permite leer un bloque, pero... ¿qué bloque? Pues en estos 4 bytes lo indicaríamos.
-
CRC: La tarjeta SD no comprueba el CRC cuando la ponemos en modo SPI, así que solo tendremos que preocuparnos de mandarlo en el primer comando de la inicialización, cuando le decimos que queremos hablar con ella en modo SPI. Con que sepais que para ese primer comando el CRC tiene que ser 0x95, teneis suficiente por lo pronto. Para el resto, con enviar un 0xFF nos vale.
Entendido un poco cómo hablamos con la tarjeta SD, tenemos inicializarla de la siguiente forma para indicarle que queremos hablar usando SPI:
- La reseteamos en modo SPI con el comando CMD0 (GO_IDLE_STATE)
- Activamos el proceso de inicialización con el comando CMD1 (SEND_OP_COND)
- Comprobamos el estado de la tarjeta con el comando CMD1 (SEND_OP_COND)
- Establecemos el tamaño de bloque a 512 bytes con el comando CMD16 (SET_BLOCKLEN)
Hecho esto, ya tendríamos la SD lista para leerla haciendo uso del protocolo SPI. Ya solo faltaría ir pidíendole bloques con el comando CMD17 (READ_SINGLE_BLOCK).
Nota: Seguro que te has dado cuenta que en el paso 2 y 3, aunque se hacen cosas diferentes, el comando que se usa es el mismo. Esto es porque el comando CMD1 (SEND_OP_COND) hace 2 cosas: 1) Le dice a la tarjeta que nos diga su estado; 2) Activa el proceso de inicialización. En ese orden.
- Comando (byte 1): Como queremos ejecutar CMD0 (GO_IDLE_STATE), tenemos que enviar 0x40 (0x40 + 0).
- Argumentos (bytes 2 a 5): Este comando no tiene ningún argumento, por lo que enviamos 4 0x00 y ya está.
- CRC (byte 6): Como adelantábamos más arriba, este es el único comando para el que tenemos que enviar un CRC bueno. No vamos a meternos en cómo se calcula, simplemente que para este comando en concreto será 0x95.
]r:10[0x40 0x00 0x00 0x00 0x00 0x95 r:8]
Como respuesta, deberemos obtener lo siguiente:
0xFF 0x01 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
El segundo byte indica el estado en el que se encuentra la tarjeta. 0x01 indica que la tarjeta se encuentra en estado IDLE, por lo que el siguiente paso será inicializarla.
- Comando (byte 1): Como queremos ejecutar CMD1 (SEND_OP_COND), tenemos que enviar 0x41 (0x40 + 1).
- Argumentos (bytes 2 a 5): Este comando no tiene ningún argumento, por lo que enviamos 4 0x00 y ya está.
- CRC (byte 6): A partir de este punto, la tarjeta SD deja de comprobar el CRC de los comandos que le mandamos, por lo que mandando simplemente 0xFF vamos bien.
[0x41 0x00 0x00 0x00 0x00 0xFF r:8]
Como respuesta, deberemos obtener lo siguiente:
0xFF 0x01 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
Podemos ver que la tarjeta sigue en modo IDLE. Es normal, ya que la tarjeta lo primero que hace es devolvernos el estado en el que se encuentra, y luego activa el proceso de inicialización. Vamos a volver a enviar el comando para obtener el estado en el que se encuentra ahora.
- Comando (byte 1): Como queremos ejecutar CMD1 (SEND_OP_COND), tenemos que enviar 0x41 (0x40 + 1).
- Argumentos (bytes 2 a 5): Este comando no tiene ningún argumento, por lo que enviamos 4 0x00 y ya está.
- CRC (byte 6): La tarjeta no comprueba el CRC, por lo que enviamos 0xFF.
[0x41 0x00 0x00 0x00 0x00 0xFF r:8]
Como respuesta, deberemos obtener lo siguiente:
0xFF 0x00 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
Perfecto! Ahora sí, la tarjeta está lista para trabajar con ella, ya no está en modo IDLE! Pero un momento, porque antes de leer, tenemos que decirle con qué tamaño de bloque queremos trabajar.
- Comando (byte 1): Como queremos ejecutar CMD16 (SET_BLOCKLEN), tenemos que enviar 0x50 (0x40 + 16 = 0x40 + 0x10).
- Argumentos (bytes 2 a 5): Este comando sí que tiene un argumento. Queremos establecer el tamaño de bloque, pero ¿a qué? Pues el estándar suele ser 512 bytes, así que no vamos a reinventar la rueda para evitarnos problemas... Indicar esto es muy sencillo, lo único que tenemos que hacer es convertir 512 bytes de decimal a hexadecimal, es decir 0x200. ¿Problema? Hay que enviar 4 bytes. ¿Solución? Rellenamos con 0's a la izquierda: 0x00000200. Si lo separamos: 0x00 0x00 0x02 0x00.
- CRC (byte 6): La tarjeta no comprueba el CRC, por lo que enviamos 0xFF.
[0x50 0x00 0x00 0x02 0x00 0xFF r:8]
Como respuesta, deberemos obtener lo siguiente:
0xFF 0x00 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
No hemos obtenido ningún error, así que todo bien.
Ahora que ya hemos terminado de hacer el paso 2 por completo, ya tenemos la tarjeta SD lista para empezar a pedirle bloques. Para esto hay que tener una cosa en cuenta:
A la tarjeta SD no le vamos pidiendo los bloques por un identificador. Es decir, no le decimos: dame el bloque 1, dame el bloque 2, dame el bloque 3...
La forma en la que funciona la lectura de la tarjeta SD es que va a devolvernos un bloque desde la dirección que le indicamos como argumento. Es decir, cuando le digamos que queremos leer la dirección 0x00 0x00 0x00 0x00, va a devolvernos los primeros 512 bytes (recordad que es a lo que hemos establecido el tamaño de bloque durante la inicialización!). Por tanto, cuando queramos leer el segundo bloque, es decir, los siguientes 512 bytes, tenemos que leer la dirección 0x00 0x00 0x02 0x00 (512 en decimal). Para el siguiente bloque, sería 0x00 0x00 0x04 0x00 (1024 en decimal)...
- Comando (byte 1): Como queremos ejecutar CMD17 (READ_SINGLE_BLOCK), tenemos que enviar 0x51 (0x40 + 17 = 0x40 + 0x11).
- Argumentos (bytes 2 a 5): Como hemos explicado antes, este comando recibe como argumento la dirección de memoria desde la que queremos obtener el bloque. Como queremos leer el primer bloque, sería 0x00 0x00 0x00 0x00.
- CRC (byte 6): La tarjeta no comprueba el CRC, por lo que enviamos 0xFF.
[0x51 0x00 0x00 0x00 0x00 0xFF r:520]
Como veis, ya empezamos a ver la información que hay en los bloques! Para parsear esto, teneis que tener en cuenta que la respuesta tiene un formato:
- Los primeros 4 bytes es una cabecera.
- Los últimos 2 bytes es el CRC. Podríamos comprobarlo, pero asumiremos que está bien.
- Si restamos esos bytes, nos quedan los 512 bytes del bloque que estamos leyendo.
De hecho, si los copiamos y los pasamos por una herramiento tipo CyberChef, obtenemos lo siguiente:
- Comando (byte 1): Como queremos ejecutar CMD17 (READ_SINGLE_BLOCK), tenemos que enviar 0x51 (0x40 + 17 = 0x40 + 0x11).
- Argumentos (bytes 2 a 5): Como hemos explicado antes, este comando recibe como argumento la dirección de memoria desde la que queremos obtener el bloque. Como queremos leer el segundo bloque, sería 0x00 0x00 0x02 0x00, ya que queremos leer a partir de la dirección 512 (los bytes anteriores ya los hemos leido).
- CRC (byte 6): La tarjeta no comprueba el CRC, por lo que enviamos 0xFF.
[0x51 0x00 0x00 0x02 0x00 0xFF r:520]
Aquí puede comprobarse a simple vista que no hay caracteres ASCII como en el bloque anterior, pero la estructura sigue siendo la misma: los primeros 4 bytes es una cabecera, y los 2 últimos el CRC.
La tarjeta SD tiene flasheada un sistema de ficheros oculto. Lo único que sabemos es que se encuentra en los bloques pares o impares. Es decir, no podemos simplemente leer los bloques de manera secuencial y volcarlos en un fichero, sino que hay que separar los bloques pares por un lado, y los impares por otro. Cuando el proceso haya terminado, uno de los ficheros contendrá un sistema de ficheros donde estará la flag :D
Para hacer todo esto, podemos automatizar todo el proceso de los pasos 2 y 3 con el siguiente script:
#!/usr/bin/env python3
import serial
import time
# Aquí es importante establecer un timeout, ya que las lecturas del puerto serie las haremos con la función
# read_until. La gracia de usar este comando es que si le decimos read_until("AAA"), bloquerá la ejecución
# hasta que en el buffer aparezca "AAA". Como esto es peligroso (por magia divina, puede que nunca aparezca AAA),
# tenemos que establecer aquí un timeout para que, si no recibimos nada en 5 segundos, nos devuelva el control a
# nosotros y podamos tratarlo.
s = serial.Serial(port="/dev/ttyUSB0", baudrate=115200, timeout=5)
# Cambiar modo
s.write(b"m\n")
time.sleep(0.1)
# SPI
s.write(b"5\n")
time.sleep(0.1)
# 125kHz
s.write(b"2\n")
time.sleep(0.1)
# Clock: Idle low
s.write(b"1\n")
time.sleep(0.1)
# Clock: Active to idle
s.write(b"2\n")
time.sleep(0.1)
# Input sample phase: Middle
s.write(b"1\n")
time.sleep(0.1)
# CS: /CS
s.write(b"2\n")
time.sleep(0.1)
# Select output type: Normal
s.write(b"1\n")
time.sleep(0.1)
# Power supplies on
s.write(b"W\n")
time.sleep(0.1)
# Inicialización del modo SPI
s.write(b"]r:10[0x40 0x00 0x00 0x00 0x00 0x95 r:8]\n")
time.sleep(0.1)
s.write(b"[0x41 0x00 0x00 0x00 0x00 0xFF r:8]\n")
time.sleep(0.1)
s.write(b"[0x41 0x00 0x00 0x00 0x00 0xFF r:8]\n")
time.sleep(0.1)
s.write(b"[0x50 0x00 0x00 0x02 0x00 0xFF r:8]\n")
time.sleep(0.1)
s.reset_input_buffer()
s.reset_output_buffer()
# Archivo para volcar los bloques pares
f1 = open("even.bin", "wb")
# Archivo para volcar los bloques impares
f2 = open("odd.bin", "wb")
# Variable para ir controlando si es un bloque par o impar de manera intuitiva. Podríamos hacerlo dividiendo directamente
# i / 512, pero creo que así es más fácil de entender.
n = 0
# Aquí recorreríamos todas las direcciones de memoria del 0 a los 2GB.
# No obstante, sabemos (os lo digo yo) que la información se encuentra en los primeros 16MB, por lo que para que no sea infinito,
# no vamos a seguir leyendo.
# Como ya hemos visto, hemos establecido el tamaño de bloque en 512 bytes, por lo que las direcciones de memoria también las vamos
# recorriendo de 512 en 512.
for i in range(0, 16 * 1024 * 1024, 512):
# Como los comandos reciben la direccion como argumento en 4 bytes diferentes, tenemos que trocear la dirección en 4. Es decir, para la dirección 0x01020304
# Aqui obtenemos 0x01
a4 = i >> 24 & 0xff
# Aqui obtenemos 0x02
a3 = i >> 16 & 0xff
# Aqui obtenemos 0x03
a2 = i >> 8 & 0xff
# Y aqui obtenemos 0x04
a1 = i & 0xff
print(f"Reading {hex(a4)} {hex(a3)} {hex(a2)} {hex(a1)}...")
# El bus pirate nos abstrae del formato en el que les pasamos los bytes. Es decir, le da igual que le pasemos 0x0F o 15, así que nos ahorramos usar la función hex()
s.write(f"[0x51 {a4} {a3} {a2} {a1} 0xFF r:520]\n".encode())
# Esperamos hasta que en el buffer aparezca SPI>, o lo que es lo mismo, que la tarjeta SD nos haya respondido y el bus pirate esté esperando a que le enviemos otro comando.
response = s.read_until(b"SPI>")
# Nos quedamos unicamente con la linea donde aparece los bytes que están escritos en el bloque, quitándole el READ: del principio.
sector_bytes_str = response.decode().split("\n")[8].lstrip("READ: ").split()
# Primero obtenemos la cabecera, desde el principio hasta que leemos 0xFE (los primeros 4 bytes siempre acaban en 0xFE)
header_bytes = bytes([ int(x, 16) for x in sector_bytes_str[:sector_bytes_str.index("0xFE")+1] ])
# Luego obtenemos la información del bloque, desde ese primer 0xFE que mencionamos antes, hasta los siguientes 512 bytes (recordemos que es el tamaño de bloque establecido).
sector_bytes = bytes([ int(x, 16) for x in sector_bytes_str[sector_bytes_str.index("0xFE")+1:sector_bytes_str.index("0xFE")+512+1] ])
# Si n es par, escribimos el bloque en el archivo even.bin
if n % 2 == 0:
f1.write(sector_bytes)
# Si n es impar, escribimos el bloque en el archivo odd.bin
else:
f2.write(sector_bytes)
# Por último, incrementamos n antes de vovler a iniciar el bucle.
n += 1
Podeis descargarlo directamente de aquí.
Una vez lo tenéis, ejecutadlo de la siguiente forma:
chmod +x read_sd_spy.py && sudo ./read_sd_spi.py
Nota: Acordaros de desconectar el Bus pirate del USB y volverlo a conectar, además de cerrar tio antes de ejecutar el script. Para ello, cerrad directamente la terminal o ejecutad la siguiente combinación: Ctrl+t q.
El script tarda un buen rato en terminar (en mi caso, unas 2 horas). Mientras tanto, podemos ir monitorizando el resultado con el siguiente comando:
watch "binwalk even.bin; binwalk odd.bin"
De primeras no saldrá nada, pero poco a poco iremos viendo cómo, en uno de los archivos, binwalk va detectando los magic numbers:
Hasta que al final, podemos ver un sistema de ficheros completo:
Una vez tenemos esto, sacamos los ficheros con binwalk:
binwalk -e odd.bin
tree
Entramos a la carpeta que ha generado binwalk y vemos los tipos de ficheros:
cd _odd.bin.extracted
file *
Podemos ver que binwalk ha conseguido extraer todos los archivos. De hecho, el archivo 600000.xz incluso lo ha extraido, dejándonos 600000.xz en el directorio. Solo nos faltaría extraer este último. Como sabemos que es un archivo tar, lo podemos extraer con el comando del mismo nombre:
tar xaf 600000
file *
Una vez ejecutado el comando, podemos ver que aparece un nuevo achivo dreg_flag.txt. Leemos el contenido y ya tenemos la flag!
cat dreg_flag.txt
A través de la herramienta QEMU podemos emular una firmware, pero su configuración en algunos casos puede ser compleja. Por eso, existen varias herramientas que nos van a ayudar simplicar el proceso de emulación de una firmware. Las herramietas que yo he usado son Firmadyne, FirmAE y Emux.
Después de realizar varias pruebas, la herramienta que más me gusta por mucho es Emux. Ha sido creada por el researcher Saumil Shah. Al principio esta herramienta sólo era compatible con ARM y se llamaba ARM-X, pero como la mayoría de los dispositivos IoT son MIPS en Octubre del 2021 se añadió el soporte para MIPS, por este motivo se cambio su nombre a Emux. Es un entorno dockerizado lo que evita bastantes problemas típicos en la configuración de la red y otras incompatibilidades. Aunque es verdad que con algunas firmwares habrá que dedicarle cierto tiempo para "configurarla" correctamente por ejemplo, evitando la carga de algunos módulos y/o servicios para poder emularlas correctamente en Emux.
Emux es más sólido y en su página web hay bastante documentación detallada de su arquitectura y varios ejemplos que merece mucho la pena mirarlos para entender como podemos configurar nuestra firmware si tenemos ciertos problemas.
Proceso de arranque de un sistema IoT (Básico)
En general y resumiendo bastante el proceso del arranque de un dispositivo IoT empieza cuando el SoC pasa el control al bootloader de la memoria ROM, luego puede pasar por una varias fases internas hasta que la última fase copia el sistema de arranque de linux en la memoria RAM, el más usado es U-BOOT, este último finalmente arranca el kernel de linux, y se monta el sistema raiz de ficheros, se inicia el proceso init y se da paso a los servicios y se muestra la ansiada shell. Pues más o menos, esto es lo que vamos hacer de una forma sencilla con Emux.
Yo voy a instalar Emux en ArchLinux pero en otra distribución o en Windows el proceso de instalación es similar.
Para poder usar Emux hay que instalar Docker y algunos paquetes e iniciar este servicio en el sistema. En Arch se hace de la siguiente forma:
Instalación de paquetes necesarios:
sudo pacman -S docker docker-compose docker-buildx
Iniciar Docker:
sudo systemctl start docker.service
Hay que crear el grupo de usuarios docker y añadirle nuestro usuario.
sudo groupadd docker
sudo usermod -aG docker dsanchez
Y finalmente descargar de git el proyecto Emux:
git clone --depth 1 --single-branch https://github.com/therealsaumil/emux.git
Una vez descargado todo, entramos en la emux para inicializar Emux por primera vez, este es un proceso que puede tardar un rato pero sólo es necesario ejecutarlo una vez. Este paso es importante porque va a crear el volumen de trabajo de Emux en Docker y van a descargar todos los ficheros que vamos a necesitar.
./build-emux-volumen
./build-emux-docker
Si todo se ha instalado correctamente deberíamos ver el nuevo volumen harambe en Docker.
$ docker volume ls
DRIVER VOLUME NAME
local harambe
Y también la imagen therealsaumil/emux de Emux en Docker.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
therealsaumil/emux 03-2023 299cd17d716a 18 hours ago 1.33GB
El siguiente paso es inicializar el container de Docker de Emux al que me voy a referirme siempre como EMUX-DOCKER en MAYUSCULAS y para referirme al cliente de Emux le voy a llamar siempre emux-docker en minúsculas porque es el formato de nombre que usan en la terminal de Linux. (Ahora lo vais a ver)
Ejecutamos el servidor Emux
./run-emux-docker
Ya dentro de la shell EMUX-DOCKER si escribimos y ejecutamos el comando launcher aparecerá un menú gráfico donde podemos elegir que firmware queremos emular, las que están listadas en la siguiente imagen ya vienen configuradas por el creador la herramienta y las podemos emular.
De momento, pulsaremos el botón Quit del menú y volveremos a la shell de EMUX-DOCKER. La idea de este tutorial es añadir a este listado de firmwares nuestra propia firmware para poder emularla. Para este ejemplo usaremos la firmware del router TPLINK WR841ND que yo la descarge de este post en nuestro canal de Discord (training router).
Lo primero que tenemos que hacer es copiar la firmware en la carpeta que está compartida entre el contenedor de Emux y nuestro sistema operativo, en mi caso ArchLinux. Desde nuestro sistema está hubicada en "<carpeta instalación emux>/workspace/" y desde dentro del contenedor de Docker (desde la shell EMUX-DOCKER o emux-docker) está hubicada en "/emux/workspace/". Esto es similar a cuando se comparte una carpeta entre VMWare/VirtualBox con nuestro Sistema Operativo.
Binwalk al rescate
Desde una nueva terminal de linux vamos a descomprimir la firmware usando la herrameinta binwalk como usuario root. NOTA: Es importante tener instaladas todas las herramientas que va a necesitar binwalk como casptone, sleuthkit, squashfs, etc para ello recomiendo mirar la documentacion de binwalk. La idea es descomprimir la firmware dentro de la carpeta compartida "<carpeta instalación emux>/workspace/"
sudo binwalk --run-as=root -1 -e <fichero firmware>
Si todo ha ido bien, veremos la carpeta _TPLINK_WR841ND_v11.bin.extracted. Dentro de este carpeta nos interesa principalemente la carpeta squashfs-root que contiene el sistema de ficheros de la firmware y un fichero que contiene el kernel, que aunque este último no los podemos usar directamente en QEMU, si los vamos a utilizar para identificar que versión del kernel utiliza nuestra firmware.
Crear una nueva carpeta en Emux para nuestra nueva firmware
Desde la terminal de EMUX-DOCKER vamos a empezar a configurar nuestro entorno. Este paso siempre lo vamos a tener que hacer cada vez que queramos añadir una nueva firmware a Emux. La idea simplemente es duplicar la carpeta /emux/template/ en una nueva carpeta con un nombre que identifique a nuestra nueva firmware. (y diferente a los que ya existen)
La carpeta template contiene diferentes versiones del kernel y ficheros de configuración necesarios y por eso se usa como punto de partida.
El nuevo nombre de esta carpeta se va a usar como identificador interno de nuestra firmware en Emux. Por lo tanto, se recomienda un nombre alfa-numérico sin espacios y todo en mayúsculas. El motivo, así lo recomienda el autor de Emux. Por ejemplo, Le podemos poner el nombre WR841NDV11. Para identificar la versión de firmware del router y su versión, ya que es posible que queramos emular otras versiones del mismo dispositivo y es posible que no funcionen con la misma configuración y así las podremos diferenciar facilmente.
Por ejemplo, es posible que algunas versiones de firmware de un mismo dispositivo sean Little Endian pero otras Big Endian y además su versión del kernel de Linux sea diferente y por lo tanto los datos en los ficheros de configuración van a ser diferentes.
Para duplicar la carpeta simplemente ejecutamos el siguiente comando en la shell EMUX-DOCKER
sudo cp -R /emux/template /emux/WR841NDV11
También copiaremos dentro de esta nueva carpeta la carpeta squashfs-root que hemos descomprimido antes con la herramienta binwalk.
$ cd /emux/WR841NDV11
$ sudo cp -R /home/r0/workspace/_TPLINK_WR841ND_v11.bin.extracted/squashfs-root/ .
Finalmente cambiaremos el propietario de todo el contenido de la carpeta WR841NDV11 al usuario r0 de Emux. Este paso es importante para que funcione bien la emulación.
$ sudo chown -R r0:r0 /emux/WR841NDV11
Buscar la versión del kernel y la arquitectura
Para que Emux puede emular nuestra firmware correctamente le tenemos que decir la versión del kernel y el tipo de arquitectura que vamos a emular. Esta información la podemos conseguir facilmente con la ayuda de las herramientas binwalk, strings y readelf.
Primero, podemos ejecutar binwalk sin ningún parametro para no descomprimir los ficheros otra vez.
binwalk <fichero firmware>
Vemos que el fichero 20200 debería contener el kernel así que vamos a buscar su versión utilizando la herramienta strings.
strings <fichero kernel> | grep -i version
La versión del kernel utilizada por nuestra firmware es la 2.6.31 la podemos ver en la línea que dice Linux version 2.6.31
Para buscar la arquitectura de la firmware también es muy sencillo, dentro del sistema de ficheros de un dispositivo IoT hay un binario que se llama Busybox que contiene en su interior las utilidades básicas de un sistema linux como son ls, cp, date, df, chmod, etc...
Con la ayuda de la herramienta readelf y la opción -e podemos ver por ejemplo los datos de la cabecera de un binario ELF donde veremos en que tipo de máquinas se puede ejecutar ese binario.
readelf -e <fichero elf>
La arquitectura detectada es MIPS R3000 y el formato es big endian. Estos 2 datos junto con la versión del kernel es lo que necesitamos para terminar de configurar nuestro entorno. En esea información vemos que la pila (GNU_STACK) tiene permisos de ejecución RWE (Read/Write/Execution) así que si encontramos una vulnerabilidad de stack-overflow no tendríamos mucho problema en desarollar el exploit si ASLR también está desactivado.
En resumen:
- Kernel: 2.6.31
- Arquitectura: MIPS Big Endian.
Configurar el entorno
Desde EMUX-DOCKER dentro de la carpeta "/emux/WR841NDV11/kernel/" hay que elegir que versión de kernel podría ser compatible con la versión 2.6.31 para una máquina MIPS en big endian.
Podemos preguntarle a QEMU las versiones de MIPS que soporta para big endian con el siguiente comando.
qemu-system-mips -machine help
Para little endian usariamos la herramienta qemu-system-mipsel.
Si estas ejecutando linux en una máquina Intel esta herramienta la puedes encontrar dentro de la carpeta "/emux/run/qemu-bin-x86_64/" en Emux, en mi caso tengo la version 7.2.0.
El siguiente paso es ver que versiones del kernel de linux tenemos disponibles para nuestro entorno, así que podemos listar el contenido de la carpeta "/emux/WR841NDV11/kernel/"
Aunque en el listado no está la versión 2.6.31, vemos una versión de kernel que podría funcionar. El kernel vmlinux-2.6.30.9-malta-be está sorportado por nuestra version de QEMU y ademas es big edian (be). Otra opción, sería compilar la misma versión o descargarla de algun sitio confiable en Internet.
Una vez seleccionada la versión que vamos a usar podemos borrar los demás ficheros de kernel del directorio "/emux/WR841NDV11/kernel/" ya que no los vamos a usar y así podemos ahorrar algo de espacio en disco.
También vamos a borrar de "/emux/WR841NDV11/" los ficheros nvram.ini, mtdparts y el directorio preload que no necesitamos para nuestra configuración.
Hay más información sobre como usar estos ficheros y la carpeta preload en el caso de que sean necesarios en la página de EMUX.
El siguiente paso, es configurar el fichero "/emux/WR841NDV11/config" con vim, nano o con el editor que más nos guste pero lo tenemos que ejecutar como root.
sudo vim config
El contenido ya modificado tiene que ser este.
Significado:
- id= debe tener el mismo valor que el nombre de la carpeta que hemos creado.
- rootfs= es el directorio que contiene el sistema de archivos
- randomize_va_space= 0 si no queremos ASLR, 1 si queremos activarlo
- initcommands= comando que se va ejecutar después de arrancar el kernel.
Añadir la nueva firmware al launcher
Como último paso hay añadir la nueva firmware al fichero /emux/devices para que sea reconcido por el lancher desde la consola EMUX-DOCKER y así lo podamos ver en el menu donde se listan todas las firmwares configuradas. Simplemente, hay que añadir una nueva linea de configuración al final de ese fichero, también lo tenemos que abrir como root.
WR841NDV11,qemu-system-mips-7.2.0,malta,,,128MB,vmlinux-2.6.30.9-malta-be,MALTA2,TPLINK WR841ND v11
Desde la consola EMUX-DOCKER ejecutamos el comando launcher y seleccionamos la opcion TPLINK WR841ND v11 que es nuestra nueva firmware.
Si todo ha ido bien veremos la siguiente pantalla
Ahora vamos abrir un cliente Emux, asi que abrimos otra terminal en linux y desde la carpeta de instalacion de Emux vamos a ejecutar el comando "./emux-docker-shell". Este es el cliente de Emux que se conecta automáticamente a EMUX-DOCKER, para nosotros este processo es transparente.
Ahora desde la consola emux-docker ejecutaremos el comando userspace y nos saldrá la siguiente venta donde vamos a elegir la opción 2 Start TPLINK WR841ND v11.
Durante el arranque vamos a ver bastante errores, pero es normal porque realmente no estamos emulado la firmware al 100% con todo el hardware que requiere, incluso es posible que algunas librerias y módulos no se cargen correctamente o falten algunos ficheros y la firmware entre en un bucle de mensajes de error constantes y es posible que no veamos la tan esperada shell de linux en esa ventana por los mensajes de errores. Veremos que si damos a la tecla return nos aparecerá la shell (#) pero veremos mas mensajes de error y asi no vamos a poder trabajar. Pero hay una solución muy sencilla.
Pero antes, vamos hacer unas comprobaciones en la consola EMUX-DOCKER. Por ejemplo, si la interface de red eth0 está funcionando. Lo podemos ver en la parte superior de la siguiente captura en la linea eth0: link up.
Como el dispositivo puede tardar unos minutos en arrancar completamente, un truco interesante es dar al botón return en la shell EMUX-DOCKER de vez en cuando para ver si el prompt de la consola cambia de algo similar a MIPSX a TL-WR841N Login:. Esto signficará que el dispositvo ya ha finalizado de arrancar y por lo tanto los servicios también deberían estar funcionando.
Ahora abrimos una nueva terminal de linux y ejecutamos una nueva shell de emux-docker antes de ejecutar el comando userspace podemos ejecutar unas herramientas que tenemos con Emux que nos van a dar información de que servicios y procesos de nuestra firmware ya están funcionando. Por ejemplo podemos ejecutar el comando emuxps (ps de linux) para ver los procesos que se están ejecutando.
En la captura anterior vemos que el servicio ssh dropbear está abierto y también el servidor web httpd está funcionando. También podemos ver los puertos de red que están abiertos con la herramienta emuxnetstat (netstat de linux/windows)
Con esta información también vemos que el servidor web está escuchando en Docker en el puerto 80. Este puerto realmente está redirigido desde el contendor Docker al puerto 20080 de nuestra máquina. Asi que deberáimos poder acceder al servidor web través de nuestro navegador web utiliando la url http://127.0.0.1:20080.
Vamos a ver si funciona correctamente, usamos el usuario admin y la contraseña admin y si todo es correcto ya estariamos dentro del panel de administración web del router.
Por ejemplo, ahora que el servicio web está funcionando podríamos usar un fuzzer para testear el aplicativo web. Además de las utilidades que hemos visto antes también tenemos emuxgdb para debugear un proceso utilizando gdb con gef, emuxmaps para mirar la memoria de un proceso y emuxkill para matar un proceso, y finalmente pero muy importante está la herramienta emuxhalt para cerrar Emux de una forma segura y así evitar que se queden ficheros temporales sin borrar que pueden dar algun problema en futuras sesiones de la misma firmware.
Estas herramientas que tenemos en Emux son muy útiles ya que normalmente no vamos a disponer de ellas dentro de nuestra firmware.
Acceder mendiante consola a nuestro dispotivo emulado
Si queremos acceder a la shell de nuestro dispositivo emulado, simplemente podemos ejecutar el comando userspace en una nueva consola de emux-docker y en el menú debemos seleccionar en esta ocasión la opción 3 Enter TPLINK WR841ND v11 CONSOLE (exec /bin/sh)
De esta forma tendremos acceso mediante una shell a nuestro dispositivo emulado como su hubiesemos accedido a través de una conexión telnet, ssh o UART. Epero que os haya gustado.
Happy IoT hacking!!
Al comenzar el CTF, recibimos herramientas de soldadura (soldador, estaño, malla, pinzas, hilo esmaltado...), las 3 pruebas del CTF (dos placas y un Arduino) y un Bus Pirate con el cableado necesario. Comenzamos analizando el hardware sobre el que tenemos que trabajar y a preparar el equipo con el software necesario. En este caso, usaremos un software para conectarnos al puerto serial como Putty o Minicom, y también FlashRom para atacar la Winbond Una vez instalado el software y listos los drivers del Bus Pirate, vamos a por la primera lectura.
En el apartado "Hacking EEPROM AT24C256 I2C 5V", tenemos documentado como realizar la lectura de esta memoria EEPROM paso a paso con el conexionado, así que no repetiré la explicación. En este caso, al realizar la lectura encontramos el contenido tal que así:
"s p i f l a s h e n c r y p t e d w i t h k e y :
d r 3 6 A E S 2 5 6 g p g ! _ "
Todo indica que, en una flash SPI, encontraremos algo cifrado con GPG y nos da la passphrase para descifrarla, tomamos nota y seguimos con la prueba.
También está documentada la lectura de esta memoria SPI en el apartado "Hacking FLASH SPI Winbond 25Q64FVSIG", por lo que atacamos la memoria con Flashrom dumpeando el contenido en un fichero. En principio, al abrirlo con un editor hexadecimal no vemos una flag obvia como en el caso anterior, por lo que analizaremos el fichero para comprender su contenido.
Usando la herramienta "file" de linux, aplicada al dump que acabamos de generar, recibiremos la siguiente salida:
Wimbond.img: GPG symmetrically encrypted data (AES256 cipher)
Tal y como nos había cantado la AT24C256, tenemos un fichero cifrado con GPG, por lo que procedemos a descifrarlo:
gpg -d -o out Winbond_cut
Introducimos la passphrase y volvemos a pasar file al fichero resultante, para ver qué tenemos entre manos:
Dump: Linux rev 1.0 ext2 filesystem data (mounted or unclean), UUID=8f239733-d0fe-4fc3-86a9-cea78fe82c2b (large files)
El fichero contiene un sistema de ficheros ext2, que debemos montar en un directorio local para ver su contenido:
mkdir /dump
mount out /dump
Si vemos el contenido del sistema de ficheros, encontraremos un fichero comprimido en tar, que al descomprimirlo contendrá un fichero xz, que contiene un fichero tar... que contiene un fichero dreg_flag.txt, que contiene lo siguiente: "L0ng@Dr3gish0otFl4gl3E7!#69ro0tedMad2024" Los pasos seguidos para descomprimir los distintos formatos fueron los siguientes:
ls -la
file unk
tar -xz unk
unxz unk
mv unk unk.xz
unxz unk.xz
ls -la
file unk
tar -xf unk
ls -la
cat dreg_flag.txt
Una vez conseguido el primer flag, nos toca meter mano al hardware, ya que nos dan un Arduino que, al parecer, solo puede ser leído mediante su interfaz SPI, pero que al intentarlo no parece funcionar. Comprobamos conexiones, configuración... y nada, imposible! Revisamos la placa y confirmamos las dudas: La placa ha sido saboteada para no poder leerse: Uno de los pines necesarios tiene la pista cortada, por lo que tendremos que rehacerla. Hay muchas maneras de atacar este problema, como buscar de qué punto de la placa viene la pista y restaurarla desde allí, hacer un puente de una zona cómoda de la pista... o intentar hacer un puente justo en la zona cortada, la cual fue mi manera de atacar el problema. Haciendo uso del escalpelo, se rasca a izquierda y derecha del corte para poder acceder a la pista, pero sin quitar el esmalte de las pistas cercanas o de los planos de tierra. Una vez listo, con estaño en la punta del soldador y algo de Flux en la zona, intentamos hacer el puente. Cogemos el polímetro y comprobamos la continuidad de la pista, y, sobre todo, que no tenemos ningún corto no deseado.
Estamos listos para leer la placa. Reconectamos el Bus Pirate y usamos AVRDude para leer el contenido. En la web de Buzzpirat tenemos documentado el uso de AVRDude con nuestra placa: "https://buzzpirat.com/docs/avrdude/" Hacemos un intento de lectura para detectar el avr a leer:
"avrdude -c buspirate -P COM1 -b 115200 -p m328pb"
AVRDude confirmará la firma del integrado y podremos seguir:
"avrdude: device signature = 0x1e9516 (probably m328pb)"
El integrado tiene eeprom y flash, pero en este caso el contenido que nos interesa se encuentra en la eeprom:
"avrdude -c buspirate -p m328pb -P COM1 -b 115200 -U eeprom:r:"eeprom.img":i"
Es posible que os encontréis con sistemas en los que, si tratáis de analizar un dump, no os cuadra nada y veis instrucciones que no tienen sentido, o las herramientas de análisis estático no detectan instrucciones.
Por ejemplo, os podéis encontrar con sistemas como los basados en M68K que tienen un bus de datos de 16 bits y trabajan en modo big endian. A veces se usan dos ROMs x8: una para la parte alta y otra para la parte baja. Pero también os podéis encontrar que se está usando una ROM x16.
Al dumpear eso, es posible que os quede intercambiada la parte alta y baja de cada word. Veamos parte de un dump de ejemplo con este problema:
He puesto la parte donde aparecen cadenas porque ahí resulta más evidente y se puede ver a ojo. En este caso, para que las herramientas de análisis puedan analizar correctamente, deberíamos intercambiar los bytes de cada palabra. Una forma de hacerlo es usar el comando dd:
dd if=dump.bin of=dump-fixed.bin conv=swab
Veamos ahora la misma parte, pero del fichero dump-fixed.bin:
Ahí ya podemos ver mejor las cadenas, y seguramente al usar herramientas de análisis también se van a detectar correctamente las instrucciones.
Cada sistema tiene sus pecularidades en cuanto a direcciones de memoria. A veces podéis tratar de analizar un dump y, aunque indiquéis la arquitectura adecuada y sepáis que en la zona que estáis tratando hay código, la herramienta no es capaz de detectarlo correctamente y muestra instrucciones pero no puede detectar referencias a variables o cadenas, ni procedimientos.
Eso puede pasar cuando no se indican bien los segmentos y, por lo tanto, el desensamblador ve referencias a zonas de memoria que no conoce. También puede pasar que el firmware redefina el mapa de memoria o se copie en otra zona y pase a ejecutar ahí.
Hay casos en los que se puede intuir la dirección base en la que corre un código. Tomemos como ejemplo esta parte de un dump de U-Boot:
Se puede ver a ojo que el código tiene como dirección base 0xbc000000. Aquí se ve de manera sencilla porque esta versión de U-Boot tiene un array con los handlers de cada comando. Si definimos correctamente el mapa de memoria en Ghidra o lo que se use para análisis estático, podremos seleccionar todo e iniciar el análisis como código para que detecte procedimientos. Luego podremos ir a una cadena como por ejemplo "Hit any key to stop autoboot" y buscar dónde se le hace referencia. Con eso he logrado saber que algunos routers requieren enviar un carácter determinado, como la 't' en lugar de cualquier cosa para entrar en la línea de comandos de U-Boot.