nixp.ru v3.0

19 октября 2017,
четверг,
08:16:12 MSK

DevOps с компанией «Флант»
fucIZ0FF написал 8 октября 2006 года в 15:06 (419 просмотров) Ведет себя неопределенно; открыл 1 тему в форуме, оставил 8 комментариев на сайте.

Не знал куда запостить … по идее относится к программированию

Значит такая история: читая статью о переполнении стэка набрел на такой код:

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. С кодом я абсолютно согласен и считаю что он правильный…. В чем проблема?

rgo
fucIZ0FF
Почему от стэка отрывается 56 байт??? Во-вторых этот код не работает и printf показывает 1. С кодом я абсолютно согласен и считаю что он правильный…. В чем проблема?

$56, потому что gcc развлекается как может.

а не работает, потому что адрес возврата лежит поглубже.

и ещё, до кучи, совет, раскидывай function и main по разным объёктникам, а то gcc может соптимизировать фразу `x = 0;' вообще из программы.

fucIZ0FF

вот мне и интересно почему адресс возврата лежит глубже …

при вызове function

[ret][buffer2][buffer1][sfp][saved eip] ….

LOW HIGH

buffer1 занимает 8 байт + 4 байта sfp = 12 байт от адреса buffer1 = адресс возврата. И плюс еще 8 байт чтобы прыгнуть на printf ….??

rgo

да потому и глубже, что gcc сдвигает указатель стека на 56 байт. Зачем он это делает, я не знаю. может как раз для того, чтоб сложнее было бы эксплуатировать переполнения. Но как бы там не было, раз делает, значит адрес возврата глубже. попробуй скомпилировать с -O2 может тогда он сделает меньше. А если нет, то читай `info gcc' выискивая какие оптимизации могут на это влиять.

metal

Три параметра могли быть переданы через регистры, но скорее всего были соложаны в стек, так что это еще 12 байт. А вот почему 56 не могу понять.

Feuerbach

А зачем gdb если у gcc есть замечательная опция -S?

rgo
metal
Три параметра могли быть переданы через регистры, но скорее всего были соложаны в стек, так что это еще 12 байт. А вот почему 56 не могу понять.

параметры уже лежат под адресом возврата. их не надо считать.

Genie

состояние регистров? pushad ?

fucIZ0FF

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

rgo

Значит так. Во-первых, уже упомянуто про опцию -S которую имеет смысл передать gcc если непонятно что он делает. Могу порекомендовать ещё, до кучи -fverbose-asm, иногда помогает сориентироваться. А во-вторых, смотри внимательнее. Ты добавляешь (пытаешься добавить) к адресу возврата 8. почему именно 8? Вот смотри, asm’овый листинг куска main:

34 0046 E8FCFFFF             call      function
  34      FF
  35 004b C745FC01             movl      $1, -4(%ebp)
  35      000000
  36 0052 8B45FC               movl      -4(%ebp), %eax
  37 0055 89442404             movl      %eax, 4(%esp)
  38 0059 C7042400             movl      $.LC0, (%esp)
  38      000000
  39 0060 E8FCFFFF             call      printf
  39      FF

следующая за `call function' инструкция имеет размер 7 байт. И если её скипнуть сместив адрес возврата, то мы как-раз и добьёмся того, что лежащий в стеке аргумент printf’у не будет перезаписан единицей. Ты же смещаешь на 8 байт…

вот это рабочий

#include 
void function (int a, int b, int c)
{
      char buffer1[5];
      char buffer2[10];
      register int* ebp asm ("ebp");
      ebp[1] += 0x7;
}
int main ()
{
      int x;
      
      x = 0;
      function(1,2,3);
      x = 1;
      printf("%d\n",x);
      return 0;
}

Я правда использовал доступ к ebp, вместо того чтобы высчитывать расстояние в байтах между началом буфера и адресом возврата.

fucIZ0FF

все равно дает segmentation fault …. а с -O2 выдает 1

fucIZ0FF

да и еще извиняюсь за такой вопрос … но почему movl 7 байт? где-то конечно слышал что push/pop вроде по 1 байту, но почему не знаю… где про это можно почитать? вроде что-то нашел http://www.x86-64.org/documentation/assembly но это для 64бит прочессора. может у кого какие доки есть?

rgo
fucIZ0FF
да и еще извиняюсь за такой вопрос … но почему 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 тоже выпускает нечто подобное для своих процов.

fucIZ0FF

ладно … со всем разобрался…

следующий код не работает и работать не будет

#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;

}

rgo
fucIZ0FF

*ptr=*ptr+10;

я предполагаю 10 получается из-за того, что твоя версия as, почему-то, кодирует смещение -4 в `movl $1, -4(%ebp)' четырьмя байтами, хотя если поставить в поле mod байта mod/rm значение 10b, можно обойтись одним. Вот они и лишние три байта.

Ещё раз повторю, используй листинги, будет проще жить.

fucIZ0FF

2 rgo:

не 4-мя а 10-тью… пробовал иправленный код на двух разных компьютерах (debian [intel] и slackware [celeron]) с разными версиями as … везде работает. твой код вывыливаеися в segmentation fault на обеих машинах.

если использовать твой код но с

ebp[1] += 10;

то все работает !

да и если можно то по подробнее про:

rgo
хотя если поставить в поле mod байта mod/rm значение 10b, можно обойтись одним. Вот они и лишние три байта.

если бы 3 байта были лишними то код бы работал! c 7 байтами я только получаю segmentation fault на обеих машинах.

чтобы получить смещение 10 я как раз и использовал «листинги» …

rgo

читай внимательнее, я сказал, что у тебя -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 у тебя?

у мну:

$ as --version
GNU assembler 2.16.1
fucIZ0FF

на одной машине версия 2.15.92.0.2, на другой 2.17.

вот кусок моего дампа:

46 0031 E8FCFFFF              call    function        #
  46      FF
  47 0036 83C40C                addl    $12, %esp       #,
  48 0039 C745FC01              movl    $1, -4(%ebp)    #, x
  48      000000
  49 0040 83EC08                subl    $8, %esp        #,

и у меня mod == 01… так что тоже 7 байт получается ….хмм … тогда откуда еще 3 байта ???…. может это размер addl (хотя по подсчетам у меня 6 получилось).

вот пример как я 10 байт получил:

bash-3.00# gdb nn
................
(gdb) disas main
.................
0x080483d8 :   call   0x8048368 
0x080483dd :   add    $0x10,%esp
0x080483e0 :   movl   $0x1,0xfffffffc(%ebp)
0x080483e7 :   sub    $0x8,%esp
0x080483ea :   pushl  0xfffffffc(%ebp)
0x080483ed :   push   $0x80484ea
.................

Как видно после возврата из function, ret будет 0×080483dd (addl). Нужно перескочить на 0×080483e7. newret=0×080483e7-0×080483dd=10 байт

fucIZ0FF

блин…. ну правильно все… нужно размер addl прибавить. addl = 3 байта

поэтому и 10 байт получается.