
Toyota mendistribusikan firmware-nya dalam format tidak berdokumen. Pelanggan saya, yang memiliki mobil merek ini, menunjukkan kepada saya file firmware, yang dimulai seperti ini: Lalu ada baris 32 digit heksadesimal. Pemilik dan pengrajin lain ingin dapat memeriksa apa yang ada di dalamnya sebelum menginstal firmware: masukkan ke dalam pembongkaran dan lihat fungsinya.
CALIBRATIONΓͺXi ΒΊ
attach.att
ΓΓ[Format]
Version=4
[Vehicle]
Number=0
DateOfIssue=2019-08-26
VehicleType=GUN1**
EngineType=1GD-FTV,2GD-FTV
VehicleName=IMV
ModelYear=15-
ContactType=CAN
KindOfECU=0
NumberOfCalibration=1
[CPU01]
CPUImageName=3F0S7300.xxz
FlashCodeName=
NewCID=3F0S7300
LocationID=0002000100070720
CPUType=87
NumberOfTargets=3
01_TargetCalibration=3F0S7200
01_TargetData=3531464734383B3A
02_TargetCalibration=3F0S7100
02_TargetData=3747354537494A39
03_TargetCalibration=3F0S7000
03_TargetData=3732463737463B4A
3F0S7300forIMV.txt ΒΈNiΒΆm5A56001000820EE13FE2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133E2030133E2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133E2030133E2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133E20911381959FAB0EE9000
81C9E03ADE35CEEEEFC5CF8DE9AC0910
38C2E031DE35CEEEEFC8CF87E95C0920
...
Khusus untuk firmware ini, dia memiliki dump konten:
0000: 80 07 80 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0010: 80 07 00 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0020: 00 00 00 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0030: 80 07 00 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0040: 80 07 00 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0050: 80 07 00 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0060: 00 00 00 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0070: 80 07 00 00 00 00 00 00 β 00 00 00 00 00 00 00 00
0080: E0 07 60 01 2A 06 00 FF β 00 00 0A 58 EA FF 20 00
0090: FF 57 40 00 EB 51 B2 05 β 80 07 48 01 E0 FF 20 00
...
Seperti yang Anda lihat, tidak ada yang mendekati string digit heksadesimal dalam file firmware. Muncul pertanyaan: dalam format apa firmware didistribusikan, dan bagaimana mendekripsinya? Pemilik mobil mempercayakan saya tugas ini.
Fragmen berulang
Mari kita lihat lebih dekat garis heksadesimal tersebut: Kita melihat delapan pengulangan dari tiga urutan , yang sangat mirip dengan delapan baris pertama dump, berakhir dengan 12 nol byte. Tiga kesimpulan bisa segera ditarik:
5A56001000820EE13FE2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133E2030133E2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133E2030133E2030133E20301
33E2030133C20EF13FE2030133E20301
33E2030133E20911381959FAB0EE9000
81C9E03ADE35CEEEEFC5CF8DE9AC0910
38C2E031DE35CEEEEFC8CF87E95C0920
...
E2030133
- Lima byte pertama
5A56001000adalah jenis header yang tidak mempengaruhi konten dump; - Konten selanjutnya dienkripsi dalam blok 4 byte, dan byte yang sama di dump sesuai dengan byte yang sama di file:
E2030133 β 00000000820EE13F β 80078000C20EF13F β 80070000E2091138 β E00760011959FAB0 β 2A0600FFEE900081 β 00000A58C9E03ADE β EAFF2000
- Dapat dilihat bahwa ini bukan enkripsi XOR, tetapi sesuatu yang lebih kompleks; tetapi blok dump yang serupa terkait dengan blok serupa di file - misalnya, mengubah satu bit berarti
80078000β80070000mengubah satu bit820EE13FβC20EF13F.
Korespondensi antar blok
Mari kita dapatkan daftar semua pasangan (blok file, blok dump), dan cari pola di dalamnya:
$ xxd -r -p firmware.txt decoded
$ python
>>> f = open('decoded','rb')
>>> data=f.read()
>>> words=[data[i:i+4] for i in range(0,4096,4)]
>>> f = open('dump','rb')
>>> data=f.read()[:4096]
>>> reference=[data[i:i+4] for i in range(0,4096,4)]
>>> list(zip(words,reference))[:3]
[(b'\x82\x0e\xe1?', b'\x80\x07\x80\x00'), (b'\xe2\x03\x013', b'\x00\x00\x00\x00'), (b'\xe2\x03\x013', b'\x00\x00\x00\x00')]
>>> dict(zip(words,reference))
{b'\x82\x0e\xe1?': b'\x80\x07\x80\x00', b'\xe2\x03\x013': b'\x00\x00\x00\x00', b'\xc2\x0e\xf1?': b'\x80\x07\x00\x00', ...}
>>> decode=dict(zip((w.hex() for w in words), (r.hex() for r in reference)))
>>> decode
{'820ee13f': '80078000', 'e2030133': '00000000', 'c20ef13f': '80070000', ...}
>>> sorted(decode.items())
[('00beb5ff', '4c07a010'), ('02057139', '0000f00f'), ('03ef5ed0', '50ff710f'), ...]
Beginilah tampilan pasangan pertama dalam daftar yang diurutkan:
00beb5ff β 4c07a010 02057139 β 0000f00f 03ef5ed0 β 50ff710f \ perubahan di bit 24 di dump mengubah bit 8, 10, 24-27 di file 04ef5bd0 β 51ff710f < 0408ed38 β 14002d06 \ 05f92ed7 β ffffd087 | 0a5d22bb β f602dffe> mengubah bit 25 di dump mengubah bit 11, 25-27 di file 0a62f9a9 β e10f5761 | 0acdc6e4 β a25d2c06 / 0aef53d0 β 53ff710f < 0aef5cd0 -> 52ff710f / perubahan di bit 24 di dump mengubah bit 8-11 di file 0bdebd6f β 4c57a410 0d0c7fec β 0064ffff 0d0fe57f β 18402c57 0d8fa4d0 β bfff88ff 0ee882d7 β eafd7f00 1001c5c6 β 6c570042 \ 1008d238 -> 42003e06> perubahan bit 1 di dump mengubah bit 0, 3, 16-19 di file 100ec5cf β 6c570040 / 109ec58f β 6c070050 10e1ebdf β 62ff6008 10ec4cdd β dafd4c07 119f0f8f β 08006d57 11c0feee β 2c5f0500 120ff07e β 20420452 125ef13e β 20f600c8 125fc14e β 60420032 126f02af β 02006d67 1281d09f β 400f3488 1281d19f β 400f3088 12a6d0bb β 40073498 12a6d1bb β 40073098 \ 12aed0bf -> 40073490> ubah ke bit 3 di dump mengubah bit 2 dan 19 di file 12aed1bf -> 40073090 /> perubahan bit 10 di dump mengubah bit 8 di file 12c3f1ea β 20560001 \ 12c9f1ea -> 20560002 / perubahan ke bit 0 dan 1 di dump mengubah bit 17 dan 19 di file ...
Memang, polanya terlihat:
- Perubahan ke bit 0-3 di dump ubah bit 0-3 dan 16-19 di file (topeng
000F000F) - Perubahan ke bit 24-25 di dump ubah bit 8-11 dan 24-27 di file (mask
0F000F00)
Hipotesis menunjukkan dirinya sendiri bahwa setiap 4 bit dalam dump mempengaruhi 4 bit yang sama di setiap 16-bit setengah dari blok 32-bit.
Untuk memeriksanya, mari "potong" 4 bit paling signifikan di setiap setengah blok, dan lihat pasangan apa yang kita dapatkan:
>>> ints=[int.from_bytes(w, 'big') for w in words]
>>> [hex(i) for i in ints][:3]
['0x820ee13f', '0xe2030133', '0xe2030133']
>>> scrambled=[((i & 0xf000f000) >> 12, (i & 0x0f000f00) >> 8, (i & 0x00f000f0) >> 4, (i & 0x000f000f)) for i in ints]
>>> scrambled=[tuple(((i >> 16) << 4) | (i & 15) for i in q) for q in scrambled]
>>> scrambled[:3]
[(142, 33, 3, 239), (224, 33, 3, 51), (224, 33, 3, 51)]
>>> [tuple(hex(i) for i in q) for q in scrambled][:3]
[('0x8e', '0x21', '0x3', '0xef'), ('0xe0', '0x21', '0x3', '0x33'), ('0xe0', '0x21', '0x3', '0x33')]
>>> [b''.join(bytes([i]) for i in q) for q in scrambled][:3]
[b'\x8e!\x03\xef', b'\xe0!\x033', b'\xe0!\x033']
>>> decode=dict(zip((b''.join(bytes([i]) for i in q).hex() for q in scrambled), (r.hex() for r in reference)))
>>> sorted(decode.items())
[('025efd97', 'ffffd087'), ('02a25bdb', 'f602dffe'), ('053eedf0', '50ff710f'), ...]
>>> decode=dict(zip((b''.join(bytes([i]) for i in q[1:]).hex() for q in scrambled), (r.hex()[1:4]+r.hex()[5:8] for r in reference)))
>>> sorted(decode.items())
[('018d90', '0f63ff'), ('020388', '200e06'), ('050309', 'c03000'), ...]
Setelah menukar sub-blok 4-bit di kunci sortir, korespondensi antara pasangan sub-blok menjadi lebih eksplisit:
018d90 β 0f63ff
020388 β 200e06 \
050309 β c03000 \ | xx0xxx0x xx0xxx3x
05030e β c0f000 | |
05036e β c06000 | /
050c16 β c57042 |
050cef β c57040 |
05971e β c88007 > xCxxx0xx x0xxx5xx
0598ef β c07050 |
05bfef β c07010 |
05db59 β c9000f |
05ed0e β cff000 <
060ecc β 264fff |
065ba7 β 205fff |
0bed1f β 2ff008 <|
0bfd15 β 2ff086 |
0cedcd β afdc07 <|
10f2e7 β e06a7e > xxFxxx0x xxExxxDx
118d5a β 9fdfff | \
13032b β 40010a | > xxFxxxFx xx8xxxDx
148d3d β fff6fc | /
16b333 β f00e30 |
16ed15 β fffe06 /
1b63e6 β 52e883
1c98ff β 400b57 \
1d4d97 β aff1b7 | xx00xx57 xx9Fxx8F
1ece0e β c5f500 |
1f98ff β 800d57 /
20032f β 00e400 \
200398 β 007401 |
2007fe β 042452 |
2020ef β 057490 |
206284 β 067463 > x0xxx4xx x2xxx0xx
20891f β 00f488 |
20ab6b β 007498 | \
20abef β 007490 | / xx0xxx9x xxAxxxBx
20ed1d β 0ff404 |
20fb6e β 0064c0 /
21030e β 00f000 \
21032a β 00b008 |
210333 β 000000 |
210349 β 00c008 |
21034b β 003007 |
210359 β 00000f |
210388 β 000006 > x00xx00x x20xx13x
21038b β 00300b |
210398 β 007001 |
2103c6 β 007004 |
2103d2 β 008000 |
2103e1 β 008009 |
2103ef β 007000 /
...
Korespondensi antar sub blok
Daftar di atas menunjukkan kecocokan berikut:
- Untuk topeng
0F000F00:x0xxx0xxdi dump ->x2xxx1xxdi filex0xxx4xxdi dump ->x2xxx0xxdi filexCxxx0xxdi dump ->x0xxx5xxdi file
- Untuk topeng
00F000F0:xx0xxx0xdi dump ->xx0xxx3xdi filexx0xxx5xdi dump ->xx9xxx8xdi filexx0xxx9xdi dump ->xxAxxxBxdi filexxFxxx0xdi dump ->xxExxxDxdi filexxFxxxFxdi dump ->xx8xxxDxdi file
- Untuk topeng
000F000F:xxx0xxx7di dump ->xxxFxxxFdi filexxx7xxx0di dump ->xxxExxxFdi filexxx7xxx1di dump ->xxx9xxx8di file
Kita dapat menyimpulkan bahwa setiap blok 32-bit di dump dibagi menjadi empat nilai delapan-bit, dan nilai-nilai ini diganti menggunakan beberapa tabel pencarian, untuk setiap mask. Isi keempat tabel ini tampaknya relatif acak, tetapi mari kita coba mengekstrak semuanya dari file kita:
>>> ref_ints=[int.from_bytes(w, 'big') for w in reference]
>>> ref_scrambled=[((i & 0xf000f000) >> 12, (i & 0x0f000f00) >> 8, (i & 0x00f000f0) >> 4, (i & 0x000f000f)) for i in ref_ints]
>>> ref_scrambled=[tuple(((i >> 16) << 4) | (i & 15) for i in q) for q in ref_scrambled]
>>> decode=dict(zip((b''.join(bytes([i]) for i in q).hex() for q in scrambled), (b''.join(bytes([i]) for i in q).hex() for q in ref_scrambled)))
>>> sorted(decode.items())
[('025efd97', 'fdf0f8f7'), ('02a25bdb', 'fd6f0f2e'), ('053eedf0', '5701f0ff'), ...]
>>> decode=[dict(zip((bytes([q[byte]]).hex() for q in scrambled), (bytes([q[byte]]).hex() for q in ref_scrambled))) for byte in range(4)]
>>> decode
[{'8e': '88', 'e0': '00', 'cf': '80', 'e1': 'e6', '1f': '20', 'c3': 'e2', ...}, {'03': '00', '5b': '0f', '98': '05', 'ed': 'f0', 'ce': '50', 'd6': '51', ...}, {'21': '00', '9a': 'a0', 'e0': '0a', '5e': 'f0', '5d': 'b2', 'c0': '08', ...}, {'ef': '70', '33': '00', '98': '71', '90': '6f', '01': '08', '0e': 'f0', ...}]
>>> decode=[dict(zip((q[byte] for q in scrambled), (q[byte] for q in ref_scrambled))) for byte in range(4)]
>>> decode
[{142: 136, 224: 0, 207: 128, 225: 230, 31: 32, 195: 226, 62: 244, 200: 235, ...}, {3: 0, 91: 15, 152: 5, 237: 240, 206: 80, 214: 81, 113: 16, 185: 2, 179: 3, ...}, {33: 0, 154: 160, 224: 10, 94: 240, 93: 178, 192: 8, 135: 2, 62: 1, 120: 26, ...}, {239: 112, 51: 0, 152: 113, 144: 111, 1: 8, 14: 240, 249: 21, 110: 96, 241: 47, ...}]
Ketika tabel pencarian sudah siap, kode dekripsinya cukup sederhana:
>>> def _decode(x):
... scrambled = ((x & 0xf000f000) >> 12, (x & 0x0f000f00) >> 8, (x & 0x00f000f0) >> 4, (x & 0x000f000f))
... decoded = tuple(decode[i][((v >> 16) << 4) | (v & 15)] for i, v in enumerate(scrambled))
... unscrambled = tuple(((i >> 4) << 16) | (i & 15) for i in decoded)
... return (unscrambled[0] << 12) | (unscrambled[1] << 8) | (unscrambled[2] << 4) | (unscrambled[3])
...
>>> hex(_decode(0x00beb5ff))
'0x4c07a010'
>>> hex(_decode(0x12aed1bf))
'0x40073090'
Header firmware
Pada awalnya, ada header lima byte sebelum data terenkripsi
5A56001000. Dua byte pertama - tanda tangan 'ZV'- menunjukkan bahwa format LZF sedang digunakan ; selanjutnya menunjukkan metode kompresi ( 0x00- tanpa kompresi) dan panjang ( 0x1000byte).
Pemilik mobil, yang memberi saya file untuk dianalisis, memastikan bahwa data terkompresi LZF juga ditemukan di firmware. Untung saja implementasi LZF bersifat open source dan terbilang sederhana, jadi bersama analisis saya, dia berhasil memuaskan rasa penasarannya tentang isi firmware. Sekarang dia dapat mengubah kode - misalnya, menyalakan mesin secara otomatis ketika suhu turun di bawah tingkat yang telah ditentukan untuk menggunakan mobil di musim dingin yang keras di Rusia.
