Возможно, вы уже заметили, что процессоры ARM используются повсюду — в телефонах, маршрутизаторах, смарт-телевизорах и, конечно же, в устройствах Интернета вещей. Фактически, ARM стала одной из самых распространённых архитектур процессоров в мире. И, как и традиционные ПК, устройства Интернета вещей на базе ARM уязвимы к классическим методам эксплуатации уязвимостей, таким как переполнение буфера.
Если вас интересует разработка эксплойтов или обратная разработка вредоносных программ, изучение ассемблера — это базовый навык. Понимание того, как код взаимодействует с памятью на самом низком уровне, необходимо для выявления уязвимостей и создания эффективных эксплойтов.
Учитывая широкое распространение устройств ARM и их часто игнорируемые уязвимости безопасности, число атак на них, вероятно, увеличится. И как кибервоину, вам необходимо понимать, как эти системы работают на уровне сборки, чтобы эффективно анализировать, защищать их и эксплуатировать.
В предыдущих статьях мы изучили архитектуру процессора ARM и приступили к сборке 64-битного ARM с использованием набора инструкций AArch64 — современного стандарта 64-битной ОС Raspberry Pi.
Теперь, чтобы лучше понять основы ARM, мы переключим внимание на 32-битную сборку ARM (ARMv7-A).
В данном случае мы будем изучать 32-битную сборку ARM.
Умная IP-камера на процессорах ARM Cortex-A35 (Amlogic C305X и C308X)
В отличие от современных настольных компьютеров или облачных систем, многие 32-битные устройства ARM, представленные на рынке, не имеют защиты, по умолчанию работают с правами root и не имеют регулярных обновлений или циклов исправлений. Чтобы воспользоваться их уязвимостями, необходимо понимать их набор инструкций, соглашения о вызовах, структуру памяти и двоичный интерфейс — всё это требует 32-битной сборки ARM.
Многие образцы вредоносного ПО, находящиеся в открытом доступе (Mirai, Mozi), скомпилированы для 32-битной архитектуры ARM. Анализируя реальные угрозы, вы будете дизассемблировать 32-битные исполняемые файлы ARM, а не только 64-битные.
Кроме того, 32-битный ARM проще в освоении и отлично подходит для создания прочной основы. Людей, хорошо знающих ассемблер ARM, немного, поэтому его изучение может помочь улучшить карьерные возможности и оставаться в авангарде кибербезопасности.
32-разрядные процессоры ARM имеют 16 регистров общего назначения. Вот эти регистры:
R0–R3: Регистры аргументов (используются для параметров функций и возвращаемых значений)
R4–R11: Регистры общего назначения для локальных переменных и временного хранения
R12: Регистр временных интервалов внутрипроцедурных вызовов (IP)
R13 : Указатель стека (SP) — указывает на вершину стека
R14: Регистр ссылок (LR) — хранит адреса возврата для вызовов функций
R15: Счетчик программ (PC) — указывает на текущую выполняемую инструкцию
Также имеется специальный регистр — xPSR: расширенный регистр состояния программы (объединяет флаги условий, текущий номер исключения и состояние выполнения).
Флаг Z (ноль): указывает, равен ли результат операции нулю.
Флаг C (перенос): указывает на перенос или заём в арифметических операциях.
Флаг V (переполнение): указывает на переполнение в знаковой арифметике.
Эти флаги позволяют условно выполнять инструкции на основе результатов предыдущих операций и обновляются многими арифметическими и логическими инструкциями с суффиксом «S», например, ADDS.
<операция>{<условие>}{S} <назначение>, <операнд1>, <операнд2>
Вот некоторые из самых популярных инструкций:
MOV – переместить копирует значение из исходного файла в целевой. Например:
MOV R0, R1 ; Copy R1 to R0
; означает комментарий в Ассамблее.
LDR – load копирует значение из памяти в регистр. Например:
LDR R0, [R1] ; Load the value from the address in R1 into R0
STR – store копирует значение из регистра в память. Например:
STR R0, [R1] ; Store the value in R0 to the address in R1
CMP – сравнивает и вычитает второй операнд из первого, а также устанавливает флаги условий. Например:
CMP R0, R1 ; Compare R0 with R1 (sets flags, but doesn't store result)
B – переход по метке осуществляется при выполнении условия. Например:
BEQ label ; Branch to 'label' if equal (Z flag is set)
И – побитовое И выполняет логическую операцию И над каждым битом операндов. Например:
AND R0, R1, R2 ; R0 = R1 AND R2
ORR – побитовое ИЛИ выполняет логическое ИЛИ над каждым битом операндов. Например:
ORR R0, R1, R2 ; R0 = R1 OR R2
EOR (исключающее ИЛИ) выполняет логическую операцию XOR над каждым битом операндов. Например:
EOR R0, R1, R2 ; R0 = R1 XOR R2
BIC – сброс бита выполняет операцию R1 И (НЕ R2). Например:
BIC R0, R1, R2 ; R0 = R1 AND NOT R2
PUSH – сохраняет регистры в стеке. Например:
PUSH {R0, R1} ; Push R0 and R1 onto the stack
POP – восстанавливает регистры из стека. Например:
POP {R0, R1} ; Pop values into R0 and R1 from the stack
BL – переход по ссылке переходит к подпрограмме и сохраняет адрес возврата в LR. Например:
BL myFunction ; Call subroutine 'myFunction'
BX – переход по адресу и переключение набора инструкций при необходимости. Например:
BX LR ; Return from subroutine (branch to address in LR)
SVC – вызов супервизора запускает программное прерывание для перехода в режим супервизора. Например:
SVC #0 ; Call operating system service
ADR – загрузка адреса метки в регистр. Например:
ADR R0, label ; Load the address of 'label' into R0
Если вас интересует разработка эксплойтов или обратная разработка вредоносных программ, изучение ассемблера — это базовый навык. Понимание того, как код взаимодействует с памятью на самом низком уровне, необходимо для выявления уязвимостей и создания эффективных эксплойтов.
Учитывая широкое распространение устройств ARM и их часто игнорируемые уязвимости безопасности, число атак на них, вероятно, увеличится. И как кибервоину, вам необходимо понимать, как эти системы работают на уровне сборки, чтобы эффективно анализировать, защищать их и эксплуатировать.
В предыдущих статьях мы изучили архитектуру процессора ARM и приступили к сборке 64-битного ARM с использованием набора инструкций AArch64 — современного стандарта 64-битной ОС Raspberry Pi.
Теперь, чтобы лучше понять основы ARM, мы переключим внимание на 32-битную сборку ARM (ARMv7-A).
Что такое язык ассемблера?
Ассемблер (ASM или asm) — это любой низкоуровневый язык программирования с очень строгим соответствием между инструкциями языка и инструкциями машинного кода архитектуры. В ассемблере обычно на каждую машинную инструкцию приходится один оператор (1:1). Поскольку ASM зависит от инструкций машинного кода, каждый язык ассемблера специфичен для конкретной архитектуры компьютера.В данном случае мы будем изучать 32-битную сборку ARM.
Зачем изучать 32-битный ассемблер ARM?
От промышленных контроллеров и медицинского оборудования до потребительской электроники, большинство встраиваемых и IoT-устройств по-прежнему работают на 32-битных процессорах ARM. Эти системы не модернизируются, как настольные компьютеры, — у них длительный жизненный цикл, ограниченный объём памяти и они используют более компактные и простые наборы инструкций, такие как ARMv7. Например, маршрутизаторы, IP-камеры и концентраторы для умного дома обычно работают на однокристальных системах (SoC) на базе ARMv7, часто с ядрами Linux и средами BusyBox.
В отличие от современных настольных компьютеров или облачных систем, многие 32-битные устройства ARM, представленные на рынке, не имеют защиты, по умолчанию работают с правами root и не имеют регулярных обновлений или циклов исправлений. Чтобы воспользоваться их уязвимостями, необходимо понимать их набор инструкций, соглашения о вызовах, структуру памяти и двоичный интерфейс — всё это требует 32-битной сборки ARM.
Многие образцы вредоносного ПО, находящиеся в открытом доступе (Mirai, Mozi), скомпилированы для 32-битной архитектуры ARM. Анализируя реальные угрозы, вы будете дизассемблировать 32-битные исполняемые файлы ARM, а не только 64-битные.
Кроме того, 32-битный ARM проще в освоении и отлично подходит для создания прочной основы. Людей, хорошо знающих ассемблер ARM, немного, поэтому его изучение может помочь улучшить карьерные возможности и оставаться в авангарде кибербезопасности.
Регистры
Регистры — это области памяти компьютера, где хранятся данные. При работе с ассемблером мы обычно используем эти регистры для перемещения и обработки информации, поэтому вам следует с ними познакомиться.
32-разрядные процессоры ARM имеют 16 регистров общего назначения. Вот эти регистры:
R0–R3: Регистры аргументов (используются для параметров функций и возвращаемых значений)
R4–R11: Регистры общего назначения для локальных переменных и временного хранения
R12: Регистр временных интервалов внутрипроцедурных вызовов (IP)
R13 : Указатель стека (SP) — указывает на вершину стека
R14: Регистр ссылок (LR) — хранит адреса возврата для вызовов функций
R15: Счетчик программ (PC) — указывает на текущую выполняемую инструкцию
Также имеется специальный регистр — xPSR: расширенный регистр состояния программы (объединяет флаги условий, текущий номер исключения и состояние выполнения).
Флаги
Флаги в 32-битном ассемблере ARM — это отдельные биты в специальном регистре, которые указывают состояние операций в процессоре. Регистр состояния программы имеет ширину 32 бита, но основные используемые флаги — это всего несколько ключевых битов. Наиболее интересными являются следующие флаги:Флаг Z (ноль): указывает, равен ли результат операции нулю.
Флаг C (перенос): указывает на перенос или заём в арифметических операциях.
Флаг V (переполнение): указывает на переполнение в знаковой арифметике.
Эти флаги позволяют условно выполнять инструкции на основе результатов предыдущих операций и обновляются многими арифметическими и логическими инструкциями с суффиксом «S», например, ADDS.
Инструкции ARM
Инструкции ARM следуют предсказуемому шаблону, что делает чтение дизассемблированного кода более интуитивным:<операция>{<условие>}{S} <назначение>, <операнд1>, <операнд2>
Вот некоторые из самых популярных инструкций:
MOV – переместить копирует значение из исходного файла в целевой. Например:
MOV R0, R1 ; Copy R1 to R0
; означает комментарий в Ассамблее.
LDR – load копирует значение из памяти в регистр. Например:
LDR R0, [R1] ; Load the value from the address in R1 into R0
STR – store копирует значение из регистра в память. Например:
STR R0, [R1] ; Store the value in R0 to the address in R1
CMP – сравнивает и вычитает второй операнд из первого, а также устанавливает флаги условий. Например:
CMP R0, R1 ; Compare R0 with R1 (sets flags, but doesn't store result)
B – переход по метке осуществляется при выполнении условия. Например:
BEQ label ; Branch to 'label' if equal (Z flag is set)
И – побитовое И выполняет логическую операцию И над каждым битом операндов. Например:
AND R0, R1, R2 ; R0 = R1 AND R2
ORR – побитовое ИЛИ выполняет логическое ИЛИ над каждым битом операндов. Например:
ORR R0, R1, R2 ; R0 = R1 OR R2
EOR (исключающее ИЛИ) выполняет логическую операцию XOR над каждым битом операндов. Например:
EOR R0, R1, R2 ; R0 = R1 XOR R2
BIC – сброс бита выполняет операцию R1 И (НЕ R2). Например:
BIC R0, R1, R2 ; R0 = R1 AND NOT R2
PUSH – сохраняет регистры в стеке. Например:
PUSH {R0, R1} ; Push R0 and R1 onto the stack
POP – восстанавливает регистры из стека. Например:
POP {R0, R1} ; Pop values into R0 and R1 from the stack
BL – переход по ссылке переходит к подпрограмме и сохраняет адрес возврата в LR. Например:
BL myFunction ; Call subroutine 'myFunction'
BX – переход по адресу и переключение набора инструкций при необходимости. Например:
BX LR ; Return from subroutine (branch to address in LR)
SVC – вызов супервизора запускает программное прерывание для перехода в режим супервизора. Например:
SVC #0 ; Call operating system service
ADR – загрузка адреса метки в регистр. Например:
ADR R0, label ; Load the address of 'label' into R0