Предисловие
В данной статье раскрывается метод получения доступа к адресному пространству процессора любого телефона 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);
Заключение
Выше я упомянул о перспективе. Готовая реализация одной из её идей уже существует,
и, разумеется, является примером работы с этим классом.
Вы можете найти исходники и готовый мидлет здесь:
Автор: Dimadze