Adding processor flash memory reading, viewing and writing to file.

Works when the device is running either osimage or bootloader.

- New memory reading command in osimage and bootloader.
- Extended 'hw readmem' command with length parameter, file writing and hex viewer.
- Introduced '--dumpmem' option to proxmark3 executable to support dumping from bootloader.

Simple interactive examples:
  hw readmem -f flashdump
  hw readmem -l 1024
CLI example:
  ./pm3 --dumpmem flashdump.bin

Reading from arbitrary memory ranges can be unlocked using the 'raw' option.
This commit is contained in:
Martijn Plak 2024-01-22 16:38:09 +01:00
commit e35385fde1
10 changed files with 386 additions and 12 deletions

View file

@ -2364,6 +2364,52 @@ static void PacketReceived(PacketCommandNG *packet) {
ReadMem(packet->data.asDwords[0]);
break;
}
case CMD_READ_MEM_DOWNLOAD: {
LED_B_ON();
size_t offset = packet->oldarg[0];
size_t count = packet->oldarg[1];
uint32_t flags = packet->oldarg[2];
bool isok = true;
uint8_t *base = NULL;
bool raw_address_mode = (flags & CMD_READ_MEM_DOWNLOAD_RAW) != 0;
if (!raw_address_mode) {
base = (uint8_t *) _flash_start;
size_t flash_size = get_flash_size();
// Boundary check the offset.
if (offset > flash_size) {
isok = false;
Dbprintf("reading mcu flash failed :: | out of bounds, offset %u count %u", offset, count);
}
// Clip the length if it goes past the end of the flash memory.
count = MIN(count, flash_size - offset);
} else {
// Allow reading from any memory address and length in special 'raw' mode.
base = NULL;
}
if (isok) {
for (size_t pos = 0; pos < count; pos += PM3_CMD_DATA_SIZE) {
size_t len = MIN((count - pos), PM3_CMD_DATA_SIZE);
isok = 0 == reply_old(CMD_READ_MEM_DOWNLOADED, pos, len, 0, &base[offset + pos], len);
if (!isok) {
Dbprintf("transfer to client failed :: | pos %u len %u", pos, len);
break;
}
}
}
reply_old(CMD_ACK, 1, 0, 0, 0, 0);
LED_B_OFF();
break;
}
#ifdef WITH_FLASH
case CMD_SPIFFS_TEST: {
test_spiffs();

View file

@ -321,3 +321,34 @@ bool data_available_fast(void) {
return usb_available_length();
#endif
}
uint32_t flash_size_from_cidr(uint32_t cidr) {
uint8_t nvpsiz = (cidr & 0xF00) >> 8;
switch (nvpsiz) {
case 0:
return 0;
case 1:
return 8*1024;
case 2:
return 16*1024;
case 3:
return 32*1024;
case 5:
return 64*1024;
case 7:
return 128*1024;
case 9:
return 256*1024;
case 10:
return 512*1024;
case 12:
return 1024*1024;
case 14:
default: // for 'reserved' values, guess 2MB
return 2048*1024;
}
}
uint32_t get_flash_size(void) {
return flash_size_from_cidr(*AT91C_DBGU_CIDR);
}

View file

@ -103,4 +103,7 @@ int BUTTON_HELD(int ms);
bool data_available(void);
bool data_available_fast(void);
uint32_t flash_size_from_cidr(uint32_t cidr);
uint32_t get_flash_size(void);
#endif

View file

@ -89,6 +89,37 @@ static void Fatal(void) {
for (;;) {};
}
static uint32_t flash_size_from_cidr(uint32_t cidr) {
uint8_t nvpsiz = (cidr & 0xF00) >> 8;
switch (nvpsiz) {
case 0:
return 0;
case 1:
return 8*1024;
case 2:
return 16*1024;
case 3:
return 32*1024;
case 5:
return 64*1024;
case 7:
return 128*1024;
case 9:
return 256*1024;
case 10:
return 512*1024;
case 12:
return 1024*1024;
case 14:
default: // for 'reserved' values, guess 2MB
return 2048*1024;
}
}
static uint32_t get_flash_size(void) {
return flash_size_from_cidr(*AT91C_DBGU_CIDR);
}
static void UsbPacketReceived(uint8_t *packet) {
bool ack = true;
PacketCommandOLD *c = (PacketCommandOLD *)packet;
@ -104,7 +135,8 @@ static void UsbPacketReceived(uint8_t *packet) {
DEVICE_INFO_FLAG_CURRENT_MODE_BOOTROM |
DEVICE_INFO_FLAG_UNDERSTANDS_START_FLASH |
DEVICE_INFO_FLAG_UNDERSTANDS_CHIP_INFO |
DEVICE_INFO_FLAG_UNDERSTANDS_VERSION;
DEVICE_INFO_FLAG_UNDERSTANDS_VERSION |
DEVICE_INFO_FLAG_UNDERSTANDS_READ_MEM;
if (g_common_area.flags.osimage_present)
arg0 |= DEVICE_INFO_FLAG_OSIMAGE_PRESENT;
@ -126,6 +158,54 @@ static void UsbPacketReceived(uint8_t *packet) {
}
break;
case CMD_READ_MEM_DOWNLOAD: {
ack = false;
LED_B_ON();
size_t offset = (size_t) c->arg[0];
size_t count = (size_t) c->arg[1];
uint32_t flags = (uint32_t) c->arg[2];
bool isok = true;
uint8_t *base = NULL;
bool raw_address_mode = (flags & CMD_READ_MEM_DOWNLOAD_RAW) != 0;
if (!raw_address_mode) {
base = (uint8_t *) _flash_start;
size_t flash_size = get_flash_size();
// Boundary check the offset.
if (offset > flash_size)
isok = false;
// Clip the length if it goes past the end of the flash memory.
count = MIN(count, flash_size - offset);
} else {
// Allow reading from any memory address and length in special 'raw' mode.
base = NULL;
}
if (isok) {
for (size_t pos = 0; pos < count; pos += PM3_CMD_DATA_SIZE) {
size_t len = MIN((count - pos), PM3_CMD_DATA_SIZE);
isok = 0 == reply_old(CMD_READ_MEM_DOWNLOADED, pos, len, 0, &base[offset + pos], len);
if (!isok)
break;
}
}
if (isok)
reply_old(CMD_ACK, 1, 0, 0, 0, 0);
else
reply_old(CMD_NACK, 0, 0, 0, 0, 0);
LED_B_OFF();
break;
}
case CMD_FINISH_WRITE: {
#if defined ICOPYX
if (c->arg[1] == 0xff && c->arg[2] == 0x1fd) {

View file

@ -633,20 +633,63 @@ static int CmdLCDReset(const char *Cmd) {
static int CmdReadmem(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hw readmem",
"Read memory at decimal address from ARM chip flash.",
"hw readmem -a 10000"
"Reads processor flash memory into a file or views on console",
"hw readmem -f myfile -> save 512KB processor flash memory to file\n"
"hw readmem -a 8192 -l 512 -> display 512 bytes from offset 8192\n"
);
void *argtable[] = {
arg_param_begin,
arg_u64_1("a", "adr", "<dec>", "address to read"),
arg_int0("a", "adr", "<dec>", "flash address to start reading from"),
arg_int0("l", "len", "<dec>", "length (default 32 or 512KB)"),
arg_str0("f", "file", "<fn>", "save to file"),
arg_int0("c", "cols", "<dec>", "column breaks"),
arg_lit0("r", "raw", "use raw address mode: read from anywhere, not just flash"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint32_t address = arg_get_u32(ctx, 1);
CLIExecWithReturn(ctx, Cmd, argtable, false);
// check for -file option first to determine the output mode
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
bool save_to_file = fnlen > 0;
// default len to 512KB when saving to file, to 32 bytes when viewing on the console.
uint32_t default_len = save_to_file ? 512*1024 : 32;
uint32_t address = arg_get_u32_def(ctx, 1, 0);
uint32_t len = arg_get_u32_def(ctx, 2, default_len);
int breaks = arg_get_int_def(ctx, 4, 32);
bool raw = arg_get_lit(ctx, 5);
CLIParserFree(ctx);
clearCommandBuffer();
SendCommandNG(CMD_READ_MEM, (uint8_t *)&address, sizeof(address));
uint8_t *buffer = calloc(len, sizeof(uint8_t));
if (!buffer) {
PrintAndLogEx(ERR, "error, cannot allocate memory ");
return PM3_EMALLOC;
}
const char *flash_str = raw ? "" : " flash";
PrintAndLogEx(INFO, "reading "_YELLOW_("%u")" bytes from processor%s memory",
len, flash_str);
DeviceMemType_t type = raw ? MCU_MEM : MCU_FLASH;
if (!GetFromDevice(type, buffer, len, address, NULL, 0, NULL, -1, true)) {
PrintAndLogEx(FAILED, "ERROR; reading from MCU flash memory");
free(buffer);
return PM3_EFLASH;
}
if (save_to_file) {
PrintAndLogEx(INFO, "saving to "_YELLOW_("%s"), filename);
saveFile(filename, ".bin", buffer, len);
} else {
PrintAndLogEx(INFO, "---- " _CYAN_("processor%s memory") " ----", flash_str);
print_hex_break(buffer, len, breaks);
}
free(buffer);
return PM3_SUCCESS;
}
@ -1150,7 +1193,7 @@ static command_t CommandTable[] = {
{"lcd", CmdLCD, IfPm3Lcd, "Send command/data to LCD"},
{"lcdreset", CmdLCDReset, IfPm3Lcd, "Hardware reset LCD"},
{"ping", CmdPing, IfPm3Present, "Test if the Proxmark3 is responsive"},
{"readmem", CmdReadmem, IfPm3Present, "Read memory at decimal address from flash"},
{"readmem", CmdReadmem, IfPm3Present, "Read from processor flash"},
{"reset", CmdReset, IfPm3Present, "Reset the Proxmark3"},
{"setlfdivisor", CmdSetDivisor, IfPm3Present, "Drive LF antenna at 12MHz / (divisor + 1)"},
{"setmux", CmdSetMux, IfPm3Present, "Set the ADC mux to a specific value"},

View file

@ -1134,6 +1134,12 @@ bool GetFromDevice(DeviceMemType_t memtype, uint8_t *dest, uint32_t bytes, uint3
SendCommandNG(CMD_FPGAMEM_DOWNLOAD, NULL, 0);
return dl_it(dest, bytes, response, ms_timeout, show_warning, CMD_FPGAMEM_DOWNLOADED);
}
case MCU_FLASH:
case MCU_MEM: {
uint32_t flags = memtype == MCU_MEM ? CMD_READ_MEM_DOWNLOAD_RAW : 0;
SendCommandBL(CMD_READ_MEM_DOWNLOAD, start_index, bytes, flags, NULL, 0);
return dl_it(dest, bytes, response, ms_timeout, show_warning, CMD_READ_MEM_DOWNLOADED);
}
}
return false;
}

View file

@ -54,6 +54,8 @@ typedef enum {
SIM_MEM,
SPIFFS,
FPGA_MEM,
MCU_FLASH,
MCU_MEM,
} DeviceMemType_t;
typedef enum {

View file

@ -636,6 +636,11 @@ static void show_help(bool showFullHelp, char *exec_name) {
PrintAndLogEx(NORMAL, " --unlock-bootloader Enable flashing of bootloader area *DANGEROUS* (need --flash)");
PrintAndLogEx(NORMAL, " --force Enable flashing even if firmware seems to not match client version");
PrintAndLogEx(NORMAL, " --image <imagefile> image to flash. Can be specified several times.");
PrintAndLogEx(NORMAL, "\nOptions in memory dump mode:");
PrintAndLogEx(NORMAL, " --dumpmem <dumpfile> dumps Proxmark3 flash memory to file");
PrintAndLogEx(NORMAL, " --dumpaddr <address> starting address for dump, default 0");
PrintAndLogEx(NORMAL, " --dumplen <length> number of bytes to dump, default 512KB");
PrintAndLogEx(NORMAL, " --dumpraw raw address mode: dump from anywhere, not just flash");
PrintAndLogEx(NORMAL, "\nExamples:");
PrintAndLogEx(NORMAL, "\n to run Proxmark3 client:\n");
PrintAndLogEx(NORMAL, " %s "SERIAL_PORT_EXAMPLE_H" -- runs the pm3 client", exec_name);
@ -660,6 +665,109 @@ static void show_help(bool showFullHelp, char *exec_name) {
}
}
static int dumpmem_to_file(const char *filename, uint32_t addr, uint32_t len, bool raw, bool in_bootloader) {
int res = PM3_EUNDEF;
uint8_t *buffer = calloc(len, sizeof(uint8_t));
if (!buffer) {
PrintAndLogEx(ERR, "error, cannot allocate memory ");
res = PM3_EMALLOC;
goto fail;
}
size_t read = 0;
DeviceMemType_t type = raw ? MCU_MEM : MCU_FLASH;
if (GetFromDevice(type, buffer, len, addr, NULL, 0, NULL, 1000, true)) {
res = PM3_SUCCESS;
read = len; // GetFromDevice does not report the actual number of bytes received.
}
if (res == PM3_SUCCESS) {
FILE *fd = fopen(filename, "wb");
if (!fd) {
PrintAndLogEx(ERR, _RED_("Could not open file") " %s >>> ", filename);
res = PM3_EFILE;
goto fail2;
}
fwrite(buffer, 1, read, fd);
fclose(fd);
}
fail2:
free(buffer);
fail:
return res;
}
static int dumpmem_pm3(char *serial_port_name, const char *filename, uint32_t addr, uint32_t len, bool raw) {
int ret = PM3_EUNDEF;
bool in_bootloader = false;
if (serial_port_name == NULL) {
PrintAndLogEx(ERR, "You must specify a port.\n");
return PM3_EINVARG;
}
if (OpenProxmark(&g_session.current_device, serial_port_name, true, 60, true, FLASHMODE_SPEED)) {
PrintAndLogEx(NORMAL, _GREEN_(" found"));
msleep(200);
} else {
PrintAndLogEx(ERR, "Could not find Proxmark3 on " _RED_("%s") ".\n", serial_port_name);
ret = PM3_ETIMEOUT;
goto finish;
}
// Determine if we're talking to a bootloader or main firmware.
SendCommandBL(CMD_DEVICE_INFO, 0, 0, 0, NULL, 0);
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_UNKNOWN, &resp, 1000) == false) {
PrintAndLogEx(ERR, "Could not get device info.");
goto finish2;
}
uint32_t dev_info = resp.oldarg[0];
in_bootloader = (dev_info & DEVICE_INFO_FLAG_CURRENT_MODE_BOOTROM) != 0;
if (in_bootloader) {
if ((dev_info & DEVICE_INFO_FLAG_UNDERSTANDS_READ_MEM) != 0) {
PrintAndLogEx(INFO, "Device is running the bootloader.");
}
else {
PrintAndLogEx(ERR, "Device is running the bootloader, but the bootloader"
" doesn't understand the READ MEM command.");
goto finish2;
}
}
PrintAndLogEx(SUCCESS,"Dump requested from address "_YELLOW_("%u")", length "_YELLOW_("%u")"%s.",
addr, len, raw ? ", in raw address mode" : "");
PrintAndLogEx(SUCCESS, _CYAN_("Memory dumping to file..."));
ret = dumpmem_to_file(filename, addr, len, raw, in_bootloader);
if (ret != PM3_SUCCESS) {
goto finish2;
}
PrintAndLogEx(NORMAL, "");
finish2:
clearCommandBuffer();
if (in_bootloader) {
g_session.current_device->g_conn->run = false;
SendCommandOLD(CMD_PING, 0, 0, 0, NULL, 0);
} else {
SendCommandNG(CMD_QUIT_SESSION, NULL, 0);
msleep(100);
}
CloseProxmark(g_session.current_device);
finish:
if (ret == PM3_SUCCESS)
PrintAndLogEx(SUCCESS, _CYAN_("All done"));
else if (ret == PM3_EOPABORTED)
PrintAndLogEx(FAILED, "Aborted by user");
else
PrintAndLogEx(ERR, "Aborted on error %u", ret);
return ret;
}
static int flash_pm3(char *serial_port_name, uint8_t num_files, const char *filenames[FLASH_MAX_FILES], bool can_write_bl, bool force) {
int ret = PM3_EUNDEF;
@ -812,6 +920,11 @@ int main(int argc, char *argv[]) {
bool debug_mode_forced = false;
int flash_num_files = 0;
const char *flash_filenames[FLASH_MAX_FILES];
bool dumpmem_mode = false;
const char *dumpmem_filename = NULL;
uint32_t dumpmem_addr = 0;
uint32_t dumpmem_len = 512*1024;
bool dumpmem_raw = false;
// color management:
// 1. default = no color
@ -1013,6 +1126,44 @@ int main(int argc, char *argv[]) {
continue;
}
// go to dump mode
if (strcmp(argv[i], "--dumpmem") == 0) {
dumpmem_mode = true;
if (i + 1 == argc) {
PrintAndLogEx(ERR, _RED_("ERROR:") " missing file specification after --dumpmem\n");
show_help(false, exec_name);
return 1;
}
dumpmem_filename = argv[++i];
continue;
}
if (strcmp(argv[i], "--dumpaddr") == 0) {
if (i + 1 == argc) {
PrintAndLogEx(ERR, _RED_("ERROR:") " missing address specification after -dumpaddr\n");
show_help(false, exec_name);
return 1;
}
uint32_t tmpaddr = strtol(argv[i + 1], NULL, 10);
dumpmem_addr = tmpaddr;
i++;
continue;
}
if (strcmp(argv[i], "--dumplen") == 0) {
if (i + 1 == argc) {
PrintAndLogEx(ERR, _RED_("ERROR:") " missing address specification after -dumplen\n");
show_help(false, exec_name);
return 1;
}
uint32_t tmplen = strtol(argv[i + 1], NULL, 10);
dumpmem_len = tmplen;
i++;
continue;
}
if (strcmp(argv[i], "--dumpraw") == 0) {
dumpmem_raw = true;
continue;
}
// go to flash mode
if (strcmp(argv[i], "--flash") == 0) {
flash_mode = true;
@ -1094,6 +1245,11 @@ int main(int argc, char *argv[]) {
if (speed == 0)
speed = USART_BAUD_RATE;
if (dumpmem_mode) {
dumpmem_pm3(port, dumpmem_filename, dumpmem_addr, dumpmem_len, dumpmem_raw);
exit(EXIT_SUCCESS);
}
if (flash_mode) {
flash_pm3(port, flash_num_files, flash_filenames, flash_can_write_bl, flash_force);
exit(EXIT_SUCCESS);
@ -1148,7 +1304,7 @@ int main(int argc, char *argv[]) {
}
// ascii art only in interactive client
if (!script_cmds_file && !script_cmd && g_session.stdinOnTTY && g_session.stdoutOnTTY && !flash_mode && !reboot_bootloader_mode) {
if (!script_cmds_file && !script_cmd && g_session.stdinOnTTY && g_session.stdoutOnTTY && !dumpmem_mode && !flash_mode && !reboot_bootloader_mode) {
showBanner();
}

View file

@ -798,7 +798,7 @@ Check column "offline" for their availability.
|`hw lcd `|N |`Send command/data to LCD`
|`hw lcdreset `|N |`Hardware reset LCD`
|`hw ping `|N |`Test if the Proxmark3 is responsive`
|`hw readmem `|N |`Read memory at decimal address from flash`
|`hw readmem `|N |`Read from processor flash`
|`hw reset `|N |`Reset the Proxmark3`
|`hw setlfdivisor `|N |`Drive LF antenna at 12MHz / (divisor + 1)`
|`hw setmux `|N |`Set the ADC mux to a specific value`

View file

@ -379,7 +379,9 @@ typedef struct {
#define CMD_LCD_RESET 0x0103
#define CMD_LCD 0x0104
#define CMD_BUFF_CLEAR 0x0105
#define CMD_READ_MEM 0x0106
#define CMD_READ_MEM 0x0106 // legacy
#define CMD_READ_MEM_DOWNLOAD 0x010A
#define CMD_READ_MEM_DOWNLOADED 0x010B
#define CMD_VERSION 0x0107
#define CMD_STATUS 0x0108
#define CMD_PING 0x0109
@ -880,6 +882,9 @@ typedef struct {
/* Set if this device understands the version command */
#define DEVICE_INFO_FLAG_UNDERSTANDS_VERSION (1<<6)
/* Set if this device understands the read memory command */
#define DEVICE_INFO_FLAG_UNDERSTANDS_READ_MEM (1<<7)
#define BL_VERSION_MAJOR(version) ((uint32_t)(version) >> 22)
#define BL_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3ff)
#define BL_VERSION_PATCH(version) ((uint32_t)(version) & 0xfff)
@ -891,6 +896,8 @@ typedef struct {
// Different versions here. Each version should increase the numbers
#define BL_VERSION_1_0_0 BL_MAKE_VERSION(1, 0, 0)
/* CMD_READ_MEM_DOWNLOAD flags */
#define CMD_READ_MEM_DOWNLOAD_RAW (1<<0)
/* CMD_START_FLASH may have three arguments: start of area to flash,
end of area to flash, optional magic.