I am trying to understand a request I reverse engineered which is sent to a Daly BMS over BLE.

Those requests change the 'sleep after' in seconds of the BMS:

  • For DDDD seconds: d2 06 00 8A DD DD 22 8A
  • For FFFF seconds: d2 06 00 8A FF FF BA 33
  • For EEEE seconds: d2 06 00 8A EE EE 76 6F
  • For FFFE seconds: d2 06 00 8A FF FE 7B F3
  • For 0000 seconds: d2 06 00 8A 00 00 BB 83

As you can see:

  • The 'asleep' request start with the fixed: d2 06 00 8A prefix
  • The data follows;
  • The last two bytes is probably a CRC-16, which could be a custom one.

I'd like to build the last two digit by code.

Therefore, how can I get further? How can I understand how the two last bytes are built?


After the answer of @poncho, I share the implementation of the reverse CRC16 I wrote in Dart.

The dart pad is right there. Hope it helps.

  • $\begingroup$ I have made a typo for the request with FFFE: I fixed it now in the question (you must have read FF FE) $\endgroup$ Jun 8, 2022 at 15:53
  • $\begingroup$ The first thing is to determine if the last 16 bits are affine. See this $\endgroup$
    – fgrieu
    Jun 8, 2022 at 16:56

2 Answers 2


How can I understand how the two last bytes are built?

The first obvious question to answer is "are the last two bytes a linear [1] function of the rest of the data?". A CRC-16 would be linear; however those aren't the only linear functions (and in any case, as you'll see, we won't care about the distinction).

The easiest way to spot check this would be to get the message for 0001 seconds; if the function creating the last two bytes is linear, then the result will be BB 83 $\oplus$ 7B F3 $\oplus$ BA 33 = 7A 43. If you see that, then the mapping is most likely linear; if not, then we know that it isn't. Note that the three tags that I xor'ed are the tags for 0000, FFFE and FFFF.

If it is indeed linear, then it's easy; just find the messages for 0002, 0004, 0008, 0010, 0020, 0040, 0080, 0100, 0200, 0400, 0800, 1000, 2000, 4000, 8000 seconds; once you have all those, then you can compute the tag for any value by xoring together the correct known tags; to compute the tag for 0120, you'd xor in the tag for 0000 (which is included if the number of bits is even), for 0100, and for 0020.

If this does correspond to a CRC, it'd actually be fairly easy to deduce the polynomial and initial state from this - however, you don't need it.

[1]: or affine; the distinction doesn't matter in this case

  • $\begingroup$ Thanks @poncho. Based on that, how can I extend it for an arbitrary list of bytes (rather than just 2)? $\endgroup$ Jun 10, 2022 at 13:35
  • 1
    $\begingroup$ @StéphanedeLuca: well, if you can specify the data directly, it's fairly obvious (e.g. ask for 00000001, then 00000002, etc). If you can't, well, you're not out of luck - with a bit of linear algebra (Gaussian Elimination), all you really need is about n known solutions (where n is the number of bits in question, plus a couple extra), and solve for the relations. Of course, if there's really a CRC behind it, there are quicker (that is, needing fewer queries) to get the CRC polynomial, however that's also rather harder to explain $\endgroup$
    – poncho
    Jun 10, 2022 at 13:43
  • $\begingroup$ I formulated an upgrade I am looking for crypto.stackexchange.com/questions/100512/… $\endgroup$ Jun 10, 2022 at 15:59

There is not enough data shown in the question to make more than an educated guess. But this seems to be a pure Modbus (aka CRC-16-ANSI, CRC-16-IBM, Bisync, ANSI X3.28) 16-bit CRC. Here is a demo.

For a frame of $\ell$ data bytes (excluding CRC), the CRC is obtained as the remainder in the polynomial division of the polynomial with binary coefficients, of degree $8\ell+31$, which coefficient (from order $8\ell+31$ down to $0$) are

  • 16 bits at $1$
  • the $\ell$ data bytes, with low-order bit of each byte first, which corresponds to the order of bits for a traditional UART
  • 16 bits at $0$

by the polynomial $x^{16}+x^{15}+x^2+1$. Said remainder is a polynomial of degree (at most) $15$, which coefficients (from order $15$ down to $0$) form the CRC, to be sent in that order (thus with coefficient of the term of degree $15$ the low-order bit of the first byte of CRC, and constant term the high-order bit of the second byte of CRC).

As usual for a CRC that's not messed up, if the CRC is entered in the computation, the outcome is constant (here zero). This ensures that the protection against any burst of error of at most 16 bits extends to a burst that spans the end of the data and the start of the CRC.

Notice that the code in the demo stores the high-order coefficient of polynomials in the low-order bits of bytes, because that's the most straightforward implementation.

  • $\begingroup$ OP posted a now-deleted answer that listed the checksums for timeouts of 0000, 0001, 0002, 0004, ... as poncho suggested. They were BB83, 7A43, 3A42, BA40, BA45, BA4F, BA5B, BA73, BA23, BA13, BAE3, B943, BC43, B643, A243, 8A43, DA43, which are all consistent with the Modbus CRC, I think. $\endgroup$
    – benrg
    Jun 10, 2022 at 21:55
  • $\begingroup$ @benrg: Yes. That code is still there. $\endgroup$
    – fgrieu
    Jun 11, 2022 at 5:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.