Инструкции. Реверс-инжиниринг программы

Последнее обновление: 10.07.2023

Формат инструкций

Общий формат инструкций на примере инструкции MOV:

31

30

29

28-24

23-22

21

20-16

15-10

9-5

4-0

Bits

Opcode

Condition Code

Opcode

Shift

0

Rm

imm6

Rn

Rd

Каждая бинарная инструкция ARM имеет длину в 32 бита и содержит ряд полей:

  • Bits: разрядность. Если равно 0, то регистры рассматриваются как 32-битные регистры. Если равно 1, то регистры рассматриваются как 64-битные регистры. Причем все регистры в одной инструкции должны быть либо 32-битными, либо 64-битными.

  • Opcode: код выполняемой операции (например, ADD (сложение) или MUL (умножение)).

  • Condition Code: бит, который указывает, должна ли инструкция обновить флаг условия (1 - флаг условия обновляется, 0 - не обновляется)

  • Shift: два бита, которые определяют применяемые операции сдвига.

  • Rm, Rn: регистры, которые хранят операнды операции. Поскольку мы имеем 32 регистра (31 регистр общего пользования X0-X30 и регистр указателя стека SP / нулевой регистр XZR), то для указания регистра требуется 5 бит.

  • Rd: регистр для помещения результата операции.

  • Imm6: непосредственный операнд операции, которые обычно представляют некоторые константные значения и для которых не нужна загрузка в регистры

В зависимости от конкретной инструкции и используемых ею значений конкретное содержимое будет меняться.

Реверс-инжиниринг программы

Возьмем простейшую программу из первой главы, которая использовала прерывание Linux для вывода строки на консоль:

.global _start          // устанавливаем стартовый адрес программы

_start: mov X0, #1          // 1 = StdOut - поток вывода
 ldr X1, =hello             // строка для вывода на экран
 mov X2, #19                // длина строки
 mov X8, #64                // устанавливаем функцию Linux
 svc 0                      // вызываем функцию Linux для вывода строки

 mov X0, #0                 // Устанавливаем 0 как код возврата
 mov X8, #93                // код 93 представляет завершение программы
 svc 0                      // вызываем функцию Linux для выхода из программы

.data
hello: .ascii "Hello METANIT.COM!\n"    // данные для вывода

Допустим, код этой программы находится в файле hello.s. Перейдем к папке файла и скопилируем программу в файл hello.o:

aarch64-none-elf-as hello.s -o hello.o

Теперь разберем скопилированный объектный файл hello.o. Для этих целей в папке с компилятором мы можем найти утилиту objdump. В завимости от типа пакета компилятора конкретное название утилиты будет отличаться. Оно формируется по типу [название_пакета]-objdumpНапример, для пакета типа baremetal, который не привяан к Linux, она называется aarch64-none-elf-objdump.exe. Выполним команду:

aarch64-none-elf-objdump -s -d hello.o

И в результате консоль выведет что-то наподобие:

c:\arm>aarch64-none-elf-objdump -s -d hello.o

hello.o:     file format elf64-littleaarch64

Contents of section .text:
 0000 200080d2 e1000058 620280d2 080880d2   ......Xb.......
 0010 010000d4 000080d2 a80b80d2 010000d4  ................
 0020 00000000 00000000                    ........
Contents of section .data:
 0000 48656c6c 6f204d45 54414e49 542e434f  Hello METANIT.CO
 0010 4d210a                               M!.

Disassembly of section .text:

0000000000000000 <_start>:
   0:   d2800020        mov     x0, #0x1                        // #1
   4:   580000e1        ldr     x1, 20 <_start+0x20>
   8:   d2800262        mov     x2, #0x13                       // #19
   c:   d2800808        mov     x8, #0x40                       // #64
  10:   d4000001        svc     #0x0
  14:   d2800000        mov     x0, #0x0                        // #0
  18:   d2800ba8        mov     x8, #0x5d                       // #93
  1c:   d4000001        svc     #0x0
        ...

c:\arm>

Первая часть вывода отображает двоичные данные файла в шестнадцатеричной форме, в том числе его инструкции, а также строку из секции .data. Затем идет дизассемблированный код секции .text. Например, в первой строке двоичных данных

0000 200080d2 e1000058 620280d2 080880d2   ......Xb.......

Инструкция 200080d2 будет представлять инструкцию mov, которая скомпилирована в код 0xd2800020 (обратите внимание, что во втором случае биты расположены в обратном порядке - 200080d2 - 0xd2800020 все потому что arm использует порядок little-endian)

Переложим ее в двоичный код, чтобы понять ее содержимое:

Шестнадцатеричный код

d

2

8

0

0

0

2

0

Двоичный код

1101

0010

1000

0000

0000

0000

0010

0000

То есть в итоге мы получаем инструкцию в бинарной форме 11010010100000000000000000100000, или, если разбить по частям, 1_1_0_100101_00_0000000000000001_00000

  • Самый первый бит равен 1, что значит, что данная инструкция использует 64-битные регистры (X0, а не W0)

  • Второй бит в комбинации с битами с 4-ого по 9-й образуют код операции mov - 1_100101.

  • Третий бит равен 0, что значит, что инструкция не устанавливает никаких флагов, которые могли бы повлиять на ветвление программы

  • Следующие два бита 00 указывают, что никаких операций сдвига не выполняется

  • Следующие 16 бит представляют непосредственный операнд для команды mov - 0000000000000001 - число 1

  • И последние 5 бит представляют регистр для загрузки. Значение 00000 или грубо говоря 0 представляет регистр X0

Ряд использумых инструкций фактически является псевдонимами и сокращениями от других инструкций. Иногда возникает необходимость видеть код без псевдонимов. Например, в коде выше инструкция mov фактически представляет псевдоним. Чтобы выполнить дизассемблирование без псевдонимов, добавим флаг no-aliases

aarch64-none-elf-objdump -s -d -M no-aliases hello.o

И в результате консоль выведет что-то наподобие:

c:\arm>aarch64-none-elf-objdump -s -d -M no-aliases hello.o

hello.o:     file format elf64-littleaarch64

Contents of section .text:
 0000 200080d2 e1000058 620280d2 080880d2   ......Xb.......
 0010 010000d4 000080d2 a80b80d2 010000d4  ................
 0020 00000000 00000000                    ........

Contents of section .data:
 0000 48656c6c 6f204d45 54414e49 542e434f  Hello METANIT.CO
 0010 4d210a                               M!.


Disassembly of section .text:

0000000000000000 <_start>:
   0:   d2800020        movz    x0, #0x1
   4:   580000e1        ldr     x1, 20 <_start+0x20>
   8:   d2800262        movz    x2, #0x13
   c:   d2800808        movz    x8, #0x40
  10:   d4000001        svc     #0x0
  14:   d2800000        movz    x0, #0x0
  18:   d2800ba8        movz    x8, #0x5d
  1c:   d4000001        svc     #0x0
        ...

c:\arm>
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850