文章目录
缓冲区溢出(Buffer overflow)攻击 方法是很久之前就发表出来的技术,但是至今为止仍依然被广泛使用。这其中最大的原因就是目前还有很多程序很容易遭受buffer overflow(BOF)攻击。到目前为止,有许多相关对缓冲区溢出的解决方法的文档,但是大部分文档只针对技术性的方法进行说明,对于其原理和系统构造的说明非常少。但是理解BOF最不能缺少的就是对计算机在运行过程中的关于数据和结构的存储方法、函数调用和返回过程以及函数执行过程底层的准确信息。
本文章会铺垫以后学习BOF所需的前提知识8086系统内存和CPU寄存器的构造。
*本人是韩国计算机本科在读,平时只看韩语和英文的文档,第一次尝试用中文整理文章所以可能出现用词不当的情况(当我在中文描述不清楚的点会在旁边用英文描述),请各位大神到时候指点一下,谢谢!!!
*都是从谷歌和韩国黑客论坛进行整理翻译的,就是想巩固一下知识点。
一、8086 内存结构 (8086 Memory Architecture)

8086 basic memory structure
8086系统的基本内存构造跟<图1>类似。系统在初始化开始的时候,系统将内核加载到内存中,并检查可用的内存区域。系统将运行过程中必要的命令在内核中调用,所以内核必须在低位地址中。在32bit系统中 CPU可以处理的数据由于以32bit为单位,所以可以处理的内存地址的范围从0~。 最近的PC设备的CPU是64bit的所以它能处理从0~
.

segmented memory model
现在我们要了解的是一个进程中即一个程序为了运行所需要的内存结构。当操作系统执行一个进程时,进程被划分为一个称为段的单元,这些段被组合在一起并存储在可用内存区域中。其结构如<图2>所示。
如<图2>所示,当今的系统能够执行多任务(multi-tasking),因此可以存储多个进程并并行工作。 所以在可用的内存地址中科院保存多个段(segments)。段如上面所解释的一样,是由一个进程所组成的,所以段在内存的位置实际储存在哪里将在运行时候决定。
一个段的结构如<图2>右侧所示。每个段都由代码段、数据段和堆栈段构成。系统最多生成16383个段,其大小和类型都可以用不同的方式创建。一个段的最大大小为字节。
代码段 code segment 包含系统可以理解的命令, 即指令(instruction)。这是编译器生成的机器码 (machine code)。在执行指令的时候,会有多个分支进程、跳转和系统调用,需要注意的是在使用分支进程和跳转的时候要明确指出指定位置上的命令。但是,segment因为编译过程中不知道它将保存的位置,所以无法指定确切地址。因此,这时候逻辑地址(logical address)在segment中使用。逻辑地址(logical address)是和实际内存上的物理地址(physical address)是有相关联的。也就是说,segment是由段选择子(segment selector)找到其起始位置(offset), 并且从其起始位置的逻辑地址将决定该指令是否执行。因此,物理地址(实际内存地址)可以说是起始位置(offset)+逻辑地址(logical address) 。

logical address, physical address
如<图3>所示,假设段在实际中内存的地址是0x80010000。 在代码段(code segment)内包含的一个指令instruction IS 1 所指的地址是0x00000100, 这是逻辑地址,而且该指令的实际内存上的地址是段的起始地址 0x80010000 加上其在段的逻辑地址0x00000100, 即为0x80010100。
数据段 data segment 包含程序在运行使所使用到的数据。在这里指的数据是全局变量(global variable)。 当在程序中声明全局变量时,该变量存储在数据段data segment中。数据段进一步分为四种,该模块的数据结构、接收高级别数据的数据模块、动态生成的数据和与其他程序共享的共享数据部分。
堆栈段 stack segment 是存储当前执行的处理程序(handler)、任务(task)和程序(program)的数据区域。我们使用的缓冲区(buffer)会在这个堆栈段上。此外,程序使用可以创建多个堆栈,并且可以在每个堆栈之间切换。而且这也是局域变数(local variables)储存的位置。 堆栈在最初被生成的时候, 会被按照它所需的大小生成。并且它会根据程序的指令将数据保存的过程中,会有一个称为堆栈指针(stack pointer - SP)的寄存器指向最顶部。在堆栈中存储和读取数据的过程是通过指令执行PUSH和POP。
二、8086 CPU寄存器结构
到目前了解了单个段的构造,那么CPU为了运行进程,进程必须加载到CPU中。并且为了将分布在各个地方的指令集合(Instruction set)和数据读取和存储需要各种保存空间。另外,这些数据是CPU需要快速读取到的数据所以会使用CPU内部的内存中,这样的储存空间叫做寄存器(register)。 一般情况下,系统程序的寄存器的结构和<图4>类似。

Program register configuration of general system
寄存器根据用途又分为通用寄存器(General-Purpose register)、段寄存器(segment register)、程序状态控制寄存器(Program status and control register)以及指令指针寄存器(instruction pointer)。
通用寄存器(General-Purpose register) 存储用于计算逻辑和数学运算中使用的操作数和计算地址所用到的操作数以及内存指针。
段寄存器(segment register)是 储存代码段(code segment),数据段(data segment), 堆栈段(stack segment)的寄存器。
状态控制寄存器(Program status and control register) 包含用于检查程序当前状态或条件的标志的寄存器。
指令指针寄存器(instruction pointer) 是包含内存中要执行的下一条指令所在地址的寄存器.
通用寄存器(General-Purpose register)

General-Purpose Registers
通用寄存器是允许程序员操作的寄存器。可以理解为一种4个32bit变量。 在过去,使用16bit的时候,各个寄存器被叫做AX, BX, CX, DX.. 等,但是随着32bit的系统的出现,在前面添加E(Extended)变为EAX, EBX, ECX, EDX。 AX的寄存器的上位部分叫做AH下位部分叫做AL。 EAX, EBX, ECX, EDX这些可以由程序员根据需求自定义使用。 具体分析一下各个寄存器的使用目的。
EAX–操作数和运算结果的存储
EBX-指向DS段中数据的指针
ECX–字符串处理或循环计数器
EDX–I/O指针
ESI—DS寄存器指向的数据段中某些数据的指针。在字符串处理中指向源(source)。
EDI–表示ES寄存器所指向的数据段中的数据的格式。在字符串处理中指向目标(destination)。
ESP—指向SS寄存器指向的堆栈段顶部的指针
EBP—SS寄存器指向堆栈上一段数据的指针。
段寄存器(segment register)

segment register
段寄存器如<图6>所示,作为指针,指向进程的特定段。 CS寄存器指向代码段,DS,ES,FS,GS寄存器指向数据段,SS寄存器指向堆栈段。 根据段寄存器指向的地址,我们使用段内的特定的数据和指令。
<图7>将各个寄存器指向的段进行了进一步说明。

Segments pointed to by segment register
状态控制寄存器(Program status and control register)
状态控制寄存器是一组状态标志、控制标志和系统标志构成的。当系统进行初始化的时候,该寄存器将获得0x00000002的值。然后1,3,4,15,23~31 bit 被定义着,所以无法通过程序改变。 <图8>展示了状态寄存器的构造。

Configuration of flag register
指令指针寄存器(instruction pointer)
指令指针寄存器是包含要执行的下一条指令的当前代码段的偏移量值。它可以指向一个指令范围内线性指令集的下一个位置。此外,它还有带有JMP、Jcc、CALL、RET和IRET指令的地址值。EIP寄存器不可由程序直接访问,由控制传输指令(JMP、Jcc、CALL、RET)或中断(interrupt)和异常(exception)控制。读取EIP寄存器的方法是通过执行CALL指令,然后从过程堆栈(procedure stack)返回的指令获取地址。修改过程堆栈的返回指令指针的值,并通过执行返回指令(RET、IRET)间接指定EIP寄存器的值。
总结
以上就是本文要讲的内容,本文讲了8086系统内存和CPU寄存器的构造。了解如此复杂的结构的原因是我们进行缓存器溢出攻击需要理解适当的使用填充(padding)和找到返回地址的准确位置的汇编代码。
后面发布的文章会进行buffer相关的内容。