Чтобы изменить обратный адрес в пределах function()
, чтобы пропустить x = 1
в main()
, вам нужно две части информации.
1. Расположение адреса возврата в кадре стека.
Я использовал gdb для определения этого значения. Я устанавливаю точку останова на function()
(break function
), выполняю код до точки останова (run
), извлекаю местоположение в памяти текущего кадра стека ( p $rbp
или info reg
), а затем получить местоположение в памяти buffer
(p &buffer
). Используя полученные значения, можно определить местоположение обратного адреса.
(скомпилированный с GCC -g
флаг, включающий символы отладки и выполненный в 64-битной среде)
(gdb) break function
...
(gdb) run
...
(gdb) p $rbp
$1 = (void *) 0x7fffffffe270
(gdb) p &buffer
$2 = (char (*)[5]) 0x7fffffffe260
(gdb) quit
(адрес указателя кадра + размер слова) - адрес буфера = количество байтов от локальной переменной буфера до адреса возврата
(0x7fffffffe270
+ 8) - 0x7fffffffe260
= 24
Если вы испытываете затруднения в понимании того, как работает стек вызовов, может помочь чтение прологов стек *1043* и Статьи Википедии. Это показывает сложность создания примеров «переполнения буфера» на языке C. Смещение 24 от buffer
предполагает определенный стиль заполнения и параметры компиляции. GCC с радостью вставит стека канареек в наше время, если вы не скажете этого.
2. Количество байтов, добавляемых к адресу возврата, чтобы пропустить x = 1
.
В вашем случае указатель сохраненной инструкции будет указывать на 0x00000000004002f4
(<main+35>
), первая инструкция после функции возвращается. Чтобы пропустить присваивание, необходимо указать указатель сохраненной инструкции на 0x00000000004002fb
(<main+42>
).
Ваш расчет, что это 7 байтов, является правильным (0x4002fb
- 0x4002fb
= 7 ).
Я использовал gdb для дизассемблирования приложения (disas main
) и также проверил расчет для моего случая. Это значение лучше всего определить вручную, изучив разборку.
Обратите внимание, что я использовал 64-битную среду Ubuntu 10.10 для тестирования следующего кода.
#include <stdio.h>
void function(int a, int b, int c)
{
char buffer[5];
int *ret;
ret = (int *)(buffer + 24);
(*ret) += 7;
}
int main()
{
int x = 0;
function(1, 2, 3);
x = 1;
printf("x = %i \n", x);
return 0;
}
выход
x = 0
Это действительно просто изменение адреса возврата function()
, а не фактическое переполнение буфера. При фактическом переполнении буфера вы бы переполняли buffer[5]
, чтобы перезаписать адрес возврата. Тем не менее, большинство современных реализаций используют такие методы, как стеки канареек для защиты от этого.