学习汇编的一些记录

一、一些常用汇编指令

汇编指令(书上比较重要的)

jmp word ptr 内存单元地址(段内转移)

功能:从内存单元地址处开始存放一个字,是转会目的地的偏移地址。(实际上要去IP)

jmp dword ptr

功能:内存单元地址处开始存放着两个字,高地址处的字是转移地址的目的段地址,低地址处是转移的目的偏移地址

ret

pop IP

retf

pop IP
pop CS

call

push IP//将call指令下一句的IP入栈,在call内部会有个ret将IP又出栈,从而返回到call指令下一句。
jmp near ptr 标号

更多细节,和例子还是在书上看。

汇编指令(书上没有的)

var_4 = dword ptr -4

var_4 = dword ptr -4;   这是解释代码,可解释成var_4 是 esp - 4处的空间;
var_0 = dword ptr 8;    var_0 是 esp +8处的空间;

在IDA中VAR 是代表的函数中的局部变量.

你要知道在汇编中访问局部变量,是通过ebp-x的来定位栈中位置.

var a = dwod -10h
var b = dwod - 8h
var c = dwod - 4h

_main proc nea
push ebp
mov ebp,esp
sub esp,10h
mov [ebp-4],0   //同过这种方式访问局部变量,
等价于mov [ebp+c],0

那么我把var c 换个有意思的名字如 var Max = dword - 4h

mov [ebp+Max],0   //这个时候,就更清楚了。

lea

LEA 取有效地址指令 (Load Effective Address )
  指令格式:LEA 目的,源
  指令功能:取源操作数地址的偏移量,并把它传送到目的操作数所在的单元。
  LEA 指令要求原操作数必须是 存储单元 ,而且目的操作数必须是一个除段寄存器之外的16位或32位寄存器。当目的操作数是16位通用寄存器时,那么只装入有效地址的低16位。使用时要注意它与MOV指令的区别,MOV指令传送的一般是源操作数中的内容而不是地址。
  例1 假设:SI=1000H , DS=5000H, (51000H)=1234H
  执行指令 LEA BX , [SI]后,BX=1000H
  执行指令 MOV BX , [SI]后,BX=1234H
  有时,LEA指令也可用取偏移地址的MOV指令替代。
  例2 下面两条指令就是等价的,他们都取TABLE的偏移地址,然后送到BX中,即
  LEA BX,TABLE
  MOV BX,OFFSET TABLE

MOVZX指令

汇编语言数据传送指令MOV的变体。无符号扩展,并传送。

movzx是将源操作数的内容拷贝到目的操作数,并将该值0扩展至16位或者32位。但是它只适用于无符号整数。 他大致下面的三种格式。

  movzx 32位通用寄存器, 8位通用寄存器/内存单元

  movzx 32位通用寄存器, 16位通用寄存器/内存单元

  movzx 16位通用寄存器, 8位通用寄存器/内存单元

  举个例子。例如

  令eax=00304000h

  若执行 movzx eax, ax后 eax = 00004000h 。

  若执行 movzx eax, ah后 eax = 00000040h。

  又如:

  MOV BL,80H

  MOVZX AX,BL

  运行完以上汇编语句之后,AX的值为0080H。由于BL为80H,最高位也即符号位为1,但在进行无符号扩展时,其扩展的高8位均为0,故赋值AX为0080H。

  总结:

  movzx其实就是将我们的源操作数取出来,然后置于目的操作数,目的操作数其余位用0填充。

text指令

test 对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。

test eax ,eax 不改变值的大小,只改变标志位ZF。运算结果为0,则zf=1 ,否则zf=0

所有的对操作数进行算术和逻辑运算的指令,都会根据运算结果修改ZF标志。

jnz指令

JNZ : jump if not zero 结果不为零则转移

cmd

 cmp(compare)指令进行比较两个操作数的大小

 例:cmp oprd1,oprd2

 为第一个操作减去第二个操作数,但不影响第两个操作数的值,它影响flag的CF,ZF,OF,AF,PF.

CDQ

把EAX的第31位(这个位数是从右往左数的)复制到EDX的每个位去,常和除法连用。

如果EAX = 0x0000000A
那么执行CDQ后 EDX = 0x00000000,这样EDX:EAX就是0x000000000x0000000A的64bit的了,用来作为被除数(如果是除数是32位,就需要64位来作为被除数)
如果EAX = 0xFFFFFFFA
那么执行CDQ后 EDX = 0xFFFFFFFF

sar

sar是算术右移指令,功能是将操作数右移,符号位保持不变。
例如,sar eax,1//假设eax是0x0000000A,执行完之后eax就会变成0x00000005,相对于除以2。

imul和mul

imul 有符号乘法,将被乘数与乘数均作为有符号数。
mul 无符号乘法,将被乘数及乘数均作为无符号数。
可以有三个操作数:imul eax,eax,0Ch
第3操作数是乘数,
第2操作数是被乘数,
运算结果放入第1操作数。
也可以是两个操作数:imul eax,0Ch
目标操作数(第一个操作数)乘以源操作数(第二个操作数)

idiv(带符号数除法)/div(无符号除法)

写法:idiv reg;/div reg
作用:如果操作数是8位,结果商在AL、余数在AH中;

如果操作数是16位,结果商在AX,余数在DX中;

如果操作数是32位,结果商在EAX,余数在EDX中;

注意:不能直接实现8位数除8位数、16位数除16位数、32除32,若需要这样,则必须先把除数符号扩展或零扩展到16、32、64位,然后用除法指令。

跳转

JE   ;等于则跳转
JNE  ;不等于则跳转

JZ   ;为 0 则跳转
JNZ  ;不为 0 则跳转

JS   ;为负则跳转
JNS  ;不为负则跳转

JC   ;进位则跳转
JNC  ;不进位则跳转

JO   ;溢出则跳转
JNO  ;不溢出则跳转

JA   ;无符号大于则跳转
JNA  ;无符号不大于则跳转
JAE  ;无符号大于等于则跳转
JNAE ;无符号不大于等于则跳转

JG   ;有符号大于则跳转
JNG  ;有符号不大于则跳转
JGE  ;有符号大于等于则跳转
JNGE ;有符号不大于等于则跳转

JB   ;无符号小于则跳转
JNB  ;无符号不小于则跳转
JBE  ;无符号小于等于则跳转
JNBE ;无符号不小于等于则跳转

JL   ;有符号小于则跳转
JNL  ;有符号不小于则跳转
JLE  ;有符号小于等于则跳转
JNLE ;有符号不小于等于则跳转

JP   ;奇偶位置位则跳转
JNP  ;奇偶位清除则跳转
JPE  ;奇偶位相等则跳转
JPO  ;奇偶位不等则跳转

retn

pop ip
pop n//sp+n

前言:主要还是给自己看的。

二、堆栈平衡

堆栈平衡的原理

https://www.cnblogs.com/cat47/p/12285209.html这个上面讲的还可以。下面总结总结。

堆栈平衡主要分为两中情况

情况1:

就是call调用函数的时候,函数内部不能改变栈顶sp,如果有改变(例如函数内部有push或者pop指令,但是一般函数调用时,函数内部都会把esp给ebp,最后又把ebp给esp,所以一般不会变),也必须要在ret执行之前改为原来的栈顶sp,否则ret,pop的IP,可能将不是call的下一句的IP。

情况2:

用堆栈来传递参数,先改变原来的堆栈,在函数内使用了这个堆栈来传递参数,回到主函数后,要恢复原来的堆栈。

具体恢复方法有两种,一种是直接在主函数中用add或者sub修改sp的值。

另一种是,在函数内部就直接将sp修改。

其中的retn 8的意思就是

pop ip
pop 8

retn在最下面也有简单介绍

实验目的

自己动手了解堆栈平衡的原理,并观察调试过程中堆栈的变化情况。

实验样本1-调用一个空函数

由vc++6.0编译的 堆栈平衡.exe文件,代码如下

#include<stdio.h>

void kong()
{
}
int main(void)
{
    int a = 1, b = 2;
    kong();
    printf("%d\n", a+b);
    getchar();//主要是为了找到main函数。
}

调试过程

找到main函数
先F9到达EIP处,然后F8一直调,会在main函数处停下来。进入main函数。

注意这时候ESP,EBP的值,和在堆栈中的情况。

ESP:0019FF34
EBP:0019FF70


执行

push ebp

将ebp的值入栈,ESP-4:0019FF30,EBP不变,堆栈变化如下,注意灰色那一行

执行

mov ebp esp

将esp的值给ebp,ebp的作用可以理解为esp的替身(ebp的值只要在pop ebp之前都不会发生改变,不会受esp期间被改变的影响),用来访问栈内局部变量,参数,函数返回地址。这时候

ESP:0019FF30
EBP:0019FF30

执行灰色部分代码

不用了解具体过程,大概就是开辟一个4c空间的堆栈,执行后

ESP:0019FED8//灰色部分有3个push,esp-4c-12
EBP:0019FF30

堆栈情况为

继续执行下面两句指令。

将1存入ebp-4的地址,将2存入ebp-8的地址。堆栈情况如下

由于接下来调用的一个函数是空函数所以,不需要用push指令来开辟一段栈空间,用来传递参数。如果函数用来传递了参数,最后需要将开辟的堆栈还原,意思就是是esp要回到原来的值。

执行下一句语句

call 堆栈平衡1.401005
//相对于 pop eip//eip是call指令的下一句的地址。esp的值会变成esp-4:0019FED4
        jmz 00401005


进入call的函数,一个空函数。

第一句和上面的一样,直接看堆栈变化

EBP:0019FF30
ESP:0019FED0


0019FED0处存着ebp的值。

这些指令配合使用

push ebp
mov ebp,esp
...//中间的指令大概就是将esp-40,又push了一些东西,又pop了一些东西
mov esp,ebp
pop ebp

直接看mov esp,ebp执行前的堆栈情况

ESP:0019FE90
EBP:0019FED0


执行后

ESP:0019FED0
EBP:0019FED0


这时候ESP重新回到了原来进入这个函数的值,再

pop ebp

EBP变为0019FF30,ESP变为0019FED4,存着返回主函数的地址。

执行ret指令(相对于pop ip)回到主函数。ESP+4

ESP:0019FF30
EBP:0019FED0


由于是个空函数,没有传递参数,前面也没有开辟新的堆栈,所以不需要将esp进行加减,但如果之前调用了参数,就必须要在函数内将用rent指令将esp改了,或者在函数外用,add或者sub来改esp的值,来维持堆栈平衡。
继续下两条指令
mov eax,dword ptr ss:[ebp-4]
add eax,dword ptr ss:[ebp-8]



中ebp-4的1先给eax寄存器,再将ebp-8的2和eax的1用add指令加起来,将最后的结果传个eax寄存器,得到结果3。

最后的输出函数

知道EAX:累加(Accumulator)寄存器,常用于函数返回值,这几条语句是用来输出最后结果的,就可以了。

实验样本-裸函数1

void __declspec(naked)plus()
{
    __asm
    {
        ret
    }
}
int main(int argc,char*argv[])
{
    plus();
    return 0;
}

整个主函数

调用的裸函数

实验样本-裸函数2

#include<stdio.h>

int __declspec(naked)plus(int a,int b)
{
    __asm
    {
        mov eax,dword ptr ds:[esp+8]
        add eax,dword ptr ds:[esp+4]
        ret
    }
}
int main(int argc,char*argv[])
{
    int a;
    a=plus(1,2);
    printf("%d",a);
    return 0;
}


三、不同C语言语句在汇编中的显示情况及简单分析

1.if-else

源代码如下

#include<stdio.h>

int main(void)
{
    int a=1,b=2,c;
    if(a<b)
    {
        c=0;
    }
    else
    {
        c=1;
    }
    return 0;
}

对应汇编代码如下

1:    #include<stdio.h>
2:
3:    int main(void)
4:    {
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,4Ch
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-4Ch]
0040101C   mov         ecx,13h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
5:        int a=1,b=2,c;
00401028   mov         dword ptr [ebp-4],1
0040102F   mov         dword ptr [ebp-8],2
6:        if(a<b)
00401036   mov         eax,dword ptr [ebp-4]
00401039   cmp         eax,dword ptr [ebp-8]//cmp是比较指令,相对于sub指令,并改变一些标志寄存器的值。
0040103C   jge         main+37h (00401047)//前面的数大于或等于后面的数就跳转。实际上与SF,OF两个寄存器有关。
7:        {
8:            c=0;
0040103E   mov         dword ptr [ebp-0Ch],0
9:        }
10:       else
00401045   jmp         main+3Eh (0040104e)
11:       {
12:           c=1;
00401047   mov         dword ptr [ebp-0Ch],1
13:       }
14:       return 0;
0040104E   xor         eax,eax
15:   }

2.for循环

简单的累加代码

#include<stdio.h>

int main(void)
{
    int i,sum;
    sum=0;

    for(i=0;i<5;i++)
    {
        sum=i+sum;
    }

    printf("%d",sum);
}

对应汇编代码

1:    #include<stdio.h>
2:
3:    int main(void)
4:    {
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,48h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-48h]
0040101C   mov         ecx,12h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
5:        int i,sum;
6:        sum=0;
00401028   mov         dword ptr [ebp-8],0
7:


8:        for(i=0;i<5;i++)
0040102F   mov         dword ptr [ebp-4],0
00401036   jmp         main+31h (00401041)
00401038   mov         eax,dword ptr [ebp-4]
0040103B   add         eax,1
0040103E   mov         dword ptr [ebp-4],eax
00401041   cmp         dword ptr [ebp-4],5
00401045   jge         main+42h (00401052)
9:        {
10:           sum=i+sum;
00401047   mov         ecx,dword ptr [ebp-4]
0040104A   add         ecx,dword ptr [ebp-8]
0040104D   mov         dword ptr [ebp-8],ecx
11:       }
00401050   jmp         main+28h (00401038)
//8到11就是for循环

12:
13:       printf("%d",sum);
00401052   mov         edx,dword ptr [ebp-8]
00401055   push        edx
00401056   push        offset string "%d" (0042201c)
0040105B   call        printf (00401090)
00401060   add         esp,8
14:   }

3.while循环

#include<stdio.h>

int main(void)
{
    int i=0;
    int sum=0;
    while(i<5)
    {
        sum=sum+i;
        i++;
    }
    printf("%d",sum);
}

while循环对应的汇编代码

1:    #include<stdio.h>
2:
3:    int main(void)
4:    {
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,48h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-48h]
0040101C   mov         ecx,12h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
5:        int i=0;
00401028   mov         dword ptr [ebp-4],0
6:        int sum=0;
0040102F   mov         dword ptr [ebp-8],0
7:        while(i<5)
00401036   cmp         dword ptr [ebp-4],5
0040103A   jge         main+40h (00401050)
8:        {
9:            sum=sum+i;
0040103C   mov         eax,dword ptr [ebp-8]
0040103F   add         eax,dword ptr [ebp-4]
00401042   mov         dword ptr [ebp-8],eax
10:           i++;
00401045   mov         ecx,dword ptr [ebp-4]
00401048   add         ecx,1
0040104B   mov         dword ptr [ebp-4],ecx
11:       }
0040104E   jmp         main+26h (00401036)
12:       printf("%d",sum);
00401050   mov         edx,dword ptr [ebp-8]
00401053   push        edx
00401054   push        offset string "%d" (0042201c)
00401059   call        printf (00401080)
0040105E   add         esp,8
13:   }

4.do_while的汇编语句

#include<stdio.h>

int main(void)
{
    int i=0;
    int sum=0;

    do
    {
        sum=sum+i;
        i++;
    }
    while(i<5);
    printf("%d",sum);
}

do_while的汇编代码

1:    #include<stdio.h>
2:
3:    int main(void)
4:    {
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,48h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-48h]
0040101C   mov         ecx,12h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
5:        int i=0;
00401028   mov         dword ptr [ebp-4],0
6:        int sum=0;
0040102F   mov         dword ptr [ebp-8],0
7:
8:        do
9:        {
10:           sum=sum+i;
00401036   mov         eax,dword ptr [ebp-8]
00401039   add         eax,dword ptr [ebp-4]
0040103C   mov         dword ptr [ebp-8],eax
11:           i++;
0040103F   mov         ecx,dword ptr [ebp-4]
00401042   add         ecx,1
00401045   mov         dword ptr [ebp-4],ecx
12:       }
13:       while(i<5);
00401048   cmp         dword ptr [ebp-4],5
0040104C   jl          main+26h (00401036)
14:       printf("%d",sum);
0040104E   mov         edx,dword ptr [ebp-8]
00401051   push        edx
00401052   push        offset string "%d" (0042201c)
00401057   call        printf (00401080)
0040105C   add         esp,8
15:   }

5.数组的在堆栈中的情况

#include<stdio.h>

int main(void)
{
    int b;
    int a[]={1,2,3,4,5};

    scanf("%d",b);
}

堆栈中的样子

刚开始将数组中的数放到堆栈里面

放完之后

需要注意的点,数组中的数是在堆栈中是从上到下开始排的(我认为这可能和大端序和小端序,《逆向核心原理》中第3章有关),而且ebp-4位置存的是cccccccc,这应该是那个参数b

6.一个用函数来实现两个乘除的简单代码

c源代码

原c代码如下,其中的运算都为有符号运算,由于之前没有写关于乘除的汇编会是怎么样,所以这次顺便弄一个乘除的。:

#include<stdio.h>

int function(int x,int y)
{
    int z;

    z=(x*y)/5;//注意这里面是故意取5的,为了看余数的值会储存在哪个寄存器中。
    return z;    
}

int main(void)
{
    int a=3,b=4,c;

    c=function(a,b);
    printf("%d",c);
    return 0;
}

实际上,如果你如果是z=(x*y)/2,其对应的汇编代码会有一些不同。就像这样。

00401038   mov         eax,dword ptr [ebp+8]
0040103B   imul        eax,dword ptr [ebp+0Ch]//前两句实际上就是x*y
0040103F   cdq
00401040   sub         eax,edx
00401042   sar         eax,1//这是通过向右位移1位来达到除以2的目的,好像只有除以2的时候汇编代码会成为这样。
00401044   mov         dword ptr [ebp-4],eax

imul,cdq,sar这三个指令请在上面第一部分查看具体用法。

开始调试源程序

主函数

初始堆栈空间

执行这一句后的堆栈空间

401060    55    push ebp

执行这几句后的堆栈空间

401066    53                 push ebx
401067    56                 push esi
401068    57                 push edi
401069    8D7D B4    lea      edi,dword ptr ss:[ebp-4C]
40106C    B9 13000000         mov ecx,13
401071    B8 CCCCCCCC         mov eax,CCCCCCCC
401076    F3:AB             rep stosd

执行这几句后的堆栈空间

401078    C745 FC 03000000    mov dword ptr ss:[ebp-4],3
40107F    C745 F8 04000000    mov dword ptr ss:[ebp-8],4
401086    8B45 F8                mov eax,dword ptr ss:[ebp-8]
401089    50                    push eax
40108A    8B4D FC                mov ecx,dword ptr ss:[ebp-4]
40108D    51                    push ecx

function函数框架

进入function函数后的堆栈空间

执行这一句后的堆栈空间

401020    55    push ebp



执行完这几句的堆栈空间

401021    8BEC            mov ebp,esp
401023    83EC 44            sub esp,44
401026    53                   push ebx
401027    56                push esi
401028    57                push edi
401029    8D7D BC            lea edi,dword ptr ss:[ebp-44]
0040102C    B9 11000000    mov ecx,11
401031    B8 CCCCCCCC        mov eax,CCCCCCCC
401036    F3:AB            rep stosd

接下来是计算的指令,就自己根据本文第一部分常用的汇编指令,来慢慢分析了

401038    8B45 08                mov eax,dword ptr ss:[ebp+8]
0040103B    0FAF45 0C        imul eax,dword ptr ss:[ebp+C]
0040103F    99    cdq
401040    B9 05000000            mov ecx,5
401045    F7F9                idiv ecx
401047    8945 FC                mov dword ptr ss:[ebp-4],eax
0040104A    8B45 FC            mov eax,dword ptr ss:[ebp-4]

然后直到ret指令前的堆栈空间

执行ret后,回到主函数


执行这一句,保持堆栈平衡
add esp,8

后面的内容就不在调试了。

7.结构体指针

调试一个返回结构体指针的文件

c代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct _Student
{
    char stuName[20];
    int stuNo;
    int stuAge;
}Student,*STUDENT;

int function(Student **p)
{
    STUDENT q;//在子函数内部定义了一个结构体指针变量

    q=(Student*)malloc(sizeof(Student));//向堆申请一段内存空间,返回的地址值给结构体指针变量。
    strcpy(q->stuName,"wuhao"); //给结构体赋值
    q->stuNo=2020122188;
    q->stuAge=18;

    *p=q;//将这个结构体地址返回给主函数的结构体指针变量。

    return 0;
}

int main()
{
    Student *p;//定义了一个结构体指针变量
    function(&p);//传入结构体指针变量的地址

    if(p->stuNo==2020122188 && p->stuAge==18 && !strcmp(p->stuName,"wuhao"))//检验
    {
        printf("sucess!\n");
        printf("姓名:%s\n",p->stuName);
        printf("学号:%d\n",p->stuNo);
        printf("年龄:%d\n",p->stuAge);
    }
    else
    {
        printf("wrong!");
    }
    getchar();
}

调试过程

进入子函数前

进入子函数

回到主函数查看