На главную | Содержание | Назад | Вперёд
Наши друзья

 

 

Структура полезной нагрузки для платформы SPARC

Как и для платформы MIPS, платформа SPARC является RISC-архитектурой и каждая машинная команда имеет размер 32 бит. Некоторые модели компьютеров могут работать как с прямым, так и обратным порядком байтов. SPARC-команды имеют следующий формат
тк
Регистр-
Спецификатор
Исходный
Флаг
Второй исходный

получатель
команды
регистр
SR
регистр или константа

Здесь поле ТК имеет размер 2 бит и указывает на тип команды, регистр-получатель имеет размер 5 бит, спецификатор команды и исходный регистр тоже занимают по 5 бит; однобитовый флаг SR показывает, используется ли константа или второй исходный регистр, и в последнем поле (13 бит) хранится значение второго исходного регистра или константы в зависимости от установленного флага SR.
Окно регистров для платформы SPARC
На платформе SPARC используется особая система для управления регистрами. В SPARC применяется технология окна регистров, когда определенные банки регистров "перемещаются" при вызове функции. Обычно используются 32 регистра.

gO-g7 — общие регистры. Они не изменяются между вызовами функций. В специальном регистре gO хранится значение нуля (т.н. источник нуля).
iO-i7 — входные регистры. Регистр i6 используется как указатель фрейма. В регистре i7 хранится адрес возврата предыдущей функции. Значения этих регистров изменяются при вызове функции.
10-17 — локальные регистры. Значения этих регистров изменяются при вызове функции.
оО-о7 — выходные регистры. Регистр i6 используется как указатель стека. Значения этих регистров изменяются при вызове функции.
Дополнительными специальными регистрами являются рс, psr и прс. При вызове функций значения в "перемещающихся" регистрах изменяются следующим образом.
На 18 показано, что происходит при перемещении регистров. Значения регистров оО-о7 копируются в регистры 10—17. Прежние значения регистров 10—17 становятся недоступными. То же самое касается и значений регистров 10-17 и оО-о7. Единственные данные в регистрах, которые "выживают" при вызове функции, — это данные из регистров оО-о7, которые копируются в регистры 10—i7. Выходные регистры для вызывающей функции становятся входными регистрами для вызванной функции. При возврате значения вызванной функции, значения входных регистров копируются обратно в выходные регистры вызывающей функции. Локальные регистры являются локальными для каждой функции и не участвуют в этом обмене данными.

Функция 1 вызывает функцию 2. Значения выходных регистров функции 1 становятся значениями входных регистров функции 2. Это единственные значения регистров, которые передаются функции 2. Когда функция 1 инициирует команду вызова, текущее значение счетчика команд (programm counter — рс) записывается в регистр о7 (адрес возврата). Когда управление передается функции 2, то адрес возврата таким образом заносится в регистр i7.
Функция 3 вызывает функцию 3. Снова повторяется тот же процесс обмена данными в регистрах. Значения выходных регистров функции 2 заносятся во входные регистры функции 3. При возвращении значения функции происходит противоположный процесс: значения входных регистров функции 3 заносятся в выходные регистры функции 2. При возврате значения для функции 2 значения входных регист­ров функции 2 заносятся в выходные регистры функции 1.
Использование стека на платформе SPARC
На платформе SPARC команды save и restore используются для управления стеком вызовов. При использовании команды save, значения входных и локальных регистров сохраняются в стеке. Выходные регистры становятся входными (как мы только что рассказали). Предположим, что мы используем следующую простую программу.
func2 () j
fund ()
func2 () ;
void main ()
fund () ;
Функция main () вызывает функцию fund (). Поскольку в SPARC применяется отложенная передача управления, то будет выполнена следующая команда. В данном случае мы размещаем как дополнительную команду пор. При выполнении команды call, значение счетчика команд (рс) записывается в регистр о7 (адрес возврата).
0x10590 <main+4>: call 0x10578 <funcl>
0x10594 <main+8>: пор
Теперь выполняется функция fund (). Прежде всего эта функция вызывает команду save. Команда save сохраняет значения входных и локальных регистров в стеке и перемещает значения регистров оО-о7 в регистры iO-i7. Таким образом, адрес возврата функции хранится в регистре i7.
0x10578 <funcl>: save %sp, -112, %sp
Затем функция fund () вызывает функцию func2 (). В качестве команды, выполняющейся при отложенной передаче управления, мы используем пор.
0x1057c <funcl+4>: call 0x1056c <func2>
0x10580 <funcl+8>: nop

Теперь выполняется функция func2 (), она сохраняет окно регистров и просто возвращает значение. Для возвращения используется команда ret, причем значение возвращается к адресу, сохраненному во входном регистре \1 плюс 8 байт (пропуская команду отложенной передачи управления после оригинального вызова). Команда отложенной передачи управления после ret — это команда restore, которая восстанавливает окно регистров для предыдущей функции.
0x1056с <func2>: save %sp, -112, %sp
0x10570 <func2+4>: ret 0x10574 <func2+8>: restore
Функция fund () повторяет тот же процесс, возвращаясь к адресу, сохраненному в регистре \1 плюс 8 байт. Затем происходит восстановление.
0x10584 <furicl + 12>: ret 0x10588 <funcl+16>: restore
Теперь мы вернулись в функцию main. Эта функция повторяет те же действия, и программа завершается.
0x10598 <main+12>: ret 0x1059c <raain+16>: restore
Как показано на 19, когда функция 1 вызывает функцию 2, то адрес возврата сохраняется в регистре о7. Значения локальных и входных регистров размещаются в стеке по текущему адресу указателя стека для функции 1. Затем стек растет сверху вниз (к младшим адресам). Локальные переменные для стекового фрейма функции 2 растут по направлению к данным, сохраненным в стековом фрейме для функции 1. При возвращении значения функции 2 искаженные данные восстанавливаются в локальных и входных регистрах. Однако сам адрес возврата из функции 2 не искажается, поскольку он хранится не в стеке, а в регистре i7.
Поиск вызовов функций на платформе SPARC
Следует помнить, что в конце каждой функции вызывается команда ret для возврата к предыдущей функции. Команда ret получает адрес возврата из регистра \1. Это означает, что для изменения адреса возврата требуется реализовать как минимум два этапа атаки на вызов функции.
Предположим, хакер осуществляет переполнение локального буфера в функции 2 для искажения данных, сохраненных во входных и локальных регистрах. Возврат функции 2 происходит нормально, поскольку адрес возврата сохранен в регистре i7. Теперь хакер "находится" в функции 1. Значения регистров iO-i7 для функции 1 восстанавливаются из стека. Данные в этих регистрах будут искажены из-за проведенной атаки на переполнение буфера. Поэтому при возврате из функции 1 осуществится переход по теперь уже искаженному адресу, сохраненному в регистре i7.

 

На главную | Содержание | Назад | Вперёд
 
Яндекс.Метрика