Почему eip не меняется при попытке переполнения буфера?

Я пытаюсь играть с переполнением буфера. Я не понимаю, что здесь происходит со значением eip.

Вот код C:

void copy(char *arg) {
  char msg[256];
  strcpy(msg,arg);
}

Сборка для него:

0x804847d <copy+25>:    call   0x8048368 <strcpy@plt>
0x8048482 <copy+30>:    leave  
0x8048483 <copy+31>:    ret    

Я ввожу в качестве аргумента строку типа "_\xAA\xBB\xCC\xDD" с размером, рассчитанным таким образом, что последние 4 байта будут 4 байта после $ebp (чтобы перезаписать реальный обратный адрес). И это, кажется, работает.

в ГДБ:

(break before strcpy)
x/2wx $ebp
0xbffffb38: 0xbffffb58  0x080484d2
n
(just after strcpy execution)
x/2wx $ebp
0xbffffb38: 0x80cdd189  0x080484b6
...
n
...
x/2wx $ebp
0xbffffb38: 0x80cdd189  0x080484b6

Таким образом, обратный адрес был 0x080484d2, а после моего переполнения он стал 0x080484b6, чего я и хочу. но программа завершается: "Не удается получить доступ к памяти по адресу 0x80cdd18d".

Я не знаю, почему $eip не был установлен на мой адрес, и из-за адреса кода в 0x08048... Я вполне уверен, что $ebp+4 был местом, содержащим обратный адрес

Я попробовал еще раз со строкой на 4 байта меньше, и на этот раз она перезаписала $ebp, а не $ebp+4, и после возврата $eip было установлено значение, содержащееся в $ebp+4.

Любые объяснения?


person Thomas    schedule 03.11.2013    source источник
comment
Возможно, попытка доступа к 0x80cdd18d была результатом выполнения инструкций по адресу 0x80484b6. Откуда вы знаете, что он не прыгнул туда?   -  person    schedule 03.11.2013
comment
Я продолжал делать следующее на gdb, и он перешел на 0x80cdd18d, не проходя через 0x80484b6.   -  person Thomas    schedule 03.11.2013
comment
Сообщение «Не удается получить доступ к памяти» не означает, что %eip вашей программы попал туда. Это даже не означает, что ваша программа пыталась получить доступ к адресу! Это означает, что gdb по какой-то причине хотел получить доступ к этому адресу в адресном пространстве подчиненного процесса, но не смог. Такое предупреждение часто можно увидеть во время сеанса gdb программы, которая ведет себя неправильно. А ваш точно плохо себя ведет - у него переполнение буфера! Попробуйте использовать stepi вместо next для одношаговых инструкций после strcpy, и вы сможете лучше понять, что происходит.   -  person    schedule 03.11.2013
comment
Вы можете найти полезным display/i $pc (и, возможно, некоторые другие интересные регистры) и stepi через последовательность возврата.   -  person gsg    schedule 04.11.2013
comment
Благодаря вашим комментариям я разместил ответ, проблема заключалась в том, что значение в $ebp было изменено во время перезаписи   -  person Thomas    schedule 04.11.2013


Ответы (2)


Итак, спасибо @Wumpus Q. Wumbley, это помогло мне понять некоторые вещи.

Выполнение next прыжков leave и ret вместе. ret — это инструкция, которая изменяет eip, она должна быть эквивалентна pop eip. Но leave изменяет указатели стека esp и ebp раньше (особенно потому, что, когда я перезаписываю ebp+4, я меняю значение, содержащееся в ebp)

TLDR: Не перезаписывая значение в ebp, оно работает успешно.

person Thomas    schedule 03.11.2013
comment
next не перепрыгивает через leave и ret (в смысле не выполняет их). Он просто не возвращается к подсказке gdb после каждой инструкции. Его цель состоит в том, чтобы продолжаться до тех пор, пока текущая строка source не будет завершена. Иногда для этого требуется несколько инструкций, иногда много. next, step, nexti и stepi все имеют свое применение, но stepi в этом случае наиболее полезен, так как он не заботится о строках исходного кода. Когда вы используете эксплойты переполнения буфера, состояние запущенного процесса резко отличается от того, что выражено в отладочной информации на уровне исходного кода. next путается. - person ; 04.11.2013
comment
так что это объясняет половину проблемы, но не говорит, почему это не сработало в первую очередь leave, затем ret заставляет ebp указывать на старый ebp, а esp указывает на ebp (+- несколько байтов), а eip на значение в эбп+4. Так что при перезаписи значения в ebp все будет ок кроме старого ebp который станет неверным. Но esp и eip будут хороши, так почему же они не работают? - person Thomas; 04.11.2013
comment
Поврежденный старый %ebp будет загружен в реальный регистр %ebp с помощью инструкции leave. Затем ваш ret приведет вас к адресу, который вы выбрали в своем эксплойте. Все, что находится по этому адресу, будет иметь проблемы, если для этого потребуется пригодный для использования %ebp. - person ; 04.11.2013
comment
Хорошо, проблема как раз перед 'ret'. Когда я делаю шаг (чтобы выполнить ret), он говорит, что не может получить доступ к памяти по адресу .... Так что у него не было времени вытолкнуть eip, он (gdb или процессор?) просто сделал какую-то проверку, чтобы увидеть, есть ли ebp является допустимым указателем стека, а не таковым. - person Thomas; 04.11.2013
comment
gdb любит печатать некоторую информацию о текущем кадре стека каждый раз, когда программа останавливается. Если у вас нет допустимого кадра стека в результате переполнения буфера, это, вероятно, вызовет некоторые предупреждения. Вам просто нужно научиться игнорировать их и печатать то, что вам нужно, вручную. - person ; 04.11.2013

Если это для x86 (в отличие от x86-64), обычный пролог функции включает в себя отправку ebp и последующее присвоение ему значения esp, что оставит адрес возврата в стеке ebp+4.

Взгляните на дизассемблирование вашей функции и посмотрите, выглядят ли первые инструкции так:

pushl   %ebp
movl    %esp, %ebp

Если это так, то это является причиной смещения.

person gsg    schedule 03.11.2013
comment
да, это x86 и первые инструкции выглядят так, как вы говорите. Итак, теперь, когда подтверждено, что ebp+4 должен содержать RA, почему это не работает? - person Thomas; 03.11.2013
comment
Кажется, что вы успешно управляете значением eip, а что-то еще идет не так. Трудно сказать, что без дополнительной информации. - person gsg; 04.11.2013