Недавно я начал изучать управление памятью и прочитал об относительных адресах и физических адресах, и у меня возник вопрос:
Когда я печатаю адрес переменной, показывает ли он относительный (виртуальный) адрес или физический адрес, где переменная находится в памяти?
И еще один вопрос, касающийся управления памятью:
Почему этот код создает одно и то же значение указателя стека для каждого прогона (из Руководства Shellcoder , стр. 28)? Любая программа, которую я запускаю, производит этот адрес?
// find_start.c
unsigned long find_start(void)
{
__asm__("movl %esp, %eax");
}
int main()
{
printf("0x%x\n",find_start());
}
Если мы скомпилируем это и запустим несколько раз, мы получим:
shellcoders@debian:~/chapter_2$ ./find_start
0xbffffad8
shellcoders@debian:~/chapter_2$ ./find_start
0xbffffad8
shellcoders@debian:~/chapter_2$ ./find_start
0xbffffad8
shellcoders@debian:~/chapter_2$ ./find_start
0xbffffad8
Буду признателен, если кто-нибудь сможет прояснить мне эту тему.
3 ответа
Когда я печатаю адрес переменной, отображается ли он относительный (виртуальный) адрес или физический адрес, в котором переменная находится в памяти?
Аналогом относительного адреса является абсолютный адрес. Это не имеет никакого отношения к расположению между виртуальным и физическим адресами.
В большинстве распространенных современных операционных систем, таких как Windows, Linux и MacOS, если вы не пишете драйвер, вы никогда не встретите физические адреса. Они обрабатываются внутри операционной системой. Вы будете работать только с виртуальными адресами.
Почему этот код выдает одно и то же значение указателя стека для каждого прогона (из руководства шеллкодера, стр. 28)?
В большинстве современных операционных систем каждый процесс имеет собственное адресное пространство виртуальной памяти. Исполняемый файл загружается по предпочтительному базовому адресу в этом виртуальном адресном пространстве, если это возможно, в противном случае он загружается по другому адресу (перемещается). Предпочтительный базовый адрес исполняемого файла обычно сохраняется в его заголовке. В зависимости от операционной системы и процессора, куча, вероятно, создается по более высокому адресу, поскольку куча обычно растет вверх (в сторону более высоких адресов). Поскольку стек обычно растет вниз (в сторону более низких адресов), он, вероятно, будет создан ниже нагрузки адрес исполняемого файла и растут к адресу 0.
Поскольку предпочтительный адрес загрузки одинаков при каждом запуске исполняемого файла, вполне вероятно, что адреса виртуальной памяти совпадают. Однако это может измениться, если используется рандомизация пространства макетов адресов. Кроме того, то, что адреса виртуальной памяти одинаковы, еще не означает, что адреса физической памяти тоже одинаковы.
Любая программа, которую я буду запускать, производит этот адрес?
В зависимости от вашей операционной системы вы можете установить предпочтительный базовый адрес, по которому ваша программа загружается в виртуальную память в настройках компоновщика. Многие программы могут по-прежнему иметь тот же базовый адрес, что и ваша программа, возможно, потому что вы оба использовали один и тот же компоновщик с настройками по умолчанию.
Виртуальные адреса только для одной программы? Допустим, у меня есть 2 программы: program1 и program2. Может ли программа2 получить доступ к памяти программы1?
Программа2 не может напрямую обращаться к памяти программы1, поскольку они имеют отдельные адресные пространства виртуальной памяти. Однако одна программа может запросить у операционной системы разрешение на доступ к пространству доступа другого процесса. Операционная система обычно предоставляет это разрешение при условии, что у программы достаточно привилегий. В Windows это можно сделать, например, с помощью функции WriteProcessMemory. Linux предлагает аналогичные функциональные возможности, используя ptrace и записывая в {{ X0 } } . См. эту ссылку для получения дополнительной информации.
Вы получаете виртуальные адреса. Ваша программа никогда не увидит физические адреса. Когда-либо.
Может ли программа2 получить доступ к памяти программы1?
Нет , потому что вы не можете иметь адреса, которые указывают на память программы. Если у вас есть виртуальный адрес 0xabcd1234 в процессе program1, и вы пытаетесь прочитать его из процесса program2, вы получаете 0xabcd1234 для program2 (или вылетает, если в program2 такого адреса нет). Это не проверка прав доступа - это не значит, что процессор идет в память и видит: «О, это память программы1, я не должен к ней обращаться». Это собственное пространство памяти program2.
Но да , если вы используете «разделяемую память», чтобы попросить ОС разместить одинаковую физическую память в обоих процессах.
И да , если вы используете ptrace
или /proc/<pid>/mem
, чтобы попросить ОС хорошо читать из памяти другого процесса, и у вас есть разрешение на это, тогда это будет сделано ,
почему этот код выдает одно и то же значение указателя стека для каждого прогона (из руководства шеллкодера, стр. 28)? любая программа, которую я буду запускать, будет производить этот адрес?
Очевидно, эта программа всегда имеет это значение указателя стека. У разных программ могут быть разные указатели стека. А если вы поместите больше локальных переменных в main
или вызовете find_start
из другой функции, вы получите другое значение указателя стека, потому что в стек будет помещено больше данных.
Примечание: даже если вы запустите программу дважды в одно и то же время, адрес будет одинаковым, потому что это виртуальные адреса, и у каждого процесса есть свое собственное виртуальное адресное пространство. Это будут разные физические адреса, но вы не видите физических адресов.
В примере переполнения стека в книге, которую я упоминал, они перезаписывают адрес возврата в стеке на адрес эксплойта в переменных окружения. как это работает ?
Все это работает в рамках одного процесса.
Сосредоточив внимание только на небольшой части вашего вопроса.
#include <stdio.h>
// find_start.c
unsigned long find_start(void)
{
__asm__("movl %esp, %eax");
}
unsigned long nest ( void )
{
return(find_start());
}
int main()
{
printf("0x%lx\n",find_start());
printf("0x%lx\n",nest());
}
gcc so.c -o so
./so
0x50e381a0
0x50e38190
Здесь нет никакой магии. Виртуальное пространство позволяет создавать программы одинаково. Мне не нужно знать, где будет жить моя программа, каждую программу можно скомпилировать для одного и того же адресного пространства, при загрузке и запуске они могут видеть одно и то же виртуальное адресное пространство, потому что все они отображаются в отдельные / разные физические адресные пространства.
readelf -a so
(но я предпочитаю objdump
Objdump -D так
Disassembly of section .text:
0000000000000540 <_start>:
540: 31 ed xor %ebp,%ebp
542: 49 89 d1 mov %rdx,%r9
545: 5e pop %rsi
....
000000000000064a <find_start>:
64a: 55 push %rbp
64b: 48 89 e5 mov %rsp,%rbp
64e: 89 e0 mov %esp,%eax
650: 90 nop
651: 5d pop %rbp
652: c3 retq
0000000000000653 <nest>:
653: 55 push %rbp
654: 48 89 e5 mov %rsp,%rbp
657: e8 ee ff ff ff callq 64a <find_start>
65c: 5d pop %rbp
65d: c3 retq
000000000000065e <main>:
65e: 55 push %rbp
65f: 48 89 e5 mov %rsp,%rbp
662: e8 e3 ff ff ff callq 64a <find_start>
667: 48 89 c6 mov %rax,%rsi
66a: 48 8d 3d b3 00 00 00 lea 0xb3(%rip),%rdi # 724 <_IO_stdin_used+0x4>
671: b8 00 00 00 00 mov $0x0,%eax
676: e8 a5 fe ff ff callq 520 <printf@plt>
67b: e8 d3 ff ff ff callq 653 <nest>
Итак, две вещи или, может быть, более двух вещей. наша точка входа _start находится в оперативной памяти по низкому адресу. низкий виртуальный адрес. в этой системе с этим компилятором я ожидаю, что все / большинство программ будут запускаться в одном и том же месте или в одном и том же месте, или в некоторых случаях это может зависеть от того, что находится в моей программе, но оно должно быть где-то на низком уровне.
Указатель стека, хотя, если вы проверяете выше и сейчас, когда я набираю вещи:
0x355d38d0
0x355d38c0
Это изменилось.
0x4ebf1760
0x4ebf1750
0x31423240
0x31423230
0xa63188d0
0xa63188c0
Несколько раз в течение нескольких секунд. Стек является относительной, а не абсолютной вещью, поэтому нет необходимости создавать фиксированный адрес, который всегда будет одинаковым. Должен находиться в пространстве, связанном с этим пользователем / потоком и виртуальным, поскольку он проходит через MMU по соображениям защиты. Нет никаких причин для виртуального адреса не совпадать с физическим адресом. Код / драйвер ядра, который управляет MMU для платформы, запрограммирован, чтобы делать это определенным образом. у вас может быть адресное пространство для кода, начинающееся с 0x0000 для каждой программы, и вы можете пожелать, чтобы адресное пространство для данных было одинаковым, с нуля. но для стека это не имеет значения. и на моей машине, моей ОС, эта конкретная версия в этот конкретный день не соответствует.
Первоначально я думал, что ваш вопрос отличается от факторов, специфичных для вашей сборки, и настроек. Для конкретной сборки один вызов find_start будет иметь фиксированный относительный адрес для указателя стека, каждая функция, которая использует стек, вернет его так, как он был найден, при условии, что вы не можете изменить компиляцию программы во время выполнения указатель стека для отдельного экземпляра вызова, при котором вложенность будет одинаковой, потребление стека каждой функцией по пути будет одинаковым.
Я добавил еще один слой и, рассмотрев беспорядок дизассемблирования, main, nest и find_start, указал указатель стека (неоптимизированный), поэтому для этих прогонов они разнесены на 0x10. если бы я добавил / удалил больше кода для каждой функции, чтобы изменить использование стека в одной или нескольких функциях, эта дельта могла бы измениться.
Но
gcc -O2 so.c -o so
objdump -D so > so.txt
./so
0x0
0x0
Disassembly of section .text:
0000000000000560 <main>:
560: 48 83 ec 08 sub $0x8,%rsp
564: 89 e0 mov %esp,%eax
566: 48 8d 35 e7 01 00 00 lea 0x1e7(%rip),%rsi # 754 <_IO_stdin_used+0x4>
56d: 31 d2 xor %edx,%edx
56f: bf 01 00 00 00 mov $0x1,%edi
574: 31 c0 xor %eax,%eax
576: e8 c5 ff ff ff callq 540 <__printf_chk@plt>
57b: 89 e0 mov %esp,%eax
57d: 48 8d 35 d0 01 00 00 lea 0x1d0(%rip),%rsi # 754 <_IO_stdin_used+0x4>
584: 31 d2 xor %edx,%edx
586: bf 01 00 00 00 mov $0x1,%edi
58b: 31 c0 xor %eax,%eax
58d: e8 ae ff ff ff callq 540 <__printf_chk@plt>
592: 31 c0 xor %eax,%eax
594: 48 83 c4 08 add $0x8,%rsp
598: c3 retq
По какой-то причине оптимизатор не распознал возвращаемое значение.
unsigned long fun ( void )
{
return(0x12345678);
}
00000000000006b0 <fun>:
6b0: b8 78 56 34 12 mov $0x12345678,%eax
6b5: c3 retq
Соглашение о вызовах выглядит хорошо.
Поместите find_start в отдельный файл, чтобы оптимизатор не смог его удалить
gcc -O2 so.c sp.c -o so
./so
0xb1192fc8
0xb1192fc8
./so
0x7aa979d8
0x7aa979d8
./so
0x485134c8
0x485134c8
./so
0xa8317c98
0xa8317c98
./so
0x2ba70b8
0x2ba70b8
Disassembly of section .text:
0000000000000560 <main>:
560: 48 83 ec 08 sub $0x8,%rsp
564: e8 67 01 00 00 callq 6d0 <find_start>
569: 48 8d 35 f4 01 00 00 lea 0x1f4(%rip),%rsi # 764 <_IO_stdin_used+0x4>
570: 48 89 c2 mov %rax,%rdx
573: bf 01 00 00 00 mov $0x1,%edi
578: 31 c0 xor %eax,%eax
57a: e8 c1 ff ff ff callq 540 <__printf_chk@plt>
57f: e8 4c 01 00 00 callq 6d0 <find_start>
584: 48 8d 35 d9 01 00 00 lea 0x1d9(%rip),%rsi # 764 <_IO_stdin_used+0x4>
58b: 48 89 c2 mov %rax,%rdx
58e: bf 01 00 00 00 mov $0x1,%edi
593: 31 c0 xor %eax,%eax
595: e8 a6 ff ff ff callq 540 <__printf_chk@plt>
Я не позволил ему встроить те функции, которые он может видеть в гнезде, поэтому он включил его, удалив изменение стека, которое пришло с ним. Так что теперь значение вложенное или нет то же самое.
Похожие вопросы
Новые вопросы
c++
C ++ - это язык программирования общего назначения. Первоначально он был разработан как расширение C и имеет аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде (который должен быть) скомпилирован с помощью компилятора C ++. Используйте тег для конкретной версии для вопросов, связанных с конкретной версией стандарта [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] или [C ++ 23] и т. Д. .