Introducción
Nordic nRF51 es una serie de chips de sistema en un chip (SoC) desarrollados por Nordic Semiconductor. Estos chips están diseñados específicamente para aplicaciones de baja potencia y baja energía en el ámbito de la conectividad inalámbrica. La serie nRF51 incluye varios modelos, como el nRF51822 y el nRF51802, entre otros.
Estos chips son ampliamente utilizados en dispositivos de bajo consumo, como dispositivos vestibles (wearables), sensores IoT (Internet de las cosas), dispositivos de salud, y otros dispositivos que requieren una conectividad eficiente y un consumo de energía mínimo. En el caso de que necesitemos realizar un análisis de ingeniería inversa a algunos de estos dispositivos será necesario obtener una copia del firmware del propio chip del dispositivo.
Para ello vamos a utilizar un depurador del tipo CMSIS-DAP, que es especificación de protocolo y una implementación de firmware que permite el acceso al puerto de acceso de depuración (DAP) CoreSight de un microcontrolador ARM Cortex a través de USB, en este caso el SWD (Serial Wire Debug). Al necesitar hardware especializado, podemos utilizar un Flipper Zero, o placa de desarrollo. Existen soluciones más económicas en el mercado, pero en este caso se va a utilizar la del Flipper Zero.
Preparación del entorno
En primer lugar debemos localizar en el dispositivo a copiar el chip utilizado, en este caso el nRF51822, por lo que tomamos sus especificaciones. Ahora necesitamos localizar los tres puertos necesarios a conectar en el depurador: el SWDIO (pin 23), el SWDCLK (pin 24), y el GND (pin 13). En esta foto se ilustran los pines.
Si tenemos suerte encontraremos que los pines tienen una pista en la PCB y terminan en un punto de pruebas en el cuál tendremos que soldar el cable o incluso terminen en un conector de tipo Dupont. En el peor de los casos tendremos que soldar un cable de un grosor muy fino en la patilla del chip, debido a su reducido tamaño.
Conexión del depurador
Podemos utilizar dos opciones como depurador: El propio Flipper Zero o su placa de desarrollo. Se analizarán las dos opciones.
Opción 1: Conexión del depurador al Flipper Zero
Teniendo ya el acceso a los puertos de depuración podemos realizar la conexión. En el caso del Flipper Zero, conectaremos el pin SWDCLK al pin 2-A7, el pin GND al 8-GND, y el SWDIO al pin 3-A6. Disponemos más información sobre el GPIO del Flipper Zero en su documentación. Después de instalar la aplicación DAP Link la abriremos y conectaremos el Flipper Zero a nuestro ordenador.
Se reconocerá como un dispositivo CMSIS-DAP.
Opción 2: Conexión del depurador a la placa de desarrollo
Teniendo ya el acceso a los puertos de depuración podemos realizar la conexión. En el caso del Flipper Zero, conectaremos el pin SWDCLK al pin 10-IO1, el pin GND al 11-GND, y el SWDIO al pin 12-IO2. Disponemos más información sobre el GPIO de la placa de desarrollo del Flipper Zero en su documentación. Necesitamos tener instalado el firmware original BlackMagic en la placa.
Cuando conectemos la placa por USB-C creará un punto de acceso al cuál nos conectaremos para acceder a su página de configuración http://blackmagic.local. Cambiaremos la opción USB mode de BlackMagicProbe a DapLink y reiniciaremos la placa.
Ahora en nuestro ordenador se reconocerá la placa como un dispositivo CMSIS-DAP.
$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 004: ID 303a:4002 CMSIS-DAP CMSIS-DAP ESP32S2 Device
Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Conexión del depurador con OpenOCD
OpenOCD (Open On-Chip Debugger) es una herramienta de código abierto diseñada para brindar soporte de depuración y programación para una amplia variedad de dispositivos embebidos. En el caso de que no la tengamos instalada, podemos instalarla así en los sistemas operativos Linux Debian.
$ sudo apt install openocd
A continuación ejecutamos el programa con las dos opciones -f que especifican los archivos de configuración de OpenOCD a utilizar, el primero referido al depurador CMSIS-DAP y el segundo al chip nRF51.
$ openocd -f /usr/share/openocd/scripts/interface/cmsis-dap.cfg -f /usr/share/openocd/scripts/target/nrf51.cfg
Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x303a:0x4002, serial=DAP
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 1000 kHz
Info : SWD DPIDR 0x0bb11477
Info : [nrf51.cpu] Cortex-M0 r0p0 processor detected
Info : [nrf51.cpu] target has 4 breakpoints, 2 watchpoints
Info : starting gdb server for nrf51.cpu on 3333
Info : Listening on port 3333 for gdb connections
Con la salida [nrf51.cpu] Cortex-M0 r0p0 processor detected comprobamos que la conexión es correcta, si la salida fuera Error: Error connecting DP: cannot read IDR significará que la conexión es incorrecta. Ahora podemos controlar la línea de comandos de OpenOCD realizando una conexión telnet al puerto 4444.
$ telnet localhost 4444
Copia de la memoria del chip nRF51
Ahora que tenemos acceso a la línea de comandos de OpenOCD y la conexión con el chip es correcta podemos obtener la dirección en memoria del código que se está ejecutando en el microprocesador con el comando flash banks.
> flash banks
#0 : nrf51.flash (nrf51) at 0x00000000, size 0x00040000, buswidth 1, chipwidth 1
#1 : nrf51.uicr (nrf51) at 0x10001000, size 0x00000000, buswidth 1, chipwidth 1
La memoria flash empieza en la dirección de memoria 0x0 y tiene un tamaño de 0x40000 bytes, por lo que acaba en la dirección de memoria 0x4000. Antes de copiar la memoria pararemos la ejecución del procesador para que no se pare la copia, con el comando reset halt.
> reset halt
Ahora ya podremos iniciar con la copia del firmware usando el comando dump_image, con los parámetros nombre del archivo, dirección de memoria inicial y tamaño.
> dump_image firmware.bin 0x0 0x40000
Después de unos segundos tendremos la copia del firmware en el archivo firmware.bin. Se recomienda realizar este proceso dos veces y comparar los archivo por si acaso la copia no se ha realizado correctamente. Al abrir el archivo en un editor hexadecimal pueden ocurrir dos casos:
Que nos encontremos un archivo con alta entropía, es decir, con código ensamblador ejecutable en hexadecimal lo que significará en la copia es correcta o que todo el archivo este compuesto por el byte 0x00, lo que significará que el chip está protegido contra intentos de lectura de memoria. En el segundo caso tendremos que utilizar otro método para obtener la copia del firmware. Al terminar la copia reiniciaremos el dispositivo con el comando reset.
> reset
Copia de la memoria del chip nRF51 protegido
El chip nRF51 puede estar configurado para denegar la lectura de la memoria desde el puerto de depuración. Este chip tiene el procesador ARM Cortex-M0, el cuál tiene una vulnerabilidad mediante la cuál se pueden leer direcciones de memoria de forma arbitraria y cargarlas a un registro para finalmente leerlas. Este proceso se encuentra explicado de una manera más extensa en este artículo.
Disponemos de un programa Python desarrollado por SpiderLabs que nos facilita esta tarea. Se requiere utilizar Python en su versión 2.7 y realizar un cambio en la línea 70 ya que no es compatible con las últimas versiones de OpenOCD. Se cambia la línea upper() por lower(). Finalmente lo ejecutaremos y tendremos acceso a la copia del firmware en el archivo out.bin.
$ python2 nrfdump.py
Open On-Chip Debugger
[*] Reading RBPCONF to establish known memory address / value...
[***] RBPCONF is: 0xffff00ff
[*] Searching for usable instruction...
[*] pc = 0x6d0
[*] pc = 0x6d2
[*] pc = 0x6d4
[***] Known value found in register r4 for pc = 0x6d4
[*] Checking which register is the source...
[*] register: r0
[*] register: r1
[*] register: r2
[*] register: r3
[*] register: r4
[***] Found source register: r4
[***] The state of the game:
Known address: 0x10001004
Known value at the address: 0xffff00ff
Instruction address: 0x6d4
Register in: r4
Register out: r4
[*] Dumping memory (0x0 - 0x40000) to output file: testest.bin ...
0x0: ...
Conclusión
Con el archivo del firmware copiado podremos iniciar con su análisis utilizando herramientas como Ghidra o IDA Pro.