strсpy не отображается при дизассемблировании
В книге Хакинг. Искусство эксплойта приведен пример дизассемблирования программы, которая выглядит так:
#include <string.h>
#include <stdio.h>
int main()
{
char str_a[20];
strcpy(str_a, "Hello, world!\n");
printf(str_a);
}
А результат её дизассемблирования - вот такой:
(gdb) x/5i $eip
0x80483c4 <main+16>: mov DWORD PTR [esp+4],0x80484c4
0x80483cc <main+24>: lea eax,[ebp-40]
0x80483cf <main+27>: mov DWORD PTR [esp],eax
0x80483d2 <main+30>: call 0x80482c4 <strcpy@plt>
0x80483d7 <main+35>: lea eax,[ebp-40]
Я полностью переписал код на С и дизассемблировал программу, но в результате не увидел строки с вызовом функции strcpy().
Мой результат дизассемблирования:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000555555555139 <+0>: push rbp
0x000055555555513a <+1>: mov rbp,rsp
0x000055555555513d <+4>: sub rsp,0x20
=> 0x0000555555555141 <+8>: lea rax,[rbp-0x20]
0x0000555555555145 <+12>: mov DWORD PTR [rax],0x6c6c6548
0x000055555555514b <+18>: mov WORD PTR [rax+0x4],0x6f
0x0000555555555151 <+24>: lea rax,[rbp-0x20]
0x0000555555555155 <+28>: mov rdi,rax
0x0000555555555158 <+31>: mov eax,0x0
0x000055555555515d <+36>: call 0x555555555030 <printf@plt>
0x0000555555555162 <+41>: mov eax,0x0
0x0000555555555167 <+46>: leave
0x0000555555555168 <+47>: ret
Подскажите, пожалуйста, в чем может быть проблема?
Ответы (2 шт):
Вы не увидите вызова strcpy в объектном коде. И переменной str_a тоже. Потому что современный компилятор знает что делает strcpy и понимает что его вызов можно убрать, а передать на печать адрес строкового литерала. Оптимизация.
Конкретно, я поместил ваш код в temp.c и запустил gcc -S temp.c чтобы получить ассемблер в temp.s. Это вместо компиляции и дизассеблирования. И получил абракадабру:
...
leaq -32(%rbp), %rax
movabsq $8583909746840200520, %rdx
movq %rdx, (%rax)
movabsq $2851464966991735, %rcx
movq %rcx, 7(%rax)
leaq -32(%rbp), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
...
Никакого вызова strcpy тут нет. Но есть интересное. Строка "Hello, world!\n" занимает в памяти пятнадцать байт. Процессор 64-битный, он может обработать до восьми байт за раз. Инструкция movabsq $8583909746840200520, %rdx копирует первые восемь символов в регистр rdx, следующая инструкция сохраняет его в буфер на стеке. Значение 8583909746840200520 соответствует символам "Hello, w" без завершающего нуля.
Значение 2851464966991735 из следующей movabsq – "world!\n" с завершающим нулём. Две строки перекрываются на один символ, потому что в распоряжении компилятора только инструкция для сохранения восьми байт, а нам нужно пятнадцать в сумме.
Так что strcpy здесь нет, и литерала нет. Переменная на стеке есть, только у неё размер – 32 байта, не двадцать.
Компилятор не обязан следовать коду буквально, он имеет право получить результат любым другим способом, что мы и видим.
Можно заставить GCC использовать strcpy командой gcc -S -fno-builtin-strcpy temp.c:
...
.LC0:
.string "Hello, world!\n"
...
leaq -32(%rbp), %rax
leaq .LC0(%rip), %rdx
movq %rdx, %rsi
movq %rax, %rdi
call strcpy@PLT
leaq -32(%rbp), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
...
Здесь вызов появился и литерал вернули на место. Можно эксплойтить.
Подскажите, пожалуйста, в чем может быть проблема?
В зависимости от уровня оптимизации, стандартные функции С/С++ могут подставляться в точке вызова с целью сокращения и ускорения кода.
При использовании GCC (и не только) Вы можете использовать ключи -fno-builtin (отключить все встроенные функции) или -fno-builtin-strcpy (отключить только встроенную strcpy()).
Код и результаты, в зависимости от компилятора и ключей, можно взглянуть: https://godbolt.org/z/na7zjG7e4
P.S.
Это вольный перевод ответа https://stackoverflow.com/a/13059603/8585880 @R.. GitHub STOP HELPING ICE на идентичный вопрос enSO. (На мой непросвещённый взгляд, на ruSO был бы небесполезен инструмент авторизованного перевода, но .. Нужны ли переводы на Stack Overflow? Если да, какой вы видите систему?)