-
选择结构
-
通过判断 + 条件跳转指令来实现
-
-
循环结构
-
通过判断 + 条件跳转指令来实现(会有一个向上跳转的语句)
-
-
函数调用约定
-
C调用约定: 由外部平衡栈
-
标准调用约定 : 由函数内部平衡栈
-
对象调用约定 : 由函数内部平衡栈, 寄存器用于保存对象的首地址(就是this指针)
-
快速调用约定 : 由函数内部平衡栈(传参都是从右往左传递.), 用到 ecx , edx 来依次传递前两个参数.
-
-
通过 call 指令, call指令的原理是: 将call指令的下一条指令的地址压入栈中, 然后再进行跳转.
-
通过 ret 指令, 来结束函数的调用,回到调用点, 原理: 将保存在栈中的返回地址 pop 到 eip .
-
需要在函数内部访问形参的方式:
-
通过 esp 来访问, 缺点: esp寄存器是受到一些栈操作指令被改变的(例如:push,call,ret) , 当它被改变之后, 就不能通过一个固定的偏移来定位栈里面的参数了.
-
通过 ebp 来访问的 , 原理: 进入到函数内部时, 将esp的值保存到ebp, 然后ebp是不会受到栈操作指令的映 像, 可以使用一个固定的偏移来定位栈里面的参数.
-
函数编写的要求:
-
保证在函数内部不会修改寄存器的值, 如果要修改,需要保证离开函数之后, 将修改的寄存器的值恢复 回去.
-
保证离开函数之后, 栈是平衡的(进入到函数时,栈顶esp指向了哪里, 那么在离开函数之后,esp也必须还 指向了那里),ebp也一样.
-
-
在函数内部如果要使用局部变量.
-
打开函数栈帧通过 push ebp; mov ebp,esp;
-
开辟局部变量占用的栈空间:
-
通过 ebp-4 来定位在栈中的局部变量: ebp-4 是局部变量1, ebp-8 是局部变量2
-
-
int fun( ) { int nArr[]={ 1,2,3,4,5}; printf("%d",nArr[0]); }
fun proc push ebp ;\ ; 打开栈帧 mov ebp , esp;/ sub esp , 20 ; 开辟局部变量的栈空间 mov [ebp-4] , 1; 初始化局部变量 mov [ebp-8] , 2; 初始化局部变量 mov [ebp-0ch] , 3; 初始化局部变量 mov [ebp-010h] , 4; 初始化局部变量 mov [ebp-014h] , 5; 初始化局部变量 mov esp , ebp; 恢复局部变量占用的栈空间 pop ebp ; 恢复栈帧 ret fun endp
5.字符串的汇编
char* pStr = "abcdefg"; dword ptr [ebp-18h],0BA7B40h //dword ptr [pStr],offset string "abcdefg" (0BA7B40h)
在内存输入00BA7B40
6.指针
//函数 void fun2(char* p) { p = "123456"; } //调用时 fun2(pStr); 00BA19EC 8B 45 E8 mov eax,dword ptr [pStr] 00BA19EF 50 push eax 00BA19F0 E8 4F F8 FF FF call fun2 (0BA1244h) 00BA19F5 83 C4 04 add esp,4 //进函数 p = "123456"; 00BA17D8 mov dword ptr [ebp+8],0BA7B30h //00BA17D8 mov dword ptr [p],offset string "123456" (0BA7B30h)
7.二级指针
//函数 void fun3(char** pp, char*& pr) { *pp = "123456"; pr = "456789"; } //调用时 fun3(&pStr, pStr); 00BA19F8 8D 45 E8 lea eax,[pStr] 00BA19FB 50 push eax 00BA19FC 8D 4D E8 lea ecx,[pStr] 00BA19FF 51 push ecx 00BA1A00 E8 02 F9 FF FF call fun3 (0BA1307h) 00BA1A05 83 C4 08 add esp,8 //进函数 *pp = "123456"; 00BA1838 8B mov eax,dword ptr [ebp+8] //00BA1838 8B mov eax,dword ptr [pp] 00BA183B C7 mov dword ptr [eax],0BA7B30h //00BA183B mov dword ptr [eax],offset string "123456" (0BA7B30h) pr = "456789"; 00BA1841 mov eax,dword ptr [ebp+0Ch] //00BA1841 8B mov eax,dword ptr [pr] 00BA1844 C7 mov dword ptr [eax],0BA7B38h 00BA1844 C7 mov dword ptr [eax],offset string "456789" (0BA7B38h)
8.指针 引用 值传递
//函数 void fun2(int n, int* p, int& r) { n = 10; *p = 10; r = 10; } //调用时,先传最右边的 fun2(n, &n, n); //int& 00BA1A0F 8D 45 DC lea eax,[ebp-24h] 00BA1A12 50 push eax //int* 00BA1A13 8D 4D DC lea ecx,[ebp-24h] 00BA1A16 51 push ecx //int 00BA1A17 8B 55 DC mov edx,dword ptr [ebp-24h] 00BA1A1A 52 push edx //进入函数 00BA1A1B E8 29 F8 FF FF call 00BA1249 //C调用 00BA1A20 83 C4 0C add esp,0Ch //进函数 n = 10; 00BA1768 mov dword ptr [ebp+8],0Ah //00BA1768 mov dword ptr [n],0Ah *p = 10; 00BA176F mov eax,dword ptr [ebp+0Ch] 00BA1772 C7 mov dword ptr [eax],0Ah //00BA176F mov eax,dword ptr [p] //00BA1772 mov dword ptr [eax],0Ah r = 10; 00BA1778 mov eax,dword ptr [ebp+10h] 00BA177B mov dword ptr [eax],0Ah //00BA1778 mov eax,dword ptr [r] //00BA1778 mov dword ptr [eax],0Ah
9.强转
char* p = (char*)&n; *p = 0; *(int*)p = 0; p = (char*)0; char* p = (char*)&n; lea eax,[n] mov dword ptr [p],eax *p = 0; mov eax,dword ptr [p] mov byte ptr [eax],0 *(int*)p = 0; mov eax,dword ptr [p] mov dword ptr [eax],0 p = (char*)0; mov dword ptr [p],0
串操作指令
-
rep 重复操作前缀
-
默认将重复的次数保存在ecx中. 每重复一次,就递减ecx的值. 当ecx的值等于0时,就不再重复.
-
不能单独使用 , 也不能和任意的指令搭配使用,只能和串操作指令结合使用,例如:
mov ecx,10 rep inc eax; // 错误
-
-
stos
-
默认操作数是 edi 和 al/ax/eax ,
-
Q功能 : 将 al/ax/eax 的值填充到edi指向的内存中. 填充完之后, 会自增/自减(取决于 DF 方向标志) edi 的值 , edi的值在自增的时候有(1/2/4)的可能,实际增加多少,取决于指令的后缀, 它的后缀 stosb , stosw,
-
和 rep 指令结合使用之后,功能类似于 memset
-
-
lods
-
默认操作数: esi 和 eax
-
功能: 将esi指向的内存的数据保存到eax中.
-
-
movs
-
默认操作数 edi ,
-
功能 : 将esi指向的内存的数据保存到 edi 中 和 rep 结合之后, 功能类似 memcpy
-
-
cmps
-
默认操作数是edi,esi
-
功能: 将esi和edi指向的内存进行比较, 将比较的结果设置到eflags寄存器中. 和 repe 结合,功能类似 memcmp
-
-
scas
-
默认操作数:
-
功能 : 使用edi指向的字节和eax进行比较, 将比较的结果设置到标志寄存器中 和 repne 结合,功能类似 strlen
-
int main(){ char buff[100]; char buff2[] = "hello world"; // 1. 将buff全部填充为0(memset(buff,0,100)) _asm { lea edi,[buff]; mov ecx,100; mov al ,0; rep stosb; } // 2. 将buff2的内容拷贝到buff中(memcpy(buff,buu2,11) _asm { lea esi, [buff2]; lea edi, [buff]; mov ecx, 11; rep movsb; } // 3. 比较buff2和buff的内存是否一样(memcmp(buff2,buff)) int nCmpFlag = 0; _asm { lea esi, [buff2]; lea edi, [buff]; mov ecx, 11; repe cmpsb; mov [nCmpFlag ],ecx; } if (nCmpFlag == 0) { printf("内存相等\n"); } else { printf("内存不相等\n"); } // 4. 计算buff2字符串长度(strlen(buff2)) _asm { lea edi,[buff2]; mov al,0; mov ecx,0xFFFFFFFF; repne scasb; not ecx; //按位取反得到原码 dec ecx; // ecx存的是补码,-1获取反码 }
混合编程
c和汇编一起出现在同一个源文件中
内嵌汇编
-
通过 _asm 关键字来实现
-
单行内联汇编
int main() { int n; _asm mov [n] , 10 }
-
块状内联汇编
int main() { _asm { mov eax,100; add eax, 20; } }
-
在内联汇编中定义一个字节的机器码
int main() { _asm { jmp _FLAG _emit 0xe9; 直接在此处定义1个字节的机器码, ;初始值是0xe9 _FLAG: } }
使用asm文件
在64位程序中,不能使用 _asm 关键字来使用内联汇编了.
-
给项目添加一个宏汇编编译器.
-
添加一个.asm的文件
-
指定这个asm的文件使用汇编编译器来编译(否则默认是不编译的)
-
在asm中定义汇编代码及结构体
.model flat,stdcall .data ; 声明结构体的原型 MyStruct struct cChar db ? nNum dd ? MyStruct ends .code ; ; 如果在汇编的函数声明中加上了 ; 参数的声明,汇编编译器会自动 ; 加上打开栈帧的代码. fun proc obj:DWORD; fun(MyStruct* obj) ;push ebp ;mov ebp ,esp ;sub esp , 0 local obj2:MyStruct; 在函数内部定义局部变量 ; 访问局部结构体变量的字段 lea eax,[obj2]; mov [eax+MyStruct.nNum],0ah mov byte ptr [eax+MyStruct.cChar],041h ; [ebp+8] => MyStruct* obj mov eax,[ebp+8]; mov [eax+MyStruct.nNum],0ah mov byte ptr [eax+MyStruct.cChar],041h ;mov esp , ebp ;pop ebp ret 4 fun endp end
-
在源文件中调用汇编代码.
-
声明函数原型(注意要使用 extern"C"
extern "C" void __stdcall fun(MyStruct* obj);
-
直接调用函数即可.
裸函数
在函数名中加上关键字 _declspec(naked)
void _declspec(naked) fun() { _asm ret; }
-
编译器不会在裸函数的内部生成额外的代码. 写了多少就有多少.
-
如果裸函数有形参, 那么需要在函数内部自己编写汇编来打开栈帧.
-
如果裸函数有局部变量
-
局部变量不能赋初始值
-
还需自己编写汇编代码开辟局部变量所占的栈空间
// 裸函数 int _declspec(naked) fun(int n) { _asm { push ebp; mov ebp,esp; sub esp,400 } int m; m = 200; //对应汇编 mov dword ptr [ebp-8],0C8h n = 100; //对应汇编 mov dword ptr [ebp+8],64h _asm { mov eax, n; mov esp,ebp; pop ebp; ret } }
x64汇编
函数传参方式: 头4个参数依次使用 rcx,rdx,r8,r9 来传递, 第4个之后的参数使用栈来传递(从右往左) , 栈平衡着是函 数内部.
反汇编引擎和汇编引擎
反汇编引擎 - 能够将一些机器码翻译成汇编代码. 汇编引擎 - 能够将汇编代码翻译器成机器码.
关于 RadAsm 的坑
-
RadAsm 中十六进制不能以 0x 开头,需要在末尾添加 h
正确示范:mov eax, [100h]
但是执行完在OD里面是 mov eax,100h
想要实现下面的语义,
mov eax, dword ptr [0x100]
需要
mov eax, DS:[100h]
-
lea eax, [100h] 想实现上面的功能需要写这个代码 mov eax, 100h lea eax, [eax]
-
栈是什么?栈帧是什么?
-
线程最少由一个线程内核对象和一个栈组成?
-
栈: 是 ss 起始的一块特殊的内存空间
-
(栈)帧: 栈帧是栈中的一块区域,栈帧 以函数为单位
-
-
栈的操作
-
通常栈在调试器的增长方向是自上而下
-
调试器中,栈视图的最上方指向的是 esp
-
-
对栈的操作
-
入栈 push(a\f) esp-n
-
出栈 pop(a\f) esp+4
-
-
call 和 ret
-
call = push 下一条指令 + jmp 目标地址
-
ret = mov eip, [esp], add esp, 4 + n*(4)
-