SaeLog #2: Komunikacja z pamięcią EEPROM

Jedną z pierwszych rzeczy niezbędnych do rozwiązania problemu, a może tylko do poznania mechanizmu działania komunikacji programu z pamięcią EEPROM, jest analiza funkcji, które za to odpowiadają. Obiekt LogicAnalyzerDevice udostępnia dwie metody do służące do tego celu, są to ReadEeprom i WriteEeprom. Wspominałem już o nich w poprzedniej części, ale dopiero niedawno udało mi się im bliżej przyjrzeć i wyłonić obraz ich działania. Dodatkowo w czasie analizy, pojawiło się kilka ciekawych, związanych z tymi funkcjami aspektów. Mowa tutaj o implementacji buforów używanych przez program oraz szyfrowaniu transmisji z urządzeniem, o którym wcześniej nie miałem pojęcia.

Tytuł mówi o komunikacji z pamięcią EEPROM, co może być trochę mylące, bo nie będę mówił o kwestiach hardware-owych ani low-level-owych, ale o komunikacji z tym związanej, z punktu widzenia aplikacji. Czyli bardziej o samej komunikacji aplikacji z urządzeniem w celach związanych z operacjami na pamięci EEPROM. O samym firmware i hardware w kontekście działania z fizycznym układem pamięci podpiętym pod szynę I2C, pewnie będzie w niedalekiej przyszłości.

Bufory

Analizując kod aplikacji można trafić na kilka ciekawych implementacji buforów, używanych w różnych miejscach, w tym także w funkcjach związanych z zapisem i odczytem pamięci EEPROM. Warto się nad nimi trochę zastanowić i je zbadać, na pewno pomoże to w dalszej analizie kodu.

Wydaje mi się, że bufory te bazują na typowym vector-ze z standardowej biblioteki C++ lub na czymś bardzo podobnym. Przemawiać za tym może ich budowa i implementacja, a także fakt, że gdzieś trafiłem na fragment kodu, który IDA w komentarzach dorzuciła wzmiankę o vectorze.

Jako przykład do analizy może posłużyć kod zaczerpnięty z ReadEeprom. Najistotniejszy fragment zaczyna się pod adresem 511FA0:

00511FA0	lea  eax, [ebp+var_28]		; vector
00511FA3	push eax
00511FA4	call sub_47F060				; vector.ctor()
00511FA9	mov  eax, 5
00511FAE	lea  ecx, [ebp+var_28]
00511FB1	mov  [ebp+var_4], 2
00511FB8	call sub_503190				; vector.reserve/resize(5)
00511FBD	mov  ecx, [ebp+var_18]
00511FC0	sub  ecx, [ebp+var_1C]		; length = end - begin

Przydatny może być również fragment stosu:

00511F10	var_28 = dword ptr -28h
00511F10	var_1C = dword ptr -1Ch
00511F10	var_18 = dword ptr -18h
00511F10	var_14 = dword ptr -14h

Komentarzami oznaczyłem wydedukowane fragmenty i przeznaczenie poszczególnych zmiennych oraz instrukcji. Na początek warto zajrzeć do sub_47F060, który wydaje się być konstruktorem vectora.

0047F060 sub_47F060 proc near
[...]
0047F082	mov  esi, [esp+18h+arg_0]	; esi = &var_28
0047F086	push 4
0047F088	call ??2@YAPAXI@Z			; eax = operator new(4)
0047F08D	xor  ecx, ecx				; ecx = 0
0047F08F	add  esp, 4
0047F092	cmp  eax, ecx				; if eax == nullptr
0047F094	jz   short loc_47F09A		; then loc_47F09A
0047F096	mov  [eax], esi				; *eax = var_28
0047F098	jmp  short loc_47F09C
0047F09A
0047F09A loc_47F09A:
0047F09A	xor  eax, eax				; eax = 0
0047F09C
0047F09C loc_47F09C:
0047F09C	mov  [esi], eax				; *var_28 = eax
0047F09E	mov  [esi+0Ch], ecx			; *var_1C = 0
0047F0A1	mov  [esi+10h], ecx			; *var_18 = 0
0047F0A4	mov  [esi+14h], ecx			; *var_14 = 0
0047F0A7	mov  eax, esi				; eax = esi
[...]
0047F0B6	add  esp, 10h
0047F0B9	retn 4
0047F0B9 sub_47F060 endp

Działanie tego kodu jest proste, opisałem je w komentarzach. Po alokacji 4 bajtów pamięci na wskaźnik do nowego obiektu, który de facto wskazuje sam na siebie, i zapisany zostaje w 4 z 12 bajtów zmiennej var_28. Następnie inicjalizowane są 3 wskaźniki, które są kolejnymi zmiennymi ze stosu znajdującymi się za var_28...

Inicjalizacja tych wskaźników dokładnie odzwierciedla to co robi cześć kodu inicjująca vector i podstawowe byty w implementacji tego typu w wykonaniu Microsoftu. Jest do bardzo podobne do fragmentu kodu z nagłówka vector dołączonego STL-a do VC, gdzie znaleźć można template-ową klasę _Vector_val, zawierającą poniższy fragment:

	_Vector_val() {	// initialize values
		_Myfirst = pointer();
		_Mylast = pointer();
		_Myend = pointer();
	}
 
	pointer _Myfirst;	// pointer to beginning of array
	pointer _Mylast;	// pointer to current end of sequence
	pointer _Myend;		// pointer to end of array

Kod ten jest bardzo analogiczny do tego fragmentu przedstawionego wyżej. Tak się składa, że _Vector_val jest jednym z bazowych elementów implementujących typ kontenera vector. Dodatkowo 12 bajtowy var_28 może być w całości lub częścią obiektu alokatora.

Jako uzupełnienie konstruktora fajnie sprawdzić działanie destruktora. Kompilator bardzo ładnie optymalizuje kod wynikowy, często również rozwija funkcje w miejscu wywołania, gdy uzna to za stosowne. Takie rzeczy mogą trochę utrudniać analizę kodu. Kod destruktora jest rozwijany, dlatego dla przykładu możemy znów posłużyć się fragmentem z ReadEeprom:

005120B1 ; var_buffer dtor begin
005120B1	mov   eax, [ebp+var_buffer.begin]
005120B4	cmp   eax, edi						; edi = 0 -> xor edi, edi at 512072
005120B6	jz    short loc_5120C1				; jmp if var_buffer.begin == nullptr
005120B8	push  eax
005120B9	call  ??3@YAXPAX@Z					; delete(var_buffer.begin)
005120BE	add   esp, 4
005120C1 loc_5120C1:
005120C1	mov   ecx, [ebp+var_buffer.ptr]
005120C4	push  ecx
005120C5	mov   [ebp+var_buffer.begin], edi	; var_buffer.begin = 0
005120C8	mov   [ebp+var_buffer.end], edi		; var_buffer.end   = 0
005120CB	mov   [ebp+var_buffer.max], edi		; var_buffer.max   = 0
005120CE	call  ??3@YAXPAX@Z					; delete(var_buffer.ptr)
005120D3	add   esp, 4
005120D3 ; var_buffer dtor end

Kod standardowy, gdy vector posiada zalakowany bufor na dane, to zostaje on zwolniony operatorem delete, a wszystkie wskaźniki, o których pisałem wyżej zostaną wyzerowane. Na koniec zwolnienie pamięci obiektu i koniec, po vectorze nie ma śladu.

Kolejna funkcja - sub_503190, występująca w parze z konstruktorem odzwierciedla działanie typowej metody vectora służacej do rezerwacji miejsca - reserve. Choć bardziej prawdopodobne wydaję się to, że jest to resize, gdyż późniejsze operacje bezpośrednio przeprowadzane są na buforze, a nie ma nigdzie alokacji i relokacji (przynajmniej w tych miejscach, które mnie interesowały w czasie analizy, pod kątem głównego problemu).

Nie będę tutaj za bardzo przedstawiał poszczególnych fragmentów kodu, ponieważ funkcja ta może nie jest długa i skomplikowana, ale wywołuje masę innych funkcji, więc zajęłoby zbyt dużo miejsca i czasu. Jeśli rzucimy okiem na ich kod to większość sprawdza różne warunki, m. in. długość bufora, alokacji i wykonuje operacje na pamięci. W głównej mierze za pomocą standardowych memmove_s, memset oraz operatorów new i delete. Co uświadamia mnie, że istnieją małe szanse, co do możliwej mojej pomyłki odnośnie przeznaczenia tejże funkcji.

To właśnie gdzieś w drzewie wywołań tych funkcji trafiłem na wspomniany fragment ze stringiem mogącym zdradzać typ obiektu, o którym mówiłem na początku. W funkcji sub_4F6CF0 trafimy na wykorzystanie stringa:

.rdata:00BF856C aVectorTTooLong db 'vector<T> too long',0

na krótko przed rzuceniem wyjątku:

call __CxxThrowException@8 ; _CxxThrowException(x,x)

Moje przewidywania zostały udowodnione. Teraz wystarczy zmienić nazwy poszczególnych funkcji oraz dodać do definicji strukturę w postaci podobnej do:

struct VectorBuf {
	void* ptr;		// obj ptr?
	int unk[2];		// alocator?
	char* begin;	// ptr to to beginning of buffer array
	char* end;		// ptr to end of used buffer array
	char* max;		// ptr to end of allocated buffer array
};

aby IDA ładnie podpowiadała w kodzie o wystąpieniach i użyciach vectora w roli bufora.

Czytanie i zapisywanie

Pora przejść do meritum. Znając działanie używanych buforów, analiza kodu oraz działania odczytu i zapisu do EEPROM powinna być dużo łatwiejsza. W prezentowanych fragmentach kodu będę pomijał wszelkie walidacje i nieistotne części, dorzucając komentarze i zmieniając nazwy niektórym zmiennym/adresom, aby odzwierciedlały ich przeznaczenie, bazując na moich odkryciach...

Prototyp funkcji ReadEeprom po krótkiej analizie można przedstawić następująco:

void __stdcall LogicAnalyzerDevice::ReadEeprom(void* obj, int address, char* buffer, int length);

Tutaj taka mała uwaga, prócz zmiennej buffer przekazywanej w argumencie, który jest zwykłym kawałkiem pamięci, w kodzie używana jest zmienna lokalna, leżąca sobie na stosie, o nazwie var_buffer, która jest poznanym już buforem:

00511F10 var_buffer = VectorBuf ptr -28h

Jest to jedyna istotna zmienna we fragmentach interesującego kodu. A skoro jest to vectorowy bufor, to pierwszy istotny kod odpowiada za jego inicjalizację i alokację 5 bajtów, dokładnie tak jak opisałem to wyżej:

00511FA9	mov   eax, 5
00511FAE	lea   ecx, [ebp+var_buffer]
[...]
00511FB8	call  VectorBuf__Resize				; var_buffer.resize(5)

Najbardziej ciekawy fragment to budowa pakietu danych (żądania) wysyłanego do urządzenia, który wypełnia te 5 zaalokowanych wcześniej bajtów pamięci.

00511FCA	mov   edx, [ebp+var_buffer.begin]
00511FCD	mov   byte ptr [edx], 7				; buffer[0] = 0x07
[...]
00511FE0	mov   ecx, [ebp+var_buffer.begin]
00511FE3	mov   byte ptr [ecx+1], 33h			; buffer[1] = 0x33
[...]
00511FF7	mov   eax, [ebp+var_buffer.begin]
00511FFA	mov   byte ptr [eax+2], 81h			; buffer[2] = 0x81
[...]
0051200E	mov   dl, byte ptr [ebp+address]
00512011	mov   eax, [ebp+var_buffer.begin]
00512014	mov   [eax+3], dl					; buffer[3] = (char)address
[...]
00512027	mov   dl, byte ptr [ebp+length]
0051202A	mov   eax, [ebp+var_buffer.begin]
0051202D	mov   [eax+4], dl					; buffer[4] = (char)length

A później już wypchnięcie tego do urządzenia przez port USB i oczekiwanie na odpowiedź, która ładowana jest do bufora przekazanego w parametrze.

0051204D	mov   ebx, [ebp+var_buffer.end]
00512050	sub   ebx, [ebp+var_buffer.begin]
[...]
0051205D	mov   eax, [ebp+var_buffer.begin]
00512060	mov   edx, [edi]
00512062	mov   edx, [edx+8]
00512065	push  ebx							; length
00512066	push  eax							; buffer
00512067	lea   ecx, [esi+0C0h]
0051206D	push  ecx							; pipeID
0051206E	mov   ecx, edi						; WindowsUsbDevice object ptr
00512070	call  edx							; WindowsUsbDevice::Write
00512072	xor   edi, edi
00512074	mov   [ebp+address], edi
[...]
00512097	movzx edx, byte ptr [ebp+length]
0051209B	mov   ecx, [esi+4]					; WindowsUsbDevice object ptr
0051209E	mov   eax, [ecx]
005120A0	mov   eax, [eax+0Ch]
005120A3	push  edx							; length
005120A4	mov   edx, [ebp+buffer]
005120A7	push  edx							; buffer
005120A8	add   esi, 0C4h
005120AE	push  esi							; pipeID
005120AF	call  eax							; WindowsUsbDevice::Read

Żadnej magii tutaj nie ma, trochę więcej może dziać się w bliźniaczej funkcji służącej do zapisu.

Prototyp WriteEeprom jest następujący:

void __stdcall LogicAnalyzerDevice::WriteEeprom(void* obj, VectorBuf* buffer);

WriteEeprom w roli bufora z danymi do zapisu przyjmuje vector. Podobnie jak przy ReadEeprom, tutaj również istotną dla nas zmienną jest jedynie var_buffer leżący na stosie:

00511C50 var_buffer = VectorBuf ptr -24h

który alokowany jest do rozmiaru zdolnego pomieścić zapisywane dane i 5 bajtowy nagłówek pakietu:

00511D00	mov   eax, [ebp+10h]				; ebp = buffer
00511D03	sub   eax, [ebp+0Ch]				; len = buffer.end - buffer.begin
00511D06	lea   ecx, [esp+44h+var_buffer]
00511D0A	add   eax, 5						; len += 5
00511D0D	call  VectorBuf__Resize				; var_buffer.resize(len)

Budowa pakietu podobna jest jak przy odczycie, tylko z innymi wartościami identyfikującymi żądanie.

00511D21	mov   eax, [esp+44h+var_buffer.begin]
00511D25	mov   byte ptr [eax], 6				; buffer[0] = 0x06
[...]
00511D3A	mov   edx, [esp+44h+var_buffer.begin]
00511D3E	mov   byte ptr [edx+1], 42h			; buffer[1] = 0x42
[...]
00511D54	mov   ecx, [esp+44h+var_buffer.begin]
00511D58	mov   byte ptr [ecx+2], 55h			; buffer[2] = 0x55
[...]
00511D6E	mov   eax, [esp+44h+var_buffer.begin]
00511D72	mov   byte ptr [eax+3], 8			; buffer[3] = 0x08
[...]
00511D7A	mov   ebx, [ebp+10h]				; ebp = buffer
[...]
00511D81	sub   ebx, [ebp+0Ch]				; length = buffer.end - buffer.begin
[...]
00511D8E	mov   edx, [esp+44h+var_buffer.begin]
00511D92	mov   [edx+4], bl					; buffer[4] = (char)length

Tutaj mała uwagą, w parametrze nie jest przekazywany do funkcji ani adres ani offset, mówiący w którym miejscu pamięci EEPROM mają zostać zapisane dane. Za to 3 bajt pakietu wskazuje, że wykonane to ma być za 8 bajtem. Na podstawie tej informacji, mogę wnioskować, że zapisać można jedynie całość dodatkowych danych ulokowanych za identyfikatorami urządzenia w EEPROM. Ciekawa informacja.

Dalej, jak można się spodziewać, następuje dodanie do buforu, zaraz za nagłówkiem, danych jakie zostaną zapisane w EEPROM-ie. Dzieje się to w funkcji sub_512FC0, ale nim to nastąpi dosyć dużo różnej maści instrukcji warunkowych weryfikujących bufory, rozmiary i tym podobne sprawy. Według mnie trochę za dużo i za bardzo pokomplikowane jest to. Podobne rzeczy dzieją się w w/w funkcji. Nie chcę tego tutaj przedstawiać, bo to trochę dużo jest tego kodu, w gruncie nie tak bardzo istotnego, bo ważne jest finalne działanie. Ciekawi mnie jaki był oryginalny kod źródłowy, który wygenerował tyle tych instrukcji... A może to kompilator...

Pomijając te kwestie, finalnie w kodzie sub_512FC0, użyta zostaje funkcja memmove_s, do przeniesienia danych z buffer na koniec var_ buffer, co odwzorowując w pseudokodzie można przedstawić jako:

memmove_s(var_buffer.begin + 5, buffer.end - buffer.begin, buffer.begin, buffer.end - buffer.begin);

Na koniec zapis danych do portu USB, a tym samym do urządzenia, gdzie odpowiedni kod firmware zajmie się resztą.

00511E99	mov   edi, [esp+44h+var_buffer.end]
00511E9D	sub   edi, [esp+44h+var_buffer.begin]
[...]
00511EAB	mov   eax, [esp+44h+var_buffer.begin]
00511EAF	mov   edx, [esi]
00511EB1	mov   edx, [edx+8]
00511EB4	push  edi							; length
00511EB5	push  eax							; buffer
00511EB6	add   ebx, 0C0h
00511EBC	push  ebx							; pipeID
00511EBD	mov   ecx, esi						; WindowsUsbDevice object ptr
00511EBF	call  edx							; WindowsUsbDevice::Write

Bazując na informacjach, jakie uzyskałem analizując działanie opisanych wyżej funkcji, całość można skrócić do prostego zapisu formatu danych pakietu (żądania), jaki wysyłany jest do urządzenia w celu operowania na pamięci EEPROM.

 read: 07 33 81 ADR LEN
write: 06 42 55 ADR LEN [data]

Pola ADR i LEN to 1 bajtowe wartości określające adres, bądź offset względem początku pamięci oraz długość danych na jakich będzie operować dana instrukcja. Przy odczycie długość określa ilość danych do odczytania, a przy zapisie długość danych zawartych w pakiecie, jakie należy zapisać w pamięci.

Szyfrowanie

Przesyłane dane są szyfrowane. Może to za mocne słowo, lepiej tutaj pasuje "zaciemnianie". Jeśli porównamy zawartość bufora wychodzącą z wyżej opisanych funkcji, z danymi jakie udało się uzyskać z podsłuchania transmisji (chociażby ze wzmianki zawartej w SaeLog #1) to bez trudu dostrzec można, że dane te są różne. Dane wchodzące i wychodzące z funkcji Read i Write w WindowsUsbDevice są inne niż te wysyłane i odbierane na porcie USB.

Początkowo sam się zdziwiłem, gdy to odkryłem. Dopiero wraz z debugerem udało mi się zauważyć, że to właśnie w implementacji WindowsUsbDevice przed wysłaniem i po odebraniu, dane trafiają najpierw do odpowiednich funkcji. Są nimi sub_4FDBA0 i sub_4FDBE0, którym można przypisać bardziej adekwatne nazwy, jak EncryptData i DecryptData. Parametry do nich przekazywane są poprzez rejestry esi i edi, kolejno jako bufor i jego długość. A dane przekształcane są bezpośrednio w buforze źródłowym.

Algorytm szyfrowania danych przy zapisie do portu, zaszyty w kodzie sub_4FDBA0 przedstawia się następująco:

004FDBA0 sub_4FDBA0 proc near
004FDBA0
004FDBA0 var_1 = byte ptr -1
004FDBA0
004FDBA0	push ecx
004FDBA1	xor  ecx, ecx
004FDBA3	push ebx
004FDBA4	mov  [esp+8+var_1], 9Bh
004FDBA9	mov  bl, 54h
004FDBAB	test edi, edi
004FDBAD	jbe  short loc_4FDBDA
004FDBAF	nop
004FDBB0
004FDBB0 loc_4FDBB0:
004FDBB0	mov  dl, [ecx+esi]
004FDBB3	mov  al, dl
004FDBB5	xor  al, bl
004FDBB7	xor  al, 2Bh
004FDBB9	sub  al, 5
004FDBBB	xor  al, 35h
004FDBBD	sub  al, 39h
004FDBBF	xor  al, [esp+8+var_1]
004FDBC3	inc  ecx
004FDBC4	xor  al, 5Ah
004FDBC6	add  al, 50h
004FDBC8	xor  al, 38h
004FDBCA	sub  al, 45h
004FDBCC	mov  [ecx+esi-1], al
004FDBD0	mov  bl, al
004FDBD2	mov  [esp+8+var_1], dl
004FDBD6	cmp  ecx, edi
004FDBD8	jb   short loc_4FDBB0
004FDBDA
004FDBDA loc_4FDBDA:
004FDBDA	pop  ebx
004FDBDB	pop  ecx
004FDBDC	retn
004FDBDC sub_4FDBA0 endp

Odpowiednik tego kodu zapisany w języku C++ można przedstawić tak:

void EncryptData(char* buffer /*esi*/, size_t len /*edi*/) {
 
	size_t count = 0;
	char p = 0x9B;
	char h = 0x54;
 
	if (len == 0)
		return;
 
	do {
 
		char x;
		char c = buffer[count];
 
		x = (((c ^ h ^ 0x2B) - 0x05) ^ 0x35) - 0x39;
		x = (((x ^ p ^ 0x5A) + 0x50) ^ 0x38) - 0x45;
 
		buffer[count] = x;
 
		h = x;
		p = c;
		count++;
 
	} while (count < len);
 
}

Odwrotność tych operacji można znaleźć w kodzie sub_4FDBE0 służącym do deszyfracji przychodzących danych z urządzenia.

Oczywiście w urządzeniu zaimplementowano ten sam algorytm, w postaci odpowiedników tych funkcji. Dla przykładu kod deszyfrujący z firmware, znajdujący się w funkcji code_10F5:

000010F5 code_10F5:
000010F5	mov   RAM_2F, R3
000010F7	mov   RAM_30, R2
000010F9	mov   RAM_31, R1
000010FB	mov   RAM_32, R5
000010FD	mov   RAM_34, #0x9B
00001100	mov   RAM_35, #0x54
00001103	mov   RAM_37, #0
00001106
00001106 code_1106:
00001106	mov   A, RAM_37
00001108	clr   C
00001109	subb  A, RAM_32
0000110B	jnc   code_116D
0000110D	mov   R3, RAM_2F
0000110F	mov   R2, RAM_30
00001111	mov   R1, RAM_31
00001113	mov   R7, RAM_37
00001115	mov   DPL0, R7			; DPTR0 Low Byte
00001117	mov   DPH0, #0			; DPTR0 High Byte
0000111A	lcall code_FD1
0000111D	mov   R7, A
0000111E	mov   RAM_36, R7
00001120	mov   RAM_38, RAM_36
00001123	mov   A, #0x45
00001125	add   A, RAM_38
00001127	mov   RAM_38, A
00001129	xrl   RAM_38, #0x38
0000112C	mov   A, #0xB0
0000112E	add   A, RAM_38
00001130	mov   RAM_38, A
00001132	xrl   RAM_38, #0x5A
00001135	mov   A, RAM_34
00001137	xrl   RAM_38, A
00001139	mov   A, #0x39
0000113B	add   A, RAM_38
0000113D	mov   RAM_38, A
0000113F	xrl   RAM_38, #0x35
00001142	mov   A, #5
00001144	add   A, RAM_38
00001146	mov   RAM_38, A
00001148	xrl   RAM_38, #0x2B
0000114B	mov   A, RAM_38
0000114D	xrl   A, RAM_35
0000114F	mov   RAM_33, A
00001151	mov   R3, RAM_2F
00001153	mov   R2, RAM_30
00001155	mov   R1, RAM_31
00001157	mov   R7, RAM_37
00001159	mov   DPL0, R7			; DPTR0 Low Byte
0000115B	mov   DPH0, #0			; DPTR0 High Byte
0000115E	mov   A, RAM_33
00001160	lcall code_1010
00001163	mov   RAM_35, RAM_36
00001166	mov   RAM_34, RAM_33
00001169	inc   RAM_37
0000116B	sjmp  code_1106
0000116D
0000116D code_116D:
0000116D	ret
0000116D ; End of function code_10F5

Co podobnie, w zapisie bardziej ludzkim, dokładnie odpowiada operacji odwrotnej do szyfrowania:

void DecryptData(char* buffer /*esi*/, size_t len /*edi*/) {
 
	size_t count = 0;
	char p = 0x9B;
	char h = 0x54;
 
	if (len) {
		do {
 
			char x;
			char c = buffer[count];
 
			x = (((c + 0x45) ^ 0x38) - 0x50) ^ 0x5A ^ p;
			x = (((x + 0x39) ^ 0x35) + 0x5)  ^ 0x2B ^ h;
 
			buffer[count] = x;
 
			h = c;
			p = x;
			count++;
 
 
			printf("%x ", x);
		} while (count < len);
	}
}

O firmware, sposobach jego wydobycia i analizy pewnie napiszę w którymś z następnych wpisów.

Znając już sposób szyfrowania, bez problemu możemy potwierdzić, że działa to tak jak należy. Jeśli weźmiemy dane wysyłane do urządzenia przy pobieraniu identyfikatora (funkcja GetIdFromDevice) i przepuścimy przez te obliczenia, to w wyniku otrzymamy dokładnie to co wyleci z USB:

input:  07 33 81 08 08
output: DF C6 B3 51 60

Na podstawie załączonych kodów wyraźnie widać, że z prawdziwym szyfrowaniem nie ma to wiele wspólnego, ot proste operacje matematyczne i logiczne.

...

Znając już techniczne niuanse związane z komunikacją programu z urządzeniem, jeśli chodzi o pamięć EEPROM, to można spróbować stworzyć mapę pamięci odzwierciedlającą jej zawartość i znaczenie poszczególnych danych w niej ulokowanych. Może to być ciekawe, choć na razie nie jest mi to potrzebne.

Teraz można przejść do zabaw z firmware, skoro mamy pojęcie jak to działa w aplikacji i wiemy, że to właśnie tam mieści się cała magia. Na tym pewnie będę się koncentrował w najbliższym czasie.

2 przemyślenia nt. „SaeLog #2: Komunikacja z pamięcią EEPROM”

Dodaj komentarz

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