汇编相关

  • 汇编与机器代码一一对应,但是汇编代码却与高级语言不是一一对应的。
    struct Student
    {
        int num;
        int index;
        int score;
    };
    Student student = { 1,2,3 };

00FF1878 C7 45 F0 01 00 00 00 mov         dword ptr [ebp-10h],1  
00FF187F C7 45 F4 02 00 00 00 mov         dword ptr [ebp-0Ch],2  
00FF1886 C7 45 F8 03 00 00 00 mov         dword ptr [ebp-8],3


    int array[] = { 1,2,3 };
002B1878 C7 45 F0 01 00 00 00 mov         dword ptr [ebp-10h],1  
002B187F C7 45 F4 02 00 00 00 mov         dword ptr [ebp-0Ch],2  
002B1886 C7 45 F8 03 00 00 00 mov         dword ptr [ebp-8],3 

上述为两串代码所对应的机器代码和汇编语言,从其中可以看出,不同的代码可以对应同一串机器语言和汇编代码。

VS常用的快捷键

  • 调试
    F9:切换断点
    Shift +F11 :step out
    Ctr+J: 智能提示
    Tab:直接使用智能提示内容

Ctr+M,M:折叠或者展开当前方法
Ctr+M,O:折叠所有方法
Ctr+M,L:展开所有方法

项目中的重订解决方案目标可以快速重新定义项目的SDK

函数重载

  • 注意:
    返回值类型与重载无关
    调用函数重载的时候,因为实参的隐式类型转换可能会产生二义性

  • 本质:
    C++采用了name mangling或者叫name decoration技术,即编译的时候,会将函数名进行转换,从而保证重载的进行

  • 利用IDA可以分析exe文件
    release模式下,编译器会自动对代码进行优化,如将简单函数中的代码直接放到main函数中。当然VS的编译器也可以进制优化,在属性中可以修改将其优化禁止。

默认参数

在使用默认参数的情况下,本质还是push了多个参数,然后再进一步运行。

extern “C”

被extern "C"修饰的代码会按照C语言的方式去编译

//方法1
extern "C" void test() {

}
//方法2
extern "C" {
    void mytest() {

    }
}

当同时有函数的声明和实现的时候,extern "C"需要加载声明中,实现就没必要放置了。

当CPP调用C语言编写的库的时候,一般会在.h文件中声明所有的函数,然后自己的CPP文件中添加以下的代码就可以轻易地进行调用

extern "C"{
#include "test.h" 
}

当然,也可以在库函数的.h文件中直接添加extern代码进行简化。
为了保证C语言的库函数既可以在C中进行使用,也可以在CPP中进行使用,那么在需要判断下CPP特有的宏cplusplus

#ifndef _文件名_h
#define _文件名_h
#ifdef _cplusplus
extern "C"{
#endif
//C语言函数的声明

#ifdef _cplusplus
}
#endif
#endif

pragma once也可以用来防止头文件的内容被重复声明,起到与上面一样的效果,区别如下

  • #ifndef#define#endif受到C\C++标准的支持,不受编译器的任何限制。
  • 有些编译器对#progma once(较老编译器不支持,如GCC3.4之前)兼容性不够好
  • #ifndef#define#endif可以针对文件中的部分代码,但#progma once只能针对整个文件

内联函数

使用inline修饰函数的声明或者实现,编译器会直接将函数调用转为函数代码,这样会增加函数的体积,但是可以减少栈空间的利用。
内联函数和宏都可以减少函数调用的开销,而对比宏,内联函数多了语法检测和函数特性。而且宏会导致很多的问题

#define A(x) (x)+(x)
inline int sum(int x){ return x+x;}
int a=10;
A(++a);

const

struct Date{
int date;
int month;
int year;
}
Date d1={1,2,3};
const Date* p=&d1;
//在这样的情况下,p指针所指地址的对应结构体的成员变量是不能改变的,即以下的代码都是错误的,但是p可以指向另一个地址
p->year=2020;
*p=d2;
//但因为p不是常量,所以p的值可以改变,如下
p=&d2
 

const的位置问题,关键在于const右边修饰的是p还是*p

    int age = 10;
    //p1不是常量 *p1是常量
    const int * p1 = &age;
    //p2不是常量 *p2是常量
    int const  * p2 = &age;
    //p3是常量,*p3不是常量
    int * const p3 = &age;
    //都是常量
    const int * const p4 = &age;
    int const * const p5 = &age;

引用

注意点

  • 引用相当于变量的别名
  • 对引用做计算,就是对引用所指的变量做计算
  • 在定义的时候就必须初始化,一定指定了某个对象们就不能再改变
  • 可以利用引用初始化另一个,相当于一个变量有多个别名
  • 相比较于指针,引用更加安全

引用的本质

引用的本质是指针,一个引用在64操作系统下与指针的占用大小是一样的,都是8字节。
而且与实际的指针相比,引用是在变量前面加了const的指针,因为引用一旦定义就不能再引用其他的变量。
如果引用声明成常引用,那么该引用可以直接用10这样的常量来赋值

  • const引用相关
    当函数参数的值必须使用引用,但是希望可以将常量传入时,函数的形参可以声明成const int &test这样。同时声明成这样的好处是调用时,实参可以是一般的变量,也可以是常引用,也可以是常量
  • const修饰的引用可以指向不同的数据类型,但是不同的数据类型在汇编层其实就是新建了一个临时变量
int age=18;
const long &refAge=age;
age=24;
//age=24;refAge=18。相当于refAge指向的是一个临时的数据

汇编语言

通用寄存器
64bit
RAX、RBX、RCX、RDX
32bit
EAX、EBX、ECX、EDX
16bit
AX、BX、CX、DX
8bit
AH、AL、CH、CL、DH、DL、BH、BL
上述所有的低字节寄存器其实就是高寄存器的低端位
一般来说,R开头的都是64bit。E开头的是32bit

汇编要点总结

  • mov dest, src 相当于dest=src
  • [地址值] 。中括号里面放的都是内存地址
  • word是2字节,dword是4字节,qword是8字节
// 
mov         dword ptr [ebp-8],3 
  • call:函数调用

  • lea dest,[地址值]
    直接将地址值给到dest,(load effect address)

  • ret
    函数返回

  • xor op1,op2
    将op1和op2异或的值赋值给op1

  • add op1,op2

  • sub op1,op2

  • inc op

  • dec op

  • jmp 内存地址
    跳转到某个内存地址去执行代码。一般j开头的都是跳转,大多数是带条件的跳转

  • jne jump not equal
    比较结果不相等才进行跳转

    int a = 3;
00A62028 C7 45 F8 03 00 00 00 mov         dword ptr [ebp-8],3  
    int b = 4;
00A6202F C7 45 EC 04 00 00 00 mov         dword ptr [ebp-14h],4  
    if (a == b) {
00A62036 8B 45 F8             mov         eax,dword ptr [ebp-8]  
00A62039 3B 45 EC             cmp         eax,dword ptr [ebp-14h]  
00A6203C 75 0F                jne         00A6204D  
        printf("1");
00A6203E 68 CC 7B A6 00       push        0A67BCCh  
00A62043 E8 69 F3 FF FF       call        00A613B1  
00A62048 83 C4 04             add         esp,4  
    }
    else
00A6204B EB 0D                jmp         00A6205A  
    {
        printf("2");
00A6204D 68 D0 7B A6 00       push        0A67BD0h  
00A62052 E8 5A F3 FF FF       call        00A613B1  
00A62057 83 C4 04             add         esp,4  
    }
    getchar();
00A6205A 8B F4                mov         esi,esp  
00A6205C FF 15 94 B1 A6 00    call        dword ptr ds:[00A6B194h]  
00A62062 3B F4                cmp         esi,esp  
00A62064 E8 C2 F1 FF FF       call        00A6122B  
  • 引用和指针
//int *p=&a;
lea eax,[ebp-0CH]
mov dword ptr[0DH],eax
  • 注意点
    指针数组(存储指针的数组)int * p[3]
    数组指针(指向数组的指针)int(*p)[3]

软件破解

利用OD工具可以查看.exe文件的字节码。其中F2可以设置断点,然后进行软件的调试工作

暴力破解

分析代码中的逻辑,查找到关键的判断汇编的位置,将判断不相等的汇编改成空指令,即nop(90),将相应的字节换成90就可以跳过关键的判断,操作如下:右击相应的指令,然后选择二进制,用nop来填充。修改了代码之后右击选择复制到可执行文件,然后右击保存

你可能感兴趣的:(汇编相关)