【问题标题】:Understanding how EIP (RIP) register works?了解 EIP (RIP) 寄存器的工作原理?
【发布时间】:2015-02-10 07:14:00
【问题描述】:

我是计算机体系结构和处理器/内存级别发生的低级事物的完全新手。我先这么说吧。我对计算机所做的几乎总是处于高级编程水平。 C++、Java等

话虽如此,我目前正在阅读一本开始深入研究低级编程内容、汇编、寄存器、指针等的书。我很难理解 EIP 寄存器的工作原理。

从书上讲,每个内存地址都有一个字节,每个字节都有一个内存地址。

从我读到的关于 EIP 寄存器的内容来看,它指向处理器要执行的下一组指令。在本书中使用调试工具 (GDB) 进行操作时,如果您要检查特定位置的内存,请说:

x/8xb 据称它可以让您检查内存地址的前 8 个字节。但是如果每个内存地址只有1个字节,我就不明白了。有人可以帮我理解这一点吗?我一直在寻找有关此寄存器如何工作和功能的详尽解释,但我真的找不到任何东西

【问题讨论】:

  • 这是一个关于具体架构的实际问题,它是一个工程问题,而不是一个科学问题,所以我将它迁移到一个有主题的网站。
  • 它显示了从指定的内存地址按顺序增加的 8 个字节。
  • 他们并不都同一个地址。如果您将地址设置为高一到两个然后再次显示 8 个字节,您可以很容易地看到这一点。
  • 当他们说“特定地址的 8 个字节”时,他们的意思是“从该地址开始的内存块中的 8 个字节”。第二,第三个字节等等会有更大的地址。
  • 你对这个问题的答案还有兴趣吗?

标签: assembly x86 gdb cpu-registers computer-architecture


【解决方案1】:

instruction pointer 通常是微处理器上的一个寄存器(内存),对于 32 位系统,它以 4(4 个字节)递增,对于 64 位系统,它以 8(即 8 个字节)递增,因此它指向下一个指令。

当程序进入一个函数时,一个保存的指令指针(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

现在我知道namerip 之间的距离(不是值,而是它们的位置):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)
$  

如您所见,输入导致溢出并覆盖指令指针的值,并导致指令指针跳转到打印密码的函数的位置。

不要在现实生活中编写这样的代码,但希望它有助于理解它是如何工作的,并且当你不检查输入的范围时它不起作用。

【讨论】:

  • 每个内存地址都有一个字节,每个字节都有一个内存地址。正是我们在 x86 等普通 CPU 上所拥有的。它被称为“字节可寻址存储器”。一个地址的 32 位加载从 4 个不同的地址加载字节,这些地址由 4 个地址中的最低者选择。
  • 你几乎没有回答问题 title;您正在谈论修改堆栈上保存的 RIP 值,而根本没有谈论它们如何获取存储(call)或重新加载(ret)。同样重要的是要指出输入只是间接覆盖指令指针的值。 RIP 本身是一个寄存器,没有内存地址。尽管漏洞利用社区只调用返回地址rip(不是“保存的 RIP”或任何东西),但这只是简写。在计算机体系结构中,指令指针就是寄存器本身。
  • @PeterCordes "每个内存地址都有一个字节" - 例如,32 位微处理器上的寄存器是具有内存地址的内存,它包含一个 32 位显着超过一个字节的数字不是吗?还是我误解了声明?我还通过程序计数器/指令指针的链接更新了我的答案,说明了它的一般含义。我认为溢出的示例将 PC 的使用置于上下文中。有关详细信息,可以参考 Hennessy-Patterson 的书或 David Harris 的书(“数字设计”),但 PC 仅用于控制流程。
  • 是的,每个字节都有自己的地址,但是你仍然可以说像mov rax, -123这样的7字节指令有一个地址,这是它的最低字节的地址.即使指令字节实际上来自 7 个不同的地址。或者对于数据,mov rax, [rdi]从 RDI 中的地址进行 8 字节加载。它是一个字节的地址,也是一个 qword(以及 word 和 dword)的地址。但是在可字寻址的机器上(如某些 DSP),只有整个字有单独的地址,并且最小可能的负载是一个字。 en.wikipedia.org/wiki/Word-addressable
猜你喜欢
  • 1970-01-01
  • 2017-07-02
  • 2021-08-17
  • 2014-10-21
  • 1970-01-01
  • 2016-02-20
  • 2012-12-11
  • 2011-02-18
  • 2018-02-17
相关资源
最近更新 更多