MultiDrive – free backup, clone & wipe disk utility from Atola Technology

All times are UTC - 5 hours [ DST ]


Forum rules


Please do not post questions about data recovery cases here (use this forum instead). This forum is for topics on finding new ways to recover data. Accessing firmware, writing programs, reading bits off the platter, recovering data from dust...



Post new topic Reply to topic  [ 20 posts ] 
Author Message
 Post subject: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 22nd, 2026, 16:59 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
Until now I have been using the following algorithm to calculate the CRCs for Seagate's ROM segments:

https://github.com/eurecom-s3/hdd_firmware_tools/blob/master/scripts/hdd_crc.py

It expects that the "Data length must be a multiple of 4". This is true for the main ROM section. However, I have now encountered blocks of data in the NvCache area which are not always 4-byte multiples, yet these still have 16-bit CRCs associated with them. When those same blocks are 4-byte multiples, the aforementioned algorithm works.

Does anyone know what modifications I need to make to the Python algorithm to make it work with data lengths that are 2-byte multiples?

I have tried padding the block with 0x0000, but this doesn't produce the correct result. That said, the ROM dump I am using probably has an error in the NvCache section, so I can't be sure.

_________________
A backup a day keeps DR away.


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 22nd, 2026, 19:33 
Offline

Joined: October 3rd, 2005, 0:40
Posts: 4753
Location: Hungary
could you post an example, pls?
A good one would be more helpful...

pepe

_________________
Adatmentés - Data recovery


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 22nd, 2026, 19:53 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
I'll post a few examples after I compile my analysis. I have several ROMs, one of which tested as bad, a second which has an unknown problem, and a third which someone claims is from a good donor.

I'm attending to an ongoing medical emergency (not mine), so it might be tomorrow before I can get around to it.

Thanks for your interest.

This is one of the threads whose ROMs I am analysing (bad patient, good donor?):

https://groups.google.com/g/datarecoverycertification/c/Q6f7ekwIQb8

The data block I'm trying to checksum (CRC-16) is in the first DaT block immediately after the NvC header at offset 0x180000.

The first 0x20 bytes is the DaT header. It sums to 0x0000 by simple addition. It's the following 0x10032 (?) bytes that I'm having trouble with.

_________________
A backup a day keeps DR away.


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 22nd, 2026, 20:01 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
My analysis so far ...

Code:
https://forum.hddguru.com/viewtopic.php?f=1&t=43770


Boot 0x0100MVM0148CV10
SpinUp
TCC:001F

Trans
Rst 0x0100MVM0148CV10
SAdd:0x0055A3C5
NVCScram Saved HB Size:020A
LD: MD_Alloc:00000078 HB_Alloc:0000 MD_Alloc:80380000 HB_Alloc:8032 FlashRestore MD_Saved:00000078 HB_Saved:00000000 MD_CRC:0154 <--------- NvL - CRC OK
PP: MD_Alloc:0000B400 HB_Alloc:0000 FlashRestore

PVTSize: 00002C00 CompSize: 0000008C DeCompSize: 00002C00 MD_Saved:0000008C HB_Saved:00000000 MD_CRC:8183  <--------------------- RLE -  bad CRC (0x380A)
WCD: MD_Alloc:00012000 HB_Alloc:02E6
FlashRestore..............................................................................................................................................................................................................................................................................................................................................................................................
Failed power-on replay: 0x0052


Offset(h) 00   02   04   06   08   0A   0C   0E

00000000  584E 7643 E6D1 3409 4244 4600 0000 0000  .NvC............
          && N  v C ??????????????
00000010  0000 0000 3F00 8743 0000 0000 0000 0000
00000020  0400 0000 1000 0000 0A02 0204 5800 0002
                              @@@@      %%%%%%%
00000030  9824 0104 B8D8 0101 0000 0000 0000 0000
          #######   ~~~~~~~
00000040  0000 0000 0000 0000 0000 0000 0000 0000
00000050  0000 0000 0000 0000
........
00000050                      2044 6154 0201 C32B          .DaT....
                              ^^ D  a T      ====
00000060  6000 0100 E539 1100 6200 0100 0000 0000
                    ++++      *********
00000070  0000 0000 0000 0000
........
00000070                      646A 9F3C 0100 0300          dj......
00000080  6200 0100 6000 0000 0200 0100 FFFF FFFF
          ---------
00000090  EEEE EEEE 0000 0000 0000 4000 2000 0100
000000A0  FFFF FFFF FFFF FFFF 8016 6E69 3000 D501
........
000100B0  0000 2000 0000 0000 0200 FFFF FFFF FFFF
000100C0  FFFF B021 0700 0000 0000 4000 0000 0000
000100D0  0300 FFFF FFFF FFFF FFFF

& -- size of NvC header is 0x58 bytes

? -- 16-bit little-endian checksum word for NvC header, chosen so that sum of words in header is 0x0000  <--- not sure which word **********************

@ -- NVCScram Saved HB Size = 020A

% -- relative offset to first DaT region is 0x58

# -- relative offset to second DaT region is 0x12498

~ -- relative offset to third DaT region is 0x1D8B8

^ -- size of DaT header is 0x20 bytes

= -- 16-bit little-endian checksum word for DaT header, chosen so that sum of words in header is 0x0000

+ -- 16-bit CRC calculated over data bytes in following block of data = 0x39E5 ??  <--- this doesn't work if I use the same algorithm as ROM segments ****

* -- size of following block of data = 0x10062

- -- size of block of data in first DaT region's data area -- should match DaT header





Offset(h) 00   02   04   06   08   0A   0C   0E

00012490                      2044 6154 0401 D3E3          .DaT....
                              ^^ D  a T      ====
000124A0  0000 FFFF 8381 9A00 8C00 0000 0000 0000
                    ++++      *********
000124B0  0000 0000 0000 0000
........
000124B0                      0C52 4C45 002C 0000          .RLE....
                              $$ R  L E
000124C0  8000 0000
........
000124C0            FFFF FFA8 0100 DF00 FFFF FFFF
000124D0  FFFF FF05 0100 7F00 FFFF FFFF FFFF FFFF
000124E0  FFB1 0100 FD00 FFFF FFFF FF40 0100 FE00
000124F0  FFFF FF49 0100 FB00 FFFF FFFF FFFF FFFF
00012500  FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
00012510  FFFF FFFF FFEC 0100 7F00 FFFF FFBD 0100
00012520  FB00 FFFF FFFF FFFF FFFF FFFF FFFF FFFF
00012530  FFFF FFFF FFFF FF97 0100 BF00 FFFF FFFF
00012540  FFFF FFF8

^ -- size of DaT header is 0x20 bytes

= -- 16-bit little-endian checksum word for DaT header, chosen so that sum of words in header is 0x0000

+ -- 16-bit CRC calculated over data bytes in following block of data = 0x8183 -- same algorithm as ROM segments

* -- size of following block of data = 0x8C

$ -- size of RLE header is 0x0C bytes





Offset(h) 00   02   04   06   08   0A   0C   0E

0001D8B0                      2044 6154 0101 1864          .DaT....
                              ^^ D  a T      ====
0001D8C0  0000 FFFF 5401 9B00 7800 0000 0000 0000
                    ++++      *********
0001D8D0  0000 0000 0000 0000
........
0001D8D0                      784E 764C C900 9B00          .NvL....
                              $$ N  v L
0001D8E0  9B00 0304 0201 9A00 3405 0000 0000 0000
0001D8F0  0000 0000 0000 0000 0000 0000 0404 0100
0001D900  0200 0000 0000 0000 0000 0000 0000 0000
0001D910  0000 0000 0000 0000 0000 0000 0000 0000
0001D920  0000 0000 0000 0000 0000 0000 0000 0000
0001D930  0000 0000 0000 0000 0000 0000 0000 0000
0001D940  0000 0000 0000 0000 0000 0000 0000 0000

^ -- size of DaT header is 0x20 bytes

= -- 16-bit little-endian checksum word for DaT header, chosen so that sum of words in header is 0x0000

+ -- 16-bit CRC calculated over data bytes in following block of data = 0x0154 -- same algorithm as ROM segments

* -- size of following block of data = 0x78

$ -- size of NvL header is 0x78 bytes






https://groups.google.com/g/datarecoverycertification/c/Q6f7ekwIQb8

Boot 0x0100M
VM0185
CV11   

SpinUp
TCC:001F


Trans
Rst 0x0100M
VM0185
CV11   
SAdd:0x0055DAFB
   NVCScram Saved HB Size:020A
    LD: MD_Alloc:00000078 HB_Alloc:0000 MD_Alloc:80380000 HB_Alloc:8032 FlashRestore MD_Saved:00000078 HB_Saved:00000000 MD_CRC:87A1
    PP: MD_Alloc:0000B400 HB_Alloc:0000 FlashRestore
PVTSize: 00002C00 CompSize: 00001C48 DeCompSize: 00002C00 MD_Saved:00001C48 HB_Saved:00000000 MD_CRC:962E
   WCD: MD_Alloc:00012000 HB_Alloc:02E6 FlashRestore...............................................................................................................................................................................................................................................................................................................................................................................................

Failed power-on replay: 0x0053^z


https://groups.google.com/group/datarecoverycertification/attach/50e048ad1008d/ROM.bin?part=0.2 (patient ROM)
https://groups.google.com/group/datarecoverycertification/attach/50e048ad1008d/ROM.txt?part=0.1 (PC3000 ROM parsing)
https://groups.google.com/group/datarecoverycertification/attach/5175598d01ac4/donor.bin?part=0.1 (donor ROM)


These are the NvCache regions:

Offset(h) 00   02   04   06   08   0A   0C   0E

00180000  584E 7643 0532 461C C003 F200 0000 0000  XNvC.2F.À.ò.....
00180010  0000 0000 0000 6710 0000 0000 0000 0000  ......g.........
00180020  0000 0000 1800 0000 0A02 0204 5800 0002  ............X...
00180030  9824 0104 B8D8 0101 0000 0000 0000 0000  ˜$..¸Ø..........
00180040  0000 0000 0000 0000 0000 0000 0000 0000  ................
00180050  0000 0000 0000 0000                      ........


Offset(h) 00   02   04   06   08   0A   0C   0E

00180050                      2044 6154 0201 E640           DaT..æ@
00180060  0000 0100 5125 1200 3200 0100 0000 0000  ....Q%..2.......
00180070  0000 0000 0000 0000                      ........


Offset(h) 00   02   04   06   08   0A   0C   0E

00192490                      2044 6154 0401 97B3           DaT..—³
001924A0  0000 FFFF 2E96 6F00 481C 0000 0000 0000  ..ÿÿ.–o.H.......
001924B0  0000 0000 0000 0000 0C52 4C45 002C 0000  .........RLE.,..
001924C0  3C1C 0000                                <...


Offset(h) 00   02   04   06   08   0A   0C   0E

0019D8B0                      2044 6154 0101 ECDD           DaT..ìÝ
0019D8C0  0000 FFFF A187 7A00 7800 0000 0000 0000  ..ÿÿ¡‡z.x.......
0019D8D0  0000 0000 0000 0000 784E 764C 8C00 7A00  ........xNvLŒ.z.
0019D8E0  7A00 0304 0204 6E00 7803 0000 0000 0000  z.....n.x.......
0019D8F0  0000 0000 0000 0000 0000 0000 0404 0C00  ................
0019D900  6000 0000 0000 0000 0000 0000 0000 0000  `...............
0019D910  0000 0000 0000 0000 0000 0000 0000 0000  ................
0019D920  0000 0000 0000 0000 0000 0000 0000 0000  ................
0019D930  0000 0000 0000 0000 0000 0000 0000 0000  ................
0019D940  0000 0000 0000 0000 0000 0000 0000 0000  ................

As I understand it, the simplest solution is to find a working ROM and patch the area from 0x180000 to 0xFFFFFF from donor to patient. Your ROM has massive amounts of data in the first two DaT regions. In a good ROM these areas should be mostly clear, apart from the headers.

PC3000 parses the ROM, but not these areas.






https://www.hardmaster.info/articles/20-09-2023.html


Seagate Exos X16  ST12000NM001G


Boot0x0100MAA035KSN03
Trans
Rst0x0100MAA035KSN03
(P)SATAReset

NVCScramSavedHBSize:020A
LD:
PP:
TW:
WCD:
GC:
NVCFormatCorrupt:0331000B

Prod Desc: MobulaBP.CMR.mDRAM.SATA.Combo.Def5xxe.OCZ.MynaPlus2.1.Cust.KB12
Package Version: MMAEA12B0.SDN1.AA035K.SN03
Serial #: ********
Changelist: 01717748
Model #: ST12000NM001G-2MV103
ID: 101
Servo FW Rev: B753
Heads: F
PCBA SN: 0000C032L36X
Default factory config: SD&D
Active config: SD&D

After repair:

NVCScramSavedHBSize:020A
LD:MD_Alloc:000000F0HB_Alloc:0000DiscRestoreMD_Saved:000000F0HB_Saved:00000000MD_CRC:562E
PP:MD_Alloc:0000F000HB_Alloc:0000DiscRestore
PVTSize:000D9E24   CompSize:00001B64   DeCompSize:000D9E24MD_Saved:00001B64HB_Saved:00000000MD_CRC:6B6D
TW:MD_Alloc:00000400HB_Alloc:002EDiscRestoreMD_Saved:00000160HB_Saved:00000000MD_CRC:0000
WCD:MD_Alloc:00040000HB_Alloc:1293DiscRestoreMD_Saved:00000020HB_Saved:00000000MD_CRC:56D4
GC:MD_Alloc:00005000HB_Alloc:0000DiscRestoreMD_Saved:00004348HB_Saved:00000000MD_CRC:E3FF
DSP0004
2Sync:0 4





ST1200MM0088


Offset(h) 00   02   04   06   08   0A   0C   0E

00000000  484E 7643 F500 4808 B681 F901 0000 0000  .NvC............
00000010  0000 0000 6E00 73DE 0000 0000 0000 0000
00000020  0000 0000 0000 0000 1202 0305 6818 0003
00000030  881C 0004 A8D0 0005 C8E8 0001 0000 0000
00000040  0000 0000 0000 0000



Offset(h) 00   02   04   06   08   0A   0C   0E

00001860                      2044 6154 0301 C564  .........DaT....
00001870  0000 0000 0000 6F00 4801 0000 0000 0000
00001880  0000 0000 0000 0000
........
00001880                      0000 0000 0000 0000
00001890  0000 0000 0000 0000 0000 0000 0000 0000
.......         zero filled area
000019C0  0000 0000 0000 0000 0000 0000 0000 0000



Offset(h) 00   02   04   06   08   0A   0C   0E

00001C80                      2044 6154 0401 EB7B          .DaT....
00001C90  0000 FFFF 87CE 7600 941B 0000 0000 0000
00001CA0  0000 0000 0000 0000
........
00001CA0                      0C52 4C45 0466 0200          .RLE....
00001CB0  881B 0000
........
00001CB0            0100 FC00 FF12 0100 FD00 FF2A
00001CC0  0300 EDFF FB00 FF04 0300 7FFE FB00 FF07
00001CD0  0200 3FFE FFAF 0200 F7FE FF30 0100 0300
........
00003810  56D5 AA6A 55AD AA55 D5AA 5A55 ABAA 55B5
00003820  AA56 55AB 6A55 ADAA 56D5 AA6A 55AD AA55
00003830  D5AA 5A55 ABAA 55FD FF25 0001


Offset(h) 00   02   04   06   08   0A   0C   0E

0000D0A0                      2044 6154 0501 FB65          .DaT....
0000D0B0  0000 FFFF 0000 8000 0000 0000 0000 0000
0000D0C0  0000 0000 0000 0000


Offset(h) 00   02   04   06   08   0A   0C   0E

0000E8C0                      2044 6154 0101 31BD          .DaT....   <--- This DaT region has a size of 0x20 which is the size of the LoG header, ie the LoG is empty.
0000E8D0  0000 FFFF 2EA8 0001 2000 0000 0000 0000                          Therefore, it would appear that the LoG entries are remnants of previous events.
0000E8E0  0000 0000 0000 0000
........
0000E8E0                      204C 6F47 6E00 8000          .LoG....
0000E8F0  8000 0005 0304 0100 0404 1100 0504 0000
0000E900  0504 0000 0000 0000
........
0000E900                      FFFF FFFF FFFF FFFF   <-- These entries are little-endian words which appear to increment consistently by 10.
0000E910  FFFF FFFF 0101 0B01 1501 1F01 2901 3301       It would appear that these LoG entries are remnants of previous events.
0000E920  3D01 4701 5101 5B01 6501 6F01 7901 8301
0000E930  8D01 9801 A201 AC01 B601 C001 CA01 D401
0000E940  DE01 E801 F201 FC01 0602 1002 1A02 2402
0000E950  2E02 3902 4302 4D02 5702 6102 6B02 7502
0000E960  7F02 8902 9302 9D02 A702 B102 BB02 C502
0000E970  D002 DA02 E402 EE02 F802 0203 0C03 1603
0000E980  2003 2A03 3403 3E03 4803 5203 5C03 6603
0000E990  7103 7B03 8503 8F03 9903 A303 AD03 B703
0000E9A0  C103 CB03 D503 DF03 E903 F303 FD03 0804
0000E9B0  1204 1C04 2604 3004 3A04 4404 4E04 5804
0000E9C0  6204 6C04 7604 8004 8A04 9404 9E04 A904
0000E9D0  B304 BD04 C704 D104 DB04 E504 EF04 F904
0000E9E0  0305 0D05 1705 2105 2B05 3505 4005 4A05
0000E9F0  5405 5E05 6805 7205 7C05 8605 9005 9A05
0000EA00  A405 AE05 B805 C205 CC05 D605 E105 EB05
0000EA10  F505 FF05 0906 1306 1D06 2706 3106 3B06
0000EA20  4506 4F06 5906 6306 6D06 7806 8206 8C06
0000EA30  9606 A006 AA06 B406 BE06 C806 D206 DC06
0000EA40  E606 F006 FA06 0407 0E07 1907 2307 2D07
0000EA50  3707 4107 4B07 5507 5F07 6907 7307 7D07
0000EA60  8707 9107 9B07 A507 B007 BA07 C407 CE07






ST12000NM000J

Offset(h) 00   02   04   06   08   0A   0C   0E

00000000  606E 7643 A5CA 28EA 3300 2A00 0000 0000  .nvC............
00000010  0000 0000 0000 303F 0000 0000 0000 0000
00000020  0000 0000 1100 0000 0A02 0305 0000 0000
00000030  6000 0002 8000 0403 A004 0404 C0F4 0405
00000040  E044 0501 8000 0104 0000 0000 0000 0000
00000050  0000 0000 0000 0000 0000 0000 0000 0000
........
00000060  2044 6154 0209 61DD 0000 1800 C380 0100  .DaT............
00000070  4000 0000 0000 0000 0000 0000 0000 0000
........
00000080  646A 9F3C 0300 0100 4000 0000 0000 0000  djŸ<....@.......    DaT1_data.bin:  CRC16 = 0x80C3 <-- The CRC matches, so the Seagate algorithm is correct.
00000090  0000 0000 0000 FFFF 1000 0000 0000 0000  ......ÿÿ........                                       But how to account for sizes that are not dword multiples?
000000A0  EEEE EEEE 0000 0000 1000 0000 1000 0100  îîîî............
000000B0  3153 434D 0000 0000 0000 0000 0000 0000  1SCM............



Offset(h) 00   02   04   06   08   0A   0C   0E

00040080  2044 6154 0301 8740 0000 0000 9324 0200   DaT............
00040090  6001 0000 0000 0000 0000 0000 0000 0000
........
000400A0  0000 0000 0000 0000 0000 0000 0000 0000
000400B0  FFFF FFFF FFFF FFFF 0000 0000 0000 0000
........        zero filled
000401F0  0000 0000 0000 0000 0000 0000 0000 0000


Offset(h) 00   02   04   06   08   0A   0C   0E

000404A0  2044 6154 0401 08DA 0000 FFFF DD73 1700  .DaT............
000404B0  8018 0000 0000 0000 0000 0000 0000 0000
........
000404C0  0C52 4C45 C42A 0C00 7418 0000 FFFF FFFF
000404D0  FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
........
00040CC0  FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
00040CD0  FFFF FFFF FFFF FFB6 0100 1F00 00FF 00FF
00040CE0  00FF 00FF 00FF 00FF 00FF 00FF 00FF 00FF
........
00041D30  00FF 00FF 00FF 00FF 00FF 00FF 00FF 0043



Offset(h) 00   02   04   06   08   0A   0C   0E

0004F4C0  2044 6154 0501 8A52 0000 FFFF 84D0 1D00  .DaT............
0004F4D0  5043 0000 0000 0000 0000 0000 0000 0000
........
0004F4E0  02FF FFFF 1800 0000 0000 0000 0000 0000
0004F4F0  0000 0000 0000 0000 0000 0000 0000 0000
0004F500  04FF FFFF 0002 0000 0A00 010F 004E 432F
0004F510  8EBE 0300 0000 0303 005E 5D00 0000 0000
........
000537C0  0000 0000 0000 0000 0000 0000 0000 0000
000537D0  0000 0000 0000 0000 07FF FFFF 1800 0000
000537E0  5F06 CDAB 0100 0000 9B51 37FC 0100 0000
000537F0  0000 0000 0000 0000 0BFF FFFF 1800 0000
00053800  B402 1900 0000 0000 BC02 1C00 0000 0000
00053810  DA02 2800 0000 0000 0CFF FFFF 0400 0000
00053820  0000 0000 FFFF FFFF FFFF FFFF FFFF FFFF




Offset(h) 00   02   04   06   08   0A   0C   0E

000544E0  2044 6154 0101 E6DF 0000 FFFF 1A85 EF00  .DaT............
000544F0  9000 0000 0000 0000 0000 0000 0000 0000
........
00054500  904E 764C 0000 0000 3000 3000 0305 0000  .NvL............  <-- CRC is correct when applied to 0x90-byte block
00054510  0204 0A00 0300 0000 0000 0000 0000 0000
00054520  0000 0000 0000 0000 0304 0A00 0000 0000
00054530  0000 0000 0000 0000 0000 0000 0000 0000
00054540  0404 0401 2700 0000 0000 0000 0000 0000
00054550  0000 0000 0000 0000 0504 C800 B900 0000
00054560  0000 0000 0000 0000 0000 0000 0000 0000
00054570  0000 0000 0000 0000 0000 0000 0000 0000
00054580  0000 0000 0000 0000 0000 0000 0000 0000
........
00054590  FFFF FFFF FFFF FFFF FFFF FFFF F000 2201   <-- It would appear that these entries are remnants of previous events.
000545A0  5401 8601 B801 EA01 1C02 4E02 8002 B202       They appear to increment by 0x32.
000545B0  E402 1603 4803 7A03 AC03 DE03 1004 4204
000545C0  7404 A604 D804 0A05 3C05 6E05 A005 D205
000545D0  0406 3606 6806 9A06 CC06 FE06 3007 6207
000545E0  9407 C607 F807 2A08 5C08 8E08 C008 F208
000545F0  2409 5609 8809 BA09 EC09 1E0A 500A 820A
00054600  B40A E60A 180B 4A0B 7C0B AE0B E00B 120C
00054610  440C 760C A80C DA0C 0C0D 3E0D 700D A20D
00054620  D40D 060E 380E 6A0E

_________________
A backup a day keeps DR away.


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 23rd, 2026, 6:25 
Offline

Joined: October 3rd, 2005, 0:40
Posts: 4753
Location: Hungary
Cs16 and CRC16 is always calculated over multiple of 4 bytes. If the size is anything else, it should be considered an error.
btw, where do you see sizes not multiple of 4?

_________________
Adatmentés - Data recovery


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 23rd, 2026, 9:02 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
Code:
00000050                      2044 6154 0201 C32B          .DaT....
                              ^^ D  a T      ====
00000060  6000 0100 E539 1100 6200 0100 0000 0000                                    <-- CRC of next data block is 0x39E5, size is 0x10062
                    ++++      *********
00000070  0000 0000 0000 0000
........
00000070                      646A 9F3C 0100 0300          dj......                  <-- beginning of data
00000080  6200 0100 6000 0000 0200 0100 FFFF FFFF                                    <-- size of this data block is 0x10062
          ---------
00000090  EEEE EEEE 0000 0000 0000 4000 2000 0100
000000A0  FFFF FFFF FFFF FFFF 8016 6E69 3000 D501
........
000100B0  0000 2000 0000 0000 0200 FFFF FFFF FFFF
000100C0  FFFF B021 0700 0000 0000 4000 0000 0000
000100D0  0300 FFFF FFFF FFFF FFFF                                                   <-- end of data

_________________
A backup a day keeps DR away.


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 23rd, 2026, 11:03 
Offline

Joined: June 25th, 2006, 13:51
Posts: 142
Location: Italy
Understanding what the algorithm actually does

Looking at this carefully, the key insight is what the 4-byte grouping actually means. It is not really a "4-byte block" algorithm — it is a standard CRC-16/ARC (polynomial 0xA001, initial 0, reflected) applied to a stream of bytes, but with the bytes of each 32-bit little-endian word consumed most-significant-byte first (indices 3, 2, 1, 0 within each word).

In other words, the firmware treats the buffer as a stream of 32-bit LE words and feeds the CRC the MSB of each word first. For a trailing 16-bit half-word, the natural and almost certainly correct extension is the same thing at 16-bit granularity: process byte 1 first, then byte 0.

Why padding with 0x0000 probably didn't work

CRC-16/ARC is not invariant to trailing zero bytes. Appending \x00\x00 after your data is equivalent to running two extra hdd_update_crc16_byte(0, crc) steps, which changes the value. So the answer is almost never "pad and run the 4-byte version"; it is "handle the 2-byte tail natively, without any padding".

There is also a subtlety about the reverse-within-word order. If the trailing half-word originally occupied the low half of a 32-bit slot (bytes 0–1, with bytes 2–3 unused), then in the 4-byte algorithm those two unused bytes would have been consumed first (indices 3, 2) before your real data (indices 1, 0). If the trailing half-word occupied the high half of the slot, the reverse would be true. The firmware's actual choice depends on how the CRC routine increments its pointer — but for NvCache records whose length is genuinely 2-byte-granular (not padded to 4), Option A below matches every Seagate implementation I have seen documented.

Three variants worth trying, in descending order of likelihood

  • Option A — Process the 2-byte tail MSB-first, no padding (most likely correct): consume data[last-1], then data[last-2].
  • Option B — Pad \x00\x00 as the high half of a final 32-bit word: same as A but with two zero updates inserted before the tail, i.e. 0x00, 0x00, data[last-1], data[last-2]. This is equivalent to appending \x00\x00 and then running the original 4-byte algorithm. If you already tried exactly this, Option B is ruled out.
  • Option C — Pad \x00\x00 as the low half: two zero updates after the tail, i.e. data[last-1], data[last-2], 0x00, 0x00.

Cleaned-up implementation (Python 3)

Below is a drop-in replacement that accepts any length that is a multiple of 2 and lets you select the tail-handling mode. It also removes the Python 2 ord()/chr() calls since indexing a bytes object in Python 3 already returns an int.

Code:
# -*- coding: utf-8 -*-
# Seagate HDD firmware CRC-16/ARC (poly 0xA001, init 0, reflected).
# Bytes are consumed MSB-first within each little-endian word.
# Extended to handle buffers whose length is a multiple of 2 (not only 4).

hdd_crc_table = [0, 0xC0C1, 0xC181, 0x140, 0xC301, 0x3C0, 0x280, 0xC241,
    0xC601, 0x6C0, 0x780, 0xC741, 0x500, 0xC5C1, 0xC481, 0x440,
    0xCC01, 0xCC0, 0xD80, 0xCD41, 0xF00, 0xCFC1, 0xCE81, 0xE40,
    0xA00, 0xCAC1, 0xCB81, 0xB40, 0xC901, 0x9C0, 0x880, 0xC841,
    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040]


def hdd_update_crc16_byte(data, crc):
    return hdd_crc_table[(data ^ crc) & 0xff] ^ (crc >> 8)


def hdd_crc16(data, tail_mode="msb_first"):
    """
    CRC-16 over `data`. Length must be a multiple of 2.
    Each full 4-byte word is consumed MSB-first (indices 3, 2, 1, 0).
    A trailing 2-byte half-word is handled according to `tail_mode`:
      - 'msb_first'  (default, Option A): consume byte 1 then byte 0
      - 'pad_high'   (Option B): insert two zero updates, then consume 1, 0
      - 'pad_low'    (Option C): consume 1, 0, then two zero updates
    """
    if len(data) % 2 != 0:
        raise ValueError("Data length must be a multiple of 2")

    crc = 0
    i = 0
    n = len(data)

    while i + 4 <= n:
        crc = hdd_update_crc16_byte(data[i + 3], crc)
        crc = hdd_update_crc16_byte(data[i + 2], crc)
        crc = hdd_update_crc16_byte(data[i + 1], crc)
        crc = hdd_update_crc16_byte(data[i + 0], crc)
        i += 4

    if i < n:
        if tail_mode == "msb_first":
            crc = hdd_update_crc16_byte(data[i + 1], crc)
            crc = hdd_update_crc16_byte(data[i + 0], crc)
        elif tail_mode == "pad_high":
            crc = hdd_update_crc16_byte(0, crc)
            crc = hdd_update_crc16_byte(0, crc)
            crc = hdd_update_crc16_byte(data[i + 1], crc)
            crc = hdd_update_crc16_byte(data[i + 0], crc)
        elif tail_mode == "pad_low":
            crc = hdd_update_crc16_byte(data[i + 1], crc)
            crc = hdd_update_crc16_byte(data[i + 0], crc)
            crc = hdd_update_crc16_byte(0, crc)
            crc = hdd_update_crc16_byte(0, crc)
        else:
            raise ValueError("Unknown tail_mode: %r" % tail_mode)

    return crc


def hdd_crc16_checksum_buffer(data, tail_mode="msb_first"):
    body = data[:-4]
    crc = hdd_crc16(body, tail_mode=tail_mode)
    crc = (crc >> 8) ^ hdd_crc_table[crc & 0xff]
    crc = (crc >> 8) ^ hdd_crc_table[crc & 0xff]
    return body + bytes([(crc >> 8) & 0xff, crc & 0xff, 0, 0])


What a quick sanity check confirms

  • For a length-16 buffer, all three tail modes give identical results to the original 4-byte-only code (because there is no tail) — so the new version is backward-compatible.
  • tail_mode="pad_high" gives exactly the same output as running the original 4-byte algorithm on data + b"\x00\x00". If that is what you already tried, Option B is ruled out.
  • That leaves Option A (msb_first) as the overwhelmingly likely correct answer, with Option C (pad_low) as a long shot.

Recommended plan

  1. Re-run your NvCache blocks with tail_mode="msb_first" (the default). If the computed CRCs match the stored ones, you are done.
  2. If a specific block still fails, try tail_mode="pad_low" on that one block. It is rare but it does appear in a couple of firmware families where the record length is stored in 32-bit words and the CRC unit flushes after the last partial word.
  3. If neither matches, either the ROM dump has corruption in that NvCache region, or the NvCache uses a different CRC polynomial or seed than the main ROM section. It would not be unheard of — Seagate has been known to use CRC-16/CCITT (poly 0x1021) for some firmware structures and CRC-16/ARC for others. In that case, pull the CRC routine out of the ROM (look for references to a 256-entry table near known init code) and confirm the polynomial directly in the disassembly rather than guessing.

One more suggestion: since the ROM is available, the CRC table should be locatable inside the image itself. Searching for the little-endian byte pattern 00 00 00 00 C1 C0 00 00 81 C1 00 00 40 01 00 00 (the first four 32-bit-padded entries of the table) will find it instantly if it is there in the same form. If the table is different, that alone tells you the NvCache uses a different CRC.

[hr]
Technical analysis completely drafted by Claude.ai Opus 4.7 (Anthropic). - In few words didn't written a single character; if it works , this is just the surface of the power you can get using your skills with the support of contemporay AI LLMs. Opus 4.7 is only available with a PRO plan which in Italy costs 19,00 euro per month


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 24th, 2026, 11:12 
Offline

Joined: February 22nd, 2023, 13:49
Posts: 327
Location: Eastern Europe
That was a long time ago... And not on this forum. The calculator was written for research FW, for myself.

A little more convenient than a script.


Attachments:
SeagSumm.7z [327.91 KiB]
Downloaded 31 times
Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 24th, 2026, 12:40 
Offline

Joined: October 3rd, 2005, 0:40
Posts: 4753
Location: Hungary
i also wrote a py script for this, i find it more convenient to modify, add new features, cmdline args, etc...
it gets inconvenient after some months not using it, i need to refresh my python... :)
what do you use for coding exe?

_________________
Adatmentés - Data recovery


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 24th, 2026, 13:45 
Offline

Joined: February 22nd, 2023, 13:49
Posts: 327
Location: Eastern Europe
Delphi.


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 25th, 2026, 18:46 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
Here are some examples:

https://forum.hddguru.com/viewtopic.php?p=306734#p306734 (thread)

https://forum.hddguru.com/download/file.php?id=24441 (bad ST4000VX005 ROM)


https://groups.google.com/g/datarecoverycertification/c/Q6f7ekwIQb8 (thread)

https://groups.google.com/group/datarecoverycertification/attach/50e048ad1008d/ROM.bin?part=0.2 (bad ST4000VX005 ROM)
https://groups.google.com/group/datarecoverycertification/attach/5175598d01ac4/donor.bin?part=0.1 (good? donor ST4000VX005 ROM)


The attached files are the data areas of the first DaT section in each ROM.


Attachments:
DaT1_donor_CRC_BC8C.7z [18.42 KiB]
Downloaded 14 times
DaT1_CRC_2551.7z [25.8 KiB]
Downloaded 10 times
DaT1_data_CRC_39E5.7z [20.17 KiB]
Downloaded 9 times

_________________
A backup a day keeps DR away.
Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: April 26th, 2026, 5:17 
Offline

Joined: June 25th, 2006, 13:51
Posts: 142
Location: Italy
I am finishing the setup of

ghidra + Claude-code estension

to allow claude-code to interact with ghidra

though I am in stand by because anthropic had created our account as individual instead of business

for the moment I cannot pay the tokens

Then it would and will become intersting


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 1st, 2026, 16:20 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
The CRC-16 algorithm requires that the data be padded to the right with 0xFF. The three examples I was given, including the donor, appear to have bad DaT sections.

The ROM in this thread produces an error -- "Failed power-on replay: 0x005A":

https://groups.google.com/g/datarecoverycertification/c/nwv1_XnuKGM

However, one of the DaT sections confirms the algorithm.

Code:
ST_CRC16_3.exe DaT2_data_CRC_E207.bin

Warning: File length is not a multiple of 4 - 0x136

DaT2_data_CRC_E207.bin:  CRC16 (truncated) = 0x8BD1
DaT2_data_CRC_E207.bin:  CRC16 (no padding, little-endian) = 0xE752
DaT2_data_CRC_E207.bin:  CRC16 (no padding, big-endian) = 0x089C
DaT2_data_CRC_E207.bin:  CRC16 (left padding with 0x00) = 0x2A7D
DaT2_data_CRC_E207.bin:  CRC16 (right padding with 0x00) = 0xC607
DaT2_data_CRC_E207.bin:  CRC16 (left padding with 0xFF) = 0x9A7C
DaT2_data_CRC_E207.bin:  CRC16 (right padding with 0xFF) = 0xE207   <--------------


Attachments:
NvCache.7z [20.69 KiB]
Downloaded 9 times

_________________
A backup a day keeps DR away.
Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 1st, 2026, 19:04 
Offline

Joined: October 3rd, 2005, 0:40
Posts: 4753
Location: Hungary
nice... i did not have much time to invest on this but gonna check.

_________________
Adatmentés - Data recovery


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 2nd, 2026, 11:14 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
Updated NvCache notes.


Attachments:
Seagate_NvCache_2.7z [9.66 KiB]
Downloaded 8 times

_________________
A backup a day keeps DR away.
Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 2nd, 2026, 15:42 
Offline

Joined: October 3rd, 2005, 0:40
Posts: 4753
Location: Hungary
Code:
DaT2_data_CRC_E207.bin:  CRC16 (right padding with 0xFF) = 0xE207   <--------------


did this work for you where the size was 10032? I guess not...
pepe

_________________
Adatmentés - Data recovery


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 2nd, 2026, 15:47 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
No, that size never produced the correct CRC. I also noticed that the CRC for WCD was always missing from terminal whenever a playback error occurred.

One confusing observation I have is in regard to this case:

https://groups.google.com/g/datarecoverycertification/c/nwv1_XnuKGM

The CRC for WCD is bad, but the firmware still appears to be trying to flush the cache to the platters. Why would it do that if the cached data were known to be bad?

_________________
A backup a day keeps DR away.


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 2nd, 2026, 16:02 
Offline

Joined: October 3rd, 2005, 0:40
Posts: 4753
Location: Hungary
ok, not surprised, coz the block size for crc should be multiple of 8 according to my code analysis.
as for the other q i have no idea atm.
pepe

_________________
Adatmentés - Data recovery


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 2nd, 2026, 16:26 
Offline
User avatar

Joined: September 8th, 2009, 18:21
Posts: 16955
Location: Australia
Sigh, all the CRCs are now correct after padding the last qword with 0xFF.

Code:
RLE_plus_4xff_CRC_8183.bin:  CRC16 = 0x8183
DaT1_data_plus_6xff_CRC_39E5.bin:  CRC16 = 0x39E5
DaT1_plus_6xff_CRC_2551.bin:  CRC16 = 0x2551
DaT1_donor_plus_6xff_CRC_BC8C.bin:  CRC16 = 0xBC8C

_________________
A backup a day keeps DR away.


Top
 Profile  
 
 Post subject: Re: CRC-16 algorithm for Seagate ROM segments
PostPosted: May 2nd, 2026, 17:47 
Offline

Joined: October 3rd, 2005, 0:40
Posts: 4753
Location: Hungary
in fact the unused area is filled with FF so the only thing missing was to do the crc over the block rounded up to next qword boundary. The code does not do any padding.

_________________
Adatmentés - Data recovery


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 20 posts ] 

All times are UTC - 5 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group