|

OD汇编指令

NOP 无操作
PUSH 将数据压入堆栈中
push ebp——EBP寄存器中的值压入堆栈中
POP 出栈(与PUSH相反)
pop ebp——将堆栈中的值传递给EBP寄存器。其他寄存器效果相似
PUSHAD 所有通用寄存器的内容按一定顺序压入到堆栈中,相当于
PUSH EAX、PUSH ECX、PUSH EDX、PUSH EBX、PUSH ESP、PUSH EBP、PUSH ESI、PUSH EDI
POPAD 该指令与PUSHAD正好相反,它从堆栈中取值,并将它们放到相应的寄存器中,等价于
POP EDI、POP ESI、POP ESP、POP ESP、POP EBX、POP EDX、POP ECX、POP EAX
MOV 该指令将第二个操作数赋值给第一个操作数
MOV EAX, EBX 释:EBX值赋值给EAX
image.png
mov指令用于将一个值写入某个寄存器。
mov eax, [esp+8]
这一行代码表示,先将 ESP 寄存器里面的地址加上8个字节,得到一个新的地址,然后按照这个地址在 Stack 取出数据。根据前面的步骤,可以推算出这里取出的是2,再将2写入 EAX 寄存器。
在CPU内或CPU和存储器之间传送字或字节,它传送的信息可以从寄存器到寄存器,立即数到寄存器,立即数到存储单元,从存储单元到寄存器,从寄存器到存储单元,从寄存器或存储单元到除CS外的段寄存器(注意立即数不能直接送段寄存器),从段寄存器到寄存器或存储单元。
但是注意
(1) MOV指令中的源操作数绝对不能是立即数和代码段CS寄存器;
(2) MOV指令中绝对不允许在两个存储单元之间直接传送数据;
(3) MOV指令中绝对不允许在两个段寄存器之间直接传送数据;
(4) MOV指令不会影响标志位
MOVSX
(带符号扩展的传送指令)
第二个操作数可能是一个寄存器也可能是一个内存单元,第一个操作数的位数比第二个操作数多,第二个操作数的符号位填充第一个操作数剩余部分
例:movzx eax,byte ptr ds:[eax] 从eax寄存器存储的地址的值 ([ ]) 中取出第一个字节(byte)
movzx其实就是将我们的源操作数取出来,然后置于目的操作数,目的操作数其余位用0填充。
MOVZX
(带0扩展的传送指令)
类似于MOVSX,但是这种情况下剩余的部分不根据操作数的正负来进行填充,剩余的部分总是被填充为0
LEA
(取地址指令)
类似于MOV指令, 但是第一个操作数是一个通用寄存器,并且第二个操作数是一个内存单元。当计算的时候要依赖于之前的结果的话,那么这个指令就非常有用

中括号[ ]:
lea对变量没有影响是取地址,对寄存器来说加[]时取值,第二操作数不加[]非法
- lea ebx , [eax] 取eax中的值写入ebx
- lea ebx , abc=lea ebx , [abc] 取变量的地址写入ebx

mov对变量来说没有影响是取值,对寄存器来说是加[]时取地址,第二操作数不加[]是取值。
- mov ebx,eax 取eax中的值写入ebx
- mov ebx , abc=mov ebx , [abc] 取的是变量abc的值写入ebx
| | XCHG | 交换 寄存器/内存单元 和 寄存器 | | | | |

串操作指令

| | |

串操作指令寻址方式


1. ds:esi保存源操数的首地址;
2. es:edi保存目的操作数的首地址;
3. 串操作指令执行后,会自动修改esi和edi中保存的地址,增量由所使用的指令 决定,方向由DF标志位决定。
image.png
- STD 设置DF 位 设为1 逆向增长
- CLD 清除DF 位 设为 0 正向增长
| | | 串传送

movsb
movsw
movsd | 相当于 memcpy (内存拷贝);串传送指令是从ds:esi指向的源地址复制一定数量的字节储存到es:edi所指向的目的地址,指令执行后,esi和edi中的值会被自动修改。
movsb 从ds:esi指向的源地址处复制一个字节储存到es:edi所指向的目的地址处
movsw 从ds:esi指向的源地址处复制两个字节储存到es:edi所指向的目的地址处
movsd 从ds:esi指向的源地址处复制四个字节储存到es:edi所指向的目的地址处 | | 串加载

lodsb
lodsw
lodsd | 相当于获取一块内存中所有数据的和;串加载指令是从ds:esi指向的地源地址处复制1或2或4字节到al或ax或eax ,指令指令执行后,esi中的值会被自动修改

lodsb 从ds:esi指向的源地址处复制一个字节放到al中
lodsw 从ds:esi指向的源地址处复制两个字节放到ax中
lodsd 从ds:esi指向的源地址处复制四个字节放到eax中 | |

串送存

stosb
stosw
stosd | 相当于 memset(初始化一块内存);箱串送存指令是将al或ax或eax中的值复制到es:edi所指向的目的地址处,指令执行后,edi中的佱会被自动修改。

lodsb 从ds:esi指向的源地址处复制一个字节放到al中
lodsw 从ds:esi指向的源地址处复制两个字节放到ax中
lodsd 从ds:esi指向的源地址处复制四个字节放到eax中 | |

串比较

cmpsb
cmpsw
cmpsd | 串比较指令是将ds:esi指向的源地址处的一个字或两个字节或四个字与es:edi所指向的目的地址处的一个字节或两个字节或四个字节进行比较(实际是源串减去目的串,结果不回送,只影响标志位),如查相等,ZF被置1。指令执行后,esi和edi中的值会被自动修改

cmpsb 将ds:esi指向的源地址处的一个字节与es:edi所指向的目的地址处的一个字节相比较,如果相等,ZF=1
cmpsw 将ds:esi指向的源地址处的两个字节与es:edi所指向的目的地址处的两个字节相比较,如果相等,ZF=1
cmpsd 将ds:esi指向的源地址处的四个字节与es:edi所指向的目的地址处的四个字节相比较,如果相等,ZF=1 | |

串扫描

scasb
scasw
scasd | 串扫描指令是将al或ax或eax中的值与es:edi所指向的目的地址处的一个字节或两个字节或四个字节相比较(原理和串比较相同),如果相等,ZF被置1 。指令执行后,edi中的值被自动修改。

scasb 将al中的值与es:edi所指向的目的地址处的一个字节进行比较,如果相等,ZF=1
scasw 将ax中的值与es:edi所指向的目的地址处的两个字节进行比较,如果相等,ZF=1
scasd 将eax中的值与es:edi所指向的目的地址处的四个字节进行比较,如果相等,ZF=1 | | 重复前缀指令 | | | rep | 如果在串操作指令前加上“rep”,将使操作重复进行,重复的次数由ecx中的值决定。串操作指令每执行一次,ecx中的值将自动减1,当ecx中的值为0时,将结束指令操作。
rep主要和movs,lod,sstos指令一起使用。比如:
mov ecx,5
rep movsb
指令movsb将执行5次。
repe/repne/repz/repnz主要和cmps,scas指令一起使用。比如:
repe cmpsb
指令重复执行的条件是:ecx =! 0 && ZF ==1,意思是如果串的某个字节相等就继续进行串比较直至ecx中的值为0 。repne表示字节不等时继续比较,repz和repnz意思类似。 | | | | |

OD数学指令

| | | INC | 执行增加,如果是INC指令的话,就加1 | | DEC | 执行减少,如果是DEC指令的话,就减1 | | ADD | ADD指令有两个操作数,相加后的结果存放到第一个操作数中,ADD EAX,1等价于INC EAX | | ADC
(带进位的加法) | 两个操作数的和加上进位标志的值,结果存放到第一个操作数中 | | SUB | 这个指令与ADD刚好相反,它将第一个操作数减去第二个操作数的值存放到第一个操作数中 | | SBB | 该指令跟ADC正好相反,它计算两个操作数的差值,并且还要减去进位标志,结果存放到第一个操作数中 | | MUL
(无符号数的乘法) | 有两种乘法:
第一种只有一个操作数,另一个操作数是EAX,结果存放到EDX:EAX中,比如:MUL ECX
第二种有两个操作数,两个操作数相乘,结果存放于EDX:EAX中,比如:MUL DWORD PTR DS:[405000] | | IMUL
(有符号数的乘法) | IMUL指令用法类似于MUL,例如:IMUL ECX 该指令将有有符号数ECX乘以EAX,结果存放到EDX:EAX中
注:
1.除了上面一条指令外,IMUL还允许使用多个操作数,这是与MUL不同的地方
2.尽管在默认情况下是使用EAX和EDX寄存器,但是我们还可以指定其他的数据源以及目标多达三个操作数;例:
0FAF55E8 IMUL EDX, DWORD PTR [EBP-18] EDX x [EBP-18] -> EDX | | DIV
&&
IDIV | 无符号除法/有符号除法;这两个指令反别与MUL和IMUL相反
DIV只有一个操作数,该操作数必须是无符号数,结果存放到EDX:EAX中
如果是一个操作数的话,那么它和DIV类似,只不过操作数是有符号的,结果依然保存在EDX:EAX中。IDIV两个操作数的情况,第一个操作数除以第二个操作数,结果存放到第一个操作数中。三个操作数的情况,第二个操作数除以第三个操作数,结果存放到第一个操作数中 | | XADD
(交换并相加) | 这个指令其实就是XCHG和ADD两个简单指令的组合 | | NEG | 该指令的目的是将操作数的符号取反,即如果我们有一个32位的16进制数,用NEG操作以后,结果就会取反 | | | | |

逻辑指令: 逻辑指令有两个操作数,两操作数按位运算,并将结果存放到第一个操作数中

| | | AND | 只有两个二进制位都为1的时候结果才为1,其他情况,结果都为0;
例:AND ECX,EAX 将ECX设置为0001200,EAX设置为3500
1200二进制为01001000000000
3500二进制为11010100000000
两个0取0。对每一位重复此过程,当两位都为1的时候才取1,这两个数中只有一次,两位都是1的情况
按下F7键,我们将看到ECX的值为1000。相当于二进制的01000000000000 | | OR | 该指令与AND的不同之处在于,两位中只要有一位为1,结果就取1; | | XOR | 该指令运算,当两位不同时取1,相同时取0 | | NOT | 按位取反 | | | | |

比较和条件跳转

| | | CMP | CMP EAX, ECX | | | 该指令是比较两个操作数,实际上,它相当于SUB指令,但是相减的结构并不保存到第一个操作数中。只是根据相减的结果来改变零标志位的,当两个操作数相等的时候,零标志位置1 | | | 根据标志的值来决定跳转还是不跳转。最简单的例子就是配合JZ指令,如果Z标志被置为1,就跳转,否则,就不跳转 | | TEST
(逻辑比较) | 1.Test用来测试一个位,例如寄存器:
test eax,100b;b后缀意为二进制
jnz **;如果eax右数第三个位为1,jnz将会跳转

jnz跳转的条件是ZF=0, ZF=0意味着ZF(零标志)没被置位,即逻辑与结果为1。

2.Test的一个非常普遍的用法是用来测试一方寄存器是否为空:
test ecx, ecx
jz somewhere
如果ecx为零,设置ZF零标志为1,jz跳转。 | | JCC指令组 | 所有的跳转指令都会指向程序将会跳转到的地址。 | | | 具体如下: | | | JMP – 无条件跳转 | | | JE, JZ – 结果为零则跳转 ZF=1 | | | JNE, JNZ – 结果不为零则跳转 ZF=0 | | | JS – 结果为负则跳转 SF=1 | | | JNS – 结果不为负则跳转 SF=0 | | | JP, JPE – 结果中1的个数为偶数则跳转 PF=1 | | | JNP, JNPE – 结果为1的个数为奇数则跳转 | | | JO – 结果溢出了则跳转 | | | JNO – 结果没有溢出则跳转 | | | JB, JNAE – 小于则跳转 (无符号数) | | | JNB, JAE – 大于等于则跳转 (无符号数) | | | JBE, JNA – 小于等于则跳转 (无符号数) | | | JNBE, JA – 大于则跳转(无符号数) | | | JL, JNGE – 小于则跳转 (有符号数) | | | JNL, JGE – 大于等于则跳转 (有符号数) | | | JLE, JNG – 小于等于则跳转 (有符号数) | | | JNLE, JG – 大于则跳转(有符号数) | | JMP | 无条件跳转指令,即总是跳转到指定的地址 | | JE或者JZ | 若相等则跳;如果零标志位Z不为0则跳转,即,要求操作的结果为零 | | JNE或JNZ | 若不等则跳转;如果零标志位Z为0则跳转,即,要求操作的结果非零 | | JS | JS EAX,ECX
当比较的结果为负时将跳转,即,就是EAX小于ECX则跳转 | | JNS | JNS EAX,ECX
这个跳转指令与JS刚好相反。当零标志位S为0的时候跳转,也就是说EAX大于ECX的时候跳转 | | JP或JPE | 这个跳转指令是当奇偶标志位P置1的时候才会发生,也就是比较的结果中1的个数要是偶数才会跳转 | | JNP 或JNPE | 这条指令刚好与上一条指令刚好相反,当奇偶标志位P为0的时候跳转。即结果中1的个数为奇数的时候 | | JO | 当发生溢出时,即溢出标志位O置1的时候跳转 | | JNO | 跟上一条指令相反,这里是当溢出标志位O为0时跳转,即溢出没有发生时 | | JB | 第一个操作数小于第二个操作数的时候跳转 | | JNB | 和JB指令相反,这个指令是当进位/借位标志位为0的时候跳转,也就是说,结果为正的时候跳转也就是说第二个第一个操作数大于第二个操作数的时候跳转 | | JBE | JBE EXA,ECX
这个指令是小于或者等于的时候跳转,这是判断两个标志位的,当进位/借位标志位置1或者零标志位Z置1的时候将发生跳转,也就是说,EAX要小于或者等于ECX才会发生跳转 | | JNBE | 这个指令跟JBE刚好相反,当进位/借位标志位C与零标志位Z都为0时候才会发生跳转, | | JL | 这个指令当小于的时候跳转,但是与前面的JB稍微有点不同。这个指令时根据符号标志位S来决定是否跳转
JB比较两个数的时候,将它们两个都看做是正数,即认为它们是无符号数,但是JL指令要考虑符号,这就是这两条指令的主要区别 | | 跳转条件

a: above
b: below
c: carry
g:great
l: less
n: not
z: zero
e: equal

| image.png | | | | |

其他

| | | CALL | 是指将转移到指定的子程序处,它的操作数就是给定的地址;例:
Call 401362表示将转移到地址401362处,将调用401362处的子程序,一旦子程序调用完毕就返回到Call指令的下一条语句处,在这种情况下,完成401362的子程序调用以后,则会返回到40124A地址处 | | RET(RETN) | 返回到指定位置,当我们执行到ret指令的时候,栈顶存放的一般是子程序的返回地址;也就是说ret指令是子程序的结束,也就是说,如果我们call跟进的话,那么ret就能返回到call指令的下一条语句处。当然RET指令不止适用于子程序返回,例如:
PUSH 401256
RET
这里将401256压入堆栈。下面的ret指令会将401256当做子程序的返回地址,其实它并不是返回地址,但是执行ret指令后我们依然可以转移到401256地址处。这段代码和JMP 401256指令的功能是一样的 | | 循环指令 | 为了实现循环可以使用前面介绍过一些指令。例如,你可以将任意通用寄存器指定为计数器(通常ECX作为计数器使用),你可以将其初始化为需要循环的次数,然后执行循环体,接着计数器递减1,判断计数器是否为0,如果计数器不为0继续重复前面的过程,如果计数器为0,就不继续循环了,而直接执行下面的代码。代码如下:
XOR ECX,ECX
MOV ECX,15h
将计数器初始化为循环次数15h。接下来就是循环体了:
Label:
DEC ECX
该计数器每次递减1。
其实就是循环体了,循环体里面可以是任意指令。
最后,你需要添加一个判断计数器是否为0的指令以及条件跳转指令。
CMP ECX,0
JNE Label | | LOOP | LOOP指令可以帮我们完成前面例子中的事情- 将计数器ECX的值减1,判断ECX的值是否为0,如果为0就跳转到指定的地址-将像前面的例子一样。(可惜的是,大多数现代的处理器中该指令的效率不如前面模拟的例子。)
在DEC ECX指令上单击鼠标右键选择-Binary-Fill with NOPS。对TEST ECX,ECX和JNZ 401007两条指令也进行同样的操作。这三条指令用一条LOOP 401007指令就可以替代 | | LOOPZ
LOOPE | 重复循环,直到零标志位Z置1 | | LOOPNZ
LOOPNE | 重复循环,直到零标志位Z清0
此外,LOOPZ,LOOPNZ指令还将检查零标志位Z是否为0。只有计数器的值和零标志Z同时满足条件时才循环。 |