Предисловие
В данной статье раскрывается метод получения доступа к адресному пространству процессора любого телефона Siemens платформы SGold через единственную среду, в которой можно запустить хоть какой-то код, то есть через Java-машину.
Предлагаемый способ был придуман товарищем Chaos (Дмитрием Захаровым), человеком без которого, как говорится, не было возможности модифицировать и отлаживать прошивку на телефонах Siemens c BB-процессором
семейств SGoldLite и SGold-2. А я лишь его расширил и систематизировал.
Что это в перспективе даёт?
Это всё делает возможной модификацию прошивки прямо с телефона без компьютера и Data-кабеля, которого в наше время не найти, да и может иметь место недостаток финансов. Для простого пользователя это расширение аппаратных (разгон процессора, увеличение производительности, разблокировка SDRAM и прочее) и программных
(такое как легендарный исполнитель формата ELF, увеличение хипа Java и т.п.) возможностей, а для программиста — попрактиковаться в написании ARM программ, работать напрямую с «железом», процессором (который стоит в IPhone 2, между прочим), для этого имеются все условия. И естественно, это для интересующихся и любопытных, у которых может и завалялся такой аппарат. Заинтриговало?
Так дайте ему вторую жизнь!
Исходный код
Всю работу с адресным пространством я оформил в отдельный класс ASpace. Далее буду пояснять некоторые его детали.
Инициализация
Ну и перейдём наконец к методу.
Как вы догадались, сие действие происходит через Java-код, ну и, конечно же, ARM-ассемблер. В Java-машине Siemens есть встроенные классы по работе с zip-архивами. Тут-то и прокололись «хвалённые фашистские», ой, то есть немецкие инженеры, недоглядели дырку. Это применимо ко всем существующим прошивкам (а новых, к сожалению или к радости, ждать не приходится).
Собственно по заголовку, давайте рассмотрим код:
//Мнимый буфер для чтения ZIP архива public byte[] aspace_ziparray = {0,0,0,0}; //Главный, расширяемый до 2ГБ, массив public int[] mainarray = {0}; //Ошибки при инициализации public static final int ASPACE_RET_SUCCESS = 1; public static final int ASPACE_RET_UNKNOWNERROR = 0; public static final int ASPACE_RET_NOZIPFILE = -1; public static final int ASPACE_RET_NOZIPENTRY = -2; public static final int ASPACE_RET_SIGNOFIND = -3; //Минимальное смещение для прыжка с 0xA8000000 (SDRAM) до 0xB0000000 (Flash) private static final int ASPACE_OFFSET_SDRAM_TO_FLASH = 0x8000000; //Индекс для указания адреса 0x00000000 (устанавливается при инициализации) public static int mainarray_startindex = 0; /* Сигнатура для опредения начала Flash */ //Некие уникальные данные (сигнатура) по адресу 0xA0000030 private static final int[] aspace_sgold_flash_signature = { 0xA0000000, //0xA0000000 0x5FFFFFFF, //0x5FFFFFFF 0xA0000074, //0xA0000074 0x544B4A43 //CJKT Marker }; /* Метод инициализации (вызов обязателен) */ public int ASpace_Init(String any_zipfile, String any_zipentryname) { try { //Создаём экземпляр класса, передавая конструктору путь до любого архива ZipFile zf = new ZipFile(any_zipfile); if (zf != null) { //Получаем ZipEntry от любого файла в архиве ZipEntry ze = zf.getEntry(any_zipentryname); if (ze != null) { //Получаем поток InputStream zis = zf.getInputStream(ze); //Читаем за пределы массива, попадая на массив следующий за ним, //тем самым расширяя его до 2Гб zis.read(aspace_ziparray, 4, 0x7FFFFFFF); //Закрываем zip - архив zf.close(); //Прыгаем на адрес зеркала Flash //и ищем некоторую последовательность для выявления текущего //расположения индекса массива отностильно адреса 0x0 int i = ASPACE_OFFSET_SDRAM_TO_FLASH/4; for (; i > 0; i--) { int j = 0; for (int k = 0; k < 4; k++) { if (mainarray[i + k] != aspace_sgold_flash_signature[k]) { j = 1; break; } } if (j == 0) { //Нашли адрес начала зеркала Flash mainarray_startindex = i - (aspace_sgold_flash_signature_offset/4); //Смещаем индекс, чтобы соответствовал начальному адресу, т.е. 0x00 mainarray_startindex += ( 0x50000000/4 ); break; } } //Возвращаем, что успешно if (i != 0) return ASPACE_RET_SUCCESS; //Не нашли else return ASPACE_RET_SIGNOFIND; //Ошибка открытия файла внутри архива } else return ASPACE_RET_NOZIPENTRY; //Ошибка открытия файла архива } else return ASPACE_RET_NOZIPFILE; //Неизвестная ошибка } catch (Exception e) { return ASPACE_RET_UNKNOWNERROR; } }
Опишу как выглядит карта адресного пространства Siemens:
0x00000000-0x00003FFF — Внутренняя SRAM #1 (внутри микроконтроллера)
0x00080000-0x00097FFF — Внутренняя SRAM #2 (внутри микроконтроллера)
0x00400000-0x0040FFFF — Внутренний BootROM (внутри микроконтроллера)
0xA0000000-0xA7FFFFFF — Внешняя Flash (бывает 32 МБ, 64 МБ, 96 МБ, 128 МБ)
0xA8000000-0xA9FFFFFF — Внешняя SDRAM (бывает 8 МБ, 16 МБ, 32 МБ)
0xB0000000-0xB7FFFFFF — Зеркало Flash с доступом на запись
0xF0000000-0xFFFFFFFF — Порты ввода/вывода, регистры встроенных устройств в мк
Суть в том, что считывая методом read 0x7FFFFFFF байт за пределами массива aspace_ziparray, мы попадаем в область массива mainarray, тем самым Java увеличивает его длину до 2 Гб (0x7FFFFFFF). Так как всё в пределах правил, то исключения о выходе из-за границ массива не выскакивает. И содержимое этого массива есть адресное пространство,
начиная с физического адреса этого массива в SDRAM.
Естественно, это где-то в Java-хипе.
На x65-ых моделях это в районе 0xA8000000-0xA8300000.
На x75-ых моделях это в районе 0xA8500000-0xA8700000.
Так как массив int, то одна ячейка это 4 байта, т.е. перемещая индекс на 1, мы двигаемся по 4 байта. Наш массив хоть и int, но больше 2 Гб он не видит, поэтому в нашем распоряжении половина SDRAM, зеркало Flash, I/O порты и вся внутрянняя память.
Остальное, так сказать, «за кадром». Далее надо сместить индекс, чтобы он оказался в области зеркала Flash, и вести его к началу, по пути проверяя сигнатуру aspace_sgold_flash_signature, которая свидетельствует, что мы в начале Flash. Как нашли её, прыгаем на начальный адрес, записывая индекс массива mainarray, соответствующий адресу 0x00, в переменную mainarray_startindex.
Всё, инициализация пройдена.
Чтение и запись
С помощью массива mainarray, перемещая его индекс относительно mainarray_startindex, мы можем как считать, так и записать в SRAM/SDRAM/порты ввода-вывода.
Этим занимаются методы:
//Чтение слова в 32 бит по адресу public int ASpace_readword(int address); //Чтение полуслова в 16 бит по адресу public int ASpace_readhwrd(int address); //Чтение байта по адресу public int ASpace_readbyte(int address); //Запись слова в 32 бит по адресу public int ASpace_writeword(int address, int value); //Запись полуслова в 16 бит по адресу public int ASpace_writehwrd(int address, int value); //Запись байта по адресу public int ASpace_writebyte(int address, int value);
Исполнение ARM-кода
Действительно, всё это хорошо, ну а как же исполнять ARM-код?
Есть метод установки исполнителя машинного кода процессора.
В SRAM/SDRAM телефона полно различных указателей (кто не знает, читаем учебники по C/C++) на какие-либо процедуры, которые периодично вызываются, и если загрузить свой код в свободное место (а таких тоже полно) и подменить указатель на свой, то прошивка сама будет его вызывать. Но надо сделать всё грамотно, чтобы система не упала. Например, так выглядит исполнитель ARM-кода для нашего метода:
/* ARM Caller - вызов ARM ф-ии */ //Первичный адрес для внедрения public final int aspace_sgold_armcaller_newpointer = 0x803F8; //Свободный адрес хранения кода ARM Caller'a public final int aspace_sgold_armcaller_bodyaddress = 0x8E010; //Свободный адрес хранения рабочего кода для пуска public final int aspace_sgold_workcode_bodyaddress = 0x8E100; //Индексы для задания параметров private static final int ASPACE_ARMCALLER_STATE = 16; private static final int ASPACE_ARMCALLER_OLDPROC = 17; private static final int ASPACE_ARMCALLER_RUNPROC = 18; private static final int ASPACE_ARMCALLER_PARAM0 = 19; private static final int ASPACE_ARMCALLER_PARAM1 = 20; private static final int ASPACE_ARMCALLER_PARAM2 = 21; private static final int ASPACE_ARMCALLER_PARAM3 = 22; private static final int ASPACE_ARMCALLER_RETURN = 23; //Заменяемый адрес private static int aspace_armcaller_repaddress = 0; //Адрес хранения кода ARM Caller'a private static int aspace_armcaller_bodyaddress = 0; //Независымый от адреса код вызова ARM процедуры private static int[] aspace_armcaller = { 0xE92D401F, // ; STMFD SP!, {R0-R4,LR} 0xE59F0038, // ; LDR R0, =old_proc 0xE12FFF30, // ; BLX R0 0xE59F002C, // ; LDR R0, =state 0xE3500000, // ; CMP R0, #0 0x08BD801F, // ; LDMEQFD SP!, {R0-R4,PC} 0xE59F002C, // ; LDR R0, =param0 0xE59F102C, // ; LDR R1, =param1 0xE59F202C, // ; LDR R2, =param2 0xE59F302C, // ; LDR R3, =param3 0xE59F4018, // ; LDR R4, =run_proc 0xE12FFF34, // ; BLX R4 0xE58F0024, // ; STR R0, [PC,#0x24] ; ret 0xE3A00000, // ; MOV R0, #0 0xE58F0000, // ; STR R0, [PC,#0x00] ; state 0xE8BD801F, // ; LDMFD SP!, {R0-R4,PC} 0x00000000, // ; state 0x00000000, // ; old_proc 0x00000000, // ; run_proc 0x00000000, // ; param0 0x00000000, // ; param1 0x00000000, // ; param2 0x00000000, // ; param3 0x00000000 // ; ret };
Меняя тело программы, мы можем задавать параметры, следить, чтобы при одной команде код вызывался только один раз. После установки, можно запускать ARM-процедуры, задавать до 4-х параметров и получать возвращаемое значение.
Методы, которые обслуживают наш исполнитель:
/* Методы для установки/удаления ARM Caller'a (обязателен для запуска процедур) */ //Установка ARM Caller'a public int ASpace_InstallArmCaller(int replace_address, int body_address); //Удаление ARM Caller'a public int ASpace_DeInstallArmCaller(); /* Метод для пуска процедуры */ public int ASpace_RunArmCode(int address, int arg0, int arg1, int arg2, int arg3);
Дополнительные методы
Ну то что приведено выше, это ядро, основа. Но в классе присутствуют и другие методы.
Они собраны на их основе. Некоторые используют ARM-код:
/* Методы для включения /выключения ARM исключения Data Abort */ public void ASpace_DisableDataAbort(); public void ASpace_EnableDataAbort(); /* Метод для разметки SDRAM по 0xB8000000 (Нужен установленный ARM Caller) */ public void ASpace_SDRAMRemap(); /* Метод для поиска адреса функции по шаблону из байт */ public int ASpace_SearchFunction(int search_address, int search_size, int[] pattern, int offset); /* Метод для поиска адреса функции по шаблону из слов */ public int ASpace_SearchFunctionByWords(int search_address, int search_size, int[] pattern, int offset);
Заключение
Выше я упомянул о перспективе. Готовая реализация одной из её идей уже существует,
и, разумеется, является примером работы с этим классом.
Вы можете найти исходники и готовый мидлет здесь:
Siemens SGold Service Tool.
