Не знал куда запостить … по идее относится к программированию
Значит такая история: читая статью о переполнении стэка набрел на такой код:
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret;
ret = (int)buffer1 + 12;
(*ret) = ret+8;
}
void main() {
int x;
x = 0;
function(1,2,3);
x = 1;
printf(«%d\n»,x);
}
Цель этого кода перепрыгнуть x=1 и тем самым printf должен показать 0; В статье также дается пример ассемб кода функции function полученного с помошью gdb:
function:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
………
потому что buffer1[5] — 8 byte, buffer2[10] — 12 и указатель ret 4 байта.
у меня же:
function:
pushl %ebp
movl %esp, %ebp
subl $56, %esp
Почему от стэка отрывается 56 байт??? Во-вторых этот код не работает и printf показывает 1. С кодом я абсолютно согласен и считаю что он правильный…. В чем проблема?
Последние комментарии
- OlegL, 17 декабря в 15:00 → Перекличка 21
- REDkiy, 8 июня 2023 года в 9:09 → Как «замокать» файл для юниттеста в Python? 2
- fhunter, 29 ноября 2022 года в 2:09 → Проблема с NO_PUBKEY: как получить GPG-ключ и добавить его в базу apt? 6
- Иванн, 9 апреля 2022 года в 8:31 → Ассоциация РАСПО провела первое учредительное собрание 1
- Kiri11.ADV1, 7 марта 2021 года в 12:01 → Логи catalina.out в TomCat 9 в формате JSON 1
$56, потому что gcc развлекается как может.
а не работает, потому что адрес возврата лежит поглубже.
и ещё, до кучи, совет, раскидывай function и main по разным объёктникам, а то gcc может соптимизировать фразу `x = 0;' вообще из программы.
вот мне и интересно почему адресс возврата лежит глубже …
при вызове function
[ret][buffer2][buffer1][sfp][saved eip] ….
LOW HIGH
buffer1 занимает 8 байт + 4 байта sfp = 12 байт от адреса buffer1 = адресс возврата. И плюс еще 8 байт чтобы прыгнуть на printf ….??
да потому и глубже, что gcc сдвигает указатель стека на 56 байт. Зачем он это делает, я не знаю. может как раз для того, чтоб сложнее было бы эксплуатировать переполнения. Но как бы там не было, раз делает, значит адрес возврата глубже. попробуй скомпилировать с -O2 может тогда он сделает меньше. А если нет, то читай `info gcc' выискивая какие оптимизации могут на это влиять.
Три параметра могли быть переданы через регистры, но скорее всего были соложаны в стек, так что это еще 12 байт. А вот почему 56 не могу понять.
А зачем gdb если у gcc есть замечательная опция -S?
параметры уже лежат под адресом возврата. их не надо считать.
состояние регистров? pushad ?
2 rgo:
в function с -O2 от стэка всего 28 байт отнимается .. c O3 тоже самое. и в функции main тоже 28 байт хотя должно быть всего 4 (int x). после чего не изменяя код программы я опять перекомпилировал с -O2 и в main вместо 28 уже было 8 а в function 36!
Эти значения меняются почти с каждой компиляцией кода. в инфе по gcc я ничего подобного не нашел! gcc версии 3.4.2 … нужно попробовать более рание версии
2 metal:
как раз на 12 байт (по крайней мере из моих вычислений)… если можно то подробнее насчет передачи через 3 регистра ?
2 Genie:
состояние регистров до выполнения чего? в функции function?
—————————————————————————-
пусть адрес возврата лежит на X байт глубже:
#define X 2
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret,*ptr;
int i;
ret = (int)buffer1 + 12;
ptr=ret;
for(i=0;i<=X;i++) *(ptr+i) = ret + 8;
//printf(«ret address: 0x%x\n»,ptr);
//printf(«ret address: 0x%x\n»,ptr+1);
//printf(«ret value: 0x%x\n»,*ptr);
//printf(«ret address: 0x%x\n»,*(ptr+1));
}
void main() {
int x;
x = 0;
function(1,2,3);
x = 1;
printf(«%d\n»,x);
}
и методом тыка начал менять X значение. при #define X 2 программа работает и выводит как и прежде 1. Но уже при X 3 дает segmentation fault .. значит return address лежит на 3*4=12 байт глубже.
почему на 12 байт глубже я понятия не имею но это не из-за того что от esp отнимается 56, 36 , 24 и т.д. байт. Отсчет ведется от адреса buffer1 и размер стэка не важен.
segmentation fault потому что *(ptr+i) = ret + 8; скорее всего неверно …
надо только найти правильный адрес printf
Значит так. Во-первых, уже упомянуто про опцию -S которую имеет смысл передать gcc если непонятно что он делает. Могу порекомендовать ещё, до кучи -fverbose-asm, иногда помогает сориентироваться. А во-вторых, смотри внимательнее. Ты добавляешь (пытаешься добавить) к адресу возврата 8. почему именно 8? Вот смотри, asm’овый листинг куска main:
следующая за `call function' инструкция имеет размер 7 байт. И если её скипнуть сместив адрес возврата, то мы как-раз и добьёмся того, что лежащий в стеке аргумент printf’у не будет перезаписан единицей. Ты же смещаешь на 8 байт…
вот это рабочий
Я правда использовал доступ к ebp, вместо того чтобы высчитывать расстояние в байтах между началом буфера и адресом возврата.
все равно дает segmentation fault …. а с -O2 выдает 1
да и еще извиняюсь за такой вопрос … но почему movl 7 байт? где-то конечно слышал что push/pop вроде по 1 байту, но почему не знаю… где про это можно почитать? вроде что-то нашел http://www.x86-64.org/documentation/assembly но это для 64бит прочессора. может у кого какие доки есть?
на самом деле movl — может занимать и два байта и восемь и, по-моему, вплоть до пятнадцати в особо злых случаях.
во-первых, если сначала скомпилировать C код в asm, воспользовавшись опцией -S, а затем asm в листинг используя `as -a' то as тебе всё напишет подробно. и я приводил кусок такого листинга выше.
а во-вторых, на intel.com можно найти pdf’ки под названием `IA-32 Intel Software
Developer’s Manual’, там есть полное описание всех инструкций, и того как они кодируются. AMD тоже выпускает нечто подобное для своих процов.
ладно … со всем разобрался…
следующий код не работает и работать не будет
#include
void function (int a, int b, int c)
{
char buffer1[5];
char buffer2[10];
register int* ebp asm («ebp»);
ebp[1] += 0×7; //????????????????
}
int main ()
{
int x;
x = 0;
function(1,2,3);
x = 1;
printf(«%d\n»,x);
return 0;
}
вот рабочий код:
#include
void function (int a, int b, int c)
{
char buffer1[5];
char buffer2[10];
long *ptr;
register int* ebp asm («ebp»);
ptr=(long)ebp+4;
*ptr=*ptr+10;
// ebp[1] += 0×7+4;
//printf(«0x%x\n»,ebp);
}
int main ()
{
int x;
x = 0;
function(1,2,3);
x = 1;
printf(«%d\n»,x);
return 0;
}
я предполагаю 10 получается из-за того, что твоя версия as, почему-то, кодирует смещение -4 в `movl $1, -4(%ebp)' четырьмя байтами, хотя если поставить в поле mod байта mod/rm значение 10b, можно обойтись одним. Вот они и лишние три байта.
Ещё раз повторю, используй листинги, будет проще жить.
2 rgo:
не 4-мя а 10-тью… пробовал иправленный код на двух разных компьютерах (debian [intel] и slackware [celeron]) с разными версиями as … везде работает. твой код вывыливаеися в segmentation fault на обеих машинах.
если использовать твой код но с
ebp[1] += 10;
то все работает !
да и если можно то по подробнее про:
если бы 3 байта были лишними то код бы работал! c 7 байтами я только получаю segmentation fault на обеих машинах.
чтобы получить смещение 10 я как раз и использовал «листинги» …
читай внимательнее, я сказал, что у тебя -4 кодируется четырьмя байтами. кодируем `movl $1, -4(%ebp)’, так как это делается по науке, то есть в семь байт, так как кодирует as у меня:
байт #0: 0xc7. старшие семь бит — это коп, плюс единичка тк работаем с двордами а не с байтами
#1: 01000101 — байт modrm. старшие два бита, поле mod, == 01, что указывает но то что смещение при задании адреса (та самая -4) влезает в один байт. потом 000 — это просто продолжение коп, и 101 значит что к смещению надо добавить значение ebp.
#2: FC — это как раз -4 закодированная одним байтом
#3,4,5,6: — а это единица которую надо запихнуть по месту назначения.
У тебя же, по ходу дела, поле mod == 10, и потом вместо одного байта FC стоят байты: FC FF FF FF, то есть та же -4, но четырёхбайтовая. то есть лишних три байта. 7+3 == 10. вот и результат.
Так это или нет? я телепат?
кстати а что за версия as у тебя?
у мну:
на одной машине версия 2.15.92.0.2, на другой 2.17.
вот кусок моего дампа:
и у меня mod == 01… так что тоже 7 байт получается ….хмм … тогда откуда еще 3 байта ???…. может это размер addl (хотя по подсчетам у меня 6 получилось).
вот пример как я 10 байт получил:
Как видно после возврата из function, ret будет 0×080483dd (addl). Нужно перескочить на 0×080483e7. newret=0×080483e7-0×080483dd=10 байт
блин…. ну правильно все… нужно размер addl прибавить. addl = 3 байта
поэтому и 10 байт получается.