SaeLog #7: Firmware z detekcją typu EEPROM
• tech • 1309 słów • 7 minut czytania
Ta notatka jest częścią serii Saleae Logic Hack. Zapoznaj się z pozostałymi wpisami.
Nawiązując do moich hacków firmware’u analizatora przedstawionych w poprzednim poście z serii SaeLog, zastanawiałem się czy dokonana modyfikacja kodu jest dobra. Oczywiście jest, dla kogoś kto posiada płytkę z pamięcią adresowaną 2-bajtowo. Mimo to czuję jakiś niesmak, bo wielokrotnie wspominałem o dodaniu obsługi większych kostek, to finalnie moje modyfikacje firmware-u ograniczyły się do sztywnej “zamiany” obsługi 1-bajtowo adresowanych EEPROM-ów na 2-bajtowe. Nie mogłem spać po nocach, a po głowie ciągle chodziła mi myśl, aby spróbować dostosować kod do obsługi obu typów pamięci. No i zdecydowałem się na to spojrzeć jeszcze raz.
Detekcja typu podłączonej pamięci
Akurat wykrycie jaka kostka pamięci została podpięta do układu Cypressa jest banalne. Można byłoby próbować połączyć z każdą kostką EEPROM, wskaże dla każdego typu przewidziano inne adresy. Ale sam układ w procesie bootowania dokonuje takiej detekcji, a wyniki udostępnia w rejestrze kontrolno-statusowym magistrali I2C - I2CS
(0xE678
).
Rodzaj pamięci determinują wartości bitów ID0
i ID1
. Ich znaczenie przedstawia się następująco (tabela 13-12 z dokumentacji technicznej):
Skoro urządzenie posiada pamięć EEPROM, to wystarczy ograniczyć się do sprawdzenia wartości tylko jednego z bitów, zależnie od potrzeby stanu dla konkretnej wersji. W mojej ocenie najlepiej skorzystać z ID1
, który będzie sygnalizował obecność 2 bajtowo adresowanej pamięci:
// addr = 0x0 for 8 bit addr eeprom
// addr = 0x1 for 16 bit addr eeprom
BYTE addr = (I2CS & 0x10) >> 4;
Wypada wspomnieć, że dane odnośnie wykrytej pamięci uzupełniane są w czasie bootowania, więc późniejsze ewentualne manipulacje połączeniem z układem pamięci nie będą „wykrywane”. Ale w docelowym urządzeniu nie powinno to mieć miejsca.
Modyfikacja hacka/kodu firmware
Modyfikacja mojego poprzedniego hacka nie wydaje się być trudna. O ile dotyczy to docelowej funkcji wysyłającej dane adresowe dla pamięci EEPROM przez magistrale I2C, to kompozycja pakietu adresowego samego urządzenia to już inna bajka.
Obecnie oryginalny kod wysyłający bajt adesowy urządzenia I2C zajmuje 6 bajtów:
mov DPTR, #0xE679
mov A, #0xA0
movx @DPTR, A
I niestety nijak nie da się go prosto ulepszyć, bo nie ma miejsca tutaj na jakiekolwiek sensowne manipulacje, więc trzeba tradycyjnie skoczyć w inne miejsce i tam coś pokombinować.
Żeby nie powtarzać wiele razy tego podobnego kodu, a będzie on używany kilka razy w funkcji odczytującej jak i zapisującej dane do pamięci EEPROM, to postanowiłem dorzucić prostą funkcję, która ustawia adres fizyczny układu, przyjmując w akumulatorze parametr odzwierciedlający operację (odczyt/zapis), czyli de facto wartość ostatniego bitu w adresie urządzenia I2C.
; ACC = 0x0 for write operation
; ACC = 0x1 for read operation
00000000 code_setaddr:
00000000
00000000 add A, #0xA0
00000002 mov R7, A
00000003 mov DPTR, #0xE678
00000006 movx A, @DPTR
00000007 jnb ACC4, code_set_data
0000000A mov A, R7
0000000B add A, #2
0000000D mov R7, A
0000000E
0000000E code_set_data:
0000000E mov A, R7
0000000F mov DPTR, #0xE679
00000012 movx @DPTR, A
00000013 ret
Powyższy kod zajmuje 20 bajtów, więc korzystając ze znanej już metody optymalizacji oryginalnego kodu, udało mi się wydobyć trochę miejsca, przekierowując jeden z “czerwonych bloków” zlokalizowanych w końcowej części funkcji pod adresem 0x0B2D
- 0x0B47
:
00000B2D ljmp code_A35
Co finalnie pozwoliło odzyskać 23 bajty, gdzie docelowo wrzuciłem powyższą funkcję code_set_addr
.
Samo wykorzystanie funkcji jest proste, w miejscach występowania starego kodu trzeba ustawić wartość w akumulatorze i walnąć call-a, pamiętając, żeby wypełnić całe 6 bajtów nowym kodem.
Modyfikacja głównej części kodu, pętli wysyłającej na magistralę kolejne bajty adresowe dla pamięci, ogranicza się w istocie tylko do dodania nowego warunku, który dla 1-bajtowych EEPROM-ów pominie wysyłanie pierwszej połowki adresu będącej 0
.
Poniżej zmodyfikowany kod dla funkcji ReadEEPROM
, z małymi poprawkami w stosunku do poprzedniej wersji:
00000A1D mov A, #0
00000A1F lcall code_B30 ; code_setaddr
00000A22 nop
[...]
00000A32 jb ACC1, code_A70 ; jmp to do-while start
00000A32 ; instead of to code_A50
[...]
; ---------------------------------------------------------------------------
00000A50 ; orginal code begin
00000A50 code_A50:
00000A50 mov DPTR, #0xE679
00000A53 mov A, RAM_42
00000A55 movx @DPTR, A ; I2DAT = RAM_42
00000A56
00000A56 code_A56:
00000A56 mov DPTR, #0xE678
00000A59 movx A, @DPTR
00000A5A mov R7, A
00000A5B mov A, R7
00000A5C jnb ACC0, code_A56 ; while (I2CS & DONE)
00000A5F mov DPTR, #0xE678
00000A62 movx A, @DPTR
00000A63 mov R7, A
00000A64 mov A, R7
00000A64 ; orginal code end
; ---------------------------------------------------------------------------
00000A65 jnb ACC1, code_A35 ; jmp if !(I2CS & ACK)
00000A65 ; to STOP (red) code
00000A68
00000A68 mov A, RAM_42 ; break if RAM_42 != 0
00000A6A jnz code_A86 ; jmp to code after send addr
00000A6C
00000A6C mov RAM_42, R3 ; RAM_42 = R3 (real addr)
00000A6E sjmp code_A50 ; loop, another iterate
00000A70
00000A70 code_A70: ; do-while start
00000A70 mov DPTR, #0xE678
00000A73 movx A, @DPTR
00000A74 jnb ACC4, code_A7C
00000A77 ; if (I2CS & 0x10):
00000A77 mov R3, RAM_42 ; R3 = RAM_42
00000A79 mov RAM_42, #0 ; RAM_42 = 0
00000A7C
00000A7C code_A7C:
00000A7C sjmp code_A50 ; jmp to start loop
[...]
00000A91 mov A, #1
00000A93 lcall code_B30 ; code_setaddr
00000A96 nop
Kod dla funkcji zapisującej - WriteEEPROM
:
00000D86 mov A, #0
00000D88 lcall code_B30
00000D8B nop
[...]
00000D9B jb ACC1, code_DD9
[...]
; ---------------------------------------------------------------------------
00000DB9 ; orginal code begin
00000DB9 code_DB9:
00000DB9 mov DPTR, #0xE679
00000DBC mov A, RAM_5A
00000DBE movx @DPTR, A
00000DBF
00000DBF code_DBF:
00000DBF mov DPTR, #0xE678
00000DC2 movx A, @DPTR
00000DC3 mov R7, A
00000DC4 mov A, R7
00000DC5 jnb ACC0, code_DBF
00000DC8 mov DPTR, #0xE678
00000DCB movx A, @DPTR
00000DCC mov R7, A
00000DCD mov A, R7
00000DCD ; orginal code end
; ---------------------------------------------------------------------------
00000DCE jnb ACC1, code_D9E
00000DD1 mov A, RAM_5A
00000DD3 jnz code_DEC
00000DD5 mov RAM_5A, R5
00000DD7 sjmp code_DB9
00000DD9
00000DD9 code_DD9:
00000DD9 mov DPTR, #0xE678
00000DDC movx A, @DPTR
00000DDD jnb ACC4, code_DE5
00000DE0
00000DE0 mov R5, RAM_5A
00000DE2 mov RAM_5A, #0
00000DE5
00000DE5 code_DE5:
00000DE5 sjmp code_DB9
Patch/diff w formacie IDA ukazujący wszystkie wymagane zmiany w kodzie i zapobiegający ręcznemu przeklepywaniu kodu dla zainteresowanych ;)
This difference file has been created by IDA
firmware.hex
00000A1D: 90 74
00000A1E: E6 00
00000A1F: 79 12
00000A20: 74 0B
00000A21: A0 30
00000A22: F0 00
00000A34: 1B 3B
00000A65: 20 30
00000A67: 1B CD
00000A68: 90 E5
00000A69: E6 42
00000A6A: 78 70
00000A6B: E0 1A
00000A6C: FF 8B
00000A6D: EF 42
00000A6E: 44 80
00000A6F: 40 E0
00000A70: FF 90
00000A71: 90 E6
00000A72: E6 78
00000A73: 78 E0
00000A74: EF 30
00000A75: F0 E4
00000A76: 90 05
00000A77: E6 AB
00000A78: 78 42
00000A79: E0 75
00000A7A: FF 42
00000A7B: EF 00
00000A7C: 30 80
00000A7D: E6 D2
00000A91: 90 74
00000A92: E6 01
00000A93: 79 12
00000A94: 74 0B
00000A95: A1 30
00000A96: F0 00
00000B2D: 90 02
00000B2E: E6 0A
00000B2F: 78 35
00000B30: E0 24
00000B31: FF A0
00000B32: EF FF
00000B33: 44 90
00000B34: 40 E6
00000B35: FF 78
00000B36: 90 E0
00000B37: E6 30
00000B38: 78 E4
00000B39: EF 04
00000B3A: F0 EF
00000B3B: 90 24
00000B3C: E6 02
00000B3D: 78 FF
00000B3E: E0 EF
00000B3F: FF 90
00000B40: EF E6
00000B41: 20 79
00000B42: E6 F0
00000B43: 03 22
00000D86: 90 74
00000D87: E6 00
00000D88: 79 12
00000D89: 74 0B
00000D8A: A0 30
00000D8B: F0 00
00000D9D: 1B 3B
00000DCE: 20 30
00000DD0: 1B CD
00000DD1: 90 E5
00000DD2: E6 5A
00000DD3: 78 70
00000DD4: E0 17
00000DD5: FF 8D
00000DD6: EF 5A
00000DD7: 44 80
00000DD8: 40 E0
00000DD9: FF 90
00000DDA: 90 E6
00000DDB: E6 78
00000DDC: 78 E0
00000DDD: EF 30
00000DDE: F0 E4
00000DDF: 90 05
00000DE0: E6 AD
00000DE1: 78 5A
00000DE2: E0 75
00000DE3: FF 5A
00000DE4: EF 00
00000DE5: 30 80
00000DE6: E6 D2
Jeszcze nie udało mi się przygotować wersji dla formatu hex-owego. Przydałoby się jakieś narzędzie modyfikujące wersje IntelHex bazując na IDA-owym diffie. Nad czymś podobnym pracuję, powinno się pojawić w mojej strzykawce.
…
Myślę, że tym wpisem wyczerpałem temat hackowania kodu firmware analizatora Logic. Modyfikacje są uzupełnieniem poprzedniej notatki, a to co mi pozostaje to kwestia wstrzykniecia/zaktualizowania firmware-u zawartego w kodzie aplikacji. Będę się starał w krótce poruszyć ten temat.
Komentarze (0)