滴水逆向三期笔记与作业——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:
将float的12.5转换为16进制
1、整数部分:
12 ---> 1100
2、小数部分:
0.5 ---> 0.5*2=1 1
3、1100.1 ---> 1.1001 * 2的3次方,即指数为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;
}
海哥牛逼