- 第一章 CPU 简介
- 第二章 Hello,world!
- 第三章 函数开始和结束
- 第四章 栈
- Chapter 5 printf() 与参数处理
- Chapter 6 scanf()
- CHAPER7 访问传递参数
- Chapter 8 一个或者多个字的返回值
- Chapter 9 指针
- Chapter 10 条件跳转
- 第 11 章 选择结构 switch()/case/default
- 第 12 章 循环结构
- 第 13 章 strlen()
- Chapter 14 Division by 9
- chapter 15 用 FPU 工作
- Chapter 16 数组
- Chapter 17 位域
- 第 18 章 结构体
- 19 章 联合体
- 第二十章 函数指针
- 第 21 章 在 32 位环境中的 64 位值
- 第二十二章 SIMD
- 23 章 64 位化
- 24 章 使用 x64 下的 SIMD 来处理浮点数
- 25 章 温度转换
- 26 章 C99 的限制
- 27 章 内联函数
- 第 28 章 得到不正确反汇编结果
- 第 29 章 花指令
- 第 30 章 16 位 Windows
- 第 31 章 类
- 三十二 ostream
- 34.2.2 MSVC
- 34.2.3 C++ 11 std::forward_list
- 34.3 std::vector
- 34.4 std::map and std::set
16.2 缓冲区溢出
Array[index]中 index 指代数组索引,仔细观察下面的代码,你可能注意到代码没有 index 是否小于 20。如果 index 大于 20?这是 C/C++经常被批评的特征。 以下代码可以成功编译可以工作:
#!cpp
#include <stdio.h>
int main()
{
int a[20];
int i;
for (i=0; i<20; i++)
a[i]=i*2;
printf ("a[100]=%d
", a[100]);
return 0;
};
编译后 (MSVC 2010):
#!bash
_TEXT SEGMENT
_i$ = -84 ; size = 4
_a$ = -80 ; size = 80
_main PROC
push ebp
mov ebp, esp
sub esp, 84 ; 00000054H
mov DWORD PTR _i$[ebp], 0
jmp SHORT $LN3@main
$LN2@main:
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
$LN3@main:
cmp DWORD PTR _i$[ebp], 20 ; 00000014H
jge SHORT $LN1@main
mov ecx, DWORD PTR _i$[ebp]
shl ecx, 1
mov edx, DWORD PTR _i$[ebp]
mov DWORD PTR _a$[ebp+edx*4], ecx
jmp SHORT $LN2@main
$LN1@main:
mov eax, DWORD PTR _a$[ebp+400]
push eax
push OFFSET $SG2460
call _printf
add esp, 8
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
运行,我们得到: a[100]=760826203
打印的数字仅仅是距离数组第一个元素 400 个字节处的堆栈上的数值。 编译器可能会自动添加一些判断数组边界的检测代码(更高级语言 3),但是这可能影响运行速度。 我们可以从栈上非法读取数值,是否可以写入数值呢? 下面我们将写入数值:
#!cpp
#include <stdio.h>
int main()
{
int a[20];
int i;
for (i=0; i<30; i++)
a[i]=i;
return 0;
};
我们得到:
#!bash
_TEXT SEGMENT
_i$ = -84 ; size = 4
_a$ = -80 ; size = 80
_main PROC
push ebp
mov ebp, esp
sub esp, 84 ; 00000054H
mov DWORD PTR _i$[ebp], 0
jmp SHORT $LN3@main
$LN2@main:
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
$LN3@main:
cmp DWORD PTR _i$[ebp], 30 ; 0000001eH
jge SHORT $LN1@main
mov ecx, DWORD PTR _i$[ebp]
mov edx, DWORD PTR _i$[ebp] ; that instruction is obviously redundant
mov DWORD PTR _a$[ebp+ecx*4], edx ; ECX could be used as second operand here instead
jmp SHORT $LN2@main
$LN1@main:
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
编译后运行,程序崩溃。我们找出导致崩溃的地方。 没有使用调试器,而是使用我自己写的小工具 tracer 足以完成任务。 我们用它看被调试进程崩溃的地方:
#!bash
generic tracer 0.4 (WIN32), http://conus.info/gt
New process: C:PRJ...1.exe, PID=7988
EXCEPTION_ACCESS_VIOLATION: 0x15 (<symbol (0x15) is in unknown module>), ExceptionInformation
[0]=8
EAX=0x00000000 EBX=0x7EFDE000 ECX=0x0000001D EDX=0x0000001D
ESI=0x00000000 EDI=0x00000000 EBP=0x00000014 ESP=0x0018FF48
EIP=0x00000015
FLAGS=PF ZF IF RF
PID=7988|Process exit, return code -1073740791
我们来看各个寄存器的状态,异常发生在地址 0x15。这是个非法地址—至少对 win32 代码来说是!这种情况并不是我们期望的,我们还可以看到 EBP 值为 0x14,ECX 和 EDX 都为 0x1D。 让我们来研究堆栈布局。 代码进入 main()后,EBP 寄存器的值被保存在栈上。为数组和变量 i 一共分配 84 字节的栈空间,即(20+1)*sizeof(int)。此时 ESP 指向_i 变量,之后执行 push something,something 将紧挨着_i。 此时 main() 函数内栈布局为:
#!bash
ESP
ESP+4
ESP+84
ESP+88
4 bytes for i
80 bytes for a[20] array
saved EBP value
returning address
指令 a[19]=something 写入最后的 int 到数组边界(这里是数组边界!)。 指令 a[20]=something,something 将覆盖栈上保存的 EBP 值。 请注意崩溃时寄存器的状态。在此例中,数字 20 被写入第 20 个元素,即原来存放 EBP 值得地方被写入了 20(20 的 16 进制表示是 0x14)。然后 RET 指令被执行,相当于执行 POP EIP 指令。 RET 指令从堆栈中取出返回地址(该地址为 CRT 内部调用 main() 的地址),返回地址处被存储了 21(0x15)。CPU 执行地址 0x15 的代码,异常被抛出。 Welcome!这被称为缓冲区溢出 4。 使用字符数组代替 int 数组,创建一个较长的字符串,把字符串传递给程序,函数没有检测字符串长度,把字符复制到较短的缓冲区,你能够找到找到程序必须跳转的地址。事实上,找出它们并不是很简单。 我们来看 GCC 4.4.1 编译后的同类代码:
#!bash
public main
main proc near
a = dword ptr -54h
i = dword ptr -4
push ebp
mov ebp, esp
sub esp, 60h
mov [ebp+i], 0
jmp short loc_80483D1
loc_80483C3:
mov eax, [ebp+i]
mov edx, [ebp+i]
mov [ebp+eax*4+a], edx
add [ebp+i], 1
loc_80483D1:
cmp [ebp+i], 1Dh
jle short loc_80483C3
mov eax, 0
leave
retn
main endp
在 linux 下运行将产生:段错误。 使用 GDB 调试:
#!bash
(gdb) r
Starting program: /home/dennis/RE/1
Program received signal SIGSEGV, Segmentation fault.
0x00000016 in ?? ()
(gdb) info registers
eax 0x0 0
ecx 0xd2f96388 -755407992
edx 0x1d 29
ebx 0x26eff4 2551796
esp 0xbffff4b0 0xbffff4b0
ebp 0x15 0x15
esi 0x0 0
edi 0x0 0
eip 0x16 0x16
eflags 0x10202 [ IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb)
寄存器的值与 win32 例子略微不同,因为堆栈布局也不太一样。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论