0

I'm trying to control an air conditioning unit. The app and the unit communicate using the Broadlink protocol. I can decode the settings it's sending and replay them.

The payload is 32 bytes but only the middle 15 ever change (prefixed with 19 00 bb 00 06 80 00 00 0f 00 01 01 and suffixed with zeros). I think the last two bytes are some kind of checksum, but I'm unsuccessful in recreating it.

Here's a sample (more):

9f e4 07 60 00 20 00 00 00 00 00 00 00 87 19 
9f e4 07 60 00 20 00 00 20 00 00 00 00 67 19 
9f e4 2d 60 00 20 00 00 20 00 00 00 00 41 19 
97 e4 87 60 00 20 00 00 20 00 00 00 00 ef 18 
97 e4 07 60 00 20 00 00 20 00 00 00 00 6f 19 
9f e4 87 60 00 20 00 00 20 00 00 00 00 e7 18 
a7 e4 07 60 00 20 00 00 20 00 00 00 00 5f 19 
a7 e4 07 60 00 80 00 00 20 00 00 00 00 5e b9 
a7 e4 07 40 00 20 00 00 20 00 00 00 00 5f 39 
a7 e4 07 20 00 20 00 00 20 00 00 00 00 5f 59 
a7 e4 07 20 40 20 00 00 20 00 00 00 00 1f 59

I think it's related to a sum because the same number of bits change in the sum, but I've calculated the difference and the XOR between the sum and the actual checksum and they aren't constant. Also tried reversing the bits with no luck.

1
  • If it is an app usually the easiest way to understand how the CRC is calculated is by decompiling the app.
    – Robert
    Sep 15, 2020 at 16:25

2 Answers 2

0

I'll give a stab at this. It's hard to tell without more context and information, it appears that the last two bytes are a little endian value added to the sum to make it equal some number. Possibly 0xff17 if it sums every single value. Without knowing more information, it's impossible to tell which bytes it's checking but assuming we check everything, I think they are used something like this:

def calculate(x):
    for arr in x:
        diff = 0x80EA # sum of the first 6 little endian words
        diff += (arr[12] + (arr[13] << 8)) # add seventh little endian word
        diff += (arr[14] + (arr[15] << 8)) # add eighth little endian word                                                                            
        diff += (arr[16] + (arr[17] << 8)) # add ninth little endian word   
        # diff += (arr[18] + (arr[19] << 8)) these values are all zeros...
        diff += (arr[20] + (arr[21] << 8)) # add eleventh little endian word
        diff += (arr[22] + (arr[23] << 8)) # add twelfth little endian word
        diff += arr[24]
        checksum = (arr[25] + (arr[26] << 8) & 0xffff) # grab checksum                                                                               
        print(hex((diff+checksum) & 0xffff))

When I run this with you list of 171 samples as input, I only get three unique values, and they're all within a byte of each other.

~/ python3 test.py | sort | uniq | wc
      3       3      21
~/ python3 test.py | sort | uniq -c
     21 0xff16
    146 0xff17
      4 0xff18

Assuming you are correct about the first 12 bytes not changing, perhaps these are due to transmission error (that's the point of a checksum, right?) or I am missing something in checksum algorithm.

As a side note, the first two bytes 19 00 may refer to the payload length in bytes.

1

I think there may be some noise on the line. Lauren Labell has a tool to try and automate reversing checksums: https://github.com/laurenlabell/checksum_finder

Here's what it generated:

#  start: 0 end: 0 check: 13 foldOp: <built-in function sub> finalOp: <built-in function add> magicValue: 0xaa
# ================================================================================
# Generated Code
# --------------------------------------------------------------------------------



import operator

def twosComp(n):
    return -n

def onesComp(n1, n2):
    mod = 1 << 8
    result = n1 + n2
    return result if result < mod else (result + 1) % mod  

def pad(xs,w):
    n = len(xs)
    target_n = (-(-n//w)) * w
    delta = target_n - n
    xs_padded = xs+[0]*delta
    return xs_padded

def chunk(xs,w):
    xs_chunked = [xs[i:i+w] for i in range(0,len(xs),w)]
    return xs_chunked

def to_int(x):
    return int.from_bytes(bytes(x),'big')


def preprocess(hex_str,w):
    hex_str = ''.join(hex_str.split(' '))
    xs = [x for x in bytes.fromhex(hex_str)]
    xs_padded = pad(xs,w)
    xs_chunked = chunk(xs_padded,w)
    xs_ints = [to_int(x) for x in xs_chunked]
    return xs_ints


def calculate_checksum(payload):
    magicValue = 0xaa
    mask = 0xFF

    checksum = 0
    for element in payload:
        checksum = operator.sub(checksum,element)
    checksum =  operator.add(checksum,magicValue)
    return checksum & mask

def validate_message(rawmsg):
    msgStart = 0
    msgEnd = 0
    checksumPos = 13 
    width = 1

    msg = preprocess(rawmsg,width)
    payload = msg[msgStart:]
    checksum = msg[checksumPos]
    payload[checksumPos] = 0

    return calculate_checksum(payload) == checksum

# ================================================================================
# Unit Tests
# --------------------------------------------------------------------------------

print(validate_message('9f e4 07 60 00 20 00 00 00 00 00 00 00 87 19'),'9f e4 07 60 00 20 00 00 00 00 00 00 00 87 19')
print(validate_message('9f e4 2d 60 00 20 00 00 20 00 00 00 00 41 19'),'9f e4 2d 60 00 20 00 00 20 00 00 00 00 41 19')
print(validate_message('97 e4 07 60 00 20 00 00 20 00 00 00 00 6f 19'),'97 e4 07 60 00 20 00 00 20 00 00 00 00 6f 19')
print(validate_message('a7 e4 07 60 00 20 00 00 20 00 00 00 00 5f 19'),'a7 e4 07 60 00 20 00 00 20 00 00 00 00 5f 19')
print(validate_message('a7 e4 07 20 40 20 00 00 20 00 00 00 00 1f 59'),'a7 e4 07 20 40 20 00 00 20 00 00 00 00 1f 59')

# --------------------------------------------------------------------------------
# End Generated Code
# --------------------------------------------------------------------------------

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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