堆栈及汇编基础

暑假生活开始,学习的好时间,今天就先重温一下堆栈的基础,好好巩固一下。

内存区域

不同的操作系统,一个进程被分配进入的内存区域都会不同,但是无论是哪个系统,进程使用的内存按照功都同样大致分为四种:

  1. 代码区:这个区域储存装入的要被执行的二进制机器代码,处理器会到这个区域获取指令并进行执行。
  2. 数据区:这个区域储存程序运行过程中出现的全局变量,局部变量等。
  3. 堆区:进程需要运行的时候,可以向这个区域申请空间,当运行结束之后空间将归还堆区,这就是堆的特点,动态分配和回收空间。
  4. 栈区:用于动态存储函数之间的调用关系,从而保证调用函数后能回到主函数中继续执行程序。

    Windows下高级语言写出一个程序经过编译链接之后便可以生成一个可执行文件,这个可执行文件被装载运行之后便成为了所谓的进程。

    每一个可执行程序中都包含着二进制级别的机器代码,这些代码将会被装载入代码区,处理器会一条一条的读取并运行。如果代码中有开辟动态内存的请求,则会在内存堆区中分配一个大小适合的区域给代码使用;当函数调用发生之后,栈中便会有栈帧自动保存函数的调用关系信息,以便于函数调用结束能回到主函数继续执行程序。

    栈帧:C语言中,栈中的栈帧对应着未运行完成的函数,并且是一一对应,栈帧从逻辑上理解就是一个函数执行的环境:函数的参数,函数的变量,函数的返回地址等。
    在函数栈帧中,一般包含如下几类重要信息。

  • 局部变量:为函数局部变量开辟内存空间。
  • 栈帧状态值:保存前栈帧的顶部和底部,用于在本栈弹出后可以恢复上一个栈帧。
  • 函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令的位置,以便在函数返回时能回到调用的代码区中继续执行命令。

函数调用

假设一个如下的简单函数:

int func_B(int a,int b)
{
  int i,j;
  i = a + b;
  j = a - b;
  return i * j;
}
int func_A(int c,int d)
{
  int t;
  t = func_B(c,d)+c;
  return t;
}
int main()
{
  int main;
  main=func_A(4,3);
  return main;
}

函数在进行函数调用在栈中的操作如下:

  1. 在main函数调用func_A的时候,首先在自己的栈帧中压入函数返回地址,然后为func_A创建新的栈帧并压入栈
  2. 在func_A调用func_B的时候,同样先在自己的栈帧中压入函数返回地址,然后为func_B创建新的栈帧并压入栈
  3. 在func_B返回时候,func_B专属的栈帧呗弹出统栈,这样就露出func_A的返回地址,这样就直接执行这个地址返回func_A中继续执行
  4. 在func_A返回时候,func_A专属的栈帧呗弹出统栈,这样就露出main的返回地址,这样就直接执行这个地址返回main中继续执行。

注意:在实际运行中,main函数并不是第一个被调用的函数,程序被装入内存前还有一些其他的操作。

函数调用本身的大体步骤如下:(这个对于后面pwn的学习中payload的构造有关键作用)

  1. 参数入栈:将参数从右向左依次压入系统栈中。
  2. 返回地址入栈:将当前代码去调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
  3. 代码区跳转:处理器从当前代码区跳转到被调用的入口处。
  4. 栈帧调整:(1)保存当前栈帧的状态值,以备后面恢复本栈帧时使用(EBP入栈)。
    (2)将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部。)

汇编语言

在汇编语言当中,主要是四类寄存器。

  1. 4个数据寄存器(EAX,EBX,ECX,EDX)。
  2. 2个变址寄存器(ESI,EDI)、2个指针寄存器(ESP、EBP)
  3. 6个端寄存器(ES、CS、SS、DS、FS、GS)
  4. 1个指令指针寄存器(EIP)、一个标志寄存器(EFlags)

1.寄存器

(1). 数据寄存器

数据寄存器主要用来保存操作数以及运算结果,这样就便于节省读取操作数的时间。
32位CPU4个32位通用的寄存器EAX,EBX,ECX,EDX。他们只会对低16为数据进行存储,不会影响高16位的数据。这低16位的寄存器又被称为AX、BX、DC、CX。与先前的CPU寄存器相一致。

  • EAX:累加寄存器,用于加减乘除的操作,也用于存储函数的返回值。使用频率非常高。
  • EBX:基址寄存器,作为存储器指针来用。
  • ECX:计数寄存器,在循环和字符串操作时,用他来计数;在位操作是,要用CL来指明移位的位数。
  • EDX:数据寄存器,在进行乘除运算时,它可作为默认的操作数进行操作。

(2). 变址寄存器

32位CPU有两个32位通用的变址寄存器ESI和EDI,与前者一样,只对低16位数据进程存取。

  • ESI:在内存操作指令中作为源地址指针使用,处理字符串时候通常指向源串。
  • EDI:在内存操作指令中作为目的地址指针使用,处理字符串时候通常指向目标串。

(3). 指针寄存器

EBP和ESP就是指针寄存器,主要用于存放堆栈内储存单元的偏移量,用他们可以实现多种寄存器储存操作数的寻址方式。

  • EBP: 基地址寄存器,内存放一个指针永远指向系统栈的最上面一个栈帧的底部。通过它减去一定偏移量对栈中元素进行访问。
  • ESP:栈指针寄存器,内存放一个指针永远指向系统栈的最上面一个栈帧的顶部。

(4). 段寄存器

段寄存器是根据内存分段的模式而设置的。内存单元的物理地址由段寄存器的值和一个偏移量组合而成的,这样可用两个较少位数的值组合成一个可访问的较大物理空间的内存地址。

  • CS(Code): 代码段寄存器,其值为代码段的段值
  • DS(Date): 数据段寄存器,其值为数据段的段值
  • ES(Extra): 附加段寄存器,其值为附加数据段的段值
  • SS(Strack):堆栈段寄存器,其值为堆栈段的段值
  • FS(Flag): 标志段寄存器,其值为附加数据段的段值
  • GS(Global):全局段寄存器,其值为附加数据段的段值

8086 CPU依赖其内部的四个段寄存器实现寻址1M字节物理地址空间。8086把1M字节地址空间划分为若干逻辑段,当前使用段的段值存放在段寄存器中。由段寄存器和段内偏移形成20位地址。

汇编中表示:

段值:偏移

计算方法:

物理地址 = 段值×16 + 偏移

举个

用16进制表示的逻辑地址1234:3456H所对应的存储单元的物理地址为15796H。

(5). 指令指针寄存器

  • EIP:存放个下一次将要执行的指令在代码段中的偏移量。

(6). 标志寄存器

8086 CPU中有一个16位的标志寄存器,包含了9个标志,主要用于反映处理器的状态和运算结果的某些特征。

9个标志寄存器可以分为两组,第一组6个标志寄存器主要受加减运算和逻辑运算结果的影响,称为运算结果标志,第二组标志不受运算结果的影响,称为状态控制标志。

1. 进位标志CF(Carry Flag)

进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。

2. 奇偶标志PF(Parity Flag)

奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值1,否则其值为0

3. 辅助进位标志AF(Auxiliary Carry Flag)

在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:
(1)、在字操作时,发生低字节向高字节进位或借位时;
(2)、在字节操作时,发生低4位向高4位进位或借位时。

4. 零标志ZF(Zero Flag)

零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位

5. 符号标志SF(Sign Flag)

符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1

6. 溢出标志OF(Overflow Flag)

溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0(“溢出”和“进位”是两个不同含义的概念)

7. 中断允许标志IF(Interrupt-enable Flag)

中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下
(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;
(2)、当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求

8. 追踪标志TF(Trap Flag)

当追踪标志TF被置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。

9. 方向标志DF(Direction Flag)

方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节——字符串操作指令——中给出。在微机的指令系统中,还提供了专门的指令来改变标志位DF的值

2. 主要指令

汇编中的指令可以参照这个网址进行学习(也涉及了上面的寄存器知识点):
http://www.freebuf.com/news/others/86147.html

在参加夏令营跟着读汇编呢也遇到了一些难以理解的内容:

movsb:即字符串传送指令,这条指令按字节传送数据。通过SI和DI这两个寄存器控制字符串的源地址和目标地址,比如DS:SI这段地址的N个字节复制到ES:DI指向的地址,复制后DS:SI的内容保持不变。

cld:(CLear Direction flag)则是清方向标志位,也就是使DF的值为0,在执行串操作时,使地址按递增的方式变化,这样便于调整相关段的的当前指针。这条指令与STD(SeT Direction flag)的执行结果相反,即置DF的值为1。

Rep:指令就是“重复”的意思,术语叫做“重复前缀指令”,因为既然是传递字符串,则不可能一个字(节)一个字(节)地传送,所以需要有一个寄存器来控制串长度。这个寄存器就是CX,指令每次执行前都会判断CX的值是否为0(为0结束重复,不为0,CX的值减1),以此来设定重复执行的次数。因此设置好CX的值之后就可以用REP MOVSB了。

Contents
  1. 1. 内存区域
  2. 2. 函数调用
  3. 3. 汇编语言
    1. 3.1. 1.寄存器
      1. 3.1.1. (1). 数据寄存器
      2. 3.1.2. (2). 变址寄存器
      3. 3.1.3. (3). 指针寄存器
      4. 3.1.4. (4). 段寄存器
      5. 3.1.5. (5). 指令指针寄存器
      6. 3.1.6. (6). 标志寄存器
        1. 3.1.6.1. 1. 进位标志CF(Carry Flag)
        2. 3.1.6.2. 2. 奇偶标志PF(Parity Flag)
        3. 3.1.6.3. 3. 辅助进位标志AF(Auxiliary Carry Flag)
        4. 3.1.6.4. 4. 零标志ZF(Zero Flag)
        5. 3.1.6.5. 5. 符号标志SF(Sign Flag)
        6. 3.1.6.6. 6. 溢出标志OF(Overflow Flag)
        7. 3.1.6.7. 7. 中断允许标志IF(Interrupt-enable Flag)
        8. 3.1.6.8. 8. 追踪标志TF(Trap Flag)
        9. 3.1.6.9. 9. 方向标志DF(Direction Flag)
    2. 3.2. 2. 主要指令
|