Page 3 of 3

Re: can we replicate BTH data gs450h/prius?... Unlocking the PHEV

Posted: Tue Oct 21, 2025 5:36 pm
by 0tik
One checksum can have more than one value of the sum of the previous bytes. That rules out HVC checksum algorythm. I am convinced that figuring out byte meaning ie. voltage reistance and temperature won't help with CRC cracking.
Artificial intelligence told me this

Code: Select all

ChatGPT powiedział:
Zakończono analizę
Błąd analizy

Tried brute-force checks and common alternatives. No exact match found yet.

What I ran (fail/pass):

Tested common CRC‑16 variants (CRC-16/IBM, ARC, MODBUS, X25, XMODEM, CCITT-FALSE, KERMIT, MCRF4XX, DNP, etc.) against the five payloads (excluding last two bytes). result: no match.

Tested Fletcher‑16 (formula sum1/sum2). result: no match.

Tested simple 16‑bit sum (big/little-endian), and 16‑bit one’s‑complement Internet checksum. result: no match.

Attempted a heavier brute-force (varying init) for two common polynomials (0x1021, 0x8005) but the run timed out before completion.

Conclusion so far: the checksum is not a plain byte-sum, Fletcher‑16, nor any of the standard CRC‑16 variants I tried with standard params. Likely candidate remains a CRC‑16 variant with unusual parameters (nonstandard init/xor/reflection), a custom polynomial/seed, a nonstandard word ordering, or a bespoke weighted/rolling checksum.

If you want a full brute-force search on your machine (polynomial × init × reflect × xorout), use the optimized table-driven script below. It builds a CRC table per polynomial and tests all init (0..0xFFFF) quickly, checking whether a single xorout value makes every payload produce 0x25F4 (also checks little-endian 0xF425). It will print any matching parameter sets.

Save and run the script with Python 3 (it prints progress and exits on match or finishes scanning all values).

# brute_crc16_search.py
# Usage: python3 brute_crc16_search.py
# Scans candidate polynomials/reflect combos and all 16-bit init values for a consistent xorout that yields target checksum
# for all provided payloads. Targets checked as big-endian (0x25F4) and little-endian (0xF425).
from itertools import product
from time import time

# payloads: paste your payload hex arrays (excluding final two checksum bytes) here as lists of ints.
payload_hex_lines = [
"2C D3 0F DB D8 1 AA 0 0F CF D8 0 A8 0E A6 78 2D D2 0E D2 D9 5B AA 0 0E DD D9 DB AA F7 E8 DD 2E D1 0E DE D9 0 A8 0 0E 0 DB 6E 88 0 25 6B 2F D0 0D DB DB FB AA 0 0D D5 D8 0 A8 DD A8 46 30 CF 0D DE D8 FC AA 0 0E E8 DB F9 AA FB A9 77 31 CE 0E DE D6 FB AA 0 0D 0 D9 0 88 0",
"2C D3 0E D9 D5 1 AA 0 0E CE D6 0 A8 0D A6 6D 2D D2 0E D0 D7 5B AA 0 0E DC D7 DB AA FF E8 DE 2E D1 0F DD D8 0 A8 0 0F 0 DB 6E 88 0 25 6B 2F D0 0F DB DB FB AA 0 0F D4 D8 0 A8 E0 A8 4C 30 CF 0F DD D8 FD AA 0 0E E7 D9 FA AA FB A9 77 31 CE 0F DD D5 FB AA 0 0E 0 D9 0 88 0",
"2C D3 0D DA D7 1 AA 0 0D CE D7 0 A8 0E A6 70 2D D2 0D D1 D8 5B AA 0 0E DD D8 DB AA FF E8 E1 2E D1 0F DE D9 0 A8 0 0E 0 DB 6E 88 0 25 6C 2F D0 0E DC DC FB AA 0 0E D5 D8 0 A8 DE A8 4B 30 CF 0F DE D8 FD AA 0 0E E8 DA F9 AA FB A9 79 31 CE 0E DD D5 FB AA 0 0E 0 DA 0 88 0",
"2C D3 0F DB D8 1 AA 0 0F CF D8 0 A8 0E A6 78 2D D2 0E D2 D9 5B AA 0 0E DD D9 DB AA F7 E8 DD 2E D1 0E DE D9 0 A8 0 0E 0 DB 6E 88 0 25 6B 2F D0 0D DB DB FB AA 0 0D D5 D8 0 A8 DD A8 46 30 CF 0D DE D8 FC AA 0 0E E8 DB F9 AA FB A9 77 31 CE 0E DE D6 FB AA 0 0D 0 D9 0 88 0",
"20 DF 0C CD D9 1 AA 0 0C D2 DA 0 A8 0C A6 68 21 DE 0C DE D7 5B AA 0 0C DD D9 DB AA FF E8 EB 22 DD 0C DF DA 0 A8 0 0C 0 DA 6E 88 0 25 68 23 DC 0D D4 DA FB AA 0 0D DF DA 0 A8 DE A8 4B 24 DB 0D DF DC FC AA 0 0C E9 DB F9 AA FA A9 7A 25 DA 0C DE D7 FA AA 0 0D 0 DB 0 88 0"
]

def parse_hex_line(s):
    toks = s.split()
    arr = [int(t,16) for t in toks]
    # assume last two bytes are checksum; payload excludes them
    return arr[:-2]

payloads = [parse_hex_line(l) for l in payload_hex_lines]

# target values
target_be = 0x25F4
target_le = 0xF425
targets = [target_be, target_le]

# CRC table generator (byte-wise table) - supports reflected or normal
def make_crc16_table(poly, reflect=False):
    tbl = []
    for byte in range(256):
        if reflect:
            crc = byte
            for _ in range(8):
                if crc & 1:
                    crc = (crc >> 1) ^ poly
                else:
                    crc >>= 1
            tbl.append(crc & 0xFFFF)
        else:
            crc = byte << 8
            for _ in range(8):
                if crc & 0x8000:
                    crc = ((crc << 1) & 0xFFFF) ^ poly
                else:
                    crc = (crc << 1) & 0xFFFF
            tbl.append(crc & 0xFFFF)
    return tbl

def crc16_table(data, table, init=0x0000, reflect=False, xor_out=0x0000):
    crc = init & 0xFFFF
    if reflect:
        for b in data:
            crc = (crc >> 8) ^ table[(crc ^ b) & 0xFF]
        crc = crc ^ xor_out
    else:
        for b in data:
            crc = ((crc << 8) & 0xFFFF) ^ table[((crc >> 8) ^ b) & 0xFF]
        crc = crc ^ xor_out
    return crc & 0xFFFF

# Candidate polynomials to try (start with common ones). Expand list if needed.
candidate_polys = [
    0x1021, 0x8005, 0xA001, 0x8408, 0x3D65, 0x8BB7, 0x0589, 0x1021
]

# candidate inits to try first (fast)
candidate_inits = [0x0000, 0xFFFF, 0x1D0F, 0x0001]

start = time()
for poly in candidate_polys:
    for reflect in (False, True):
        table = make_crc16_table(poly if not reflect else poly, reflect=reflect)
        # try small set of inits quickly
        for init in candidate_inits:
            raws = [crc16_table(p, table, init=init, reflect=reflect, xor_out=0) for p in payloads]
            # compute required xor for each target endianness
            for tgt in targets:
                xors = [raw ^ tgt for raw in raws]
                if all(x == xors[0] for x in xors):
                    print("MATCH", hex(poly), "reflect", reflect, "init", hex(init), "xor_needed", hex(xors[0]), "target", hex(tgt))
# If no quick match, expand init brute-force across full 0..0xFFFF for chosen poly/reflect combos:
# Example loop (commented out for safety; uncomment to run full brute):
# for poly in candidate_polys:
#     for reflect in (False, True):
#         table = make_crc16_table(poly, reflect=reflect)
#         for init in range(0, 65536):
#             raws = [crc16_table(p, table, init=init, reflect=reflect, xor_out=0) for p in payloads]
#             for tgt in targets:
#                 xors = [raw ^ tgt for raw in raws]
#                 if all(x == xors[0] for x in xors):
#                     print("FOUND", hex(poly), reflect, hex(init), hex(xors[0]), hex(tgt))
#                     raise SystemExit
print("done scan (partial). elapsed", time()-start)


What this script does:

Checks a short list of common polynomials with both reflection modes and several typical init seeds; for each (poly,init,reflect) it computes raw CRCs (xor_out = 0) and determines whether a single xor_out value would make every payload equal the checksum stored (either 0x25F4 or 0xF425). If so, it prints those parameters.

A full brute-force inner loop for init in 0..65535 is included but commented out; enable if you run locally and want exhaustive search. The table-driven implementation is much faster than bitwise loops.

Status: partial brute force run produced no match for the standard variants and small init sets. Next step for exhaustive confirmation is to run the provided script with the full init loop and/or expand candidate_polys to brute-force more polynomials.
Tried CRC RevEng without luck too.

Perhaps this is where I cut the loses and move on. Figuring out communication and how to hack it was fun but there was no math involved. I am really bad at math and don't see myself even trying to grasp any of that crc algorythm crap. Lex is a comfy and warm car. Literaly - on the cold mornings I get inside and its already warm! (That's the trauma coming from driving an old EV with not much insulation). It has great sound system. Going broom-broom fast when traffic light goes green is fun too. But the fun and comfortable-ness are the only pros of this car. From the practical point of view its a really bad car. Fits as much luggage as an i-miev. Burns twice as much fuel as a Prius. 1067 liters over 11 415km with average 8,86l/100km hits my pocket quite hard. Plus the fact that I can be lazy because I don't have to charge my EV because there's a backup car ready for a trip isn't good for my pocket either. I think it's time to sell it and move on to something with a future.

Re: can we replicate BTH data gs450h/prius?... Unlocking the PHEV

Posted: Wed Oct 22, 2025 4:03 pm
by maciek16c
I analyzed log and i think there might be no checksum. It looks like each 96 Byte packet has 6 16 byte groups which have the same layout. I added to my github Excel file where i plotted some values and named few bytes. Each 96byte packet has current in 13 places and each one is different (next sample because current must be updated fast) so HVECU is probably reading data without buffer (so no checksum)
Data is probably multiplexed too, because when plotting byte containing module voltage it alternates between few modules (i have one broken cell and it's clearly visible)
Bytes labeled MPX are probably switching what data is present in this 16Byte structure. There are 32 values which repeat

Re: can we replicate BTH data gs450h/prius?... Unlocking the PHEV

Posted: Wed Oct 22, 2025 5:21 pm
by 0tik
Does that mean that one value is sent more than once in a single data packet? That would explain DTC error. All instances of that repeated byte need to be spoofed.

Re: can we replicate BTH data gs450h/prius?... Unlocking the PHEV

Posted: Wed Oct 22, 2025 6:40 pm
by maciek16c
Current is sent multiple times, but each time it's next sample. I will try to add constant offset to each current sample later this week.
There might also be problem with multiplexing or something like that. Based on data in bytes i named MPX there are 5 different packets which repeat in loop. Maybe there could be offset added to chosen byte only when this MPX byte has certain value?