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.
One thought on “SaeLog #7: Firmware z detekcją typu EEPROM”