2014-11-19

Первое знакомство с ARM и патчинг бутлоадера

Никогда бы не подумал, что возможно пропатчить ARM'овский бинарь, при этом не написав для этой архитектуры ни одной программки и тем более не прочитав ни одной книжки. Но как оказалось это возможно и для этого достаточно иметь опыт патчинга под x86 платформу.


С чего всё начиналось. Решил как то на досуге собрать для своего смартфона прошивку на основе CyanogenMod 11 (один из форков андройда). Предварительно из стоковой прошивки выдрал все необходимые данные (в том числе и Device Tree Script). Несколько дней помучился с компиляцией проекта и в итоге получил три образа: system.img, recovery.img и boot.img. Образ boot.img включает в себя: линукс-подобное ядро, параметры ядра, образ ramdisk.img и Flattened Device Tree Blob (DTB).

На следующем рисунке изображены стадии загрузки андройда:

Краткая информация о бутлоадерах и образах.

После передачи управления к бутлоадеру происходит проверка нажатых клавиш. Если были нажаты VolumePlus+Power, то бутлоадер будет пытаться загрузить recovery.img. Ну а если была нажата только клавиша Power, то бутлоадер попытается загрузить boot.img.

Так вот после заливки в свое устройство образов system.img и boot.img (образ recovery.img не обязательно заливать) получил вечный режим "fastboot", который используется бутлоадером для сервисного обслуживания устройства (данный режим предусматривает ожидание команд, поступающих через USB соединение).

Если бутлоадер по какой либо причине не сумел запустить boot.img, то он переключается в режим "fastboot". Вот и получилось, что мой кастомный образ boot.img чем то не понравился бутлоадеру. В итоге получил вечный режим "fastboot". Скинул образ бутлоадера (раздел "aboot") на комп и открыл на просмотр. В глаза сразу бросились различные строки, соответствующие логированию различных событий (в том числе и ошибок). Сразу встал вопрос о возможности прочтения этих сообщений. После гугления информации по данному вопросы выяснилось, что бутлоадеры некоторых производителей поддерживают команды, которые возвращают в консоль все отладочные сообщения бутлоадера: "fastboot oem log" или "fastboot oem dmesg". Но в образе моего бутлоадера не было похожих команд. Да и на попытки использования оных команд бутлоадер никак не реагировал.

В образе "aboot" на глаза попалась такая сигнатура: "oem device-info". Эта строка определяет команду, которая возвращает в консоль список внутренних настроек бутлоадера и их значения (в данном бутлоадере настроек всего три). Данная команда мне никак не помогла в получении отладочных сообщений (при патчинге кода я вернусь к этой команде).

Далее нагуглил исходный код бутлоадера, на базе которого производитель моего смартфона смастерил данный бутлоадер (ссылка на исходники: AU_LINUX_ANDROID_LNX.LA.3.7.04.04.04.030.313.tar.gz). Исследование исходников показало, что логирование отладочных сообщений происходит в специальный буфер. Для наглядности приведу часть кода, который определяет буфер, его размер, а так же функцию "log_putc", наполняющую данный буфер символами:

#define LK_LOG_BUF_SIZE    (4096)      /* align on 4k */
#define LK_LOG_COOKIE      0x474f4c52  /* "RLOG" in ASCII */
 
struct lk_log {
         struct lk_log_header {
                 unsigned cookie;
                 unsigned max_size;
                 unsigned size_written;
                 unsigned idx;
         } header;
         char data[LK_LOG_BUF_SIZE];
};
 
static struct lk_log log = {
         .header = {
                 .cookie = LK_LOG_COOKIE,
                 .max_size = sizeof(log.data),
                 .size_written = 0,
                 .idx = 0,
         },
         .data = {0}
};
 
static void log_putc(char c)
{
         log.data[log.header.idx++] = c;
         log.header.size_written++;
         if (unlikely(log.header.idx >= log.header.max_size))
                 log.header.idx = 0;
}
 
void _dputc(char c)
{
         log_putc(c);
         jtag_dputc(c);
} 

Можно сразу заметить, что буфер "RLOG" статический и ограничен в размере (4 КБ).

Мною было решено, что для прочтения буфера "RLOG" поможет только патчинг кода в образе "aboot". Немного помучился с правильной загрузкой образа "aboot" в IDA, но в итоге получилось. И тут передо мною представился впервые ARM кодес в чистом виде. Немного по изучал бутлоадер изнутри, сравнил с имеющимися исходниками, дал наименования некоторым функциям (в соответствии с исходниками). Нашёл функцию boot_linux_from_mmc , которая занимается загрузкой boot.img. Так же нашёл функцию cmd_oem_devinfo , которая занимается исполнением команды "oem device-info".

Далее решил всё же ознакомиться синтаксисом ARM. В этом мне помогла статья Изучение системы команд процессора ARM , которую я бегло прочитал в метро за 30 минут.

Настало время патчинга. Новый код вывода информации из буфера "RLOG" решил встроить в функцию cmd_oem_devinfo , т.к. отказавшись от вывода параметра "Charger screen enabled" можно получить довольно много места для нового кодеса.

Основательно изучил функцию cmd_oem_devinfo и определил план действий.

ROM:0FF10E94                cmd_oem_devinfo                      
ROM:0FF10E94 70 40 2D E9               STMFD     SP!, {R4-R6,LR}
ROM:0FF10E98 08 55 05 E3               MOV       R5, #0x5508
ROM:0FF10E9C 94 60 9F E5               LDR       R6, =aAndroidBoot_0 ; "ANDROID-BOOT!"
ROM:0FF10EA0 40 D0 4D E2               SUB       SP, SP, #0x40
ROM:0FF10EA4 F3 5F 40 E3               MOVT      R5, #0xFF3
ROM:0FF10EA8 00 45 05 E3 F3 4F 40 E3   MOV       R4, #aTrue ; "true"

ROM:0FF10EB0 40 10 A0 E3               MOV       R1, #0x40
ROM:0FF10EB4 14 30 96 E5               LDR       R3, [R6,#(dword_FF4DFC4 - 0xFF4DFB0)]
ROM:0FF10EB8 0D 00 A0 E1               MOV       R0, SP
ROM:0FF10EBC 78 20 9F E5               LDR       R2, =aDeviceTampered ; "\tDevice tampered: %s"
ROM:0FF10EC0 00 00 53 E3               CMP       R3, #0
ROM:0FF10EC4 05 30 A0 01               MOVEQ     R3, R5
ROM:0FF10EC8 04 30 A0 11               MOVNE     R3, R4
ROM:0FF10ECC BD 18 00 EB               BL        snprintf
ROM:0FF10ED0 0D 00 A0 E1               MOV       R0, SP
ROM:0FF10ED4 3A 0C 00 EB               BL        fastboot_info

ROM:0FF10ED8 10 30 96 E5               LDR       R3, [R6,#(dword_FF4DFC0 - 0xFF4DFB0)]
ROM:0FF10EDC 40 10 A0 E3               MOV       R1, #0x40
ROM:0FF10EE0 58 20 9F E5               LDR       R2, =aDeviceUnlocked ; "\tDevice unlocked: %s"
ROM:0FF10EE4 00 00 53 E3               CMP       R3, #0
ROM:0FF10EE8 0D 00 A0 E1               MOV       R0, SP
ROM:0FF10EEC 05 30 A0 01               MOVEQ     R3, R5
ROM:0FF10EF0 04 30 A0 11               MOVNE     R3, R4
ROM:0FF10EF4 B3 18 00 EB               BL        snprintf
ROM:0FF10EF8 0D 00 A0 E1               MOV       R0, SP
ROM:0FF10EFC 30 0C 00 EB               BL        fastboot_info
ROM:0FF10F00 18 30 96 E5               LDR       R3, [R6,#(dword_FF4DFC8 - 0xFF4DFB0)]
; ------------------ patched zone begin ----------
ROM:0FF10F04 40 10 A0 E3               MOV       R1, #0x40
ROM:0FF10F08 34 20 9F E5               LDR       R2, =aChargerScree_0 ; "\tCharger screen enabled: %s" 
ROM:0FF10F0C 00 00 53 E3               CMP       R3, #0
ROM:0FF10F10 0D 00 A0 E1               MOV       R0, SP
ROM:0FF10F14 05 30 A0 01               MOVEQ     R3, R5
ROM:0FF10F18 04 30 A0 11               MOVNE     R3, R4
ROM:0FF10F1C A9 18 00 EB               BL        snprintf
ROM:0FF10F20 0D 00 A0 E1               MOV       R0, SP    
ROM:0FF10F24 26 0C 00 EB               BL        fastboot_info
; ------------------ patched zone end ------------
ROM:0FF10F28 18 00 9F E5               LDR       R0, =0xFF35864  ; empty str
ROM:0FF10F2C A6 0C 00 EB               BL        fastboot_okay
ROM:0FF10F30 40 D0 8D E2               ADD       SP, SP, #0x40
ROM:0FF10F34 70 80 BD E8               LDMFD     SP!, {R4-R6,PC}
ROM:0FF10F34                ; ------------------------------------------
ROM:0FF10F38 B0 DF F4 0F               off_FF10F38     DCD aAndroidBoot_0      ; "ANDROID-BOOT!"
ROM:0FF10F3C 10 55 F3 0F               off_FF10F3C     DCD aDeviceTampered     ; "\tDevice tampered: %s"
ROM:0FF10F40 28 55 F3 0F               off_FF10F40     DCD aDeviceUnlocked     ; "\tDevice unlocked: %s"
ROM:0FF10F44 40 55 F3 0F               off_FF10F44     DCD aChargerScree_0     ; "\tCharger screen enabled: %s"
ROM:0FF10F48 64 58 F3 0F               dword_FF10F48   DCD 0xFF35864           ; "\0" 

Функция fastboot_okay занимается передачей в консоль пустой строки, что свидетельствует об окончании процесса передачи ответа на команду.
Функция fastboot_info занимается передачей в консоль строки, ссылка на которую указывается в регистре R0. Стоит заметить, что функция fastboot_info обрезает длинные строки до 59 символов, т.к. в реализации протокола обмена данными по USB-каналу буфер для ответов имеет размер 60 байт. Так же отмечу, что функция fastboot_info в самом начале вызывает инструкцию "STMFD SP!, {R4,LR}", что свидетельствует о том, что значение регистра R4 сохраняется на стеке и будет восстановлено при окончании выполнения функции. Этот факт нужно учесть, т.к. позволяет использовать регистр R4 для своих целей.

Так вот, в листинге я уже отметил зону кода, которую можно смело патчить. Для начала решил как буду читать буфер "RLOG". Сделал по простому: цикл, в теле которого буду вызывать периодически функцию fastboot_info. Для хранения текущего указателя на данные воспользуемся регистром R4, значение которого будем увеличивать на 59 с каждой итерацией цикла. Признаком выхода из цикла будет служить "\0" в начале строки.

Для начала написал асм-кодес данного цикла, а затем уже перевёл каждую инструкцию в опкоды. Для перевода инструкций в опкоды просто напросто использую поиск соответствующего текста в листинге IDA.

Приведу сразу патченный кусочек кода:
ROM:0FF10F08 34 40 9F E5      LDR     R4, =RLOG_data  ; R4 = RLOG_data
                        :loop
ROM:0FF10F0C 04 00 A0 E1      MOV     R0, R4          ; R0 = R4
ROM:0FF10F10 2B 0C 00 EB      BL      fastboot_info   ; (0x0FF13FC4 - 0x0FF10F10 - 8) / 4 = 0x0C2B
ROM:0FF10F14 00 10 D4 E5      LDRB    R1, [R4]
ROM:0FF10F18 3B 40 84 E2      ADD     R4, R4, #59     ; R4 += 59
ROM:0FF10F1C 00 00 51 E3      CMP     R1, #0
ROM:0FF10F20 F9 FF FF 1A      BNE     loop    ; if (*R4 != 0) goto loop;
ROM:0FF10F24 00 00 A0 E1      NOP
                                            ; RLOG_data = 0x0FF4C6E4
ROM:0FF10F44 E4 C6 F4 0F      off_FF10F44     DCD RLOG_data

Думаю комментарии к новому кодесу излишни, т.к. всё довольно просто.

На всякий случай нужно пропатчить размер буфера "RLOG", что бы гарантировать наличие 59 нулей в конце этого буфера:
ROM:0FF4C6D4 52 4C 4F 47    RLOG_cookie       DCB "RLOG"
ROM:0FF4C6D8 00 10 00 00    RLOG_max_size     DCD 0x1000 ; <=== 0x0F80  
ROM:0FF4C6DC 00 00 00 00    RLOG_size_written DCD 0       
ROM:0FF4C6E0 00 00 00 00    RLOG_idx          DCD 0
ROM:0FF4C6E4 00 00 00 00    RLOG_data         DCD 0 
После патчинга образа "aboot" и заливки его в смартфон, начал проверять работу команды "fastboot oem device-info". В итоге получил полный список отладочных сообщений, среди которых было сообщение: "ERROR: Unable to find suitable device tree for device (221/0x00020000/11/0)". Поиск этого сообщения в исходниках бутлоадера привёл меня к выводу, что проблема возникает на этапе проверки структуры DTB, которая находится внутри образа boot.img. Если быть точным, баг расположен в функции dev_tree_get_entry_info. Сразу напрашивается вопрос: почему стоковый образ boot.img нормально загружается этим бутлоадером? Ответ получил после сравнения DTB из стокового boot.img и моего boot.img. Как оказалось в стоке используется вторая версия структуры DTB, а в моём образе использовалась первая версия. В бутлоадере разные ветки кода отвечают за проверку каждой версии структуры DTB. Пришлось при сборке прошивки указать в параметрах утилиты dtbToolCM дополнительный параметр "--force-v2".

1 комментарий:

  1. Спасибо за статью. А можете подробнее рассказать как правильно загрузить образа "aboot" в IDA?

    ОтветитьУдалить