寄存器
如上是OD展示的寄存器,逐条说明常用的寄存器和标志位含义:
EIP:寄存器指向即将要执行的指令的地址(EIP中的地址,就是下一步要执行指令的地址)
ESP:里面的内容永远指向堆栈的最顶端
EAX:(十六进制EAX=12345678)(?AX=5678和和?AH=56和?AL=78) AX是最后四位,AL是最后两位
ECX:和上面EAX一样
EBX:和上面EAX一样
EDX:和上面EAX一样
注:EIP是不能直接修改的,必须在窗口选中某处右键设置为新的EIP
有符号就是负数,无符号就是正数
O标志:溢出标志位(O 0):当指令运算结果,即操作改变了符号的时候返回错误值设置的(0 1 :就是二进制的符号位改变了,也就是正数变负数)
A标志:辅助进位标志
P标志:基偶标志位(当指令运算结果,窗口中执行的结果用二进制表示,如果1的总个数是双数那P标志位为1)
Z标志:当指令运算结果为0的话Z标志位就是1
S标志:符号标志:当指令运算结果为负数的话,S标志结果为1
C标志:进位标志位(也可以叫借位标志位);某条指令运算的无符号运算结果,超过最大值(无符号最大数:FFFFFFFF),也就是EAX=FFFFFFFF,add eax,1,超过最大了,那么C标志位就是1了
为什么 EAX=FFFFFFFF
add eax,1
EAX=00000000 变成0了?因为进位了,那个1在EAX外面了即:1 00000000
T标志:跟踪标志位
D标志:方向标志位
CF就是无符号溢出标志位,OF是有符号溢出标志位,拿16位举例,范围0X0000~0XFFFF,既然0减1,应该等于-1,那么就是溢出了
指令集
NOP指令:空指令
使用位置:更改call,在字节的计算上(也就是HEX 数据栏中:2个为一组算一个字节),修改导致(数据栏中)不足的字节用NOP自动填充下去
90NOP填充是无操作的,00NOP(一个指令)填充是有操作的
NOP的机器码(二进制)表示:HEX的90
PUSH指令:将push指令后面的数据压入到堆栈中
POP指令:把该数据存入(变为)堆栈的最顶上,类似于剪切(pop eax、pop ebx、等等)
PUSHAD指令:将寄存器的从上向下的顺序,依次push到堆栈中,最后EDI在堆栈中的最顶端(寄存器顺序:EAX——>EDI,堆栈内对应:EAX数据——>最顶EDI数值)
POPAD指令:将堆栈内的数值从下向上pop到寄存器中(寄存器顺序:EDI——>EAX,堆栈内对应:最顶——>第八位)
PUSHA和POPA都是16位的操作指令,PUSHAD和POPAD是32位的;两个其实都差不多
(主要PUSHA和POPA,指的是EAX中的EX、CX、DX、BX、SP、BP、SI、DI,是后八位数值)
MOV指令:赋值操作指令(可以说是复制)
mov eax,ecx:将ecx中的内容复制给eax,让eax中的内容与ecx相同。
dword:4字节长度
word:2字节长度
/
mov dword ptr ds:[0x402000],eax // 4字节长度
mov word ptr ds:[0x402000],ax // 2字节长度
mov byte ptr ds:[0x402000],al // 1字节长度byte
// dword word byte决定需要复制的数值
/
MOVSX指令:(会判断原来的数值是正还是负,再根据正负用0000还是FFFF)
MOVSX EAX,BX // 2字节BX复制到4字节EAX,因为填充不足,所以会用占位符来占位(负数的二进制符号为1,会用FFFF填充剩下的字符)
至于具体是0000还是FFFF填充,那么就看EAX中的前4字节是否是正数(00001234)还是负数(0000A123)了(这里A123用F填充EAX,1234用0填充EAX)
16位的最大正数:7FFF,负数开始是:8000
32位的最大正数:7FFFFFFF,负数开始是:80000000 符号位0还是1决定正负
(MOV指令是不行的)
MOVZX指令:(不考虑正负,直接用0000填充)
LEA指令:(两个数字为一个字节)
mov 寄存器,[401000] //有[],取得是[]内的地址的数值,在lea
lea 寄存器,dword ptr ds:[ecx+0x16] // 这里的括号是取ecx+0x16的结果,相当于:lea 寄存器,ecx+0x16
lea意思就是:取计算结果的地址
lea指令可以用来将一个内存地址直接赋给目的操作数
例如:lea eax,[ebx+8]就是将ebx+8这个地址值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。(与我们平时使用到的指令涉及到的[]情况是不一样的,看下面的说明即可:)
而mov指令则恰恰相反
例如:mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。
说明:
如果是:
push 0x00401000 那么就是压入这个地址
push [00401000] 那么就是压入这个地址对应的数据的内容(只需要选中地址,右键,在数据窗口中跟随选择,那么就能看到地址对应的数据内容)
XCHG指令:内容交换
xchg eax,ebx 结果就是eax的内容变为ebx的,ebx的内容变成eax的。
ADD指令:加法指令
ADD EAX,333 // 数据添加
ADD EAX,EBX // 寄存器相加
ADD dword ptr ds:[0x402000],0x2
ADD dword ptr ds:[0x402000],ecx // 0x402000地址的数值和ecx相加,给0x402000地址
// 左下数据框中的数据是反着来的,即:14 01 00 00,在寄存器的ECX中就是00 00 01 14
SUB指令:减法指令(和加法一样使用)
ADC指令:
ADC EAX,0x5 // 相当于EAX=EAX+0x5+C // 这个C是进位标志位,为1就加1,为0就加0
SBB指令:
SBB EAX,0x2 // 相当于EAX=EAX-0x2-C // 这个C是进位标志位,为1就加1,为0就加0
INC指令:(递增)
inc eax // 每次执行都会eax自增数值1 // 类似i++
DEC指令:(递减)
dec eax // 每次执行都会eax自减数值1 // 类似i--
MUL指令:(无符号乘法)
MUL ECX // 代表的含义就是:EAX*ECX=EAX和EDX存储
// 举例:EAX=FFF FFF9、ECX=0000 0005、那么结果应该是4 FFFF FFDD
// 存储的时候,EAX存储FFFF FFDD,EDX存储0000 0004
0000 0004 FFFF FFDD
EDX EAX
DIV指令:(无符号除法)
div cl (16进制的)
// 举例:EAX=0000 0017,ECX=0000 0003
// 结果就是:EAX=0000 02 07 // 17/3 熵7余2,那么熵在al,余数在ah
div cx (16进制的)
// 举例:EAX=0000 0017,ECX=0000 0003
// 结果就是:EAX=0000 0007,EDX=0000 0002 // 17/3 熵7余2,那么熵在ax,余数在dx
div ecx (32进制的)
// 举例:EAX=0000 0017,ECX=0000 0003
// 结果就是:EAX=0000 0007,EDX=0000 0002 // 17/3 熵7余2,那么熵在eax,余数在edx
IMUL指令:(有符号的乘法运算(0和F))
IMUL al:意义就是al*al
IMUL cl:意义就是al*cl
// 对此来说,cl取al,cx取ax,ecx取eax
IMUL ax,cx,0x2:意义就是cx*0x2放入ax
IDIV指令:
执行的操作:与DIV相同,但操作数必须是带符号的数,商和余数也都是带符号的数,且余数的符号和被除数的符号相同
XADD指令:(交换并相加)(单独交换:XCHG,单独相加:ADD)
XADD eax,ecx // eax和ecx交换数值并相加,将结果存储在eax中
NEG指令:(取反)(即正变负)
neg eax // EAX = 0000 0050变为 FFFF FFB0 // 可以左下的command进行计算:? -121110得到十六进制
-----------------------------------------------------
网上说的,二进制的算法:
neg指令执行的操作是把eax的每个二进制位取反之后再加1,得到的十六进制就是结果,以F补全空位
作用: 将操作数取反再加一, 可以用来求一个数的相反数.
格式 NEG OPR
执行操作
(OPR)<-- -(OPR)
亦即把操作数按位求反后末位加1,因而执行的操作也可表示为:
(OPR)<-- 0FFFFH - (OPR) + 1
NEG指令对标志的影响与用零作减法的SUB指令一样。
-----------------------------------------------------
但在OD中可以左下的command进行计算:? 十六进制
例如 ? -50 得到FFFF FFB0 这就是neg的作用,这里的计算是直接取十六进制反数
点击EAX修改数据时,有符号位展示的是十进制数,求十进制的取反也可以得到取反对应的十六进制
AND指令:(与)
AND EAX,ECX // EAX与ECX的各个位相与,得到的结果给EAX
// 同为1,为1,只要有0就为0
OR指令:(或)
OR EAX,ECX // EAX与ECX的各个位相或,得到的结果给EAX
// 只要有1就为1
XOR指令:(异或)
XOR EAX,ECX // EAX与ECX的各个位相异或,得到的结果给EAX
// 相同为0,不同为1
NOT指令:(反) // EAX与ECX的各个位相反,得到的结果给EAX
NOT EAX
// 各位取反
完整的EAX:0000 0000 0000 0000 0000 0000 0101 0000 32位
取反: 1111 1111 1111 1111 1111 1111 1010 1111
得到十六进制: FFFF FFAF
CMP指令:(比较指令:比较的是地址空间中的数值)
第一种情况:Z标志位
Z标志:当指令运算结果为0的话Z标志位就是1(变红就是当前得到的结果,白色是上一步的)
举例:cmp eax,ecx // cmp只做减法,但是结果不放在eax中(即不存储结果),而是将影响Z标志位为0还是1
// 本质为 sub eax,ecx做的减法放入eax
------------结果相同,Z标志位为1,结果不同,Z标志位为0
问题:
通常情况下CMP指令都是比较寄存器里的数值
那就是字符串相等的话,也会提示不等。(举例的这个比较寄存器里的字符串,这种例子基本上很少有的)
这是因为比较的内容并不是字符串,而是寄存器里的数值而已,也就是字符串的地址,也就是我们语言的字符串指针。
如果要比较字符串的话,是需要取寄存器里的数值作为地址,然后再取地址里的值(HEX 数据)进行运算,也就是我们C语言的指针,是相同的意思。
第二种情况:S标志位
S标志:符号标志:当指令运算结果为负数的话,S标志结果为1
cmp eax,ecx 如果走本质是减法运算的话,部分情况下,S标志位也是受影响的
第三种情况:cmp eax,[0x数值地址]
cmp eax,[402000]——————>>>这样写,OD自动转为cmp eax,dword ptr ds:[0x402000]
-------------------------------------------
cmp中需要注意的:
1、cmp eax,ecx 这种情况下,比较的是寄存器中的数值是否相等,也就是字符串的地址,也就是我们语言的字符串指针
也叫寄存器数值比较
2、cmp [0x402000],[0x402011] 或 cmp eax,[402011]其中eax=0x402000
要比较字符串的话,是需要取寄存器里的数值作为地址,然后再取地址里的值进行运算,也就是我们C语言的指针,是相同的意思
这叫字符串值比较
// 将数据地址:402000的地方在左下角写入数据字符串(xxx),在402010的地方写上(aaa);写完一个字符串需要写一个00,因为以00结尾字符串数据,然后原本写402010就写成402011,修改EAX和ECX分别为上面的地址进行cmp,我们发现地址对应的ASCII字符串是不一样的,进行比较发现。Z标志位为红色0,所以不同。
// 也就是说,cmp比较的就是十六进制数值地址对应的数据内容,那些地址类似与字符串指针比较操作
所谓的做减法,也就是说:
例如:402000地址对应的字符串(xxx)的数据的十六进制为:77 88 55 77 44
402011地址对应的字符串(aaa)的数据的十六进制为:99 44 F1 33 22
// 将(给机器处理)该十六进制转为二进制进行减法运算,或者(给人看)二进制再转为十进制运算
TEST指针:(逻辑比较)
test eax,eax // 意义就是判断eax是否为0
// 本质就不是减法了,本质是做了位运算(按位与的方式进行运算:都为1才为1)
测试操作数是否为0
cmp与test的区别:
cmp判断两地址中的值是否相等
test是测试这个数是否为0
(跳转指令),影响跳转的是标志位
OD运行到指定地址:首先左键点击选中该行,然后按下F4就能运行到指定位置
查看辅助的跳转路径线,没开启的话需要开启:调试——>CPU——>显示跳转方向、路径等几个选项
JMP指令:(无条件跳转)不受标志位影响
jmp 00401031 // 直接跳过中间代码,到00401031位置,jmp不受标志位影响,直接跳
JE指令:(有条件跳转)受标志位影响
Z(ZF)标志位1的时候会跳转,为0不跳转。je看的Z标志位
JZ指令:(有条件跳转)受标志位影响
JZ和JE是相同的,仅仅只是名字不同,实际上输入汇编语句使用JZ也会被OD用JE替代
JNE指令:(有条件跳转)受标志位影响
Z(ZF)标志位0的时候会跳转,为1不跳转。JNE看也是的Z标志位
JNZ指令:(有条件跳转)受标志位影响
JNZ和JNE也是相同的,仅仅名字不同,实际上输入汇编语句使用JNZ也会被OD用JNE替代
JS指令:(有条件跳转)受标志位影响
S(SF)标志位1的时候会跳转,为0不跳转。S:符号标志位
cmp eax,ecx // 本质是eax-ecx,这里eax=0000 0000 ecx=0000 0001,得到-1,S标志位为1
js 00401007
JNS指令:(有条件跳转)受标志位影响
S(SF)标志位0的时候会跳转,为1不跳转。S:符号标志位
cmp eax,ecx // 本质是eax-ecx,这里eax=0000 0001 ecx=0000 0000,得到1,S标志位为0
js 00401007
JP指令:(有条件跳转)受标志位影响
P(PF)标志位1的时候会跳转,为0不跳转。P:奇偶标志位(就是将汇编指令计算得到的结果转为二进制,二进制中1的个数为偶数的话,那么P标志位就是1)
// 修改寄存器中的数据时,有符号处为十进制数
JPE指令:(有条件跳转)受标志位影响
JP和JPE也是相同的,仅仅名字不同,实际上输入汇编语句使用JP也会被OD用JPE替代
JNP指令:(有条件跳转)受标志位影响
P(PF)标志位0的时候会跳转,为1不跳转。P:奇偶标志位(就是将汇编指令计算得到的结果转为二进制,二进制中1的个数为偶数的话,那么P标志位就是1)
// 修改寄存器中的数据时,有符号处为十进制数
JPO指令:(有条件跳转)受标志位影响
JNP和JPO也是相同的,仅仅名字不同,实际上输入汇编语句使用JNP也会被OD用JPO替代
JO指令:(有条件跳转)受标志位影响
O(OF)标志位1的时候会跳转,为0不跳转。O:溢出标志位
举例:修改EAX=7FFF FFFF,修改ECX=0000 0001
add eax,ecx
jo 0x00401008
// 加一以后,最大正整数变为8000 0000负数,所以O标志位会变为红1
JNO指令:(有条件跳转)受标志位影响
O(OF)标志位0的时候会跳转,为1不跳转。O:溢出标志位
JB指令:(有条件跳转)受标志位影响
C(CF)标志位1的时候会跳转,为0不跳转。C:借位标志位(也可以叫进位标志位)
cmp eax,ecx
jb 0x00401018 // JB的条件是:第一个操作数小于第二个操作数就跳转发生;即eax小于ecx就跳转
// 发生改变的是C(CF)标志位:借位标志位置1,发生了跳转
JNB指令:(有条件跳转)受标志位影响
C(CF)标志位0的时候会跳转,为1不跳转。C:借位标志位(也可以叫进位标志位)
cmp eax,ecx
jb 0x00401018 // JB的条件是:第一个操作数大于或等于第二个操作数就跳转发生;即eax大于或等于ecx就跳转
// 发生改变的是C(CF)标志位:借位标志位置0,发生了跳转
JBE指令:(有条件跳转)受标志位影响
受两个标志位影响:C和Z
C为1或者Z为1的时候,都可以跳转
// cmp eax,ecx 也可以说JBE的条件还有:第一个操作数小于或等于第二个操作数就都可以跳转发生;即eax小于或等于ecx就都可以跳转
JA指令:(有条件跳转)受标志位影响
实际上也叫JNBE指令,OD中输入JNBE指令的时候,会自动以JA指令填充
受两个标志位影响:C和Z
C为0与Z为0的时候,才可以跳转
// cmp eax,ecx 也可以说JNBE的条件还有:第一个操作数大于第二个操作数才可以跳转发生;即eax大于ecx才可以跳转
JL指令:(有条件跳转)受标志位影响
S(SF)标志位1的时候会跳转,为0不跳转。S:符号标志位
cmp eax,ecx
JL 0x401018 // 第一操作数小于第二操作数时就跳转(影响的标志位是S(F)标志位)
十进制有符号数比较,JB是转成十进制无符号数进行比较(JB只关心是否进位或者进位)
-----可以在寄存器上右键修改看到
CALL指令:(函数指针指令)
call就是执行了一个函数,call后面就是函数的地址;call要执行这个函数,执行完再返回这个call
---实际上call就是函数调用(F7进入call,也就是函数的执行内容,当遇到return的时候就回到主程序;有个辅助括号,说明哪儿才是结束的return)
---包括右下角的堆栈单元里面都会提示返回地址,也就是call的地址
(call就是函数的代表,F7进入函数,F8直接执行过函数;很多函数调用,至于哪个才是自定义的函数,使用:
选中call,右键跟随那么就能到call地址,不需要分析只需要小键盘的减号键就能回到主程序)
如何存储call返回地址的?
F7进call看堆栈,堆栈最开始就记录下来的初始开始地址以及最后的返回地址;
(例如:返回到 call函数的下一个地址 来自 call内函数的起始地址)
简单地说:call就是进入一个函数执行函数代码,return就是出函数
举例:PUSH 0x402000
retn
等价于:jmp 0x402000
call可以函数嵌套,所以,要耐心跟call,哪些是有用的自定义的函数或者底层用的函数,哪些就是普通函数不用管的,熟练利用F7进call,和F8单步直接执行call
堆栈中的返回地址上面的内容是call的数据,这个数据可能是push压栈的也可能是生成的,需要记住观察堆栈内容
LOOPD指令:(循环指令)
举例:xor ecx,ecx //清空ecx // 需要计数器表名循环几次,基本会用ecx做计数器,不约而同的规则
mov ecx,6 // 定义ecx循环6次
// 开始循环
dec ecx // ecx自减一,每次循环减一
test ecx,ecx // 判断是否为0 或者用cmp ecx,0x0
// 等于0就不循环了,执行跳转
jne 00401007
LOOP举例:
xor ecx,ecx
mov ecx,0x6
loopd 0x0040100A // (相当于执行了:dec ecx、test ecx,ecx、jnz 0x0040100A)
使用的小差别:首测循环的时候loopd没有减一,导致ecx为1的时候就执行了,而另一种方式首次减一了的所以执行的时候ecx为0的时候执行的
-----但是都是循环定义的次数,只是ECX结果有差别
LOOP指令:是8位指令,CL
LOOPW指令:是16位指令,CX
LOOPD指令:是32位指令,ECX
shl指令是汇编语言中的一个指令:
格式:SHL DST,1 。
功能:将目的操作数顺序左移1位或CL寄存器中指定的位数。左移一位时,操作数的最高位移入进位标志位CF,最低位补零。
特点:SHL指令将影响CF和OF两个标志位。如果移位次数为1,且移位后的符号位的值发生变化,则OF=1,否则OF=0.如果移位次数不为1,则OF的值不确定。
SHL指令使目的操作数逻辑左移一位,最低位用0填充。最高位移入进位标志位,而进位标志位中原来的数值被丢弃。
若将1100 1111左移1位,该数就变为 1001 1110
SHL指令将影响CF和OF两个标志位。如果移位次数为1,且移位后的符号位的值发生变化,则OF=1,否则OF=0.如果移位次数不为1,则OF的值不确定。
例如:
BL左移一位。最高位复制到进位标志位,最低位填充0:
mov bl, 8FH ; BL = 10001111b
shi bl, 1 ; CF = 1, BL = 00011110b
当一个数多次进行左移时,进位标志位保存的是最后移出最高有效位(MSB)的数值。
mov al, 10000000b
shi al, 2 ; CF = 0, AL = 00000000b
步骤:
1.将一个寄存器或者内存单元中过的数据向左移动bit位。
2.将溢出的这一位放到CF中。
3.最低位用0补充。
位元乘法:
数值进行左移即行了位元乘法。
任何操作数左移 n 位,将该数乘以 2n。
例如,十进制数 10 左移两位,其结果与10 * 2n相同。
mov al, 10 ; 00001010
shl dl ,2 ; 00101000
SHR指令
SHR指令使目的操作数逻辑右移一位,最高位用0填充。最低位置复制到进位标志位CF,而进位标位中原来的数值被丢弃。
例如:
AL中的最低位0被复制到进位标志位,而AL中的最高位用0填充。
mov al, 0D0H ; aL = 11010000b
shr al, 1 ; aL = 01101000b, CF = 0
在多位移操作中,最后一个移出位0的数值进入进位标志位。
mov al, 00000010b
shr al, 2 ; AL = 00000000b, CF = 1
总结:
当一个数多次进行右移时,进位标志位保存的是最后移出最低有效位的数值。
移位指令分为逻辑移位指令和算术移位指令,分别具有左移与右移。
SHL逻辑左移;左移,最低位补0,最高位进入CF。
SHR逻辑右移;右移,最高位补0,最低位进入CF。
SAL算术左移;左移,与SHL功能相同。
SAR算术右移;右移,最高位不变(符号位,若为负,则进1),最低位进入CF。
CDQ扩展指令:是一个让很多人感到困惑的指令,用于扩充EAX的存储范围
这个指令把 EAX 的第 31 bit 复制到 EDX 的每一个 bit 上。 它大多出现在除法运算之前。它实际的作用只是把EDX的所有位都设成EAX最高位的值。也就是说,当EAX <80000000, EDX 为00000000;当EAX >= 80000000, EDX 则为FFFFFFFF。
例如 :
假设 EAX 是 FFFFFFFB (-5) ,它的第 31 bit (最左边) 是 1,
执行 CDQ 后, CDQ 把第 31 bit 复制至 EDX 所有 bit
EDX 变成 FFFFFFFF
这时候, EDX:EAX 变成 FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。
备注:
EDX:EAX,这里表示EDX,EAX连用表示64位数
CDQ指令的功能是该指令先把edx的每一位置成eax的最高位,意思就是说把EAX由32位扩展成64位(EDX:EAX),EDX表示原来EAX的符号,
这个是进行除法之前的准备操作!
eax除以ecx,商保存在eax,余数放在edx ,基本上没edx什么事,除了后面保存余数
先前把符号位保存到edx里面,等除法做完了 ,符号位又回到eax里面了
其他指令:
;CBW(Convert Byte to Word): 将 AL 扩展为 AX
;CWDE(Convert Word to Extended Double): 将 AX 扩展为 EAX
;CDQ(Convert Doubleword to Quadword): 将 EAX 扩展为 64 位数 EDX:EAX
;CWD(Convert Word to Doubleword): 将 AX 扩展为 DX:AX
;它们都是用符号位填充多出的空间
;它们对 EFLAGS 无影响, 也无参数
int3指令:断点指令(机器码为:CC)
sete指令:
sete al
当前标志位是什么,al就是什么
寻址方式
直接寻址:不用执行一眼就看得出来地址,写清楚地址的
例如:直接表明地址了
MOV DWORD PTR [00401000],EAX
MOV AXE, WORD PTR [00401000]
CALL 401000
JMP 401000
间接寻址:需要程序执行到,才知道地址的,就是间接寻址
例如:未写出地址,需要执行到才知道地址
MOV DWORD PTR[EAX],ECX
CALL EAX
JMP [EBX + 8]