DS4231 eeprom hack II

This is the continuation of the thread:

I also use a Brady printer with cartridges protected by a DS4231.
very annoying getting the message that the cartridge is "empty" when it is not.

The Datasheet explains why just only copying the data from a new cartridge memory chip to an used one does not work on the same printer:

Each device has its own unalterable and unique 64-bit ROM
registration number that is factory lasered into the chip.
The registration number is used to address the device in
a multi-drop, 1-Wire net environment.

This means that every printer device might keep record of the status of the cartridges that have been used on that printer or in devices that are handled by a phone app like the M511 or M211 it would be easy sending that info to a Internet server.

It seems someone is already making those chips.

2 Likes

Welcome, what is your question?

As I clearly said, my post is a follow up to the quoted thread providing additional information. Then mine is not a question, but an answer to the closed thread.

I find your sarcasm and lack of genuine interest in the topic or my person highly inappropriate.

1 Like

I've also been playing with these EEPROMS. Here is the code (written by me, with inspiration from many others):

#include <OneWire.h>
//Use this to dump the contents of an EEPROM to the serial console.  Note that reading beyond the end of the memory returns FF FF since the data line is pulled high.  This program intentionally reads beyond 128 bytes to get the protection registers, manufacturer ID and any other hidden info.
const int EEPROM_pin = 3;  // change to correct pin for your setup

// CRC calculation function using the polynomial X^8 + X^5 + X^4 + 1
uint8_t ds2431_crc8(const uint8_t *addr, uint8_t len) {
    uint8_t crc = 0;

    while (len--) {
        uint8_t inbyte = *addr++;
        for (uint8_t i = 8; i; i--) {
            uint8_t mix = (crc ^ inbyte) & 0x01;
            crc >>= 1;
            if (mix) crc ^= 0x8C;  // Polynomial: X^8 + X^5 + X^4 + 1
            inbyte >>= 1;
        }
    }
    return crc;
}

void printAddress(uint8_t *address) {
    for (uint8_t i = 0; i < 8; i++) {
        if (address[i] < 16) Serial.print("0");
        Serial.print(address[i], HEX);
        Serial.print(" ");
    }
}

void setup() {
    Serial.begin(9600);
}

void loop() {
    byte data[1536];  // Container for the device data (1024/8)=128
    byte pageSize = 192;  // Size of each page
    byte leemem[3] = {0xF0, 0x00, 0x00};  // Read memory command
    byte ccrc;
    byte ccrc_calc;
    OneWire myWire(EEPROM_pin);  // Use correct pin

    myWire.reset_search();
    uint8_t addr[8];

    while (myWire.search(addr)) {
        Serial.print("Found device with ROM: ");
        printAddress(addr);
        Serial.println();

        // Calculate CRC for the first 7 bytes of the ROM
        uint8_t calculated_crc = ds2431_crc8(addr, 7);
        if (calculated_crc != addr[7]) {
            Serial.println("CRC is not valid!");
            Serial.print("Calculated CRC: ");
            Serial.println(calculated_crc, HEX);
            Serial.print("Received CRC: ");
            Serial.println(addr[7], HEX);
            return;
        }

        myWire.reset();
        myWire.select(addr);  // Select the specific device

        myWire.write(leemem[0], 1);  // Read data command, leave ghost power on
        myWire.write(leemem[1], 1);  // LSB starting address, leave ghost power on
        myWire.write(leemem[2], 1);  // MSB starting address, leave ghost power on

        ccrc = addr[7];  // DS2431 generates a CRC for the command we sent
        //ccrc_calc = ds2431_crc8(leemem, 3);  // Calculate CRC of the commands
        //ccrc_calc = ds2431_crc8(addr, 7); 

        Serial.print("Command bytes: ");
        for (int i = 0; i < 3; i++) {
            Serial.print(leemem[i], HEX);
            Serial.print(" ");
        }
        Serial.println();

        Serial.print("old calculated CRC: ");
        Serial.println(calculated_crc, HEX);

        Serial.print("old DS2431 readback CRC: ");
        Serial.println(ccrc, HEX);

        

        Serial.println("Data is: ");
        for (byte i = 0; i < pageSize; i++) {
            data[i] = myWire.read();
            Serial.print(data[i], HEX);
            Serial.print(" ");
            if ((i + 1) % 8 == 0) {
                Serial.println();
            }
        }
        Serial.println();
        delay(5000);
    }

    if (!myWire.search(addr)) {
        Serial.println("No more devices found.");
        myWire.reset_search();
        delay(3000);
    }
}
1 Like

By the way, the chips I'm playing with are different. They are the DS2431. Still 128 bytes, but they are EEPROMs with an optional EPROM emulation function that isn't used by Brady in my situation. Here is the dump from one of those sticker reels:
Found device with ROM: 2D ** ** ** ** 00 00 **
Command bytes: F0 0 0
Data:
2 0 4 1D FE 39 0 F0
A4 6 64 0 64 0 DC 5
EE 2 DC 5 EE 2 0 0
0 0 0 0 7D 0 C1 94
98 9F 64 64 64 0 0 0
1 70 17 70 17 1F 5 A2
7D 0 1 64 0 0 0 0
0 42 33 33 2D 31 33 36
2D 34 33 34 0 0 0 0
0 0 0 0 0 0 0 0
0 0 1C 0 0 0 0 0
0 0 0 0 0 0 0 0
3C B2 1 32 32 72 72 0
0 0 0 0 50 0 0 0
0 0 0 0 0 0 0 0
0 1 34 1 16 0 92 75 -end of 128 byte data region
0 0 0 0 0 55 0 0 - control registers
0 0 0 0 0 0 0 55 - end of memory

1 Like

Finally, I also spent a great deal of time writing code to allow my arduino to write to these DS2431 devices. I use the OneWire library available here: GitHub - PaulStoffregen/OneWire: Library for Dallas/Maxim 1-Wire Chips

This code will write the 128 bytes in the four 32 byte pages defined at the top. It then idles in the main loop() to prevent wearing out the re-write cycles of the memory. For reasons I don't understand, the CRC sent immediately after writing the scratchpad is correct, but the CRC sent after reading the scratchpad data is random garbage. So my program uses the first CRC, but still does a scratchpad read and prints it to the serial monitor for troubleshooting and manual data verification by visual inspection.

#include <OneWire.h>

const int EEPROM_pin = 3;  // Correct pin
uint16_t last_crc = 0;  //Initialize variable
// Data to be written to the EEPROM
byte page1[32] = {0x02, 0x00, 0x04, 0x1D, 0xFE, 0x39, 0x00, 0xF0, 0xA4, 0x06, 0x64, 0x00, 0x64, 0x00, 0xDC, 0x05, 0xEE, 0x02, 0xDC, 0x05, 0xEE, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0xC1, 0x94};
byte page2[32] = {0x98, 0x9F, 0x64, 0x64, 0x64, 0x00, 0x00, 0x00, 0x01, 0x70, 0x17, 0x70, 0x17, 0x20, 0x05, 0x6E, 0x7D, 0x00, 0x01, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x33, 0x33, 0x2D, 0x31, 0x33, 0x36};
byte page3[32] = {0x2D, 0x34, 0x33, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
byte page4[32] = {0x3C, 0xB2, 0x01, 0x32, 0x32, 0x72, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x34, 0x01, 0x16, 0x00, 0x92, 0x2F, 0xA5};

void printAddress(uint8_t *address) {
    for (uint8_t i = 0; i < 8; i++) {
        if (address[i] < 16) Serial.print("0");
        Serial.print(address[i], HEX);
        Serial.print(" ");
    }
}

void writePage(OneWire &myWire, uint8_t *addr, uint8_t page, byte *data) {
  Serial.println("Write Page Called");
  uint16_t last_crc = 0;  //Initialize variable
    byte TA1 = page * 8;
    byte TA2 = 0;
    byte ES;
    byte crc_read[2];
    byte crcInput[11];
    crcInput[0] = 0x0F;
    crcInput[1] = TA1;
    crcInput[2] = TA2;
    myWire.reset();
   // Serial.println("reset pulse sent 1");
    myWire.select(addr);
    myWire.write(0x0F); // Write Scratchpad
    myWire.write(TA1);  // Target address LSB
      //Serial.print("TA1 last 3 bits must be 0: ");
      //Serial.println(TA1, BIN);
    myWire.write(TA2);  // Target address MSB
    Serial.print("Writing data: ");
    for (int i = 0; i < 8; i++) {
        myWire.write(data[i]); // Write data to scratchpad
        crcInput[3 + i] = data[i];  //copy the same data to crcinput
        Serial.print(data[i], HEX); //Print the output data
        Serial.print(" ");
    }
    Serial.println();
    Serial.println("inverted CRC Data Read from chip after write");
    byte readDataChipCRC[2];
    for (int i = 0; i < 2; i++) {
        readDataChipCRC[i] = myWire.read();
        Serial.print(readDataChipCRC[i], HEX);
        Serial.print(" ");
    }

    Serial.println();
    //Serial.println("reset pulse not sent 2");
    myWire.reset();
    myWire.select(addr);
    myWire.write(0xAA); // Read Scratchpad
    byte readData[13];
    Serial.print("Read scratchpad data: ");
    for (int i = 0; i < 13; i++) {
        readData[i] = myWire.read();
        Serial.print(readData[i], HEX);
        Serial.print(" ");
    }
    Serial.println();

    ES = readData[2];  // Ending offset/data status byte
    crc_read[0] = ~readData[11];  //LSB inverted - this is always junk data
    crc_read[1] = ~readData[12]; //MSB inverted - this is always junk data
    Serial.print("crc_read value:");
    Serial.print(crc_read[1], HEX);
    Serial.print(" ");
    Serial.print(crc_read[0], HEX);
    Serial.println();

    uint16_t calculated_crc = ~OneWire::crc16(crcInput, 11, last_crc);
    last_crc = calculated_crc;
    uint16_t received_crc = (readDataChipCRC[1] << 8) | readDataChipCRC[0];
    
    if (calculated_crc != received_crc) {
        Serial.println("CRC mismatch, write aborted!");
        Serial.print("Calculated CRC: ");
        Serial.println(calculated_crc, HEX);
        Serial.print("Received CRC: ");
        Serial.println(received_crc, HEX);
        Serial.print("Inverted CRCs: RX ");
        Serial.println(~received_crc, HEX);
        Serial.print("Inverted CRCs: Calculated ");
        Serial.println(~calculated_crc, HEX);
        return;
    }
     else{ 
    //Serial.println("reset pulse sent 3");
    myWire.reset();
    myWire.select(addr);
    myWire.write(0x55); // Copy Scratchpad
    myWire.write(TA1);  // Target address LSB
    myWire.write(TA2);  // Target address MSB - always 00 since memory is so small.
    myWire.write(0x07);   // ES - Ending offset/data status byte 0x07 if everything is correct
    delay(40);  // give operation time to complete
    Serial.print("Page ");
    Serial.print(page);
    Serial.println(" written successfully.");
    }
   
  //Serial.println("End of WritePage function call");
    
}

void readPage(OneWire &myWire, uint8_t *addr, uint8_t page) {
    byte TA1 = page * 8;
    byte TA2 = 0;

    myWire.reset();
    myWire.select(addr);
    myWire.write(0xF0); // Read Memory
    myWire.write(TA1);  // Target address LSB
    myWire.write(TA2);  // Target address MSB

    Serial.print("Row ");
    Serial.print(page);
    Serial.println(" data:");
    for (int i = 0; i < 8; i++) {
        byte b = myWire.read();
        Serial.print(b, HEX);
        Serial.print(" ");
    }
    Serial.println();
}

void setup() {
    Serial.begin(9600);
    delay(4000); // Allow time to open Serial Monitor
    Serial.println("Started");

    OneWire myWire(EEPROM_pin);
    uint8_t addr[8];

    if (myWire.search(addr)) {
        Serial.print("Found device with ROM: ");
        printAddress(addr);
        Serial.println();
    
          for (uint8_t row = 0; row < 16; row++) {
              Serial.print("Writing row ");
              Serial.println(row);
              if (row < 4){
                writePage(myWire, addr, row, page1 + row * 8);
                }
              if ((row >= 4) && row < 8){
                writePage(myWire, addr, row, page2 + (row - 4)* 8);
              }
              if ((row >= 8) && row < 12){
                writePage(myWire, addr, row, page3 + (row - 8) * 8);
              }
              if ((row >= 12) && row < 16){
                writePage(myWire, addr, row, page4 + (row - 12) * 8);
              }

          }

          for (uint8_t row = 0; row < 16; row++) {
              readPage(myWire, addr, row);
          }
        
    } else {
        Serial.println("No devices found.");
    }
}

void loop() {
    while (true) {
        Serial.println("Job done!");
        delay(1000);
        Serial.println("Work work");
        delay(1000);
        Serial.println("Zug Zug");

    }
}

1 Like

I looked at your memory dump I can clearly see the hex sequence
42 33 33 2D 31 33 36 2D 34 33 34 => B33-136-434 (ASCII)
which is the part number of a cartridge

Does it match your cartridge?

Which printer are you using?

Were you able to see how Brady records the remaining tape length (or sticker units) after a printing?

Were you able to write a new memory chip and fool Brady's printer?

Can you verify the assumption that the printer keeps a database with the 64-bit ROM registration number and the data of the corresponding cartridge?

Good job.

1 Like

Yes, that's it exactly! The reason for my project is the EEPROMs keep indicating the roll is empty way before it runs out of stickers. Here is a comparison between a false empty roll and a new out of package roll.

Row 22 in the picture corresponds to the last 8 bytes of memory (16th 8 byte segment). (after that are the register bytes). This is the only row that seems to change after the EEPROMs are reloaded in to the printer the bytes in this row all seem to shift left one position and the last one or 2 change to new values. Hard to say as I've only done one test of a chip I have previously read and the printer knew I'd copied the data from the new working sticker reel to the defective sticker reel.

These labels have revisions, the old chip is on Rev 0 of the PCB (red text). The new one on the right also comes with a 2nd 1 wire chip that only has the unique 64 bit ROM number and no memory. So this ROM number must be fully or partially cyptographically stored in the EEPROM memory to allow the printer to do a comparison to verify the authenticity. Why the 64 bit ROM of the DS2431 isn't enough, I don't know, but as you can see most of the data between a Rev 0 and a Rev 1 chip is identical.

1C on line 17 might be a version code or checksum.

Keep in mind this data is presented backwards here because these devices transmit the LSB first. The datasheet describes the checksum of the 64-bit ROM as the MSB, but we can see it appears as the last byte at the top of these dumps.
05 DC corresponds to the number of labels on a roll (1500) and we can see DC 05 on both lines 8 and 9.

1 Like

It would be good if you can establish the changes before and after printing a label on a working cartridge. You mention some shifting on line 22 but it's not clear.

If you copied the content of a working cartridge to a "new" memory and the printer detects it I would agree the ROM Mask is not enough to identify a cartridge and the 64 bit Mask cryptographic hash must be included in the memory data somehow but, if you copied the data of a fresh cartridge to an used cartridge then the printer already have the old cartridge ROM Mask in its database and it would immediately detect the inconsistency.

Did you try copying the data of a new cartridge to a "new" memory chip (The printer does not know the ROM Mask of the new chip) and see if the printer complains or not?

If we confirm the Mask content is somehow included in the Memory Data next step would be IDA disassembly of printer firmware and try to see in code what they do, not sure if the firmware is easy to download or not.

1 Like

It is my employer's printer, so I won't be disassembling it to try and download the firmware. I might not be able to put it back together.

I can try what you suggest, but if a version code is stored on the new V1 cartridges, the printer will be looking for that secondary ROM that my v0 board doesn't have. Both of the new V1 cartridges have been in the printer now, so their ROMs are also in its memory.

So I can try copying the new V1 data to a new chip on the V0 board, but I doubt it will work. That secondary ROM must be there to thwart exactly this attack. You'd have to replace both IC's if the printer is memorizing both ROM IDs. Interestingly the ground pads for the two 1-wire ROM chips are separately connected and the new V1 PCB is polarized so that it can't be mounted the other way around in the plastic. This allows the printer to communicate with each chip separately without having to do the longer ROM search method.

If anyone in the future is reading this the PCB in my case has 3 pads on the side opposite the chips. The outer pads are ground (one for each chip) and the center pad is the 1-wire interface.

Row 22 in the picture changes from the green text (00 01 34 01 16 00 92 75) to 01 34 01 16 00 92 2F A5 after feeding forward 4 labels and printing one.

Sorry to jump in but I think one or two of you might be able to help me with my problem with the DS2431. I am using the code here

I'm able to read and write all the data from the 4 pages, but I can only Read the data in the control register 80h to 87h. It fails writing using the conventional code to write to the data pages. I found a couple pages that might help if you understand the code which I don't. Here https://forum.reveltronics.com/viewtopic.php?t=699 and here also someone mentioned you might need to use a script to write to the control register for some reason https://forum.reveltronics.com/viewtopic.php?t=1119. The control register is Not write protected, it's blank from factory, I just need to figure out how to write the last bytes onto the control register and I'll have my project moving to the next step. Thanks for any help and insight. Also here is the datasheet https://www.analog.com/media/en/technical-documentation/data-sheets/ds2431.pdf

EDIT

My problem turned out to be a much simpler code limitation where there was >= should just have been > and was limiting the address to not be the exact size of the eeprom (128). A simple if statement I missed. I can now read/write to the entire chip, although my project is still stuck because of some type of encryption present in the first data page :(

https://www.reddit.com/r/arduino/commen ... are_button

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.