逆向分析 --- 一个可执行文件的函数分类
编译后的可执行文件(如 Windows 上的 .exe
或 Linux 上的 ELF 文件)中包含很多不是我们编写的函数,会莫名其妙包含进去。
函数主要按照来源可以分为以下几类:
-
用户编写的函数 (User-Defined Functions):
-
来源: 这是程序员在源代码中明确定义的函数。
-
位置: 这些函数的机器代码直接包含在可执行文件的代码段(通常是
.text
段)中。 -
特点: 这是程序逻辑的核心。所有你写的
main()
,calculate()
,processData()
等函数都属于这一类。编译器将它们的源代码翻译成机器指令并放入可执行文件。
-
-
编译器生成的启动/退出函数 (Compiler-Generated Startup/Exit Functions):
-
来源: 编译器(确切地说是链接器)在链接阶段自动添加的代码。
-
位置: 同样位于可执行文件的代码段 (
.text
) 中,通常是程序实际执行的入口点(例如 Windows 的mainCRTStartup
或 Linux 的_start
),而不是程序员写的main()
。 -
作用:
-
初始化: 设置运行环境(初始化全局变量、静态变量、设置堆栈、初始化 C/C++ 标准库、准备命令行参数
argc/argv
、环境变量等)。 -
调用 main: 在初始化完成后,调用程序员编写的
main()
函数。 -
清理: 在
main()
返回后,执行清理工作(调用atexit
注册的函数、刷新输出缓冲区、销毁全局/静态对象、关闭标准流等)。 -
系统调用退出: 最终调用操作系统提供的系统调用(如
exit()
或ExitProcess()
)来正确终止程序并将main()
的返回值返回给操作系统。
-
-
特点: 程序员通常不直接编写这些函数,但它们是程序能正常运行不可或缺的部分。
-
-
静态链接库函数 (Statically Linked Library Functions):
-
来源: 来自程序员在编译链接时明确指定使用的静态库(如
.a
文件 on Linux/Unix,.lib
文件 on Windows)。 -
位置: 这些函数的机器代码在链接时被直接复制到最终生成的可执行文件的代码段 (
.text
) 中,成为可执行文件的一部分。 -
特点:
-
可执行文件体积会增大(包含了库代码的副本)。
-
程序运行时不再依赖外部的库文件。
-
库函数的版本在链接时就固定了。
-
-
-
动态链接库函数 (Dynamically Linked Library Functions - 引用):
-
来源: 来自程序员在编译链接时指定使用的动态链接库(如
.so
文件 on Linux,.dll
文件 on Windows)。 -
位置: 关键点 - 这些函数的机器代码本身并不包含在可执行文件中!
-
包含什么: 可执行文件中只包含:
-
函数引用/符号表: 一个列表,记录了程序运行时需要从哪些动态库(如
libc.so
,kernel32.dll
)中加载哪些函数(如printf
,CreateFileW
)。 -
重定位信息: 告诉动态链接器(在加载时)或程序本身(在运行时,通过 PLT/GOT 机制)如何修正对这些外部函数调用的地址。
-
-
特点:
-
可执行文件体积较小(不包含库代码)。
-
程序运行时必须能找到所需版本的动态库文件。
-
多个程序可以共享内存中的同一份库代码副本。
-
库函数可以在不重新编译主程序的情况下更新(需注意 ABI 兼容性)。
-
函数调用通常涉及额外的间接跳转(通过 PLT/GOT),性能略低于静态链接或内部函数调用(但现代优化如
-fno-plt
等可以减轻)。 -
延迟绑定 (Lazy Binding): 很多系统默认在函数第一次被调用时才解析其真实地址并填充 GOT,加快启动速度。
-
-
-
编译器辅助/运行时函数 (Compiler Helper/Runtime Functions):
-
来源: 编译器在编译用户代码时自动生成的一些特殊函数,用于实现某些语言特性或优化。
-
位置: 通常也位于可执行文件的代码段 (
.text
) 中。 -
例子:
-
栈展开/异常处理: 用于 C++ 异常处理 (
throw
/catch
) 或栈回溯的函数。 -
特殊数学运算: 处理复杂浮点运算、大整数运算(如 64位整数乘除法在32位平台上)的函数。
-
内存操作: 编译器生成的优化版
memcpy
,memset
,memcmp
(有时会链接库版本,但编译器可能内联生成特定版本)。 -
构造/析构函数: 编译器为全局/静态对象自动生成的构造函数(在
main
前执行)和析构函数(在main
后执行)。 -
协程/特殊ABI: 支持特定语言特性(如 Go 的 goroutine, Rust 的 async)或调用约定的辅助函数。
-
-
特点: 这些函数是编译器为了正确实现源代码语义或优化性能而隐式添加的。
-
总结表格:
函数类型 | 来源 | 机器代码位置 | 运行时依赖外部文件 | 主要特点 |
---|---|---|---|---|
用户编写函数 | 程序员源代码 | 在可执行文件内 (.text 段) |
否 | 程序核心逻辑 |
启动/退出函数 | 编译器/链接器添加 | 在可执行文件内 (.text 段) |
否 | 初始化环境、调用 main 、清理、退出 |
静态链接库函数 | 静态库 (.a , .lib ) |
在可执行文件内 (.text 段) |
否 | 增大体积,无运行时依赖,版本固定 |
动态链接库函数 | 动态库 (.so , .dll ) |
不在可执行文件内 | 是 | 减小体积,运行时依赖,共享库,可更新 |
编译器辅助函数 | 编译器生成 | 在可执行文件内 (.text 段) |
(通常) 否 | 实现语言特性 (异常、构造/析构)、优化辅助 |
理解这些函数类型对于分析程序行为、进行逆向工程、调试、优化性能和解决运行时依赖问题都非常重要。