Last time I wrote about this, I lied a little - There is an interesting bug in the UART loader, and it may have been exactly why Xilinx didn't document it. In short: The UART loader writes the entire UART payload to a location in memory (nominally 0x4_0000
). The ROM is architected such that when the boot mode is selected, it registers a callback that is called when the ROM wants more data from the boot device. For the UART loader, this is pretty simple - here's the whole thing:
; void uart_callback(u32 r0_offset, void* r1_dest, i32 r2_nbytes)
ROM:0000A578 PUSH {R3,LR}
ROM:0000A57C MOV R3, #uart_buff
ROM:0000A584 MOV R12, #1
ROM:0000A588 LDR R3, [R3]
ROM:0000A58C MOVT R12, #7
ROM:0000A590 ADD R3, R3, R0
ROM:0000A594 CMP R3, R12 ; 0x7_0001
ROM:0000A598 BHI exit
ROM:0000A59C SUB R12, R3, #0x70000
ROM:0000A5A0 SUB LR, R12, #1
ROM:0000A5A4 ADD R0, LR, R2
ROM:0000A5A8 CMP R0, #0
ROM:0000A5AC RSBGT R2, R0, R2 ; nbytes
ROM:0000A5B0 MOV R0, R1 ; dst
ROM:0000A5B4 MOV R1, R3 ; src
ROM:0000A5B8 BL memcpy
ROM:0000A5BC
ROM:0000A5BC exit:
ROM:0000A5BC MOV R0, #0
ROM:0000A5C0 POP {R3,PC}
The check at 0xa5a8
we can ignore, it's not important - suffice to say the length is well-checked, even though it's signed.
The interesting part is the memcpy source address calculation: There are absolutely no checks on this, and it just adds our offset
argument directly.
Well, ok - It checks to see that the raw address is not above 0x7_0001
. That's nice - if we fully control offset
at any point where dest
is something that persists after the ROM has locked itself down, we can stash stuff to look at later.
During a normal boot process, our callback is called a handful of times (no TOCTOU, sorry: most stuff ends up being cached in the caller) to read snippets of the image header (documented in the Zynq TRM).
The final call (in non-secureboot mode) is to just read the whole image, starting at the bootrom header field "Source Offset" - plus, if we're starting from a multiboot-capable image, that multiboot image base offset. UART isn't, so that is always 0.
"Source Offset" is entirely unchecked, so...Since we completely control this 32-bit field, we can also completely control the result when a constant is added. In this case, we can certainly make it equal zero, which definitely passes that singular check.
There's a script attached to exploit this - Run it, attach your favorite debugger and find the ROM readily in OCM RAM starting at 0 (mrd -bin -file whatever.bin 0 0x8000
will do the trick). The only bit that could not be determined by experimentation is the entry point I use (just a busy loop). Obviously this is just a quality of life thing :)
Anyway, hope this is a little interesting, or provides a reason to be a little more careful about pointer math, or minimally gives everyone an easier way of getting at the ROM without copyright issues or glitching or (...tbd...). Happy hacking!
small aside: if you have one of those cheap eBay Antminer boards (schematic here), the UART header is hooked up perfectly: you can select the correct mode by setting the two outermost jumpers toward the board edge, and the inner two the opposite direction.
additional aside: it is possible to stash code based at 0x4_0000. you cannot specify an entrypoint over 0x3_0000, so it's not possible to jump directly into this code. you have a single jump with very minimal (non-existant, really) register control - is it possible to write the exploit such that it dumps the bootrom entirely over UART without the aid of JTAG or another boot device? I suspect so, but have not proved it out. You can be first!