二进制入门学习笔记-1.汇编基础语法基础

寄存器基础

寄存器是CPU内部的存储设备

32位通用寄存器

寄存器 主要用途 编号 存储数据范围
EAX 累加器 0 0-0xFFFFFFFF
ECX 计数 1 0-0xFFFFFFFF
EDX I/O指针 2 0-0xFFFFFFFF
EBX DS段的数据指针 3 0-0xFFFFFFFF
ESP 堆栈指针 4 0-0xFFFFFFFF
EBP SS段的数据指针 5 0-0xFFFFFFFF
ESI 字符串操作的源指针;SS段的数据指针 6 0-0xFFFFFFFF
EDI 字符串操作的目标指针;ES段的数据指针 7 0-0xFFFFFFFF

解释:32位因此是8个F

ollydbg快捷键

按键 功能
F3 打开文件
F7 单步执行,进入函数
F8 单步执行,不进入函数

16位通用寄存器

16位通用寄存器
AX
CX
DX
BX
SP
BP
SI
DI

其实16位寄存器就是把32位砍一半

8位通用寄存器

8位通用寄存器
AH
CH
DH
BH
AL
CL
DL
BL

其实就是把16位的从中间砍一半,一半是H,一半是L

汇编指令基础

1.MOV(赋值)

  • mov eax,0x1
  • 操作码 目标操作数 源操作数
  • 将0x1值存入eax寄存器

目标操作数及源操作数可以使用如下内容 - r:通用寄存器 - m:内存 - imm:立即数

有如下规则: - 操作数宽度必须一样 - 大多数汇编指令中源操作数和目标操作数不能同时是内存单元

2.ADD(加法)

  • add eax,0x1
  • 给eax累加0x1

3.SUB(减法)

  • sub eax,0x2
  • 给eax值减去0x2

4.AND(与运算)

  • AND eax,0x2
  • 让eax和0x2做与运算并赋值给eax

5.OR(或运算)

  • OR eax,0x2
  • 让eax和0x2做或运算并赋值给eax

6.XOR(异或运算)

  • XOR eax,0x2
  • 让eax和0x2做异或运算并赋值给eax

7.NOT(非运算)

  • NOT eax
  • 对eax取反并赋值给eax

8.LEA(取地址编号)

LEA EAX,DWORD PTR DS:[0X12FFC4]

9.ADC(带进位加法)

两边不能同时为内存,数据宽度要一样
如果此时CF标志位为1则加法时会将CF值累加进去
mov al,0x01
mov cl,0x01
adc al,cl
在cf为1时结果为3,cf为0时结果为2

10.SBB(带借位减法)

跟adc一样,如果c为1多减1

11.XCHG(数据交换)

不能同时为内存,数据宽度要相同
值互换,因此不能有立即数

12.MOVS(移动数据)

内存到内存
movs dword ptr ds:[edi],dword ptr ds:[esi]
上面的代码等价于 movsdmovswmovsd,移动之后esi和edi的值会各加4或者减4

13.STOS(将AL/AX/EAX值存储到[edi]指定的内存单元)

STOS BYTE ptr es:[edi]
存储完后edi会按照d位决定加减按指定的数据宽度决定加/减多数

14.REP(按照ECX中指定的次数重复执行命令字符串指令)

mov ecx,0x10;
rep movsd;
重复执行16次movsd

15.jmp

jmp 0x4183EF
修改eip(决定了cpu下一次执行的汇编代码地址)为0x4183Ef的代码。

16.call

call 0x4183EF
修改eip为0x4183EF,并且在栈里压入返回地址(一般就是call指令下一行指令的地址)。

17.ret

相当于pop eip

18.jle

小于等于跳转

内存

计算机中常用计量单位

  • BYTE(字节) = 8bit
  • WORD (字)= 16bit
  • DWORD(双字)= 32bit

19.movsx

先符号扩展,再传送

mov AL 0xFF
MOVSX CX,AL

因为0xFF符号位为1,所以扩展按照符号位扩展全1然后传送,因此 CX=0xFFFF

20.movzx

先0扩展再传送

内存的寻址

1.内存的性质

  • 内存通过编号寻址,每一个地址代表一个内存块,内存是以字节为单位的。
  • 因此32位操作系统最大寻址空间是4GB

2.内存地址的表示方法

[0x12345678]
通过方括号包裹一个立即数的方式表示一块内存的地址

3.写内存

mov dword ptr ds:[0x12345678],0x12345678

解释如下: - dword指定写入多少个字节,可以单位为byte,word,dword - ptr代表后面这是一个指针,一个地址,不是一个普通的值 - ds表示段寄存器 - [0x12345678]表示内存的地址标号 - 0x12345678表示要写入的数据,此处八个16进制位,正好是一个dword

4.从内存中读

mov EAX,dword ptr ds:[0x12345678]
把内存地址位0x12345678的块读一个dword长度存入EAX寄存器

注: - 实验时可以使用ollydbg中写入esp寄存器当中所存的内存地址

5.OD查看数据命令

db 0x12FFDC

d(查看数据 )b(byte)

6.内存寻址方式

  • [立即数]
  • [REG] (REG表示寄存器)
  • [reg(+/*-)立即数]
  • [REG+REG*(1,2,4,8)]
  • [REG+REG*(1,2,4,8)+立即数]

堆栈

实现一个堆栈

压入数据

mov ebx,0x12ffe0(制定栈底)
mov edx,0x12ffe0(指定栈顶)
mov dword ptr ds:[edx-4],aaaaaaaa(存入一个数据)
sub edx,4(栈顶减4)

注: intel一般栈底是高地址,栈底是低地址。 一般用ebp存栈底,esp存栈顶

取值

mov esi,dword ptr ds:[ebx-4]

堆栈操作指令

压栈

push 0x12345678
相当于栈顶提升,然后存入一个数据

push是可以直接push内存的 push可以32,16位数据,不能是8位

出栈

pop eax
相当于栈顶值存入eax,然后栈顶降低

额外指令

pushad
把八个reg内容全部存栈里
popad
把寄存器内容从栈里恢复回去

标志寄存器

CF(carry flag)进位标志(无符号运算)

用来判断最高位是否会进位,例如:
mov al,0xFF
add al
此时cf变成1

PF(parity flag)奇偶标志位

判断运算结果中最低有效字节(一般就是最后八位)中1的个数,1的个数为偶数则为1,例如:
mov al,3
al表示为2进制为00000011
此时p位为1

AF(Auxiliary Carry flag)辅助进位标志位

数据宽度一半的位有没有进位,如:
mov eax 0x0000FFFF
add eax,1
此时AF为1

ZF(zero flag)零标志位

反应运算结果是否为0,如:
mov al,0x02
sub al,0x2
或者xor eax,eax

SF(sign flag)符号标志位

计算结果的最高位

OF(overflow flag)溢出标志位(有符号运算)

  • 正数和负数相加永远不会溢出
  • 正数+正数=负数 溢出
  • 负数+负数=正数 溢出

DF(direction flag)方向标志位

决定movs后esi和edi加的方向,如果是1就减,如果是0就加

C语言基础补充

裸函数

void __declspec(naked) Function(){

}

编译器不会替你生成一堆乱七八糟的东西 裸函数当中可以直接写汇编代码,如下:

void __declspec(naked) Function(){
    __asm{
        ret
    }
}

使用裸函数实现一个加法求和

#include "stdafx.h"
int __declspec(naked) plus(int x,int y,int z){
    __asm{
        push ebp
        mov ebp,esp
        sub esp,0x40
        push ebx
        push esi
        push edi
        mov ecx,0x10
        mov eax,0xcccccccc
        lea edi,dword ptr ds:[ebp-0x40]
        rep stosd //stosd:将eax的内容复制到edi的内存空间,复制四个字节,并将edi加4个字节
        mov eax,dword ptr ds:[ebp+0x8]
        add eax,dword ptr ds:[ebp+0xC]
        add eax,dword ptr ds:[ebp+0x10]
        pop edi
        pop esi
        pop ebx
        mov esp,ebp
        pop ebp
        ret
    }
}
int main(int argc, char* argv[])
{
    printf("%d",plus(1,2,3));
    return 0;
}

调用约定

调用约定 参数压栈顺序 平衡堆栈
__cdecl (从右至左入栈) 调用者清理栈(外平栈)
__stdcall (从右至左入栈) 自身清理栈(内平栈)
__fastcall (ECX/EDX传前两个,剩下的从右至左入栈) 自身清理栈(内平栈)

如需指定,在函数声明时函数名前加调用约定名即可,c/c++默认用的__cdecl。

数据类型

  • 基本类型
  • 构造类型
  • 指针类型
  • 空类型

字节对齐

#pragma pack(n)
结构体。。。
#pragma pack()

上面n的单位是字节。

typedef

为现有类型定义新的名字

typedef unsigned char word
typedef struct student{
    int x;    
    char arr[10];
}stu;