Как записывать и выполнять опкоды в секциях GNU ассемблера Linux
В среде Linux и стандартном GNU ассемблере (GAS) разработчики часто задаются вопросом: можно ли заполнить секцию бинарными опкодами, а затем перейти к её выполнению, не используя системные вызовы вроде mmap? В этой статье мы разберём технические ограничения и практические способы работы с секциями .text и .data, включая настройку прав на исполнение.
Можно ли писать опкоды напрямую в секцию .text?
В GNU ассемблере секция .text по умолчанию доступна только для чтения и выполнения (RX). Запись в неё во время выполнения программы запрещена аппаратной защитой памяти. Однако на этапе компиляции вы можете разместить в ней произвольные байты, используя директиву .byte или .long, а затем вызвать их как функцию. Например:
.section .text.globl _start_start: mov $1, %rax xor %rdi, %rdi syscallНо динамически изменять .text после запуска программы без специальных трюков (например, mprotect) нельзя.
Как сделать секцию .data исполняемой?
Секция .data обычно имеет права на чтение и запись (RW), но не на выполнение. Чтобы сделать её исполняемой, нужно изменить атрибуты секции в исходном коде ассемблера. Используйте директиву .section с флагом @progbits и параметром ax (allocatable, executable):
.section .mycode, "ax", @progbitsmy_code: .byte 0x90, 0x90, 0xC3Это создаст новую секцию с правами на выполнение. Однако встраивание опкодов в .data с последующим прыжком без изменения прав защиты памяти невозможно - система запретит исполнение.
Выполнение кода без mmap: альтернативные подходы
Системный вызов mmap с флагом PROT_EXEC - стандартный способ создания исполняемой памяти в Linux. Если вы хотите избежать системных вызовов, рассмотрите следующие варианты:
- Встраивание кода в секцию
.text: используйте метки и директивы для размещения машинного кода на этапе сборки. - Самомодифицирующийся код: измените защиту памяти через
mprotect(системный вызов), что всё равно требует обращения к ядру. - JIT-компиляция: выделите буфер с помощью
mmapилиsbrkи скопируйте туда опкоды.
Полностью избежать системных вызовов для динамического исполнения произвольного кода в user-space невозможно из-за механизмов защиты ОС.
Практический пример: заполнение секции и прыжок
Вот простой пример на GAS, который размещает опкоды в секции .text и выполняет их:
.section .text.globl _start_start: # Переход на встроенный код jmp my_code # Возврат и завершение mov $60, %rax xor %rdi, %rdi syscallmy_code: .byte 0x48, 0xC7, 0xC0, 0x3C, 0x00, 0x00, 0x00 # mov $60, %rax .byte 0x48, 0x31, 0xFF # xor %rdi, %rdi .byte 0x0F, 0x05 # syscallЭтот код выполняет системный вызов exit(0). Обратите внимание: jmp my_code работает, так как секция .text уже исполняема.
Ограничения и рекомендации
- Для динамической записи опкодов (например, из переменной) используйте
mmapсPROT_READ | PROT_WRITE | PROT_EXEC. - Не пытайтесь писать в
.textво время выполнения - это вызовет Segmentation Fault. - Для экспериментов с исполняемыми секциями используйте
objdump -dдля проверки прав доступа.