IDA逆向笔记-C/C++语言入参顺序约定和结构体

2023-11-23 01:59

本文主要是介绍IDA逆向笔记-C/C++语言入参顺序约定和结构体,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1.逆向为什么必须要了解入参顺序(调用约定)?

2.X86平台不同的调用约定

3.X64平台系统调用约定_fastcall

4.C++类入参约定_thiscall


1.逆向为什么必须要了解入参顺序(调用约定)?

          了解这部分,可以了解汇编功能代码前(序言部分)后(结束部分)的参数传递,堆栈的入(push)出(pop)栈顺序,这是逆向代码分析的根基,在浩如烟海的汇编代码中,有这些知识和信息,会增加确定度,利于总体代码的识别。

2.X86平台不同的调用约定

C语言转为汇编,因为有不同的调用约定,会有三种参数入栈顺序和栈平衡的方法,分别是:

     在32位的体系结构中,有3种调用约定,分别是cdeclstdcallfastcall,GCC的默认调用约定为cdecl。参数从右向左压栈,调用者负责在调用后清理堆栈,返回值保存在eax寄存器中,非易失寄存器为ebpespebxesiedi

C++还有一种叫thiscall的传参模式,导出类的函数,也是类似用寄存器eax来传递参数:

 1.IDA分析的时候,默认使用cdecl来解析:

 2.初始化局部变量的初始值用的是0XCCCCC,因为CC是汇编中断指令,如果不小心跑到这里,会立即终止。不会乱操作。

3.eax寄存器是放返回值的。有一种爆破的方法是:进入call调用的过程后做如下修改:

"mov eax,1"指令的作用是将eax的al赋值为01,retn则是跳出call,程序接着执行call后面的test指令(相当于c++中的return),这样的操作是因为函数retn之后,执行了一个:

4.edi,esi,edx三个寄存器是出入栈时要保留的。

5.其他寄存器的用途

(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

(3)EIP寄存器,用来存储CPU要读取指令的地址的,通过修改这个寄存器的值,可以让程序跑到一个别的地方去,比如我们自己的代码。

3.X64平台系统调用约定_fastcall

        X64默认使用_fastcall模式,rcx,rdx,r8,r9四个寄存器用于存放参数的最后前面的四个参数,但在栈上还是会预留4个寄存器的对应空间(shadow space),调用者负责栈的平衡:调用者来管理变量的初始化和管理堆栈指针(来支持可变参数),这和之前的约定不一样,参数入栈,会对齐到8个字节,栈的大小是16的整数倍。

        下面是一个选择了X64平台编译的函数。

int mix(int a,int b,int c,int d,int e,int f, int g){return a | b | c | d | e | f |g;
}
int addAll(int a,int b,int c,int d,int e,int f, int g){return a+b+c+d+e+f+g+mix(a,b,c,d,e,f,g);
}
int main(int argc,char **argv){int total;total = addAll(1,2,3,4,5,6,7);printf("result is %d\n",total);return 0;
}

对应的汇编指令为:

(gdb) disassemble *main
Dump of assembler code for function main:
0x0000000000400534 <main+0>:    push   rbp
0x0000000000400535 <main+1>:    mov    rbp,rsp
0x0000000000400538 <main+4>:    sub    rsp,0x30
0x000000000040053c <main+8>:    mov    DWORD PTR [rbp-0x14],edi
0x000000000040053f <main+11>:   mov    QWORD PTR [rbp-0x20],rsi
0x0000000000400543 <main+15>:   mov    DWORD PTR [rsp],0x7
0x000000000040054a <main+22>:   mov    r9d,0x6
0x0000000000400550 <main+28>:   mov    r8d,0x5
0x0000000000400556 <main+34>:   mov    ecx,0x4
0x000000000040055b <main+39>:   mov    edx,0x3
0x0000000000400560 <main+44>:   mov    esi,0x2
0x0000000000400565 <main+49>:   mov    edi,0x1
0x000000000040056a <main+54>:   call   0x4004c7 <addAll>
0x000000000040056f <main+59>:   mov    DWORD PTR [rbp-0x4],eax
0x0000000000400572 <main+62>:   mov    esi,DWORD PTR [rbp-0x4]
0x0000000000400575 <main+65>:   mov    edi,0x400688
0x000000000040057a <main+70>:   mov    eax,0x0
0x000000000040057f <main+75>:   call   0x400398 <printf@plt>
0x0000000000400584 <main+80>:   mov    eax,0x0
0x0000000000400589 <main+85>:   leave
0x000000000040058a <main+86>:   ret

对应的addALL里面,极有可能会有这样的汇编语句:

move  dword ptr p[rsp+20h],r9d ;将四个寄存器的值放回到堆栈中
move  dword ptr p[rsp+18h],r8d
move  dword ptr p[rsp+10h],edx
move  dword ptr p[rsp+8h],ecx

4.C++类入参约定_thiscall

__thiscall是关于类的一种调用方式,它与其他调用方式的最大区别是:   

__thiscall对每个函数都增加了一个类指针参数

  class  aa   {   void   bb(int   cc);   };   

  实际上bb的函数原形是void   bb(aa   &this,   int   cc);   

  这就是__thiscall的调用方式。

以C++打印函数为例:

cout << "Hello World! " << endl;

其编译出来的代码,反编译出来,看到是使用__thiscall调用约定的

std::ostream *__thiscall std::operator<<<std::char_traits<char>>(std::ostream *_Ostr)
{int v2; // eaxint v3; // ediunsigned int v4; // esiint v5; // ediunsigned int v6; // esiint v7; // ecxint v8; // ecx

这篇关于IDA逆向笔记-C/C++语言入参顺序约定和结构体的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/414521

相关文章

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路