- 汇编与机器代码一一对应,但是汇编代码却与高级语言不是一一对应的。
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异或的值赋值给op1add 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来填充。修改了代码之后右击选择复制到可执行文件,然后右击保存