C语言汇编
¶VC6基础操作
F7:编译、F5:调试、F9:设置断点、F10:单步步过、F11:单步进入、shift+F5:结束调试
调试过程打开寄存器窗口和反汇编窗口
¶裸函数
编译器不会管的函数
__declspec(naked) Func()
调用空的裸函数会出现错误,因为有call,却没有ret
进入后直接跳到int3,运行程序会报错
解决这种只需要加入汇编语句ret,在C语言程序加入汇编语句,需要用到__asm{}。也就是说可以自己在里面写汇编代码来实现需求。
执行完这段代码,ret回来了
¶调用约定
__cdecl(c、c++默认) | 从右至左入栈 | 调用者清理栈 |
---|---|---|
__stdcall | 从右至左入栈 | 自身清理堆栈(内平栈)子函数平衡堆栈 |
__fastcall | ECX/EDX传送前两个,剩下的从右至左,寄存器传递速度更快。当只有两个参数传递进去,不需要平衡堆栈 | 自身清理堆栈 |
所以不能通过ret来分析函数参数的个数
__cdecl
外平栈
内部只有ret
__stdcall
右边的参数先入栈
可以看到这里call之后没有add来回复堆栈平衡,进入调用函数
可以看到ret变成了ret 8,这就是内平栈
__fastcall
两个参数存放在寄存器
没有修改堆栈,所以不需要add回复堆栈
有push和mov,外面没有add,进去看
也是内平栈
¶参数个数
公式一:寄存器+ret 4=参数个数
公式二:寄存器+[esp+8]+[ebp+0x]=参数个数
¶寻找程序入口
callstack,调用窗口
main是我们写的程序的入口,但是不是真正程序的入口
因为在main函数之前需要调用如下函数
在callstack发现这个函数
Getversion()
_headp_int()
GetCommandLineA()
_crtGetEnvironmentStringA()
_setargv()
_setenvp()
_cinit()
main函数具有三个参数,所以要寻找具有三个参数的函数,找三个push,并且调用完会add 0xc
这里很符合
下断点进入,这里才是main函数
¶数据类型与数据存储
1、存储数据的宽度
2、存储数据的格式
3、作用范围
¶基本类型
¶整数类型
char、int、long、short:字节数1、4、4、2,对应上byte、dword、dword、word,long long在VC6对应__int 64
只会根据数据宽度进行操作,超出数据宽度的不会做修改
g存储的只有0x56
数据窗口是小端序
¶有符号与无符号
C语言默认是有符号数
在内存中存储时无区别,但是在类型转换、比较大小和数学运算时需要注意
¶浮点类型
float、double在存储方式遵从IEEE的规范
¶局部变量和全局变量的区分
局部变量是以ebp-开头的
全局变量在编译完之后地址就不会改变
直接放进地址,所以就是全局变量
在vs2022会因为编译器版本问题,出现不同的汇编指令,但都是大同小异
¶if语句
cmp+jcc指令
cmp相当于减法,前面一个减后面一个,因为x>y时继续执行,所以跳转指令的条件是小于等于
改成>=之后,汇编指令变成了jl,汇编指令是和C语言反着来的
<
==
¶多分支语句
x<=y的情况直接跳转到else中
x>y则往后执行,执行完之后jmp跳转到else语句的后面
因为mov两边不能都是地址,所以需要用到寄存器
¶if、else if、else
¶返回值
内部得到eax的值,eax一般用来存储返回值
参数传递4个字节
压栈的时候都是eax
¶循环语句
¶switch语句反汇编
当分支较少时采取if……else if ……else的方式
¶case连续
创建大表
sub
这里修改参数为103,case条件也修改,发现sub的值发生了变化,所以可以知道sub的值对应最小的case条件的值,这样对应上了大表的位置,所以sub是为了跳转到生成的大表
正好是case 103的地址
¶case连续但中断
101、102、103的情况使用default的地址填充
只要有断开的,就会浪费一片内存地址,所以当间隔太远就不会使用这种方式
但是当间隔比较大的时候,出现了新情况
因为已经清空了edx,所以可使用该寄存器,这句话相当于把0x004010dd+eax的值对应地址的内容放入到dl中,dl是八位,对应1个字节,这个就是小表
当连续但相差较远时会采用小表
¶case不连续
当差值太大,不会生成大表,会直接采取if……else结构
先判断大于je,再判断等于cmp+jmp
¶while循环反汇编
je是当ZF标志位为0是跳转,test是按位与操作,用于判断寄存器的值是否为0
¶do……while反汇编
¶for循环反汇编
¶数组在内存的存储和寻址
这里数组的存储是从高位往低位存储,也就是从右到左开始存储到缓冲区中,替换缓冲区的数值
可以看到数组的存储
再看看数组引用
[ebp-4]是第一个形参的地址
[ebp-1ch]对应上数组首元素的地址,eax是存储着第一个形参,*****4是因为是int型数组,如果是short,则*2
,数组比较常见的形式就是[ebp+寄存器*数组类型对应的字节大小-数字]
因为数组下标存在变量,而mov两边不能同时是地址,所以需要先用寄存器存储下标,而直接引用就不需要,即arr[1]
¶字符串存储
因为每个寄存器最多存储四个字节,所以需要用到多个寄存器来存储
在数据窗口中是这样存放的
寄存器可以重复使用,比如长度不够的情况