SaeLog #7: Firmware z detekcją typu EEPROM

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):

saelog7-i2c-addr-tab

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 0x0B2D0x0B47:

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.

Jedno przemyślenie nt. „SaeLog #7: Firmware z detekcją typu EEPROM”

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *