Cara mendekripsi firmware mobil dalam format yang tidak diketahui



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



  1. Lima byte pertama 5A56001000adalah jenis header yang tidak mempengaruhi konten dump;
  2. Konten selanjutnya dienkripsi dalam blok 4 byte, dan byte yang sama di dump sesuai dengan byte yang sama di file:
    • E2030133 β†’ 00000000
    • 820EE13F β†’ 80078000
    • C20EF13F β†’ 80070000
    • E2091138 β†’ E0076001
    • 1959FAB0 β†’ 2A0600FF
    • EE900081 β†’ 00000A58
    • C9E03ADE β†’ EAFF2000
  3. 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 bit 820EE13F→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 file
    • x0xxx4xxdi dump -> x2xxx0xxdi file
    • xCxxx0xxdi dump -> x0xxx5xxdi file
  • Untuk topeng 00F000F0:
    • xx0xxx0xdi dump -> xx0xxx3xdi file
    • xx0xxx5xdi dump -> xx9xxx8xdi file
    • xx0xxx9xdi dump -> xxAxxxBxdi file
    • xxFxxx0xdi dump -> xxExxxDxdi file
    • xxFxxxFxdi dump -> xx8xxxDxdi file
  • Untuk topeng 000F000F:
    • xxx0xxx7di dump -> xxxFxxxFdi file
    • xxx7xxx0di dump -> xxxExxxFdi file
    • xxx7xxx1di 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.






All Articles