Recover GD32F303RI Program Data

Two megabytes is where this family stops being a microcontroller with a lot of flash and starts being a storage device that happens to execute code. The GD32F303RI keeps the 64-pin body and the 96 KB of SRAM of its one-megabyte sibling and doubles the array, which means bank one grows to 1536 KB and the page count above the seam rises to 384. Every one of those pages is four kilobytes, and the last one ends at 0x081FFFFF. Getting the arithmetic right is most of the job.

Two megabytes and 384 pages above the seam

Bank zero is unchanged: pages zero to two hundred and fifty-five, two kilobytes each, covering the first half megabyte. Bank one begins at page two hundred and fifty-six and runs to page six hundred and thirty-nine, four kilobytes each, which places the final page base at 0x081FF000 and the last byte of the array at 0x081FFFFF. Two hundred and fifty-six pages below the seam, three hundred and eighty-four above it, six hundred and forty in total. A tool that keeps a single page-size variable will lose track of exactly where the seam is, and the symptom is a dump that looks plausible until it is compared with the map.

Index arithmetic that has to be right

The trap is not the seam itself but the habit of computing an address from an index. Below the seam, base plus index times two kilobytes works. Above it, the correct expression is 0x08080000 plus the excess index times four kilobytes. Both formulas agree at the seam and diverge immediately after it, so a spot check taken at page 256 will pass and every later page will be wrong by an increasing amount. The only defence is a page table generated once, printed, and checked against three known addresses: the first page, the first page of bank one, and the last page of the array.

Bank one has its own control register, its own lock bit and its own key register, and the magic values are the same pair used everywhere else on the part. Unlocking bank one while bank zero is locked is legal and is sometimes the right move, since a recovery that only needs the upper region does not have to touch the lower one. The a locked STM32F102RB report shows a two-bank unlock done in the wrong order and STM32F103R4 unlocking the same operation done correctly.

Dump strategy and checksumming

At two megabytes the image no longer fits comfortably in the working memory of a small fixture, so streaming is mandatory: read a block, hash it, write it out, move on. Block size should match the page size of the region being read, four kilobytes above the seam and two below, so that a failed block can be re-read without disturbing its neighbours. Keep a manifest that records, for each block, the address, the length, the hash and the number of attempts. When a session is interrupted, the manifest is what allows it to resume rather than restart.

Two complete passes with a power cycle between them remain the standard. On a device this large, a single differing block is not evidence of damage; it is evidence that something changed between reads, and the usual causes are a supply that is not stable, a fixture contact that is not seated, or a product that wakes up and writes to its own flash. All three are worth ruling out before the array is blamed. STM32F103R6 cracking and STM32F103VET6 work both describe sessions where the second pass disagreed and the cause was outside the die.

The tail page

One detail deserves its own heading. The last page of the array, at 0x081FF000, is frequently blank on production devices, because linker scripts rarely fill the top of a two-megabyte part. A blank tail page is normal and should be recorded as such. What is not normal is a tail page that contains data while the pages immediately below it are blank, since that pattern suggests a partial erase or an interrupted update, and the image should be treated as suspect until the boundary is explained. Work of this type is covered by extracting IC code, with the reconstruction phase described under the MCU reverse-engineering workflow.

The deliverable for an RI is a binary, a six-hundred-and-forty-entry page map, per-block hashes, the option block, and a short note on the state of the tail. Everything else about the job follows the family pattern: one security byte, one protection mask, one mass-erase exit, and a fetch penalty above the first quarter of the array that shapes how the original firmware was laid out.