Siemens SGold: взлом через Java

в 16:27, , рубрики: j2me, java, siemens, ненормальное программирование, метки: , ,

Предисловие

В данной статье раскрывается метод получения доступа к адресному пространству процессора любого телефона 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.

Автор: Dimadze

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js