Эта статья написана mamaich'ем, и выложена тут мною по причине недоступности по оригинальному адресу: http://mamaich.fuckru.net/sl45i/sl45_re.htm

Модификация прошивки в телефоне Siemens SL45(i)

Материал этой статьи основывается на информации, добытой мною из интернета, а так же обнаруженой RizaPN, Chaos, и другими авторами патчей на прошивки в форумах GSM-Forum и Клуб любителей Siemens SL45(i).

 

Данный документ все еще находится в процессе написания, так что статья наверняка содержит в себе ошибки, неточности, а так же язык местами кривоват.

Дата последнего изменения: 8 сентября 2003 г.

Как известно, "сердцем" телефона является микросхема Infineon E-GOLD+ PMB6850, в которую входит микропроцессор Infineon C166CBC (согласно SL45 Level 2.5e Repair Documentation). Документацию по этому процессору можно найти на сайте производителя, здесь (пара глав даже переведена на русский), тут и тут. В качестве флеш-памяти в телефоне используются две микросхемы Intel: 28F160C3 (16-Mbit Top Boot Device, код 88C2) и 28F320C3 (32 Mbit Top Boot Device, код 88C4). Документацию по ним можно списать тут. Так же небольшой ассемблер/дизассемблер ADIS16X и много всего интересного можно взять на этой странице. Небольшой недоделанный дизассемблер с исходниками желающие могут списать отсюда.

В качестве компилятора я рекомендую Tasking C166 compiler. Этот компилятор используется внутри Siemens для написания прошивок. Для декомпиляции прошивки используется IDA 4.30 Advanced или более поздняя версия. Она регулярно появляется на FTP сервере сайта http://www.exetools.com/.

Я не буду здесь приводить описание архитектуры процессоров C166, для этого есть документация. Опишу только вкратце структуру памяти телефона. Память выглядит так:

 

 FF0000 - FFFFFF  EEPROM
 C00000 - FEFFFF  Второй блок Flash
 BF0000 - BFFFFF  Первый EEPROM, в SL45i не используется вообще
 A00000 - BEFFFF  Первый блок Flash
 800000 - 9FFFFF  Отображены адреса C00000-DFFFFF
 400000 - 7FFFFF  Отображены адреса C00000-FFFFFF
 100000 - 3FFFFF  Отображены адреса D00000-FFFFFF
 0E0000 - 0FFFFF   Память отсутствует (всё пространство забито нулями)
 080000 - 0DFFFF   RAM (Heap и т.п.)
 050000 - 07FFFF   Отображены адреса C50000-C7FFFF
 018000 - 04FFFF   RAM (Heap и т.п.)
 010800 - 017FFF  Word-writeable память 
 010000 - 0107FF  Внутреннее ПЗУ процессора
 00F000 - 00FFFF  Память регистров (SFR/ESFR) и внутренняя память процессора
 000200 - 00EFFF  RAM, в том числе внутренняя память процессора
 000000 - 0001FF  Таблица прерываний

 

Участок памяти 010800-017FFF является записываемым пословно. То есть запись в него возможна только по чётным адресам, причем по 16 битов за раз. В эту область памяти копируются наиболее часто вызываемые функции прошивки. Такие как memcpy, обработка картинок перед выводом на экран, часть функций, реализующих java и т.п.

В момент включения телефона внутреннее ПЗУ процессора отображено на адреса 000000 -0007FF, и управление получает обработчик нулевого прерывания (адрес 000000). Там находится команда перехода на обработчик сигнала Reset. Обработчик первым делом инициализирует минимум аппаратуры (временно выключает Watchdog timer, устанавливает регистры стека, DPP, SYSCON и переключает внутреннее ПЗУ процессора на адреса 010000-0107FF). После этого идет проверка на подключение data-кабеля, и если кабель найден - идёт загрузка бутблока и передача на него управления. Иначе - производится переход на один из адресов: 07FFFC или 03FFFC, в зависимости от того, какой из них содержит в себе команду JMPS:

sub_42E:
      extp #1Fh, #1
      movb rl0, 0FFFCh 	; 7FFFCh
      cmpb rl0, #0FAh  	; код команды JMPS
      jmpr cc_Z, loc_44C
      extp #0Fh, #1
      movb rl0, 0FFFCh 	; 3FFFCh
      cmpb rl0, #0FAh 	
      jmpr cc_Z, loc_450
      ret
loc_44C:
      jmps 7, 0FFFCh 		; 7FFFCh
; ────────────────────────────────

loc_450:
      jmps 3, 0FFFCh 		; 3FFFCh
; End of function sub_42E

В случае SL45i переход производится на адрес 07FFFC. Там находятся несколько jump-ов, и в итоге настоящей точкой, которая получает управление в фуллфлеше после включения телефона является процедура с адресом C7FADA.

Для загрузки бутблока, код загрузчика "перебирает" различные скорости СОМ-порта, ожидая услышать байт 55h. Получив этот байт, он отвечает посылкой байта A0h сигнализируя о готовности получить бутблок. Затем загрузчик ожидает байт, содержащий в себе размер бутблока, и начинает загружать блок с адреса 00FA00, считая параллельно его контрольную сумму. После бутблока следует байт контрольной суммы, и если этот байт совпадает с вычисленной телефоном, то загрузчик посылает байт A5h, и передает командой JMPS управление по адресу 00FA00. Если контрольные суммы не совпали - загрузчик сообщает об этом посылкой байта 5Ah, и ожидает перепосылки бутблока. Контрольная сумма считается классически - XOR между всеми байтами бутблока.

Приведу код из ПЗУ, отвечающий за загрузку бутблока.

loc_310:
      ....
; Код установки скорости порта и инициализации оборудования пропущен

loc_3B0:
      mov r0, #6			; Сделать 5 попыток получить байт 55h

loc_3B2:
      sub r0, #1			; Это последняя попытка? 
      jb Z, loc_310			; Если да - попробовать другую скорость СОМ-порта
      callr WaitForInput		; Подождать получения байта из COM-порта
      cmpb S0RBUF, #55h			; Получено 55h?
      jnb Z, loc_3B2			; Если нет - попытаться еще раз.

loc_3C2:				
      mov S0TBUF, #0A0h			; Сообщить о готовности принять бутблок

loc_3C6:			
      callr WaitForInput
      movbz r4,	S0RBUF			; Получить длину бутблока в байтах
      mov r0, #0FA00h			; R0 - адрес по которому будет загружен бут
      add r4, r0			; R4 содержит адрес конца бутблока в памяти
      movb rl5,	#0			; RL5 - содержит контрольную сумму

loc_3D4:
      callr WaitForInput		; Получить следующий байт
      movb [r0], S0RBUF			; Записать его в память
      xorb rl5,	[r0+]			; Обновить контрольную сумму
      cmp r0, r4			; Получен весь бут?
      jmpr cc_NZ, loc_3D4		; Если нет - грузить его дальше
      callr WaitForInput		; Если да - получить байт контрольной суммы
      cmpb rl5,	S0RBUF			; Контрольные суммы совпали?
      jmpr cc_Z, loc_3EE		; Если да - идём на loc_3EE
      mov S0TBUF, #5Ah 			; Если нет - шлём 5Ah
      jmpr cc_UC, loc_3C6		; и пытаемся получить бут еще разок
; ───────────────────────────────────────────────────────────────────────────

loc_3EE:				; Бутблок получен удачно,
      mov S0TBUF, #0A5h			; сообщаем об этом посылкой A5h
      bclr S0TIR

loc_3F4:				; Ждём окончания передачи байта 
      jnb S0TIR, loc_3F4		; через COM-порт
      jmps 0, unk_FA00			; И передаем управление полученному коду
; ───────────────────────────────────────────────────────────────────────────

WaitForInput:				; Эта процедура ожидает появления 
      jnb S0RIR, WaitForInput		; очередного байта в COM-порту
      bclr S0RIR
      ret
; End of function WaitForInput

Приведу так же дизасемблированные буты от Unisiemens. 

В первую очередь Unisiemens посылает файл GoBoot.bin, который сперва инициализирует аппаратуру, затем копирует свой код на адреса 00FC00 и занимается подгрузкой следующих бутблоков. Код загрузки - аналогичен предыдущему, только управление на загруженный код передается командой CALLS, а не JMPS.

sub_FA00:				; Код файла GoBoot.bin
		diswdt			; Выключить Watchdog Timer
		mov	DPP3, #3
		nop
		mov	CP, #0FB00h	; Далее идет инициализация аппаратуры
		mov	BUSCON0, #5AFh
		mov	BUSCON1, #5AFh
		mov	ADDRSEL1, #7
		extr	#1
		bset	IRQ74IC.15
		mov	SYSCON,	#1406h
		mov	r1, #0
		mov	DPP0, r1
		mov	DPP1, #3
		; assume dpp1: 3 (page 0xC000)
		mov	DPP3, #3
		mov	r0, [r1]
		mov	r2, r0
		cpl	r0
		movb	[r1], rl0
		movb	[r1+1],	rh0
		mov	[r1+2],	r2
		cmp	r0, [r1]
		mov	word_FF14, r1
		extr	#1
		mov	XADRS5,	#4
		extr	#1
		mov	XADRS6,	#304h
		extr	#1
		mov	XBCON5,	#4AFh
		extr	#1
		mov	XBCON6,	#4AFh
		mov	r3, #7C00h	; Скопировать 5Ch байт начиная с адреса 00FA98 
		mov	r13, #sub_FA98	;  на адреса 00FC00 и дальше
		mov	r14, #7C5Ch	; Напомню что DPP2==3, а значит число 7C00 указывает 
		callr	sub_FA92	;  на адрес FC00
		mov	word_FB40, r14	; Опять непонятная работа с SFR-ами
		bset	P4.1
		bset	DP4.1
		mov	T3, #0C62h
		mov	T3CON, #0C7h 
		mov	word_FB42, ZEROS
		mov	T3IC, #50h 
		bfldh	PSW, #0F8h, #0 
		jmps	0, unk_FC1C	; По адресу FC1C находится код нашей процедуры 
; End of function sub_FA00		;  sub_FAB4 (FC1C-FC00+FA98=FAB4)

sub_FA8C:				; Эта процедура занимается копированием памяти
		mov	r2, [r13+]	; с адресов R13 по R14 на адрес R3 блоками по 
		mov	[r3], r2	; два байта
		add	r3, #2
sub_FA92:	
		cmp	r3, r14
		jmpr	cc_NZ, sub_FA8C
		ret
; End of function sub_FA8C

sub_FA98:				; Это обработчик прерывания T3INT
		scxt	r1, word_FB42	;  он будет установлен в InitBoot.bin
		cmpi1	r1, #14h	; Он и весь код после него скопирован 
		jmpr	cc_C, loc_FAA4	;  на адрес FC00
		bclr	T3R

loc_FAA4:
		movb	word_FB42, rl1
		bmovn	P4.1, P4.1
		mov	T3, #0C62h
		pop	r1
		reti
; End of function sub_FA98

sub_FAB4:				; Эта процедура оказывается скопирована на адрес FC1C
					; Её код - аналогичен коду загрузчика, приведенному
		callr	WaitForInput	; выше
		movbz	r4, S0RBUF
		mov	r0, #0FA00h
		add	r4, r0
		movb	rl5, #0

loc_FAC2:				
		callr	WaitForInput
		movb	[r0], S0RBUF
		xorb	rl5, [r0+]
		cmp	r0, r4
		jmpr	cc_NZ, loc_FAC2
		callr	WaitForInput
		cmpb	rl5, S0RBUF
		jmpr	cc_Z, loc_FADC
		mov	S0TBUF,	#5Ah ; 'Z'
		jmpr	cc_UC, loc_FAB4
; ───────────────────────────────────────────────────────────────────────────

loc_FADC:				; Единственное отличие - переход на 
					; загруженный бутблок производится 
		calls	0, sub_FA00	; командой CALLS, что дает возможность
		mov	S0TBUF,	#0A5h 	; загрузить несколько разных бутблоков
		bclr	S0TIR		

loc_FAE6:				
		jnb	S0TIR, loc_FAE6
		jmpr	cc_UC, loc_FAB4
; End of function sub_FAB4
WaitForInput:				
		jnb	S0RIR, WaitForInput
		bclr	S0RIR
		ret
; End of function WaitForInput

Следующим Unisiemens посылает файл InitBoot.bin. Этот файл занимается инициализацией памяти, установкой обработчика T3INT и тестом двух участков памяти - 000000-00BFFF и 030000-03FFFF. Первый участок содержит в себе таблицу прерываний и туда будет загружен AllBoot.bin, а второй - будет использоваться AllBoot.bin в качестве буфера при получении данных для записи во флеш-память. 

sub_FA00:				; Код файла InitBoot.bin
      mov r1, ZEROZ
      mov CPUCON2, r1
      mov P9, r1
      mov ADDRSEL3, r1
      mov BUSCON3, r1
      mov ADDRSEL4, r1
      mov BUSCON4, r1
      mov r4, ZEROZ
      mov DPP0,	#0			; Здесь начинается тестирование памяти

loc_FA24:				
      callr TestMemoryBlock		; Проверить 16K памяти адресуемой DPP0
      mov DPP1,	#0
      ;	assume dpp1: 0 (page 0x0)
      mov r2, #408Ch			; Записать по адресу 00008C
      mov r3, #0FAh 			; код команды JMPS 0, 0FC00h
      mov [r2],	r3			; то есть установить обработчик прерывания T3INT
      mov r3, #0FC00h	
      mov [r2+2], r3
      bset IEN				; и разрешить прерывания
      cmp r4, #0			; Тест памяти вернул ошибку?
      jmpr cc_NZ, loc_FA6A		; Если да - loc_FA6A
      mov word_FB42, ZEROS		; Если нет - переходим к следующему блоку
      add DPP0,	#1
      cmp DPP0,	#3
      jmpr cc_NZ, loc_FA54
      add DPP0,	#9			; Пропускаем блок 00C000-02FFFF

loc_FA54:				; Тестируем память до адреса 03FFFF
      cmp DPP0,	#10h
      jmpr cc_C, loc_FA24
      mov r0, #unk_FBBE			; Очищаем блок памяти 00FB42-00FBBE

loc_FA5E:
      mov [r0],	ZEROZ
      cmpd2 r0,	#word_FB42
      jmpr cc_NZ, loc_FA5E
      rets				; Выход назад в GoBoot.bin
; ───────────────────────────────────────────────────────────────────────────

loc_FA6A:				; Обработчик ошибки оперативной памяти
      add r4, #10h
      mov r3, r4
      mov r1, #byte_FA8F
      mov r4, #SendRL2

loc_FA78:				; Шлется набор байтов FF FF FF 02 18
      movb rl2,	[r1+]			; для индикации ошибки в памяти
      callr SendRL2
      cmp r1, r4
      jmpr cc_NZ, loc_FA78
      movb rl2,	rl3
      callr SendRL2			; Затем шлется код ошибки
      xorb rl3,	#0E5h 
      movb rl2,	rl3
      callr SendRL2			; И еще раз код XOR E5H

loc_FA8C:				
      jmpr cc_UC, loc_FA8C
; End of function sub_FA00

byte_FA8F:db 0FFh, 0FFh, 0FFh, 2, 18h

SendRL2:				; Процедура посылает байт из RL2
      bclr S0TIR			; через COM-порт
      movb S0TBUF, rl2
loc_FA9A:	
      jnb S0TIR, loc_FA9A
      ret
; End of function SendRL2

TestMemoryBlock:			; Процедура тестирует 16К байтов 
      mov r2, ONES			; Адресуемых через DPP0
      mov r1, #4000h			

loc_FAA8:				; Сперва блок памяти забивается 0FFFFh
      mov [-r1], r2
      cmp r1, #0
      jmpr cc_NZ, loc_FAA8

loc_FAAE:				; Затем проверяется - точно ли он содержит 
      and r2, [r1+]			; байты 0FFFFh?
      cmp r1, #4000h
      jmpr cc_NZ, loc_FAAE
      cmp r2, #0FFFFh
      jmpr cc_NZ, loc_FAEE		; Если нет - ошибка

loc_FABC:				; Затем в каждое слово записывается его адрес
      mov r2, DPP0
      shl r2, #0Eh
      or r2, r1
      cpl r2
      mov [-r1], r2
      mov r3, [r1+2]			; Потом читается
      cmp r2, [r1]			; И сравниваются записанное и прочитанное значения
      jmpr cc_NZ, loc_FAEC		; Не совпало - ошибка
      cmp r1, #0
      jmpr cc_NZ, loc_FABC
      mov r2, ZEROZ
      mov r1, #4000h

loc_FADC:				; Затем память забивается нулями
      mov [-r1], r2
      or r2, [r1]
      cmp r1, #0
      jmpr cc_NZ, loc_FADC
      cmp r2, #0
      jmpr cc_NZ, loc_FAEA		; И проверяется как она забилась
      ret
; ───────────────────────────────────────────────────────────────────────────

loc_FAEA:				
      add r4, #1			; Если не удалось забить память	нулями

loc_FAEC:
      add r4, #1			; Если прочитанное значение не равно записанному

loc_FAEE:
      add r4, #1			; Если не смогли забить	память FFFFками
      ret
; End of function TestMemoryBlock

Как видно из кода память тестируется очень тщательно, чтобы избежать порчи содержимого флеша в случае ошибок.

Код загрузчика LoadBoot.bin крайне примитивен:

sub_FA00:				; Код файла LoadBoot.bin
      movb rl3,	#0A6h 			; Послать 0A6h как сигнал о готовности
      callr SendRL3			; принять большой бут
      callr RecvRL3			; Получить младший байт размера бутблока
      movb rl2,	rl3			; Получить старший байт размера
      callr RecvRL3
      movb rh2,	rl3			; R2 содержит размер бутблока 
      mov r4, #200h			; Загрузить бут начиная с 000200
      movb rl5,	#0			; RL5 содержит в себе контрольную сумму
      mov DPP0,	#0

loc_FA18:
      callr RecvRL3			; Получить следующий байт бутблока
      movb [r4], rl3			; Записать его в память
      xorb rl5,	rl3
      add r4, #1
      sub r2, #1			; Получен весь бут?
      jmpr cc_NZ, loc_FA18
      callr RecvRL3			; Если да - получить его контрольную сумму
      cmpb rl3,	rl5			; и сравнить с посчитанной
      jmpr cc_NZ, loc_FA2E		
      jmps 0, 200h 			; Если суммы совпали - передать управление 
; ───────────────────────────────────────────────────────────────────────────

loc_FA2E:				; Если не совпали -
      movb rl3,	5Ah 			; Послать 5Ah
      callr SendRL3
      srst				; И перезагрузиться

SendRL3:				; Посылает значение регистра RL3
      movbz r3,	rl3			; через COM-порт
      mov S0TBUF, r3
      bclr S0TIR
loc_FA40:		
      jnb S0TIR, loc_FA40
      ret
; End of function SendRL3
RecvRL3:
      jnb S0RIR, RecvRL3
      bclr S0RIR
      movbz r3, S0RBUF
      ret
; End of function RecvRL3

Как видно из кода - этот загрузчик сперва шлет байт A6h, чтобы просигнализировать о своей работе, затем ждёт два байта размера бутблока, далее получает сам бутблок и загружает его с адреса 000200, после чего получает байт контрольной суммы, и если она совпала с посчитанной - передает управление на полученный код командой JMPS 0, 200h.

Из-за большого размера декомпилированный вид файла AllBoot.bin я здесь не привожу. Его IDB-файл можно списать тут. Файл AllBoot.bin содержит в себе много лишнего кода, в частности он содержит в себе поддержку таких микросхем Flash, которые в SL42/SL45/SL45i никогда не использовались. Протокол по работе с ним прекрасно описан в документации к V_Klay, а информацию по программированию микросхем Flash можно найти тут.

 

Приведу пример бутблока, просто говорящего "Hello from mobile!" через COM-порт сразу после сввоей загрузки.

Запускаем Tasking EDE, закрываем в нём все открытые проекты, и делаем "File -> New Project Space". Придумываем имя  project space и указываем ему новый каталог. В появившемся окне "Project Properties" во вкладке "Members" жмем кнопарик "Add new project to project space (ALT-N)". Придумываем имя проекту. Добавляем в него файлы с такими названиями:

cstart.asm     - в нем будет находиться точка входа в проект

test.c            - тело нашего проекта

Архив файлов проекта можно списать тут.

После закрытия окна, обнаруживаем что Tasking создал нам файл start.asm, содержащий сгенерированный стартап-код. Безжалостно убиваем его, так как свой код мы напишем сами.

Вот текст нашего файла cstart.asm:

$CASE				; Это набор директив для компилятора, 
$GENONLY			;  их описание смотрите в документации
$DEBUG
$NOLOCALS
$CHECKCPU16
$CHECKBUS18
$NOMOD166			; disable the internal set of SAB 80C166 SFRs
@IF( ! @DEFINED(__STDNAMES ) )	
	$STDNAMES(reg166.def)	; use non-extended set of SFR's by default
@ELSE
	$STDNAMES(@__STDNAMES)	; use processor specific register definition 
@ENDI

	
EXTERN	_main:FAR		; start label user program.
PUBLIC	__CSTART

;*****************************************************************************
;* __CSTART - вот эта процедура получит управление при старте программы
;*****************************************************************************
__CSTART_MY	SECTION CODE WORD PUBLIC 'CMYINIT'

__CSTART 	PROC  FAR 
	mov 	DPP0,#0		; Устанавливаем регистры DPP как указано в настройках проекта
	mov 	DPP1,#1
	mov 	DPP2,#2
	mov 	DPP3,#3
	mov 	r0,#0FB00h	; Устанавливаем вершину User stack
	call    _main		; Вызываем функцию main() 
	rets 			; и возвращаемся туда, откуда нас вызвали.
__CSTART	ENDP
__CSTART_MY	ENDS

CSTART_RBANK    REGDEF R0-R15
			
	END

Как видно - тело этого файла, это просто вызов функции main(), которая описана в файле test.c. Однако тут есть одна тонкость. Процедуру __CSTART я кладу в секцию CMYINIT, а в настройках Linker/Locator я укажу, чтобы линкер помещал её в самое начало сгенерированного файла. Если этого не сделать - непонятно какая функция будет засунута компилятором в самое начало файла.

Вот текст файла test.c:

#include <reg167.h>

void SEND(int a)	// процедура посылки байта через СОМ-порт
{
	S0TIR=0;	
	S0TBUF=(unsigned char)(a);	
	while(S0TIR==0) ;
}

void print(char *c)	// посылка строки через СОМ
{
	while(*c)
		SEND(*(c++));
}

/****************/
/* main program */
/****************/
void huge main (void)  
{    
	print("Hello from mobile!\r\n");
#pragma asm				
	srst
#pragma endasm	
}

Перед компиляцией необходимо установить настройки проекта и сообщить Linker/Locator что наш загрузчик будет находиться по адресу 00FA00 и иметь в размере максимум 256 байт. Для этого нажимаем в дереве проектов правую кнопку мыши на названии нашего проекта, выбираем Project Options, и в этом окне:

В ветке Application:

    Memory Model    - Paged, так как демо-версия компилятора ограничивает нас моделью Small, а мы можем захотеть адресовать всю память.

    Startup    - снимаем галку "Generate system startup code...", чтобы он нам не мешал.

Ветка "C Compiler":

    Libraries    - снимаем галку "Link standard run-time...", на этот раз мы обойдемся без сишного рантайма, чтобы уменьшить размер получаемого бут-блока.

В ветке Linker/Locator:

    Output Format    - ставим галку Intel HEX record (так как это самый удобный формат для дальнейшей работы с ним), все остальные снимаем.

    Memory    - добавляем участок памяти, в который линкер поместит сгенерированный код:

                        ROM: Start = 0xFA00, End = 0xFAFF

    и снимаем галку "Mark internal RAM area as RAM". Наивный линкер считает, что код может находиться только в ПЗУ, поэтому приходится так извращаться. Если бы мы использовали глобальные переменные, то надо было бы добавить еще одну Memory Area под RAM.

    Memory -> Reserved Dedicated Address    - снимаем галку Reserve memory for default ROM monitor resources, просто на случай если вдруг нам понадобится память 000200-000FFF.

    Classes - добавляем класс:

                    CMYINIT: Start = 0xFA00, End = 0xFA1F, Unique: No

    то есть кладём нашу функцию __CSTART в самое начало блока памяти.

    Interrupt Vector Table    - снимаем галку "Generate vector table"

    Stack and heap    - говорим "32 words (0xFB00-0xFBFF)", чтобы линкер засунул свой стек куда подальше и разрешил нам использовать память 00FA00-00FAFF. Стек мы все-равно используем тот, который нам дал код в ПЗУ.

Остальные ветки не трогаем.

После компиляции проекта получаем HEX-файл, который скармливаем моей программе, переводящей HEX-файл в BIN, начинающийся с заданного адреса в памяти:

    	H386toBinSkip.exe имя_проекта.hex MyBoot.bin 0xFA00

Архив программы H386toBinSkip с исходником лежит тут.

Для теста этого файла воспользуемся программой SendBoot.exe, архив которой лежит здесь.

Запускаем SendBoot.exe и получаем такой ответ:

Waiting for responce...
 00
 A0

Sending boot....................................................................
......................................
Boot sent!
Responce:
Hello from mobile!

То есть видим, что наш бут выполнился. Код программы SendBoot.exe довольно неуклюжий, возможно он не будет работать под Win9x. Я его предоставляю только в образовательных целях.

Для того, чтобы этот бут был полноценным и мог работать дольше нескольких секунд без перезагрузки телефона необходимо в него добавить инициализацию оборудования и установить обработчики прерываний, как это делалось в InitBoot.bin.

Так же приведу декомпилированный бут-блок, использующийся внутри Bfb95eg.dll для включения различных сервисных режимов.

sub_FA00:
      diswdt
      mov SYSCON, #1446h
      extr #2
      bset ALTSELOP3.15
      bclr IRQ75IC.15
      mov ADDRSEL1, #9
      mov BUSCON1, #4BFh
      extr #2
      mov XARDS1, #0EF0h
      mov XBCON1, #4AFh
      einit
      mov DPP0,	#40h 			; Бессмысленная посылка данных по несуществующим адресам
      ;	assume dpp0: 40h (page 0x100000)
      movb rl0,	#0			; зачем это делается - непонятно. Все работает и без этих команд
      movb 200h, rl0 ; 100200h
      movb rl0,	#0FFh
      movb 201h, rl0 ; 100201h
      mov DPP3,	#3
      movb rl0,	#0
      movb byte_F600, rl0
      movb rl0,	#0FFh			; Первый параметр - код функции
      movb byte_F601, rl0
      movb rl0,	#0FFh			; Второй параметр (обычно 80h)
      movb byte_F602, rl0
      movb rl0,	#0FFh			; Третий параметр (0 или 1), в прошивке он игнорируется
      movb byte_F603, rl0
      mov r0, #80h ; 'А'
      or C1MCR14, r0
      srst
; End of function sub_FA00

Перед посылкой этого бут-блока, он модифицируется Bfb95eg.dll в памяти, и на места отмеченные в комментариях "первый-третий параметры" (от начала бутблока они находятся на байтах 46h, 4Eh, 56h) записываются константы:

  2, 80h, 1 - Включить телефон
            5, 80h, 1 - режим Battery Care
            7, 80h, 1 - режим Service Mode
            8, 80h, 1 - режим Burn in test

Вполне возможно, что существуют и другие режимы. 

Как видно - этот бут-блок инициализирует минимум железа, после чего производит запись нескольких байтов по адресам:

 00F600 - записывается 0

 00F601 - записывается код режима

 00F602 - записывается 80h или 81h для какого-то TDMA режима (работают оба значения)

 00F603 - записывается 0 или 1, но прошивка похоже игнорирует это значение

 00EFE0 (в коде он назван C1MCR14) - устанавливается бит 7 для сигнализации прошивке о необходимости входа в один из сервисных режимов.

Затем происходит перезагрузка телефона, и уже код во флеше включает заданный режим.

Тестовую программу по включению одного из перечисленных сервисных режимов можно списать отсюда. Код программы аналогичен коду SendBoot.exe. Программе в качестве параметра передается номер режима, после чего она посылает в телефон загрузчик, включающий этот режим. Пример работы с программой:

C:>ServiceMode.exe 8
Waiting for responce...
 00
 A0

Sending boot....................................................................
......................................
Boot sent!
Responce:

После чего на телефоне появится надпись "Burn In Test", и он громко запищит.

А теперь перед тем, как начать копать саму прошивку, расскажу об ассемблерном коде, генерируемом Tasking C. Именно такой код мы увидим при декомпиляции прошивки. В первую очередь остановлюсь на указателях.

Как известно из документации, C166ой процессор умеет адресовать свои 16-мегабайт памяти двумя способами: постранично - размер страницы 16К, и посегментно - один сегмент равен 64К. Причем код программы может быть адресован только посегментно, а данные - как постранично, так и посегментно. В компиляторе для адресации данных используются far и huge указатели. FAR-указатели - это страничные указатели (то есть имея FAR-указатель мы можем за раз адресовать 16К памяти). HUGE - сегментные, с их помощью можно за раз адресовать до 64К памяти, но они работают медленнее, и в прошивке практически не используются. Есть еще NEAR-указатели, для которых сегмент явно не указывается, а выбирается из значения соответствующего регистра DPP, номер которого задается в старших двух битах указателя. Пример объявления указателей на C:

 int far *FarPtr=(int far*)0x123456;            // страничный указатель

 int huge *HugePtr=(int huge*)0x654321;   // сегментный указатель

 int *NearPtr;                                             // ближние указатели я не советую использовать не зная точного значения регистров DPP.

На ассемблере к страничным указателям производится обращение либо с помощью регистров DPPx, либо через команду EXTP. К сегментным указателям - через команду EXTS. Подробнее смотрите в документации.

Системный стек, поддерживаемый процессором имеет небольшой размер - максимум 512 байт, поэтому не используется компилятором Tasking C для передачи параметров в процедуры и для локальных переменных. Для этого используется так называемый "user stack", который адресуется через регистр R0, и имеет размер до 16К. 

Параметры в функции передаются через регистры R12-R15. Если функция требует больше 4 параметров, то остальные помещаются на стек адресуемый R0 примерно таким же образом, как это делается в 80x86ых процессорах. Возвращаемое функцией значение помещается в регистр R4, если результат занимает 16 битов. Если результат - 32 бита, то старшие 16 битов помещаются в регистр R5. Например, в случае функции с прототипом на C:

 long MyFunc(char far *Str, int Param);

При ее вызове регистр R12 содержит адрес (в пределах 16К-страницы) на который указывает Str , R13 - содержит адрес страницы, на которую указывает Str, R14 - содержит значение Param. Возвращать функция будет в R4 младшие 16 битов результата, в R5 - старшие 16 битов.

 

Для желающих начать копать прошивку не с пустого места, я привожу здесь архив с декомпиляцией памяти запущенного телефона. В данном архиве структура памяти аналогична описанной в начале статьи, за исключением повторяющихся участков памяти. Все повторяющиеся участки памяти выкинуты для уменьшения размера архива, а на повторяющиеся адреса назначены mapping-и через "Processor specific analyzis options -> add mapping". Однако, маппинги назначены не на все адреса, а только на те, которые мне были интересны, так что местами в памяти находятся "дырки". При необходимости их можно "закрыть", назначив маппинги в соответствии с таблицей, приведенной в начале статьи.

В данном варианте декомпиляции сегменты создавались мною самостоятельно, так как при автоматическом создании сегментов, IDA создает сегменты всегда по 64K, а для адресации данных в прошивке используются только 16К-сегменты. Свои сегменты я назвал так: seg000 - сегменты данных, где 000 - это 16-ричный номер страницы (реальный адрес получается путем умножения ее номера на 16384), и cseg00 - сегменты кода, 00 - это старшие 8 битов адреса. При таком названии сегментов легко преобразовывать операнды команд в адреса просто нажав в IDA ALT-R, и выбрав соответствующий сегмент из списка.

 

Если кто хочет сам декомпилировать прошивку - то порядок действий такой:

1. Берем свой фуллфлеш

2. Запускаем IDA, выбираем фуллфлеш и в посвившемся окне ставим:

    Loading segment: 0xA0000    (именно такой, так как в IDA размер сегмента == 16 байт)

    Loading offset: 0

    Change processor -> Siemens C166 (IDA не знает что C166 уже давно не принадлежит Siemens)

    снимаем галку "Create Segments", а то IDA нам такого понасоздаст...

3. Жмём OK.

4. Создаем сегменты вручную, или пользуемся моим IDC-файлом.

После чего переходим на адрес точки входа в фуллфлеш, и начинаем анализировать код. IDA не догадывается где в флеше находится код, а где данные, поэтому придется ей указывать это вручную, либо писать макросы самим. Но я рекомендую не заниматься лишней работой, а воспользоваться моим архивом.

З.Ы. Я чуть позднее приведу декомпилированную прошивку в нормальный вид, и выложу ее снова.

 

 

Потом - приведу адреса в фуллфлеше, примеры поиска обработчиков меню, команд и тп, пример дампера памяти и тп.

 


Статья выложена с разрешения автора.

Обновлено: 29.06.2007
Copyright by BoBa!®