|
楼主 |
发表于 2011-2-24 13:32:36
|
显示全部楼层
七、标志寄存器
在CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种功能。
1、用来存储相关指令某些执行结果;
2、用来为CPU执行相关指令提供行为依据;
3、用来控制CPU的相关工作方式。
标志寄存器又叫EFlags寄存器,它与其它寄存器不一样,其它寄存器是用来存放搂据的,都是整个寄存器具有一个含义。而EFlags寄存器是按位起作用的,也就是说,它是每一位都有专门含义,记录特定的信息。
7.1、ZF标志位
ZF标志位记录相关指令执行后,其结果是否为0,如果结果为0,那么ZF=1;如果结果不为0,那么ZF=0。
举个例子,执行如下指令;
mov eax,1
sub eax,1
mov eax,1指令执行后,eax的值为00000001H;sub eax,1 指令执行后,eax的值为00000000H,应为执行结果为0,所以ZF=1。
mov eax,2
sub eax,1
mov eax,2指令执行后,eax的值为00000002H;sub eax,1指令执行后,eax的值为00000001H,因为执行结果不为0,所以ZF=0。
7.2、PF标志位
PF标志是奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如是1的个数为偶数,PF=1,如果为奇数,那么PF=0。
举个例子,执行指令:
mov ebx,1
add ebx,10
mov ebx,1指令执行后,ebx的值为100000011H。将100000011H转换成2进制为10001B(在计算机中,用后缀B表示二进制数,如001B就是一个二进制数,而001H则是十六进制数)。10001B中有2个1。2为偶数,所以PF=1。
mov ebx,1
add ebx,12
mov ebx,1指令执行完成后,ebx的值为00000001H;add ebx,12指令执行后,ebx的值为00000013H。将00000013H转换成2进制为10011B。10011B中有3个1。3为奇数,所以PF=0。
那么如果指行指令:sub eax,eax
eax-eax的值肯定为0,其中有0个1,而0在这里作为偶数来识别,所以PF=1。
7.3、SF标志位
SF标志位是符号标志。它记录相关批蛉 执行后,其结果是否为负。如果结果为负,SF=1;如果非负,SF=0 。
计算机中通常用补码来表示有符号的数据。计算机中的一个数据可以看做是有符号数,也可以看成是无符号数。如:00000001B,可以看作是无符号数1,或者是有符号数+1;10000001B,可以看作为无符号数129,也可以看作是有符号数-127。
上面的例子告诉我们,对于同一个二进制数,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。比如;
mov al,10000001B
add al,1
执行结果:al=10000001B
可以将ADD指令进行的运算当作无符号数的运算,那么ADD指令相当于计算,那么ADD指令相当于计算129+1,结果为130(10000010B);也可以将ADD指令进行的运算当作有符号数的运算,那么ADD指令相当于算-127+1,结果为-126(10000001B)。
SF标志,就是CPU对符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,显然相关的指令影响了它的值。
执行如下指令:
mov al,10000001B
add al,1
执行后,结果为10000010,SF=1,表示:如果指令进行的是有符号数运算,那么结果为负;
mov al ,10000001B
add al,01111111B
执行后,结果为0,SF=0,表示:如果指令进行的是有符号数运算,那么结果为非负。
7.4、CF标志位
CF标志位是进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
举个例子来说明CF标志位的作用:
mov eax,80000000
add eax ,eax
add eax, eax
第一行指令执后,eax的值为80000000H;当执行eax+eax这一运算后,要向最高位进位,此时的值为00000000H,CF标志位为1;再次执行eax+eax运算,此时不能存在进位与借位的问题,CF寄存器的值变为0。
某些指令将影响标志寄存器的多个标记位,这些被影响的标记位比较全面地记录了指令的执行结果,为相关的处理提供了所需的依据。比如指令sub eax,eax执行后,ZF、PF、SF等标志位都要受到影响,它们分别为:1、1、0。
=======================================================================
八、算术运算指令II
8.1、ADC指令
ADC指令类似于ADD指令,都是加法运算,但它们之间也有秀大的区别。DC指令被称为带进位加法,在反汇编代码中经常看到以下格式显示的ADC指令:
ADC 操作对象1,操作对象2
功能:操作对象=操作对象1+操作对象2+CF
执行如下指令:
mov ebx,2
mov eax,1
sub eax,ebx
adc ebx,1
mov ebx,2指令执行后,寄存器ebx的值为00000002H;mov eax,1指执行后,寄存器eax的值为00000001H;sub eax,ebx指令执行后,寄存器eax的值为FFFFFFFFH,也就是十进制的-1,CF的值变为1;adc ebx,1指令执行后,ebx=ebx+1+1=00000004H。而如果把上例中的adc ebx,1换成add ebx,1, ebx的最终结果为3H,因为ADD指令不加CF标志位的数值。
8.2、SBB指令
SBB类似于SUB指令,都是减法指令。SBB指令与SUB指令的关系类似于ADC指令与ADD指令的关系。SBB指令在运算的时候要使CF标志位加入到运算中来,而SUB指令则没有将CF标志位的值加入到运算中。反汇编代码中常见到的SBB指令的格式如下:
SBB 操作对象1,操作对象2
功能:操作对象1=操作对象1-操作对象2-CF
执行如下指令:
mov ebx,2
mov eax,1
mov ecx,2
sub eax,ebx
sbb ecx,1
mov ebx,2指令执行后,寄存器ebx的值为00000002H;
mov eax,1指令执行后,寄存器eax的值为00000001H;
mov ecx,2指令执行后,寄存器ecx的值为00000002H;
sub eax,ebx指令执行后,寄存器eax的值为FFFFFFFFH,也就是十进制的-1,CF的值变为1;
sbb ecx,1指令执行后,ecx=2-1-1=00000000H。
8.3、INC指令
INC指令蛔一指令,用于给操作对象进行加一操作。INC指令的格式如下:
INC 操作对象
功能:操作对象=操作对象+1
INC指令中的操作数可以是寄存器的存储器作操数,但不能是立即数和段寄存器。INC指令影响标志位OF,SF,ZF,PF和AF,不影响CF。
执行如下指令:
mov ebx,1
inc ebx
执行结果为ebx=00000002H。
8.4、DEC指令
DEC指令是减一指令,与INC指令相对。DEC指令与INC指令使用方法类似,都只有一个操作对象。
DEC 操作对象
功能:操作对象=操作对象-1
8.5、CMP指令
CMP指令是比较指令,CMP指令有两个操作数,在执行CMP指令时,类似于执行SUB指令,只不过CMP指令是不保存结果的,而只影响标志寄存器的相关标志位。在CMP之后的指令往往会通过识别标志寄存器的相关标志位来进行下一步操作。
CMP指令的格式也类似于SUB指令,其格式如下:
CMP 操作对象1,操作对象2
功能:计算机操作以象1-操作对象2,但是不会将计算结果保存到任何一个操作对象中,但是计算过后,会对标志寄存器的相关标志位进行设置。
举个例子,执行指令cmp eax,eax ,结果为0,但是并不把这个结果保存到eax中。可是标志寄存器中的相关标志位的值却受到了影响,指令执行后:ZF=1,PF=1,SF=0,CF=0
九、逻辑运算指令
逻辑运算又称布尔运算
9.1、AND指令
汇编中的AND指令是逻辑按位与运算批令,首先让我们来看一下二进制数的运算。
现在有两个二进制数,它们分别是100000001B和110000000B,这里将其记作数A和数B。为了方便理解,这里纵向放置。
100000001B
110000000B
如果相同位置上,两个数的值都是1则计算结果中该位置的数值也为1,否则为0。在这个例子中,数A的第7位是1,数B的第7位也是1,而其它位置上,不存在数A与数B都为1的情况,所以这两个数与运算的结果为10000000B。在数学和一些计算机高级编程语言上,逻辑与运算的运算符是“&”,那么上面的运算可以表示成:
100000001B & 110000000B
反汇编中看到的AND指令总是以如下形式存在的:
AND 操作对象1,操作对象2
功能:操作对象1=操作对象1 & 操作对象2
9.2、OR指令
OR在汇编中是按位逻辑或运算指令,其用法与AND指令相似。
或运算中,两个操作对象在相同位置上的数值有一个1,结果中该位置的数值就为1,否则为0。
或运算的运算符是“||”,举个例子,这里操作的还是上例中的数A和数B,将数A与数B进行与运算。
数A中第0位和第7位为1;数B中第7位和第6位为1。所以10000001B || 11000000B=11000001B。
在汇编中,逻辑按位或运算指令的格式如下:
OR 操作对象1,操作对象2
功能:操作对象1=操作对象1 || 操作对象2
9.3、XOR指令
XOR是按位异或运算指令。按位异或的数学运算符是“^”。如果把两个数进行异或运算,在相同位上数值相同为0,相异为1。举个例子:
10001B XOR 10101B就是将10001B与10101B进行异或运算。
来看最前边的一位,10001B最前边的一位是1;10101B最前边的一位也是1。二者相同,所以该位运算后的结果为0。
第二位也都是0,所以该位运算结果为0。第三位分别为0和1,二者不同,所以该位运算结果为1。第四位运算结果为0。第五位运算结果也为0。这样最终的运算结果就是00100B。
下面再进行一个异或运算:00100B ^ 10101B
第一位数值不同,所以该位运算结果为1;
第二位数值相同,所以该位运算结果为0;
第三位数值相同,所以该位运算结果为0;
第四位数值相同,所以该位运算结果为0;
第五位数值不同,所以该位运算结果为1。
最终的运算结果就是10001B。
XOR指令的格式如下:
XOR 操作对象1,操作对象2
功能:操作对象1=操作对象1 ^ 操作对象2
9.4、TEST指令
TEST指令的运算操作与AND指令类似,而功能却与CMP指令类似。
TEST指令的格式如下:
TEST 操作对象1,操作对象2
功能:操作对象1 & 操作对象2,这一点与AND指令相似,都是进行与运算,但是不同于AND指令的是,TEST运结果不保存。说它与CMP指令也很类似是因为,它影响标志寄存器中相关的标志位。跟在TEST后面的指令根据TEST运算后标志寄存器中相关标志位的数值来进行下一步操作。
=======================================================================
十、程序转移指令
汇编语言中的程序转移指令有很多,我们要学习的是CALL、RETN/RETF、JE、JNE、JB、JNB、JA、JNA、LOOP、NOP指令
其中CALL和RETN/RETF是无条件转移指令;JE、JNE、JB、JNB、JA、JNA是条件转移指令;LOOP是循环控制指令;NOP是处理器控制指令。
10.1、CALL指令
CALL指令可不是如唤指令,而是子程序调用指令。那么汇编语言中的子程序是什么呢?子程序能被其它程序调用,在实现某种功能后能自动返回到调用程序去的程序。其最后一条指令一定是返回指令,故能保证得新返回到调用它的程序中去。也可调用其它子程序,甚至可自身调用。
我们可以暂时把子程序理解为一个代码段,是一个模块化的代码面。这个代码段可以完成某一特定功能,当程序在执行过程中需要用到这一功能,将会进入这个代码段。这块代码段执行完毕后,会跳出这块代码段。而进入代码段这一过程就是子程序的调用,也就是这里所说的CALL指令所要完成的工作。
反汇编经常看到的CALL指令的基本格式如下:
CALL 地址1
功能:调用地址1处的子程序
CALL指令分为两种情况,一种是段内转移;另一种是段间转移。这两种情况类似于JMP指令的相对跳转和绝对跳转(只不过相对跳转的JMP指令会有short标识)。
在CALL指令进行的是段内转移的情况时,跟在CALL后面的地址1为一个相对位移;而CALL指令进行的是段间转移的情况时,跟在CALL后面的地址1为一个绝对内存地址。
段内转移的CALL指令等价于两条指令:
push eip
jmp 目的位置
也就是说,执行段内转移的CALL指令时,相当于先后执行以上两条指令。
段间转移的CALL指令等价于三条指令:
push CS
push eip
jmp 目的位置
10.2、RETN/RETF指令
按照前面讲CALL指令举的那个例子,CALL指令是进入子程序的指令,而例子中所说的跳出子程序这一过程也需要2条指令,它们是RETN/RETF。
RETN/RETF是跳出子程序的指令,被称为返回指令。RETN指令用于从段内转移CALL进的子程序中返回;RETF指令用于从段间转移CALL进的子程序中返回。
RETN/RETF在反汇编代码中呈现的形式如下:
RETN
RETN 操作数1
RETF
RETF 操作数1
RETN等价于一条指令:POP eip
RETF等价于两条指令:
POP eip
POP CS
而带有操作数的RETN/RETF指令则是在POP之后,执行ESP=ESP+操作数1。
10.3、条件转移指令
既然在汇编语言中存在无条件转移指令,那么与之相对的条件转移指令也一定存在。它们分别是JE、JNE、JB、JNB、JA、JNA。
上述指令中的字母“J”都是“JUMP”的意思;字母“E”是“equal”;字母“N”表示“NOT”;字母“B”表示“below”;字母“A”表示“above”。
这些条件转移指令往往跟在“test”和“cmp”指令后,按照“TEST”和“CMP”对标志寄存器相关标志位的影响来进行相应的条件转移。具体如下:
指令 含义 检测的相关标志位
je 等于则转移 zf=1
jne 不等于则转移 zf=0
jb 低于则转移 cf=1
jnb 不低于则转移 cf=0
ja 高于则转移 cf=0且zf=0
jna 不高于则转移 cf=1或zf=1
在反汇编中,以上这几条条件转移指令的使用格式都相同,如下:
条件转移指令 内存地址1
判断条件,若符合跳转条件,则跳转到内存地址1处;若不符合跳转条件,则继续执行该指令的下一行指令。
10.4、LOOP指令
LOOP指令是汇编中的循环控制指令,这条指令可是重点中的重点。
LOOP指令需要一个寄存器来做计数器,通常这个寄存器是ecx。在反汇编代码中经常看到如下形式的LOOP指令:
mov ecx,数值1
一段指令
loop 内存地址1
功能:执行内存地址1到loop指令之间的代码数值1次。内存地址1到loop指令之间的代码被称为“循环体”。
举个简单的例子:
1000C6B6 mov ebx,1
1000C6BB mov ecx,8
1000C6C0 add ebx,ebx
1000C6C2 loopd short 1000C6C0
可以看到loop后面的内存地址就是指令add ebx,ebx所在的内存地址,那么循环体就是add ebx,ebx ,执行次数是8次。总结起来就是,执行8次指令 add ebx,ebx ,执行结果为ebx=00000100H。
10.5、NOP指令
NOP指令是最简单的汇编指令之一,它表示不进行任何操作。使用格式也非常简单,只需要一个NOP就可以了,如下:
NOP
当程序执行到这句代码,程序什么都不会做,而直接执行下一条指令。
=======================================================================
十一、环境保存
环境保存又叫做环境保护,这个概念在反汇编中经常看到。 在调用子程序之前,程序当前的很多信息保存在各个寄存器当中,当进入子程序之后,各寄存器的值可能被子程序修改。那么,程序从子程序中跳出并返回到主程序后,使用被子程序修改过的寄存器,程序会出现很多错误。为什么会出现这种情况呢?我们从ESP寄存器讲起。
11.1、变化的ESP寄存器
我们知道ESP寄存器始终指向栈顶,当程序CALL进子程序后,子程序中的很多指令都可以改变ESP寄存器的值。当使用RETN/RETF指令跳出子程序回到主程序后,主程序需要接着CALL进子程序之前的状态继续运行。而此时ESP寄存器的值已经变化,如果程序需要用到CALL进子程序之前压入堆栈的某一个值,需要使用POP这样的指令将堆栈中的值存放到某一寄存器中。但是此时ESP寄存器的值已经变化了,指向的不一定是之前压入堆栈那个所需要的数据。一旦使用类似POP这样的指令将现在ESP所指向的栈顶的数据提取出来,这极有可能将错误的数据提出来,从而导致程序执行错误。
11.2、LEAVE指令
在跳出子程序之前,需要将ebp的值恢复给esp。我们知道,跳出子程序的指令是RETN/RETF,所以要将esp恢复过程放在RETN/RETF指令之前。LEAVE就是放在RETN/RETF之前的恢复指令。LEAVE指令相当于先后执行如下两条指令:
mov esp,ebp
pop ebp
mov esp,bep指令执行后,将之前保存到ebp中的esp值还原给esp。但这时的寄存器esp并不是CALL进子程序之前的状态。
pop ebp指令执行后,将esp的值加上4,使此时的esp还原为CALL进子程序之前的状态。
在子程序中,ebp寄存器保存有原始的esp,并随时用作存取局部变量的指针地址,所以在任何时刻,不要尝试把ebp用于别的用途,否则可能会出现意想不到的结果。
这里还需要注意一点,往往调用子程序不需要保存所有的寄存器,那样很多子程序会失去意义。
=======================================================================
十二、手工加花免杀
12.1、加花免杀的原理
说到加花,要先说说花指令。花指令(junk code)指的是一段无用的指令,执行花指令前后,程序的各项参数都没有变化。加花免杀指的是在程序的头部加上一段花指令,当程序执行的时候,先执行花指令。花指令执行完毕后,执行程序原本的指令。因为执行花指令做的是无用功,所以这不会影响程序的功能。但是因为在程序的头部多了这些花指令代码,程序代码被复杂化了,很多时候,杀毒软件就不能再查杀这个病毒了。
12.2、一个典型的花指令
看下面这段汇编代码:
push ebp
mov ebp,esp
add esp,-OC
add esp,OC
push eax
jmp入口
这就是一段非常简单的花指令了,我们一行一行地看:
push ebp就是反ebp压入堆栈
mov ebp,esp就是把esp的值传递给ebp
add esp,-OC就是把esp加上-OC
add esp,OC就是把esp加上OC
push eax就是把eax压入堆栈
jmp入口 就是跳转到程序首先运行的地址
从上边的
add esp,-OC
add esp,OC
这两行可以看出,这两得是无用代码,先给esp加上了“-OC”,然后又加上了“OC”。这两个值,一个正的,一个负的,总的看来,就相当于什么也没做。
这就是典型的花指令了,因为它什么也没做,但是可以使原程序的代码复杂化。
12.3、为什么要编写花指令
我们并没有免杀效果好的花指令,因为那些广为流传的花指令是很容易被杀毒软件识别的。这样就使我们的加花免杀效果大打折扣。所我这里我们就来学习自己编写花指令。
12.3.1、堆栈平衡
关于花指令的编写,网上有这样的比喻:我们把一段花指令比喻成一道数学运算题,把汇编指令(push pop等)比喻成加减乘除,把寄存器或数据(eax,ebx,1等)比喻成数字(1,2,3等),那么要保持花指令堆栈的平衡,等于保持这道数学题的结果是0。
12.3.2、pushad和popad
前面讲过环境保存的相关,在编写花指令的时候也需要注意环境保存。否则,可能导致程序执行出错。
这里给出两条非常实用的花指令,pushad和popad指令。pushad指令是将8个通用寄存器压入堆栈;popad指令则是与pushad相反。pushad将8个通用寄存器压入堆栈的顺序和popad将堆栈中的数据返回到8个通用寄存器的顺序相对,不会产生寄存器恢复错位的问题。使用这两条指令就可以轻松的实现环境的保存和恢复。
12.3.3、常用平衡指令
这里给出一些简单的平衡指令,所谓平衡指令就是实现花指令无效这一特性成对的指令。
push ebp——把基址指针寄存器压入堆栈
pop ebp ——把基址指针寄存器弹出堆栈
push eax——把数据寄存器压入堆栈
pop eax——把数据寄存器弹出堆栈
nop ——不执行
add esp,1——指针寄存器加1
sub esp,1——指针寄存器减1
inc ecx——计数器加1
dec ecx——计数器减1
sub esp,1——指针寄存器减1
sub esp,-1——指针寄存器加-1
jmp 入口地址——跳到程序入口地址
push 入口地址——把入口地址压入堆栈
retn ——返回到入口地址,效果与jmp入口地址一样
mov eax,入口地址——把入口地址转送到数据寄存器中
jmp eax ——跳到程序入口地址
jb 入口地址
jnb 入口地址 ——效果和jmp入口地址一样,直接跳到程序入口地址
注:以后编写花指令,都可以参考以下灵活组合,快速写出自己的花指令。
push ebp
pop ebp
puh eax
pop eax
push esp
pop esp
push 0
push 0
push 10 ——其中数字可以任意,注意与下面对应
push -10
nop ——可任意在中间添加
mov edi,edi ——效果与nop一样
add esp,1 ——其中数字可以任意,注意与下面对应
add esp,-1
add esp,1 ——其中数字可以任意,注意与下面对应
sub esp,1
inc ecx
dec ecx
sub eax,2 ——其中数字可以任意,注意与dec的个数对应
dec eax
dec eax
add eax,-2 ——其中数字可以任意,注意与inc的个数对应
inc eax
inc eax
jmp 下一个jmp地址
jmp 下一个地址
push ebp
mov ebp,esp ——可做为花指令的开头句
jmp 入口地址 ——跳到程序入口地址
与它效果一样的还有(以下三个):
push 入口地址
retn
jb 入口地址
jnb 入口地址
mov eax,入口地址
jmp eax |
|