Относительные и физические адреса в C ++ - PullRequest
1 голос
/ 10 февраля 2020

Я недавно начал изучать управление памятью и прочитал об относительных адресах и физических адресах, и у меня возник вопрос:

Когда я печатаю адрес переменной, показывает ли он относительный (виртуальный) адрес или физический адрес, где находится переменная в памяти?

И еще один вопрос, касающийся управления памятью:

Почему этот код выдает одно и то же значение указателя стека для каждого прогона (от Руководство по 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

Буду признателен, если кто-то сможет уточнить это топи c для меня.

Ответы [ 3 ]

3 голосов
/ 10 февраля 2020

Вы получаете виртуальные адреса. Ваша программа никогда не увидит физические адреса. Когда-либо.

может ли программа2 обращаться к памяти программы1?

Нет , поскольку у вас не может быть адресов, указывающих на память программы1. Если у вас есть виртуальный адрес 0xabcd1234 в процессе program1, и вы пытаетесь прочитать его из процесса program2, вы получаете 0xabcd1234 программы2 (или cra * sh, если в program2 такого адреса нет). Это не проверка прав доступа - это не значит, что процессор идет в память и видит: «О, это память программы, я не должен к ней обращаться». Это собственное пространство памяти программы 2.

Но да , если вы используете "разделяемую память", чтобы попросить ОС поместить одинаковую физическую память в оба процесса.

И да , если вы используете ptrace или /proc/<pid>/mem, чтобы попросить ОС приятно читать из памяти другого процесса, и у вас есть разрешение на это, то это будет сделано.

почему этот код выдает одно и то же значение указателя стека для каждого запуска (из справочника шеллкодера, стр. 28)? любая программа, которую я буду запускать, будет производить этот адрес?

Очевидно, , что программа всегда имеет это значение указателя стека. Разные программы могут иметь разные указатели стека. И если вы поместите больше локальных переменных в main или вызовете find_start из другой функции, вы получите другое значение указателя стека, потому что в стек будет помещено больше данных.

Примечание: даже если вы запустите программу дважды в одно и то же время, адрес будет одинаковым, потому что это виртуальные адреса, и у каждого процесса есть свое собственное виртуальное адресное пространство. Это будут разные физические адреса, но вы не увидите физических адресов.

В примере переполнения стека в упомянутой мной книге они перезаписывают адрес возврата в стеке на адрес эксплойта в переменные окружающей среды. как это работает?

Все это работает в рамках одного процесса.

3 голосов
/ 10 февраля 2020

Когда я печатаю адрес переменной, показывает ли она относительный (виртуальный) адрес или физический адрес, где переменная находится в памяти?

Аналог относительного адреса это абсолютный адрес. Это не имеет ничего общего с назначением между виртуальными и физическими адресами.

В большинстве распространенных современных операционных систем, таких как Windows, Linux и MacOS, если вы не пишете водитель, вы никогда не встретите физические адреса. Они обрабатываются внутри операционной системой. Вы будете работать только с виртуальными адресами.

Почему этот код выдает одно и то же значение указателя стека для каждого запуска (из справочника шеллкодера, стр. 28)?

Вкл. В большинстве современных операционных систем каждый процесс имеет свое собственное адресное пространство виртуальной памяти. Исполняемый файл загружается по предпочтительному базовому адресу в этом виртуальном адресном пространстве, если это возможно, в противном случае он загружается по другому адресу (перемещается). Предпочтительный базовый адрес исполняемого файла обычно хранится в его заголовке. В зависимости от операционной системы и ЦП, куча 1016 *, вероятно, создается по более высокому адресу, поскольку куча обычно растет вверх (по направлению к старшим адресам). Поскольку стек обычно растет вниз (в сторону более низких адресов), он, вероятно, будет создан ниже адреса загрузки исполняемого файла и увеличится до адреса 0.

Поскольку предпочтительным адресом загрузки является То же самое каждый раз, когда вы запускаете исполняемый файл, вполне вероятно, что адреса виртуальной памяти совпадают. Однако это может измениться, если используется рандомизация пространства адресов . Кроме того, то, что адреса виртуальной памяти одинаковы, еще не означает, что адреса физической памяти тоже одинаковы.

Производит ли какой-либо из программ, которые я запускаю, этот адрес?

В зависимости от вашей операционной системы вы можете установить предпочитаемый базовый адрес, по которому ваша программа загружается в виртуальную память, в настройках компоновщика. Многие программы могут по-прежнему иметь тот же базовый адрес, что и ваша программа, возможно, потому что вы оба используете один и тот же компоновщик с настройками по умолчанию.

Виртуальные адреса указаны только для каждой программы? Допустим, у меня есть 2 программы: program1 и program2. Может ли программа2 обращаться к памяти программы1?

Программа2 не может напрямую обращаться к памяти программы1, поскольку они имеют отдельные адресные пространства виртуальной памяти. Однако одна программа может запросить у операционной системы разрешение на доступ к пространству доступа другого процесса. Операционная система обычно предоставляет это разрешение при условии, что у программы достаточно привилегий. На Windows это можно сделать, например, с помощью функции WriteProcessMemory . Linux предлагает аналогичные функции, используя ptrace и запись в /proc/[pid]/mem. См. эту ссылку для получения дополнительной информации.

0 голосов
/ 10 февраля 2020

Сфокусируясь только на небольшой части вашего вопроса.

#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

Здесь нет магов c. Виртуальное пространство позволяет программам быть построенными одинаково. Мне не нужно знать, где будет жить моя программа, каждая программа может быть скомпилирована для одного и того же адресного пространства, при загрузке и запуске может видеть одно и то же виртуальное адресное пространство, поскольку все они сопоставлены с отдельными / разными физическими адресными пространствами.

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 находится в ram по низкому адресу. низкий виртуальный адрес. в этой системе с этим компилятором я бы ожидал, что все / большинство программ будут запускаться в одном и том же месте или в одном и том же месте, или в некоторых случаях это может зависеть от того, что находится в моей программе, но это должно быть где-то низко.

Хотя указатель стека, если вы проверяете выше и сейчас, когда я набираю вещи:

0x355d38d0
0x355d38c0

он изменился.

0x4ebf1760
0x4ebf1750

0x31423240
0x31423230

0xa63188d0
0xa63188c0

несколько раз в течение нескольких секунд. Стек является относительной вещью, а не абсолютной, поэтому нет необходимости создавать постоянный адрес, который всегда будет одинаковым. Нужно находиться в пространстве, связанном с этим пользователем / поток и виртуальный, так как он проходит через MMU по соображениям защиты. Нет никаких причин для виртуального адреса не совпадать с физическим адресом. Код / драйвер ядра, который управляет MMU для платформы, запрограммирован, чтобы делать это определенным образом. у вас может быть адресное пространство для кода, начинающееся с 0x0000 для каждой программы, и вы можете иметь sh адресное пространство для данных, которые будут одинаковыми, с нуля. но для стека это не имеет значения. и на моей машине, на моей ОС, эта конкретная версия в этот конкретный день не соответствует.

Первоначально я думал, что ваш вопрос будет другим в зависимости от факторов, которые c указаны для вашей сборки, и настроек. Для указанной c сборки один вызов find_start будет иметь фиксированный относительный адрес для указателя стека, каждая функция, использующая этот стек, вернет его так, как он был найден, при условии, что вы не можете изменить компиляцию программы. во время выполнения указателя стека для одного экземпляра вызова вложенность будет одинаковой, потребление стека каждой функцией по пути будет одинаковым.

Я добавил еще один слой и, глядя на разборку, основной , 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>

Я не позволил этому встроить те функции, которые он может видеть, вкладывают в него, удаляя изменение стека, которое пришло с ним. Так что теперь значение, вложенное или нет, одинаково.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...