Win32汇编基础

汇编基础

Wirte by 021. Leave a message if i messed up ! : )

汇编语言入门教程- 阮一峰的网络日志

寄存器

32位

  • 提供三种容器寄存器 : 8 , 16 , 32.

64位

  • 提供4中容器寄存器 : 8 ,16 ,32 ,64.

通用寄存器32位

  • 用户自定义使用寄存器

  • 数据宽度为32位 多余的丢弃

    • EAX

      • 返回值容器
    • ECX

      • REP执行计数器
    • EDX

    • EBX

    • ESP

      • 栈指针寄存器,栈内存起始位置到ESP指针位置为已使用内存
    • EBP

      • 栈底
    • ESI

      • movs 使用
    • EDI

      • movs 使用
    • EFL

      • 内存地址高低方向位

image-20211114110903790

非通用寄存器

EIP

  • cpu下次执行时会找EIP存的值

指令

  • mov

    • 操作数数据宽度需要相同
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    mov eax,1 ;  将1存进eax寄存器。
    mov ecx,1 ; 将1存进ecx寄存器。
    mov eax,ecx ; 将ecx存进eax寄存器



    x64模拟器:
    %rax 作为函数返回值使用。
    %rsp 栈指针寄存器,指向栈顶
    %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。
    %rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改
    %r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值

  • add 加法

    1
    add eax,ecx; 将ecx 与 eax 相加 存进eax寄存器;
  • sub 减法

    1
    sub eax,ecx; 将ecx 与 eax 相减 存进eax寄存器;
  • AND 与运算

    1
    AND eax,ecx; 将ecx 与 eax 与运算 存进eax寄存器;
  • or 或运算

    1
    or eax,ecx; 将ecx 与 eax 与或算 存进eax寄存器;
  • xor 异或运算

    1
    xor eax,ecx; 将ecx 与 eax 与异或算 存进eax寄存器;
  • NOT 非运算

    1
    not eax, 将eax取反 ,存进eax寄存器;
  • **movs 从内存到内存 **

    • 每复制一次 内存地址自增长当前数据宽度位
    1
    2
    3
    4
    5
    6
    Movs byte ptr es:[edi] , byte ptr ds:[esi]

    Movs byte ptr es:[0x00000000] , byte ptr ds:[0x00000001]

    ELF

  • stos 将AI/AX/EAX的值存储到[EDI]指定的内存单元

    1
    2
    3

    STOS WORD PTR ES:[edi]

  • REP 重复执行 ,依赖ecx 寄存器的计数器 10进制,每次执行ecx值减一

  • JMP 寄存器/立即数/内存

    • 修改EIP的值
    1
    2
    3

    jmp dword ptr ds:[eax]; 将eax的地址值,赋给EIP,cpu下次执行的将是eax里面的指令.

  • CALL

    • 修改EIP值,再将ESP-4,并将栈顶值压入在ESP-4的地址中

      Untitled 1

  • RET

    • 1,将当前栈顶的值放进EIP中

    • 2,将当前ESP指针值+4

      Untitled 2

内存地址

  • 0x0000000

    1
    数据宽度:32位.
  • 内存基本单元= 1byte = 8bit

往内存中写数据

  • mov 数据宽度 ptr ds:[内存地址] ,值 , mov byte ptr ds:[0x00000000] ,1

  • mov 数据宽度 ptr ds:[内存地址] ,值 , mov word ptr ds:[0x00000000] ,1 ; 单字宽度

  • mov 数据宽度 ptr ds:[内存地址] ,值 , mov Dword ptr ds:[0x00000000] ,1; 双字宽度

  • mov 数据宽度 ptr ds:[内存地址] ,值 , mov Dword ptr ds:[0x00000000] ,eax; 将寄存器写入内存

  • mov 数据宽度 ptr ds:[内存地址] ,值 , mov eax, Dword ptr ds:[0x00000000] ; 将内存写入寄存器

  • mov 数据宽度 ptr ds:[内存地址] ,值 , mov eax, Dword ptr ds:[ecx+4] ; 将ecx内存地址+4的位置值写入寄存器

存储模式

  • 小端模式 x86

    1
    2
    3
    4
    数据低位在低位,数据高位在高位.

    mov word ptr ds:[0x00000000],0x1a2c , 0x00000000[1a], 0x00000001[2c].

  • 大端模式 arm

    1
    2
    3
    4
    数据低位在高位,数据高位在低位.

    mov word ptr ds:[0x00000000],0x1a2c , 0x00000000[2c], 0x00000001[1a].

堆栈

  • PUSH压栈

    • push指令将数据压入栈中,并移动栈针ESP ,(eps - 数据宽度)
  • pop弹栈

    • pop指令将数据弹出栈外,并移动栈针ESP ,(eps + 数据宽度)

    • pop eax; 将栈顶的值存入eax中,esp指针 + eax值的数据宽度

函数

  • JMP调用函数(通常不用)

CALL()调用函数**

  • 基本函数调用流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
求 10 + 7 = ?

10 = 0000 1010 = 0x0a; 7 = 0000 0111 = 0x07;

模拟入参:

初始地址 esp : 0x00000128
ecx : 0x0000012c
edx : 0x00000131
0x00000135 : null;
0x00000139 mov ecx 0x0a;
0x0000013D mov edx ox07;
//加
0x00000142 add ecx ,edx;
//移动返回值
0x00000146 mov eax, ecx;

0x0000014a ret;

执行调用 call 0x00000139; 首先将 jmp eip ,esp; 然后 mov esp-4, esp; 再执行0x00000139位置;


函数执行 139 13d 142 系列函数之后到达146 ret返回点,ret首先 jmp eip ,esp; 然后 mov esp+4 ,esp;


  • 堆栈函数与平衡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
求 1 + 2 + 3 + 4 = ?

0x0000 0117 ; push 4;
0x0000 011b ; push 3;
0x0000 0120 ; push 2;
0x0000 0124 ; push 1;
0x0000 0128 ; add eax, dword ptr ds:[esp+10], 由于压入4个立即数,堆栈地址增加了16,所以将第一个数与eax相加.
............ add eax, dword ptr ds:[esp+0c],
............ add eax, dword ptr ds:[esp+08],
............ add eax, dword ptr ds:[esp+04],
............ ret;

call 0x0000 0117;

============================================================================================

堆栈平衡:
在调用函数中 应该保证函数调用前后,堆栈的一致性不发生变化;
在ret之前 应该保证堆栈指针esp 是call函数压入的esp地址;

0x0000 0117 ; add eax, dword ptr ds:[0x0000 011b]
0x0000 011b ; push 3; ------ 此时esp已经发生变化 返回时指向esp的地址和堆栈已经发生变化,再次执行就会出错
0x0000 0120 ; push 2;
0x0000 0124 ; push 1;
0x0000 0128 ; ret; ------ ret指令 :将esp值放到eip,然后将esp的值+4;

怎么平衡?

外平栈
call 0x0000 0117; add esp , 0x0c; 将esp复位;

内平栈
0x0000 0128 ; ret 0x0c; 在函数内部将esp复位;


ESP栈顶寻址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
求 1 + 2 + 3 + 4 = ?



//调用函数
0x0000 0113 : call 0x0000 0117; 压栈后栈顶指针esp;

//执行压栈
0x0000 0117 ; push 4;
0x0000 011b ; push 3;
0x0000 0120 ; push 2;
0x0000 0124 ; push 1;

//执行相加
0x0000 0128 : add eax , dword ptr ss:[esp+14] ; 执行加第一个数 1;
0x0000 012c : add eax , dword ptr ss:[esp+10] ; 执行加第2个数 2;
0x0000 0130 : add eax , dword ptr ss:[esp+c] ; 执行加第3个数 3;
0x0000 0134 : add eax , dword ptr ss:[esp+8] ; 执行加第4个数 4;
// 平栈 弹出/修正esp位置
0x0000 0130 : pop 1 ;

pop 1 ~ 4;

0x0000 0134 : ret;


EBP栈底寻址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
求 1+2 = ?

整体步骤,
1: 将参数压栈
2: 调用函数, 2.1 备份esp到ebp, 2.2 开辟新空间, 2.3 复位esp, 2.4弹出备份ebp; 2.5平栈


初始堆栈:
esp : 0x0000 0120;
ebp : 0x0000 011c;




//1. 参数压栈
push 2; esp : 0x0000 0118; eip : 0x0000 0001;
push 1; esp : 0x0000 011c; eip : 0x0000 0002;

//2. 调用函数
call 0x0000 0005; esp : 0x0000 0114; ( eip : 0x0000 0004; 指向下一条指令)
// 下一条的指令;
mov eax,1; eip : 0x0000 0004;



==============================================================



函数:
1,备份栈底ebp
push ebp; esp : 0x0000 0110; eip : 0x0000 0005;

2,记录起始ebp的值
mov ebp,esp; esp : 0x0000 0110; ebp : 0x0000 0110; eip : 0x0000 0006;

3,开辟新空间 开辟16个字节
sub esp,10; esp : 0x0000 0100; eip : 0x0000 0007;

4,执行加操作
add eax ,dword ptr ss:[ebp+c] eip : 0x0000 0008;
add eax ,dword ptr ss:[ebp+8]

5,复位堆栈
mov esp,ebp; esp : 0x0000 0110;

pop ebp; esp : 0x0000 0114;

ret c; esp : 0x0000 0120;

JCC指令集

  • JCC指令根据标志寄存器值的标志位来修改EIP的,从而实现执行跳转

标志寄存器

标志寄存器(EFLAGS) - 程序员大本营

  • 运算相关指令都会影响标志寄存器
JCC指令 中文含义 英文原意 检查符号位 典型C应用
JZ/JE 若为0则跳转;若相等则跳转 jump if zero;jump if equal ZF=1 if (i == j);if (i == 0);
JNZ/JNE 若不为0则跳转;若不相等则跳转 jump if not zero;jump if not equal ZF=0 if (i != j);if (i != 0);
JS 若为负则跳转 jump if sign SF=1 if (i < 0);
JNS 若为正则跳转 jump if not sign SF=0 if (i > 0);
JP/JPE 若1出现次数为偶数则跳转 jump if Parity (Even) PF=1 (null)
JNP/JPO 若1出现次数为奇数则跳转 jump if not parity (odd) PF=0 (null)
JO 若溢出则跳转 jump if overflow OF=1 (null)
JNO 若无溢出则跳转 jump if not overflow OF=0 (null)
JC/JB/JNAE 若进位则跳转;若低于则跳转;若不高于等于则跳转 jump if carry;jump if below;jump if not above equal CF=1 if (i < j);
JNC/JNB/JAE 若无进位则跳转;若不低于则跳转;若高于等于则跳转; jump if not carry;jump if not below;jump if above equal CF=0 if (i >= j);
JBE/JNA 若低于等于则跳转;若不高于则跳转 jump if below equal;jump if not above ZF=1或CF=1 if (i <= j);
JNBE/JA 若不低于等于则跳转;若高于则跳转 jump if not below equal;jump if above ZF=0或CF=0 if (i > j);
JL/JNGE 若小于则跳转;若不大于等于则跳转 jump if less;jump if not greater equal jump SF != OF if (si < sj);
JNL/JGE 若不小于则跳转;若大于等于则跳转; jump if not less;jump if greater equal SF = OF if (si >= sj);
JLE/JNG 若小于等于则跳转;若不大于则跳转 jump if less equal;jump if not greater ZF != OF 或 ZF=1 if (si <= sj);
JNLE/JG 若不小于等于则跳转;若大于则跳转 jump if not less equal;jump if greater SF=0F 且 ZF=0 if(si>sj)