滴水逆向三期笔记与作业——02C语言——02数据类型

海哥牛逼

一、C语言如何变成汇编

1、裸函数

裸函数使用特殊方式定义,编译器和连接器并不会为其生成提升堆栈,开辟填充缓冲区和堆栈平衡等代码。

普通函数void Function() {}可以直接调用,并运行通过;而裸函数void __declspec(naked) Function() {}直接调用运行时不通过。

  • 这是因为前者编译器为其生成了返回的汇编代码,在函数运行结束时有ret汇编代码返回call调用前的下一跳地址;而后者没有编译器生成的返回代码,执行call时将下一跳地址入栈,但结束时没有ret指令返回,使得cpu执行到了int3或其他区域,进而报错。
无参无返回裸函数
 
void __declspec(naked) Function() {
	__asm{
		ret
	}
}
或者
void __declspec(naked) Function() {
	__asm{
		//保留调用前的栈底
		push ebp
		//提升堆栈
		mov bep,esp
		sub esp,0x40
		//保存现场
		push ebx
		push esi
		push edi
		//填充缓冲区
		lea edi,dword ptr ds:[ebp-0x40]
		mov eax,0xCCCCCCCC
		mov ecx,0x10
		rep stosd
	 
		//恢复现场
		pop edi
		pop esi
		pop ebx
		//降低堆栈
		mov esp,ebp
		//恢复栈底
		pop ebp
		 
		ret
	}
}


有参有返回裸函数(参数在函数调用前使用push入栈,局部变量在缓冲区,返回值一般在eax)
 
int __declspec(naked) Plus(int x, int y){
	__asm{
		//保留调用前的栈底
		push ebp
		//提升堆栈
		mov ebp,esp
		sub esp,0x40
		//保存现场
		push ebx
		push esi
		push edi
		//填充缓冲区
		lea edi,dword ptr ds:[ebp-0x40]
		mov eax,0xCCCCCCCC
		mov ecx,0x10
		rep stosd
		 
		//函数的核心功能:参数执行计算过程
		mov eax,dword ptr ds:[ebp+0x8]
		add eax,dword ptr ds:[ebp+0xC]
		 
		//恢复现场
		pop edi
		pop esi
		pop ebx
		//降低堆栈
		mov esp,ebp
		//恢复栈底
		pop ebp
		 
		ret
	}
}



二、调用约定

1、常见的几种调用约定

在这里插入图片描述

在这里插入图片描述

三、程序的真正入口

3.1 main 或WinMain 是“语法规定的用户入口”,而不是“应用程序入口”。应用程序入口通常是启动函数。

3.2 使用vc6的堆栈调查查看
在这里插入图片描述

mainCRTStartup 和 wmainCRTStartup 是控制台环境下多字节编码和Unicode 编码的启动函数.
而WinMainCRTStartup 和wWinMainCRTStartup 是windows 环境下多字节编码和Unicode 编码的启动函数.
3.2 修改入口函数:项目右键->Setting
在这里插入图片描述
但是并不能这样修改,因为mainCRTStartup 做了很多的初始化工作。
main函数在被调用前,首先要使用以下函数进行初始化:

在这里插入图片描述
这些函数调用结束后就会调用main 函数,根据main 函数调用的特征,将3 个参数压入栈内作为函数的参数。
编译器在调用main时,传入了三个参数

在这里插入图片描述

四、数据类型

4.1 C语言中的数据类型

在这里插入图片描述

作业

在这里插入图片描述

作业1:

int __declspec(naked) Plus(int x, int y, int z){
	__asm{
	 
		//函数调用前x,y,z已经按照从右往左的顺序入栈
		 
		//保留调用前的栈底
		push ebp
		//提升堆栈
		mov ebp,esp
		sub esp,0x40
		//保存现场
		push ebx
		push esi
		push edi
		//填充缓冲区
		lea edi,dword ptr ds:[ebp-0x40]
		mov eax,0xCCCCCCCC
		mov ecx,0x10
		rep stosd
		 
		//函数的核心功能:参数执行计算过程
		//先将局部变量2,3,4放入缓冲区
		mov dword ptr ds:[ebp-0x04],0x02
		mov dword ptr ds:[ebp-0x08],0x03
		mov dword ptr ds:[ebp-0x0C],0x04
		//进行加法运算(ebp+4是函数返回地址)
		mov eax,dword ptr ds:[ebp+0x8]  //往eax中填入z
		add eax,dword ptr ds:[ebp+0xC]  //eax=z+y
		add eax, dword ptr ds:[ebp+0x10]  //eax=z+y+x
		add eax,dword ptr ds:[ebp-0xC]   //eax=z+y+x+0x02
		add eax,dword ptr ds:[ebp-0x08]   //eax=z+y+x+0x02+0x03
		add eax,dword ptr ds:[ebp-0x04]   //eax=z+y+x+0x02+0x03+0x04
		 
		//恢复现场
		pop edi
		pop esi
		pop ebx
		//降低堆栈
		mov esp,ebp
		//恢复栈底
		pop ebp
		 
		ret
	}
}

作业2:

float12.5转换为16进制
1、整数部分:
12  --->  1100

2、小数部分:
0.5  --->  0.5*2=1  1

31100.1   --->   1.1001 * 23次方,即指数为3
 
4、转为二进制完整格式
0	10000010	10010000000000000000000
即:
0100 0001 0100 1000 0000 0000 0000 0000
4    1    4    8    0    0    0    0
41 48 00 00

作业3:
1、寻找函数入口:push了3个参数+外平栈,可以推断使用的默认的__cdecl
在这里插入图片描述

2、追寻call进入函数,找到需要逆向的区域,一共五个参数,3个内存区,2个寄存器,显然是__fastcall

在这里插入图片描述
3、画函数调用的汇编图

在这里插入图片描述
可以得出该函数核心功能为f2(f1(0x01+0x03+0x04)+f2(0x01+0x03))=0x0C
其中f1为__stdcall,f2为__cdecl

4、C语言代码是

int __cdecl F2(int x, int y){
	return x + y;
}
int __stdcall F1(int x, int y, int z){
	return x + y + z;
}
int __fastcall F(int a, int b, int c, int d, int e){
	return F2(F1(a, b, c), F2(a, b));
}
int main(int argc, char* argv[]){
	F(1, 3, 4, 6, 7);
	return 0;
}

海哥牛逼