汇编与寄存器

Wednesday, October 26, 2022
本文共2051字
5分钟阅读时长
pwn

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/principle/%E6%B1%87%E7%BC%96%E4%B8%8E%E5%AF%84%E5%AD%98%E5%99%A8/。商业转载请联系作者获得授权,非商业转载请注明出处!

Kindness is the language which the deaf can hear and the blind can see. — Mark Twain

寄存器

在16位系统中(8086CPU)

数据寄存器

用于暂存数据的寄存器有AX、BX、CX、DX,这些寄存器可以起到暂存16位的数据的作用。每一个又可以分成高八位和低八位,如AX寄存器,高八位为AH,低八位为AL,将超出八位的数据存入AH或AL不会自动将数据填入AX的剩下空间,而是会溢出,产生数据丢失

  • 寄存器AX和AL通常称为累加器(Accumulator),用累加器进行的加减乘除耗时更少
  • 寄存器BX称为基地址寄存器(Base Register),可作为存储器指针来使用
  • 寄存器CX称为计数寄存器(Count Register)。在循环和字符串操作时,要用它来控制循环次数(循环的次数保存在CX中,每循环一次CX就减一,直至0循环停止)
  • 寄存器DX称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址

变址寄存器

SI和DI称为变址寄存器,用于存放存储单元在段内的偏移量,SI储存源地址(source index),DI储存目的地址(destination index)

指针寄存器

BP、SP称为指针寄存器,SP是堆栈指针(stack pointer),存放栈顶地址;BP是基址指针(base pointer),存放堆栈基址偏移,相当于为最开始的SP做备份(见上节RBP和RSP的讲解)

段寄存器

CS、DS、SS、ES被称为段寄存器,用于储存段地址,需要和偏移量一起使用才能查找到内存中的数据

  • CS为代码段(code segment),和IP寄存器一起使用,用于确定下一条指令的地址
  • DS为数据段(stack segment),用于存储数据所在的段地址,和诸如[0]一类的表达一起使用,如果没有特别说明,[0]==DS:[0]
  • SS为堆栈段(stack segment),和SP寄存器一起使用,用于确定堆栈的位置,长期指向栈顶
  • ES为扩展段(extend segment),为扩展的段寄存器

指针指令寄存器

IP被称作指针指令寄存器,储存相对于段寄存器CS的偏移量,在内存中找到CS:IP处的指令并执行

在32位系统中(i386)

在AX、BX、CX、DX等前面加E,变成了EAX、EBX、ECX、EDX等,而原本的AX、BX、CX、DX等成为了其低16位,而存取它的高十六位可以通过移位操作进行(向左移16位),或者通过bswap EAX进行(交换EAX的高、低位)

在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,但在32位CPU中,其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果,而且也可作为指针寄存器,所以,这些32位寄存器更具有通用性

在64位系统中(amd64)

将32位中的E前缀改为了R前缀,而原本32位中的名称用来代指64位中寄存器的低位,同时64位系统增加了8个通用寄存器,命名为R8~R15,在其后加上d,w,b指定长度(d最长,b最短)

因此64位系统中的寄存器比32位要多8个

✨ 32位系统中,函数的传递参数保存在栈帧中,而64位系统保存在寄存器中,分别用RDI,RSI,RDX,RCX,R8,R9作为第1-6个参数,用RAX保存返回值

汇编

系统如何执行汇编代码

当我们执行一个exe文件时,系统会将这个exe文件载入内存,并且将(R\E)CS:(R\E)IP指向这块内存的最低地址,每执行一句IP就自动增加(长度不固定),而所要执行的这一段代码在汇编内称为代码段。

同时,exe文件还会有它的栈段和数据段,这两段地址在内存中用汇编指令dw(define word)声明,dw 0就是声明一个为0的字(两个字节),dw 0,0就是声明两个字

data segment
    dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
data ends

stack segment
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

将对应指针寄存器指向对应段

我们的栈、数据、代码在哪里完全取决于对应的段寄存器和指针寄存器、指针指令寄存器指向哪里

因此,我们需要改变对应寄存器的值

代码

我们需要设置CS:IP,使其指向代码被载入的内存处

在汇编中,只需要在需要执行的代码前加上start:,最后end start即可

code segment
start:
    mov ax, stack
    mov ss, ax
    mov sp, 100h  ;初始化栈指针

    mov ax, data
    mov ds,ax   ;初始化内存指针
code ends
end start

需要将SS指向栈段,并设置SP指向栈底(一开始栈为空)

mov ax, stack
mov ss, ax
mov sp, 100h  ;初始化栈指针

数据

将DS指向数据段,就可以用[0]式的表达式存取数据了

mov ax, data
mov ds,ax   ;初始化内存指针

PUSH指令和POP指令

PUSH指令和POP指令均不会变更原来的数据,而是变更目的地的数据。

  • push ds:[0]就是把数据段中ds:[0]处的数据推入栈中,相当于mov ss:sp ds:[0]
  • pop ds:[0]就是把栈顶的数据弹入ds:[0]中,相当于mov ds:[0] ss:sp