Автор: Hex
RISC процессоры применяются во многих маленьких девайсах типа PDA, мобильных телефонах, умных кофеварках и т.д. Разновидностей процессоров - море. Но я хотел бы рассказать про ARM, т.к. именно ARM7 процессор используется в моей мобиле.
Начнем с архитектуры ARM. ARM процессоры имеют 18 регистров. 3 из которых можно назвать служебными, т.к. их можно использовать и как любые другие:
PC - указатель на текущую команду. Ничем не отличается от других регистров, в него можно писать обычным mov
LR - Link register, специальный регистр для хранения адреса возврата при вызове процедур. Т.е. в стэк он не сохраняется, а просто ложится в регистр.
SP - типа указатель на стэк, но стэковая передача это последнее, что используется в ARM ассемблере.
У ARM процессоров есть 2 режима ARM и THUMB. ARM - режим, это 32-битный режим, а THUMB - 16 битный. Набор команд в обоих режимах практически одинаковый. Команды THUMB режима имеют длину 2 байта, ARM - 4 байта.
Описание команд thumb и ARM режима можно взять тут:
http://www.atmel.com/dyn/resources/prod_do...nts/doc0673.pdf Особенно итересно то, что многие команды оперируют сразу с несколькими регистрами. Например:
ADD R3, SP, #4
что соответствует:
R3:=SP+4
Или вот например команда сохранения регистров в стэк:
PUSH {R2-R4,R7,LR}
Это не аналог pushad из ассемблера x86. Просто в ARM ассемблере можно вот так запихивать список регистров стэк.
Данные в памяти могут быть как little endian(как у intel), так big endian (как у моторолы). Так что исследуя код стоит определиться с типом данных - это сэкономит вам кучу времени.
Для разработки программ для ARM довольно много компиляторов:
http://heanet.dl.sourceforge.net/sourcefor...ude-arm-win.exe гнушный компилер со всеми вытекающими последстивиями.
http://www.goldroad.co.uk/grARM.html - простенький ARM ассемблер.
http://www.arm.com/support/downloads/index.html - официальный набор инструментов для разроботки под ARM. Тут его не возьмешь - только покупать. Ищите в сети Edonkey.
http://www.iar.com/ - альтернативное IDE для ARM. Предлагают триальный 30-дневный вариант.
Особенности ARM ассемблера генерируемого C++ ARM компиляторами
Естественно при анализе кода различных прошивок сталкиваешься не с кодом написаным на чистом ассемблере, а на коде сгенерированом сишным ARM компилятором, и естественно тут есть чему удивиться человеку привыкнувшему к ассемблеру x86.
Вызовы функций
Конвенций вызова (cdecl, stdcall и т.д) нет! Все функции используют конвенцию похожую на борладновский fastcall. Т.е. сначала регистры, а если их не хватает то параметры передаются через стэк.
Например:
ROM:0001F4E2 MOV R0, SP
ROM:0001F4E4 MOV R2, #6
ROM:0001F4E6 ADD R1, R4, #0
ROM:0001F4E8 BL memcmp
Порядок передачи параметров соответсвует номерам регистров т.е. R0 - первый, R1 - второй, R2 - третий.
То есть для
int memcmp(
const void *buf1,
const void *buf2,
size_t count
);
buf1 = R0
buf2 = R1
count = R2
Значение возвращаемое функцией передается через R0:
ROM:0001F4E2 MOV R0, SP
ROM:0001F4E4 MOV R2, #6
ROM:0001F4E6 ADD R1, R4, #0
ROM:0001F4E8 BL memcmp
ROM:0001F4EC CMP R0, #0
ROM:0001F4EE BNE loc_1F4F4
Вызов с передачей через стэк:
ROM:000BCDEC MOV R2, #0
ROM:000BCDEE STR R2, [SP]
ROM:000BCDF0 MOV R2, #128
ROM:000BCDF2 MOV R3, #128
ROM:000BCDF4 MOV R1, #14
ROM:000BCDF6 MOV R0, #0
ROM:000BCDF8 BL FillBoxColor
То есть R0-R3 содержат координаты, а пятый параметр (цвет) - записывается в стэк.
Количество операндов можно определить только аналитически т.е. приходится анализировать вызов функции и ее пролог. Частично получить информацию о количестве аргументов исходя из того какие регистры в начале функции сохраняются в стэк. Например в THUMB режиме процессор оперирует регистрами R0-R7 и служебными. Т.е. увидев функцию начинающуюся с
ROM:00059ADA getTextBounds
ROM:00059ADA PUSH {R4-R7,LR}
можно предположить что она получает аргументы через r0,r1,r2,r3 и SP. Далее уже по вызову:
ROM:0005924E ADD R0, SP, #0x14
ROM:00059250 ADD R1, SP, #0x6C
ROM:00059252 ADD R2, SP, #0x68
ROM:00059254 ADD R3, SP, #0x64
ROM:00059256 BL getTextBounds
Видим что используются только R0-R3. То есть передается 4 параметра.
Переходы
Переходы aka jumps как обычно бывают условные и безусловные. Сами переходы могут быть как относительные так и регистровыми. Причем регистровые часто используются для переключения между THUMB/ARM режимом. Безусловные короткие переходы реализуются как команда B (branch). А длинные через регистровый переход BX (Branch with exchange). Вызовы функций делаются через BL (Branch with link), т.е. переход с сохранением адреса возврата в LR. Еще можно менять адрес выполнения записывая в регистр PC:
ADD PC, #0x64
Но компиляторы Си так себя обычно не ведут. Запись в PC они используют только в ветвлениях.
Ветвления
Они же switch. Реализуются весьма оригинально:
ROM:0027806E CMP R2, #0x4D ; 'M'
ROM:00278070 BCS loc_27807A
ROM:00278072 ADR R3, word_27807C
ROM:00278074 ADD R3, R3, R2
ROM:00278076 LDRH R3, [R3,R2]
ROM:00278078 ADD PC, R3
ROM:0027807A
ROM:0027807A loc_27807A
ROM:0027807A B loc_278766
ROM:0027807C word_27807C DCW 0xAA, 0xBE, 0xC6, 0x180, 0x186; 0
ROM:0027807C DCW 0x190, 0x1A0, 0x1A8, 0x1DE, 0x1E4; 5
ROM:0027807C DCW 0x1B0, 0x212, 0x276, 0x1FE, 0x294; 10
Сначала идет проверка номера case. Он должен быть меньше 0x4D. Если номер case выше идет переход на default case, т.е. на loc_27807A.
Далее берется адрес таблицы переходов word_27807C. В этой таблице лежат не адреса переходов а смещения! А дальше по индексу case извлекается нужное смещение и прибавляется к PC. То есть для case 0 произойдет переход к адресу
0x278078(текущее значение PC) +0xAA(смещение из таблицы) + 0x4(!!!) = 0x278126.
Приходится прибавлять 4 изза особености ARM процессоров: когда происходит операция с регистром PC - результат будет на 4 больше, как пишут в документации: "to ensure it is word aligned".
Доступ к памяти
В Thumb режиме процессор может обращатся к памяти в пределах +/- 256 байт. Поэтому доступ к памяти происходит не напрямую, а через регистровую загрузку. Т.е. нельзя напрямую обратиться к адресу 0x974170, но можно через регистр. Например:
ROM:00277FF6 LDR R0, =unk_974170
ROM:00277FF8 LDR R0, [R0]
Получили значение по адресу 0x974170. Но и это еще не все! Сам адрес переменной (0x974170) хранится тут же рядом в пределах 256 байт:
ROM:00278044 off_278044 DCD unk_974170
То есть опкод команды LDR на самом деле содержит смещение операнда для команды LDR относительно текущего адреса.
Еще есть хитрая особеность оптимизации. Если какой-то адрес в можно получить относительно другого уже использованного в текущей функции, то его получают через арифметические операции или косвеный доступ. Это означает, что если функция к примеру хочет использовать одну переменную по адресу 0x100000, а другую по адресу 0x100150. То компилятор может сделать доступ как через два отдельных адреса, так и через такой код:
LDR R0, =0x100000
ADD R0, #0xFF
ADD R0, #0x51
LDR R0, [R0]
В x86 такое можно было бы трактовать как обращение к подструктуре в переделах другой структуры. А тут это обычная оптимизация. Зачем это надо? Для того чтобы минимизировать доступ к памяти. Т.е. арифметика работает быстрее загрузки данных. Да и вообще весь код ARM ассемблера изобилует разными регистровыми вычислениями, для того их и сделали аж 16 штук, чтобы как можно реже обращаться к памяти и стэку. Изза этого стэковые переменные встречаются только в очень больших функциях. Работа со стэком ничем не отличается от x86.
Переход между режимами ARM и THUMB:
Переход в THUMB делается через вызов BX, операндом которой является регистр с выставленым в 1 битом состояния.
Переход в ARM делается через вызов BX, операндом которой является регистр с выставленым в 0 битом состояния.
Процессорный модуль IDA довольно примитивен и очень часто в попытке анализа таких переключений, большое количество THUMB кода превращает в ARM и наоборот. Вручную переключить режим кода можно нажав ALT-G и в поле Value ввести 0 для ARM режима, и 1 - для THUMB.
взял это дело из базы знаний по Assembler.
А это немного не по теме- про самсунги...но всёж..
Автор: Hex
Долго не писал и все-такое, но были на то причины. Был весь в исследованиях и теперь наконец могу поделиться инфой.
Мне удалось пересмотреть прошивки всех поколений Samsung (включая CDMA), кроме смартфонов. Во всех телефонах samsung используется ARM-совместимый процессор с набором команд ARM7TDMI. Прошивки строятся на основе трех ОС: RTCX, RTK и Nucleus. И компилировались на разных компиляторах. Я встречал прошивки скомпилированые на ADS (SDT) и IAR.
Поколения самсунгов именуют на форумах абсолютно по-разному, кто-то любит делить просто на "Гуми"/"Сувон"(два города в Корее), кто-то делит на кодовые названия "Sysol", "Agere", "VLSI", "Conexant" и "Древние". Я пришел к выводу что правильнее делить их по процессору телефона.
Процессор - Модели
OM6357(aka Sysol) - E100, E700, E720, E800, E820, S50x, X100, X460, X60x
M46 (aka Conexant) - A100, A110, A200, A300, A400, M100, T208
SkyWorks (aka Conexant) - C100, C108, C110, P510, P518
ONE-C (aka VLSI) - R2XX, Nxxx, Txxx (кроме T208)
Trident (aka Agere) - Dxxx, Qxxx, Sxxx(кроме S50x), Vxxx, C200, E105, E310, E400, E600, E710, E810, X105, X400, X42x, X450 и т.д.
MSMxxxx - все CDMA
Если какую-то модель не туда записал поправьте, только аргументировано.
Соответственно, прошивки поколений очень похожи друг на друга, а иногда просто вообще близнецы с перебитыми строками версий. Например, в x100 есть следы от E100/E700/X600, иначе зачем в прошивке есть код для работы со вторым дисплеем, камерой и IRDA которых у него в жизни не было ? :)
Естественно и OS выбирается одна на все поколение:
OM6357 - RTK OSE
M46 - RTCX
SkyWorks - RTCX
ONE-C - RTK OSE
Trident - Nucleus
MSMxxxx - неизвестно, какая-то ОС от Qualcomm известно только, что собирают их в ADS/SDT.
Если вы собрались разбираться в низком уровне, то конечно же SDK от соответсвующей ОС будет как никогда к стати. Еще очень поможет символьная информация которая встречается в некоторых архивах с прошивками. Иногда попадаются прошивки с файлами lst, sym, map, out, которые содержат информацию о прошивке очень сильно помагающую в исследованиях. В частности такие файлы встречаются практически во всех прошивках c100, s500. Для остальных моделей все более запущено и приходится довольствоваться сигнатурами сделаными с символов какой-нить прошивки из этого же поколения. Так, для M46 я смог найти только одну прошивку с символами, это была прошивка от A110. Но сигнатуры, сделаные с нее, прекрасно ложатся на A200, A300 и т.д.
Интерпретация символьной информации
Формат MAP
Map файлы содержат информацию о модулях которые вошли в прошивку и имеют вид типа:
Base Size Type RO? Name
0 20 CODE RO AAA_vectors from object file obj/isr.o
20 38e8 CODE RO C$$code from object file ../../src/t9latin.o
3908 30 CODE RO C$$code from object file obj/mmi_date.o
3938 5a4 CODE RO C$$code from object file hw_slow.o
3edc 874 CODE RO C$$code from object file rtkgo.o
и т.д.
Base - это смещение в файле прошивки.
Size - длина.
Type тип региона.
RO? - тип доступа к региону.
Name - имя оригинального файла, часть которого вошла в прошивку
Как это интерпритировать? Например так(вторая строка): Начиная со смещения 20 идет блок кода(CODE) длиной 38e8 доступ к блоку Read Only. То, что блок имеет атрибут CODE далеко не означает, что можно эту область ВСЮ сделать кодом. На самом деле это код + данные. Точно также если у блока тип DATA. Это не означает, что нужно ее делать всю данными. Без файла имен/символов эту информацию можно применять разве, что только для определения размера кода прошивки (т.е. чтобы не залезть на графику). Поэтому, рассмотрим формат SYM
Формат SYM
Sym файлы это просто кладезь информации. Имеют вид типа:
Symbol Table
AAA_vectors$$Base 000000
AAA_vectors$$Limit 000020
VectorMap$$Base 1006a3c
VectorMap$$Limit 1006a60
isr$$Base 12774c
isr$$Limit 127bb0
gl_MaskIT 1000078
Rtk_RegionCount 100564c
rtk_WorthItSched 10056a0
Rtk11_Schedule 11f5c8
и т.д.
Здесь немного проще. Соответствие имя - адрес. Но вот с адресами есть некоторые заморочки. Есть набор имен, которые содержат знак доллара, они имеют особый статус.
Символы заканчивающиеся $$Base - начало области виртуального адресного пространства, $$Limit - конец. Т.е. это информация о сегментам. Из этих сегментов можно составить карту памяти и увидеть как раскидываются части бинарника на различные адреса.
Начинать строить карту памяти надо с вот таких символов:
Image$$RO$$Base 000000
Image$$RO$$Limit 1afef4
Image$$RW$$Base 1000000
Image$$RW$$Limit 107dad4
Image$$ZI$$Base 1006a60
Image$$ZI$$Limit 107dad4
RO - Read Only т.е. это адреса где располагается код.
RW - Read/Write т.е. это RAM.
ZI - Zero Initialized, опять RAM которую забивают при включении мобилы нолями.
Т.е. можно смело создавать сегменты по этим адресам. Теперь идем дальше
AAA_vectors$$Base 000000
AAA_vectors$$Limit 000020
C$$code$$Base 000020
C$$code$$Limit 127310
C$$code$$__call_via$$Base 127310
C$$code$$__call_via$$Limit 127320
Example$$Base 127320
Example$$Limit 127324
HAL_boot$$Base 127324
HAL_boot$$Limit 12735c
RtkCode$$Base 12735c
RtkCode$$Limit 127408
SysSupportCode$$Base 127408
SysSupportCode$$Limit 12744c
boot$$Base 12744c
boot$$Limit 127654
clib$$Base 127654
clib$$Limit 12774c
isr$$Base 12774c
isr$$Limit 127bb0
C$$constdata$$Base 127bb0
C$$constdata$$Limit 1afef4
C$$data$$Base 1000000
C$$data$$Limit 1005a38
Stacks$$Base 1005a38
Stacks$$Limit 1006a3c
VectorMap$$Base 1006a3c
VectorMap$$Limit 1006a60
C$$zidata$$Base 1006a60
C$$zidata$$Limit 107dad4
Вот так интересно они друг за другом идут. Если хочется - можно поразделять на сегменты по соответствующим адресам. Но это чисто логическое разделение. Только вот в sym файле эти строки раскиданы черти как. И еще рано или поздно возникает вопрос: "почему размер кода 1afef4, если длина файла прошивки 1b6950, куда девать еще 6a60 байт?". Смотрим опять на начальную карту памяти:
Image$$RW$$Base 1000000
Image$$RW$$Limit 107dad4
Image$$ZI$$Base 1006a60
Image$$ZI$$Limit 107dad4
RAM заканчивается адресом 107dad4, блок 1006a60 - 107dad4 Zero Initialized, а чем же инициализируется блок 1000000-1006a60 размер которого, как раз 6a60? Правильно, вот теми самыми оставшимися байтами. Если проанализировать стартовый код ОС, то в процедуре инициализации RAM вы найдете это самое копирование.
В более новых прошивках можно встретить вот такие надписи:
Load$$IRAM$$Base 639a74
Image$$IRAM$$Base 2010000
Image$$IRAM$$Length 0015a4
Это следует понимать так: данные длиной 15a4 грузятся с файлового смещения 639a74 по адресу 2010000.
Продолжаем анализ символов со знаком доллара:
x$litpool$ - Literal Pool, это куски данных от функций. В конце многих функций лежат указатели, строки, константы - вот x$litpool$ указывает на начало таких констант.
x$litpool_e$ - конец Literal Pool.
$T - чисто для дебагера это адреса где происходит изменение регистра PC. Т.е. по этим адресам есть команды перехода BL/BEQ/B/BX и т.д.
$$ - адреса где происходит смена режима ARM/THUMB.
Есть еще символы C$$code, что это такое определить не смог.
Остальные имена без знака доллара - это имена констант и функций. Их можно смело юзать.
Если в архиве с прошивкой идет и MAP и SYM, то это идеальный вариант т.к. когда задаешь имя взятое из Sym можно проверить лежит ли оно в области кода по данным из Map. Если да то можно смело делать ее кодом не боясь, что будут неправильно определено код/данные.
Формат LST
Это вообще рай для реверсера в этих файлах лежит все сразу. Состоят они из пяти частей:
Image Symbol Table - символы... их смысл пока не понял
Local Symbols - понятно из имени.
Global Symbols - аналог sym файла.
Memory Map of the image - карта памяти! Вся и сразу!
Image component sizes - аналог map файла.
Информация настолько подробная, что даже указан режим процессора для каждой функции.
Формат OUT
Встретил только прошивках на Nucleus. Там могут быть два файла tlink.out и tsymb.out.
tsymb.out - обычный SYM
tlink.out - MAP файл к которому примешана какая-та, большей частью бесполезная, информация линкера.
Теперь таки вооружившись символьной информацией можно грузить прошивку в IDA.
Что делать если символов нету вообще
"Когда зубной щетки нет под рукой..." правильно, берем IDA и эмулирующий дебагер и мозги в руки.
Эмулирующий отладчик для ARM можно взять тут:
http://www.lauterbach.com называется Trace32. IDA - это "must have". Для начала грузим прошивку в IDA на адрес 0. Т.е. всю прошивку грузим на адреса поумолчанию. Дальше смотрим что по адресу 0.
BOOT:00000000 B ResetHandler
BOOT:00000004 B loc_3B4
BOOT:00000008 B loc_410
BOOT:0000000C B loc_42C
BOOT:00000010 B loc_488
и т.д.
Код начинается в любом случае с нулевого адреса. Во всех samsung, и как я догадываюсь в не самсугах тоже, прошивка начинается с векторов прерываний. Это 8 команд B в ARM режиме. Т.е. 8 векторов. Адрес 0 - это вектор нулевого прерывания т.е. перезагрузки или старта прошивки. Это нулевое прерывание просто стартует мобилу, соответственно в обработчик должен вести в загрузчик системы:
BOOT:00000048 ResetHandler ; CODE XREF: BOOT:loc_0j
BOOT:00000048 MRS R0, CPSR
BOOT:0000004C BIC R0, R0, #0x1F
BOOT:00000050 ORR R0, R0, #0x13
BOOT:00000054 ORR R0, R0, #0xC0
BOOT:00000058 MSR CPSR_cxsf, R0
BOOT:0000005C LDR R3, =(InitialHWConfig+1)
BOOT:00000060 MOV LR, PC
BOOT:00000064 BX R3
Если прыжок с адреса 0 идет на несуществующий адрес - это означает что остальной код прошивки мапится на какие-то другие адреса. На какие можно довольно легко определить. К примеру есть вот такое начало:
BOOT:00000000 B 0x4003CE
А кода по адресу 4003CE нет. Смотрим тогда по смещению 3СE. Видим ARM-код. Значит получается что остальная часть прошивки смещена на 0x400000. То есть надо скопировать кусок прошивки с обработчиками перерываний, загрузить их по адресу 0, а дальше грузить прошивку с адреса 400000. Теперь код наместе. Идем дальше. Надо узнать где RAM и область портов ввода вывода. Порты обычно находятся либо в конце (адреса в районе e0000000 и выше) либо в начале памяти (до 0x200000), в зависимости от того куда грузится прошивка. Областей RAM может быть несколько. Первым делом идет инициализация портов:
BOOT:00000588 MOV R1, #1
BOOT:0000058A LDR R0, =0xE0006000
BOOT:0000058C LSL R1, R1, #0x1B
BOOT:0000058E STR R1, [R0]
BOOT:00000590 STR R1, [R0,#0x10]
BOOT:00000592 STR R1, [R0,#0x20]
BOOT:00000594 LDR R1, =loc_20102
BOOT:00000596 LDR R0, =0xE0003040
BOOT:00000598 STR R1, [R0,#4]
BOOT:0000059A LDR R1, =0x20003
BOOT:0000059C STR R1, [R0,#8]
BOOT:0000059E LDR R0, =0xE0003000
BOOT:000005A0 MOV R1, #0xC
BOOT:000005A2 STR R1, [R0,#0x24]
То есть примерно начиная с E0000000 это область портов ввода вывода, размер ее не больше сегмента поэтому можно создать сегмент размером 0x10000. Теперь идем дальше. В любой прошивке есть в RAM область, которая инициализируется нолями и область которая заполняется начальными настройками которые берутся из прошивки. Ищем циклы копирования для этого нам и понадобится дебагер.
Вот и копирование:
BOOT:000000D4 LDR R0, =0x63B018
BOOT:000000D8 LDR R1, =0x1000000
BOOT:000000DC LDR R3, =0x1045B38
BOOT:000000E0 CMP R1, R3
BOOT:000000E4 BEQ loc_F8
BOOT:000000E8
BOOT:000000E8 loc_E8 ; CODE XREF: BOOT:000000F4j
BOOT:000000E8 CMP R1, R3
BOOT:000000EC LDRCC R2, [R0],#4
BOOT:000000F0 STRCC R2, [R1],#4
BOOT:000000F4 BCC loc_E8
Копируется блок из прошивки с адреса 63B018 на 1000000. Длина = 45B38. Это первая область RAM. Теперь ищем вторую, ее инициализация нолями должна быть рядом:
BOOT:000000F8 LDR R1, =0x11ED9E4
BOOT:000000FC MOV R2, #0
BOOT:00000100 CMP R3, R1
BOOT:00000104
BOOT:00000104 loc_104 ; BOOT:00000108j
BOOT:00000104
BOOT:00000104 STRCC R2, [R3],#4
BOOT:00000108 BCC loc_100
Так и есть, забивают область с 1045B38 по 11ED9E4 нолями вот и вторая часть. Если есть какие-то области, значит будет еще инициализация нолями или копированием.
Остальные кусочки памяти можно будет находить только аналитически но основа уже есть.
Дальнейшее исследование зависит от наличия символов/сигнатур. Если есть то, все сводится к поиску нужной функции в списке имен. Что делать если нету? Для начала нужно примерно определить пределы кода и по возможности найти в коде функции. Самый примитивный и действенный способ - искать команду push, с которой начинается 60% кода в прошивке. Код прошивки обычно состоит на 90% из Thumb кода так что надо искать байт B5 (push) и пытаться сделать его кодом. Код прошивки обычно занимает менее 50 % размера, дальше идут графика и языковые ресурсы. Еще могу сказать, что очень часто в конце кода встречаются строки копирайтов, типа "Samsung corp 199x-200x ARM ADS 1.2". Кое какой код проявился, где-то 20% будет загажено самой идой, т.к. она часто не справляется с переходом THUMB/ARM. А дальше надо брать то что плохо лежит, т.е. то что оставили программисты. А что они оставили? Оставили Trace и Assert. А любой trace и assert не обходится без sprintf/printf. Надо ее найти. Найти sprintf/printf легко, нужно просто поискать строку "%s". И найти такую которая явно содержит шаблон сообщения об ошибке. По Xref находим где используется эта строка, это как раз будет sprintf, а следующая после нее будет Trace или Assert. Дальше можно исходя из сообщений об ошибках именовать функции, т.е. пройдясь по xref на функцию Trace/Assert можно найти вывод почти половины ошибок. Дальше именовать функции можно сделав поиск следующих слов:
Bad
Fail
Incorrect
Invalid
Error
Memory
File
Null
No
Critical
Abnormal
и т.д.
Вы найдете еще несколько функций вывода ошибок. Таким образом постепенно обретете информацию не основываясь ни на чем кроме самой прошивки.