. _ . _ -|-------------------------/-//--|- // / _______________ | __ _____ | \_ _/_ \_____·/ \/ \ | / / / /_ / _ / t e a m | -/-----/\________/ \__\___/--/- 5 3 | /____/\____\ --- | . | wPx _ | _ | \ \\ -|--\\-\-------------------------|- Tutorials . Pack . Number . #13 Дизассемблирование и анализ кода ARM процессоров Автор: Sobakator 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_documents/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/sourceforge/gnude/gnude-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 Вызов с передачей через стэк: MOV R0, SP 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. Благодарности: ОГРОМНЫЙ респект HEX'у за материалы, взятые с его сайта http://www.xtin.km.ru/