汇编语言学习笔记

1、NOP指令:号称最安全的指令,全名为no Operation,一条nop指令占用一个字节,什么也不做。有时编译器会使用该指令将代码对齐到偶数地址边界(类似于内存对齐)。IA-32处理器从偶数双字地址处加载代码和数据时会更快

2、变量名仅仅只是对数据段内偏移地址的引用, 类似于mov eax, [变量名称]的汇编指令中,方括号暗示了要进行寻址操作,但是许多程序(包括微软的程序)在通常情况下都不使用方括号,除了lea指令外

3、mov指令:

        指令格式: mov destination, source

        运行后的效果:目的操作数的内容改变成源操作数的内容,源操作数内容不变;等价于C++中的赋值操作:destination  = source

        mov指令的限制:1)两个操作数的尺寸必须一致;2)两个操作数不能同时为内存操作数,需要借助通用寄存器中转:先将源操作数移入一个通用寄存器中,再将通用寄存器的内容移到目目的内存位置;3)目的操作数不能为CS,EIP和IP;4)立即数不能直接送到段寄存器上,需要借助通用寄存器中转(如:mov eax, 立即数; mov ss, eax)

4、movzx指令:零扩展传送,该指令将源操作数的内容复制到目的操作数中,并将该值零扩展至16位或者32为,此指令仅适用于无符号数整数;

5、movsx指令:符号扩展传送,该指令将源操作数的内容复制到目的操作数中并将该值进行符号扩招至16位或者32位;

 6、LAHF指令(load status flag into AH):该指令将EFLAGS寄存器的低字节复制到AH寄存器,被复制的内容包括:符号标志位、零标志位、奇偶标志位和进位标志位。使用该指令可以方便的将标志值保存在变量中;

7、SAHF指令(store AH into status flag):该指令复制AH的值至EFLAGS寄存器的低字节;

8、XCHG指令(exchange data):该指令交换两个操作数的内容,注意它不接受立即操作数,除此之外,XCHG指令的操作数与MOV指令的操作数遵循同样的规则;

9、直接偏移操作数:在变量名称后面加上一个偏移值(单位为字节),可以创建直接偏移操作数,这个操作数可用于访问没有显示标号的内存地址。通过在变量的偏移地址后面加上一个常数得到的表达式被称为有效地址。有效地址以方括号括起来时就表示要对方括号内的表达式进行寻址以获取该有效地址处内存的内容(LEA指令除外)

10、INC和DEC指令:操作数自增(加1)或者自减(减1)

11、ADD/SUB指令:将同尺寸的源操作数和目的操作数相加或者相减,格式为:

        ADD/SUB 目的操作数 源操作数

        加减法操作数并不改变源操作数,相加/减的结果存储在目的操作数中。ADD指令会根据目的操作数的结果相应修改进位标志、零标志、符号标志、溢出标志和奇偶标志。SUB指令会根据目的操作数的结果相应修改进位标志、零标志、符号标志、溢出标志、辅助进位标志和奇偶标志等。

状态标志的简要描述:

        进位标志位CF(Carry Flag, 溢出标志位):用于表示无符号整数运算是否发生了溢出

        溢出标志位OF(Overflow Flags, 进位/借位标志位):用于表示有符号整数运算是否发生了溢出,分为上溢和下溢

        零标志位ZF:用于表示运算结果是否为0

        符号标志位SF:用于表示运算结果是否为负数(负数的二进制表示中,符号位为1),如果运算结果的最高有效位被置位,那么符号标志就会置位

        奇偶标志位PF:用于表示目的操作数的最低有效字节内1的个数是否为偶数

        辅助进位标志位AC:在运算结果(存储于目的操作数中)的最低位有效字节的第3位向第4位的进位值

虽然说OF与有符号数运算有关,但是无符号数进行运算时,OF也会被改变,只是此时OF无意义。同理有符号数进行运算时,CF也会发生相应的变化,但此时CF也没有意义。根本原因在于CPU执行运行时,不知道操作数的类型,它只是按照一定的规则进行计算,然后设置和使用标志位。

12、NEG指令(negate):该指令通过将数字转换为相应的补码而求得其相反数(正数的原码、反码和补码相同,负数的反码=除符号位外,原码按位取反,负数的补码=负数的反码+1);

13、PTR操作符:可以使用该操作符来重载操作数生命的默认尺寸,这在试图以不同于变量声明时所使用的尺寸属性来访问变量的时候很有用(简单来说,就是高级语言中的类型强转), PTR必须和汇编器的标准数据类型(如BYTE, WORD,DWORD等)联合使用

间接寻址:在处理数组时完全直接使用直接寻址是不现实的,不大可能为数组的每一个元素都提供一个不同的标号,也不大可能使用非常多的常量偏移去寻址数组的各个元素。处理数组的唯一可行的方法是使用寄存器作为指针并操作寄存器的值,这被称为间接寻址,操作数使用简介寻址时就称为简介操作数。在保护模式下,间接操作数可以是任何用方括号括起来的任意的32位通用寄存器(EAX、EBX等),寄存器里面存放着数据的偏移。在实地址模式下,使用16位的寄存器存放变量的偏移地址,如果要使用寄存器做间接操作数的话,只能使用SI,DI,DX或者BP寄存器。通常应当尽量避免使用BP,因为BP经常用来寻址堆栈而不是数据段

变址操作数:把常量和寄存器相加以得到一个有效地址,任何32位通用寄存器都可以作为变址寄存器,变址操作数常用形式:mov eax, [esi+4]

14、JMP指令:无条件跳转指令,通常情况下只能跳转到当前过程内的标号处

15、LOOP指令:用于重复执行一块语句,执行的次数是特定的,ECX被自动用作计数器,在每次循环之后减1。LOOP指令的执行包含两部:首先ECX减1,接着与0比较,如果ECX不等0,则跳转到目的地址(标号)处,如果ECX等于0,则不发生跳转,这时控制权将转移到紧跟在LOOP后面的指令处。LOOPD指令总是使用ECX作为循环计数器,而LOOPW总是使用CX作为循环计数器。

16、操作数类型:

        直接操作数:也就是变量的名字,代表了变量的地址。

        直接偏移操作数:在变量的名字后面加一个偏移量而生成新的偏移,这个新偏移可以用来访问内存数据。

        间接操作数:是用方括号括起来的包含数据地址的寄存器,如[esi],程序可以根据该地址寻址并返回内存数据。

        变址操作数:把常量和间接操作数结合在一起,常量和寄存器值加在一起,得到最终的结果被用来寻址,例如[arr+esi], [arr+es*8]等

17、运行时堆栈,也称为堆栈:使用两个寄存器SS和ESP,保护模式下,SS寄存器存放的就是段选择子,用户模式程序不应该对其进行修改。ESP寄存器存放的是指向堆栈内特定位置为的一个32为偏移值。我们很少需要直接操纵ESP的值,相反,ESP寄存器的值通常是由CALL、RET、PUSH和POP等指令间接修改的。ESP指向的是是最后压入(添加)到堆栈上的数据。此处的堆栈与数据结构中的数据类型堆栈(堆栈抽象数据类型)是不同的。此处的运行时堆栈在系统层上(由硬件直接实现)处理子过程调用;堆栈抽象数据类型通常用于实现于后进先出操作的算法,一般使用高级语言如C++等编写。运行时堆栈有以下几种重要的用途:1)寄存器在做多种用途的时候,堆栈可以方便地作为其临时保存区域,在寄存器使用完毕之后,可以通过堆栈恢复其原始值。2)CALL指令执行的时候,CPU使用堆栈保存当前被调用过程(即函数)的返回地址。3)调用函数的时候,可以通过压栈传递参数。4)函数内的局部变量在堆栈上创建,函数返回时,这些变量被丢弃。

运行时堆栈是一个特殊数组,是一个用于存放寄存地址和数据的临时区域,这个堆栈常用于保存返回地址、函数参数、局部变量以及函数内部使用的寄存器。

18、PUSH指令:该指令首先减少ESP 的值,然后再把一个16位或者32位的源操作数复制到堆栈上。

19、POP指令:首先将ESP所指向的堆栈元素复制到16位或者32位的目的操作数中,然后增加ESP的值。

20、PUSHFD和POPFD指令:前者在堆栈上压入32位的EFLAGS寄存器的值,POPFD指令将在堆栈顶部的值弹出并发送到EFLAGS寄存器。注意MOV指令不能复制标志寄存器的值到变量或者寄存器中,因此PUSHFD指令可能就是保存标志寄存器的最佳方式了。

21、PUSHAD和POPAD指令:PUSHAD指令在堆栈上按照如下顺序压入所有的32位通用寄存器:EAX、ECX、EDX、EBX、ESP(执行PUSHAD指令之前的值)、EBP、ESI和EDI;POPAD指令以相反顺序从堆栈中弹出这些通用寄存器。如果代码中修改了很多32位寄存器,那么可以在函数的开始和结束分别调用PUSHAD和POPAD指令保存和恢复寄存器的值

22、CALL指令和RET指令:CALL指令先将返回地址(当前CALL指令的下一条指令的地址)压入堆栈(ESP会减4)并把被调用函数的地址复制到指令指针寄存器EIP中。当程序返回时,RET指令从堆栈中弹出返回地址(此时ESP会加4)并送到指令指针寄存器EIP中。32位模式下,CPU总是EIP所指向的内存地址处的指令,16模式下,CPU总是执行IP寄存器所指向的指令。

23、AND指令:对目的操作数和源操作数进行布尔位与操作并将执行结果存放在目的操作数中,ADD指令总是会清除溢出标志和进位标志,并根据目的操作数中的值修改符号标志、零标志和奇偶标志

24、OR指令:在每对操作数的对应数据位之间进行布尔或操作并将执行结果存放于目的操作数中。OR指令总是清除溢出标志和进位标志,并根据目的操作数的值修改符号标志、零标志和奇偶标志。

25、XOR指令:该指令在目的操作数和源操作数之间执行按位异或操作,并肩执行结果存放与目的操作数中。

26、NOT指令:NOT指令对一个操作数的所有数据位取反,得到的结果称为操作数的反码,NOT指令不影响任何状态标志

27、TEST指令:TEST指令对目的操作数和源操作数进行按位与操作并设置相应的标志位,和AND指令的唯一区别在于,TEST指令不修改目的操作数。

28、CMP指令:该指令在源操作数和目的操作数执行隐含的减法操作,两个操作数都不会被修改。CMP指令对标志位的影响如下:假想减法操作完成之后,CMP指令会根据运算结果修改溢出标志位、符号标志位、零标志位、进位标志位、辅助进位标志位和奇偶标志的值。

在IA-32指令集中没有高级的逻辑结构,无论多么复杂的结构,都可以使用比较和跳转指令的组合来实现。执行条件语句时包括两个步骤,首先使用CMP、AND、SUB之类的指令修改CPUI标志,其次使用条件跳转指令测试标志值并导致向新地址的分支转移。

29、条件跳转指令Jcond:条件跳转指令在标志条件为真时分支转移到新的目的标号处,如果条件标志位假,那么立即执行条件跳转指令后的下一条指令(紧跟在条件跳转指令之后的那一条指令)。格式为: Jcond 目的地址。 格式中的cond指的是一个标志条件,用来表示一个或者多个标志的状态。

基于CPU标志值的跳转指令:

基于恒等性比较的跳转指令:基于两个操作数是否相等或者CX、ECX值是否为0的跳转指令。符号leftOp(左操作数)和rightOp(右操作数)是指CMP指令的目的(左)操作数和源操作数(右):CMP leftOp, rightOp

基于无符号数比较的跳转指令:

基于有符号数的跳转指令:

JA指令全称:jump above, JB指令全称:jump below, JG 指令全称:jump greater, JL指令全称:jump less, JE指令全称:jump equal 

30、移位指令和循环移位指令

逻辑移位指令:以0填充移位后空出来的数位

算术移位指令:以数字原来的符号位填充移位后空出来的数位

30.1、SHL(shift left)指令:对目的数进行逻辑左移,最低位用0填充,移除的最高位送入进位标志CF,原来的进位标志位值(旧值)丢失。格式为:SHL 目的操作数, 移位位数

30.2、SHR指令(shift right):对目的数进行逻辑右移,移除的数据位以0填充,最低位被复制到进位标志CF,原来的进位标志位值(旧值)丢失

30.3、SAL指令和SAR指令:SAL指令与SHL指令等价,SAR指令对目的操作数执行算术右移操作

,二者的格式与SHL、SHR指令的格式相同。

二进制乘法加速思路:无符号数左移n位就相当于乘以了$2^n$,而任何数都可以表示成2的次幂和

31、MUL(无符号乘法)指令:有三种格式,第一种将8位的操作数与AL相乘,第二种将16位的操作数与AX相乘,第三种将32位的操作数与EAX相乘。乘数和被乘数大小必须相同,乘积的尺寸是被乘数/乘数大小的两倍。三种格式都支持寄存器操作数和内存操作数,但是不接受立即立即数。指令中唯一的操作数是乘数。由于目的操作数(乘积)的位数是被乘数/乘数的两倍,因此不会发生溢出。如果乘积的高半部分不为0,就需要设置进位和溢出标志。进位标志通常用于无符号算数运算。MUL指令目的操作数如下:

32、IMUL(有符号乘法)指令:执行有符号整数的乘法运算,保留了乘积的符号位。IMUL指令在IA-32指令集中有三种格式:单操作数、双操作数和三操作数。
32.1单操作数格式:单操作数格式把乘积存储在累加器(AX,DX:AX, EDX:EDX)中:

单操作数格式中乘积的尺寸大小使得溢出不可能发生,如果乘积的高半部分不是低半部分的符号扩展,进位标志和溢出标志置位。可使用该特点确定乘积的高半部分是否可以被忽略。

32.2 双操作数格式:双操作数格式中把乘积存储在第一个操作数中,第一个操作数必须是寄存器,第二个操作数可以是寄存器、内存操作数或者立即数。双操作数格式会根据目的操作数的大小裁剪乘积,如果有效位丢失,则溢出标志和进位标志置位。

32.3三操作数格式:乘积放在第一个操作数中。

33、DIV(无符号除法)指令:执行8位、16位、32位无符号整数的除法运算。指令中唯一一个寄存器或者内存操作数是除数,DIV指令中除数、被除数、商和余数的关系:

r表示寄存器操作数(register),m表示内存操作数(memory),r或者m后面的数字表示被除数的位数(8位、16位还是32位)。

34、有符号整数除法:几乎和无符号整数除法完全相同,唯一的不同之处在于:在进行除法操作之前,隐含的被除数必须进行符号扩展(使用CBW、CWD和CDQ指令)。CBW(字节扩展至字)扩展AL的符号位至AH中,保留了数字的符号。CWD(字符号扩展至双字)指令扩展AX的符号至DX中,CDQ(双字符号扩展至8字节)指令扩展EAX的符号位至EDX中。

关于子例程,不同程序设计语言中通常使用不同的术语。C++中称子例程为函数,JAVA中称子例程为方法,MASM中称子例程为过程。

34、堆栈框架:也称为活动记录,是为传递的参数、子例程的返回地址、局部变量和保存的寄存器保留的堆栈空间。堆栈框架是按照以下步骤创建的:

        1)如果有传递的参数(C++中的实参)则压入堆栈;

        2)进入call指令调用子例程之前,将紧接着call指令的下一条指令地址(即返回地址)压入堆栈;(会导致esp  -=  4)

        3) 子例程开始执行时,EBP压入堆栈(会导致esp -= 4);

        4)EBP设置为ESP的值,从此时开始,EBP就被用作寻址所有子例程参数的基址指针使用了;

        5)如果有局部变量,ESP减去相应数值,以便在堆栈上为局部变量保留内存空间(即ESP用于寻址子例程的局部变量

        6)如果任何寄存器参数需要保存,则压入堆栈。

堆栈参数的访问:在调用函数时,C++程序使用标准的方法初始化和访问参数。C++中的函数以序言开始,序言部分的代码保存了EBP寄存器,并使EBP执行当前堆栈的顶部(通过push ebp + mov ebp, esb两条指令实现)。函数还有可能把一些寄存器压栈,这些寄存器的值将在函数返回的时候恢复。函数以收尾代码结束,在这部分代码中,EBP寄存器被恢复(通过mov esp,ebp + pop ebp两条指令实现),RET指令从函数中返回。

35、访问堆栈参数:C++使用相对基址访问堆栈参数,EBP用作基址寄存器,偏移部分是一个常量,函数一般通过EAX返回一个32位的值。

在子例程返回时,必须要有某种方法清除堆栈上的参数,否则就会导致内存泄漏以及堆栈的破坏。一个简单的办法是在在CALL指令之后使用一条Add指令给ESP加上一个值(外平栈),以使得ESP指向正确的返回地址

36、STDCALL调用约定:处理堆栈清理的另一种方法是使用STDCALL调用约定,可以在子例程的返回指令(即RET指令)后面提供一个整数参数以修复ESP的值,这个整数值必须等于堆栈参数消耗的堆栈空间字节数(内平栈)

37.1、通过堆栈传递8位和16位的参数:在保护模式下堆栈传递参数时,最好使用32位的操作数。虽然可以在堆栈上压入16位的操作数,但这会导致ESP无法对齐在双字地址边界上,因此可能会导致缺页故障,程序的性能也会降低。因此在传递8位或者16位的堆栈参数时,应当将其扩展到32位再压栈

37.2、传递长整数参数:在使用堆栈传递长整数(由多个字构成的)参数时,可以先把高位字压栈,然后再把低位字压栈,这样实际上是以小尾顺序(低字节在低地址,即小端序)把长整数压栈了。(小端序和大端序的解释参照这篇博文

38、局部变量:高级语言程序中,在单个函数中创建、使用、销毁的变量称为局部变量,局部变量相比较于全局变量有以下特点:

        1)只有在局部变量所在的函数中才能看到和修改局部变量,这个特点有助于避免程序源码中多处修改一个变量导致的bug;

        2)局部变量使用的存储空间在函数结束后即释放(通过mov esp, ebp指令实现的,这只是单纯的将esp指针值改变了,但是并没有将使用的内存进行清零操作,这也就导致了内存垃圾值的产生);

        3)一个函数内的局部变量的名字可以和其他函数内的局部变量名相同,不会发生名字冲突;

        4)对于递归函数可能由多个线程同时执行的过程而言,局部变量是必须的;

局部变量是在运行时栈上创建的,在内存中其位置通常在基址指针(EBP)之上(EBP+正数偏移量,因为堆栈方向是从高到低的)

39、LEA指令:LEA指令返回间接操作数的偏移地址。由于间接操作数可能使用一个或者多个寄存机,因此其偏移值是在运行时计算的。

40、基本字符串操作指令如下图:

40.1、MOVSB、MOVSW和MOVSD指令:这三个指令从ESI指向的内存位置复制数据到EDI指向的内存位置,同时ESI和EDI寄存器自动增加或者减少(根据方向标志的值而定):

增减的数值如下表所示:

参考链接:

1、《intel汇编语言程序设计第5版》,本文是从该书摘抄出的笔记

2、https://www.cnblogs.com/FishSmallWorld/p/17161883.html

3、溢出OF和进位CF标志位的判定_cf和of判断的方法-CSDN博客

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!对于逆向学习汇编语言学习笔记,我可以给你一些基本的指导。首先,汇编语言是一种低级语言,它与计算机的底层硬件密切相关。逆向工程则是通过分析和理解已编译的程序来获取程序的内部信息。 以下是一些学习汇编语言和逆向工程的建议: 1. 学习基础知识:了解计算机体系结构、寄存器、内存和指令集等基础概念是必要的。可以先阅读相关的书籍或在线教程,掌握这些基本概念。 2. 掌握汇编语言的语法和指令集:每种计算机体系结构都有自己的汇编语言语法和指令集。选择一种你感兴趣的体系结构(如x86、ARM等),并学习它的汇编语言。 3. 练习编写和调试汇编代码:通过编写简单的汇编代码来熟悉语法和指令集。使用调试器来单步执行代码并观察寄存器和内存的变化。 4. 分析已编译程序:选择一个目标程序进行逆向分析。使用反汇编器将程序转换为汇编代码,并分析代码的逻辑和功能。这有助于理解程序的结构和运行过程。 5. 使用调试器进行动态分析:通过调试器来动态地执行程序,并观察程序在运行时的行为。使用断点、内存查看器和寄存器查看器等工具来分析程序的状态和数据。 6. 学习逆向工程工具和技术:了解常用的逆向工程工具和技术,如IDA Pro、OllyDbg、Ghidra等。掌握这些工具的使用可以提高你的逆向分析能力。 7. 参考优秀资源:阅读与逆向工程和汇编语言相关的书籍、论文和博客,关注相关的社区和论坛。与其他逆向工程师交流经验也是很有帮助的。 记住,逆向工程是一个需要耐心和实践的过程。持续学习和实践将帮助你提高逆向分析的技能。祝你在学习汇编语言和逆向工程的过程中取得好成果!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值