linux下SO中INIT函数中添加自己的代码

本文介绍如何将自定义函数置于SO文件的初始化(init)和终结(fini)阶段执行。通过GCC属性指针实现函数放置,并演示了从源码编译到最终运行的全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://0nly3nd.sinaapp.com/?p=642

0×1

so文件是一个elf格式的文件

通过对elf文件格式的了解。so在被加载之前,会执行init段的代码。在结束的时候,会执行fini段的代码。

上次分析过360的加固,他们采用的就是在init中执行代码的解密函数。(这也是为什么在jni_onload中无需解密的原因)

今天就展示一下怎么将自己的函数放入init段,并执行。

0×2

so代码

s.c

编译生成so:

0×3

调用so

ts.c

s.h

编译生成可执行文件:

0×4

指定程序加载so的路径

新建名为e的文件:

0×5

运行

终端中输入:./e &

可以看出,最新执行的是Init,最后执行的是Fini。

今天了解到一种动态调试手法,据说能够断在init中,明天再来试试。

 

参考文章:mypyg.iteye.com/blog/845915


<think>嗯,用户程序在Linux中的入口函数应该放在哪里呢?首先,我得回忆一下C语言程序的结构。通常我们写的C程序都是从main函数开始的,比如: ```c int main() { // 代码 } ``` 但有时候可能听说过其他入口函数,比如_start。那是不是入口函数其实不是main,而是其他函数呢? 我记得在链接过程中,链接器会默认使用某个入口点。比如,使用gcc编译时,默认的入口函数可能是_start,而不是main。这时候,main函数实际上是被_start调用的。那如果用户想自己定义入口函数,是不是可以覆盖默认的_start呢? 可能需要查看链接器的选项。比如,在gcc中可以通过`-e`参数指定入口函数。例如: ```bash gcc -e my_entry -o my_program my_program.c ``` 这样程序就会从my_entry开始执行,而不是默认的_start。但是这样做的话,用户需要自己处理一些初始化工作,比如设置栈、处理参数等,这些通常是由_start完成的。 那常规情况下,为什么我们写main函数就能自动成为入口呢?可能因为C运行时库(如glibc)的启动代码负责调用main。如果绕过C库,直接使用自己的入口点,可能需要自己处理更多底层细节。 比如,一个最简单的汇编程序可能直接定义_start: ```asm .global _start _start: mov $60, %rax mov $0, %rdi syscall ``` 这个程序直接调用exit系统调用,返回0。编译时需要告诉链接器不要使用标准启动文件: ```bash gcc -nostdlib -o program program.s ``` 这时候入口点就是_start,而不是默认的C库启动代码。 所以,对于用户程序来说,入口函数的位置取决于是否使用标准库。如果使用标准库,main是用户定义的入口,但实际入口是_start,由C库处理。如果不使用标准库,用户可以直接定义_start或其他自定义入口点。 另外,可能还有其他的入口函数,比如当使用某些特定的框架或库时,它们可能有自己的入口定义。但一般情况下,对于普通的C程序,main是用户需要写的入口函数,而实际的入口点由C运行时处理。 有没有可能用户程序没有main函数?比如,如果写了一个函数名为_start,并且正确链接,那么程序会从那里开始。但是这种情况下,用户需要自己处理初始化和清理工作,比如调用libc的初始化函数,或者直接进行系统调用。 总结一下,用户程序的入口函数通常放在main,但实际的入口点由C运行时库的_start处理。用户可以通过编译选项自定义入口函数,但需要自行处理相关初始化。</think>在 Linux 系统中,用户程序的**入口函数**与编程语言和链接方式密切相关。以下是核心逻辑和具体实现: --- ### 一、默认入口函数:`_start`(由 C 运行时库控制) 1. **C 语言程序的本质入口** 用户编写的 `main` 函数并非真正的程序起点,而是被**C 运行时库(如 glibc)的 `_start` 函数**调用。 ```c // 用户视角的入口 int main() { printf("Hello World"); return 0; } ``` - **编译链接过程**: ```bash gcc -o program program.c ``` 实际链接流程隐含了 C 库的初始化代码(`crt1.o` 等),其中包含 `_start` 函数。 `_start` 负责: - 初始化堆栈 - 处理命令行参数(`argc`, `argv`) - 调用 `main` 函数 - 处理 `main` 的返回值并调用 `exit` 2. **查看入口点** 使用 `readelf` 工具可验证入口地址: ```bash readelf -h program | grep "Entry point" ``` 输出示例: ``` Entry point address: 0x401040 ``` 此地址通常指向 `_start`(由 C 库定义)。 --- ### 二、自定义入口函数(绕过 C 运行时库) #### 1. **直接定义 `_start`(需手动处理系统调用)** ```asm .global _start .section .text _start: mov $1, %rax # write 系统调用号 mov $1, %rdi # 文件描述符 stdout=1 lea msg(%rip), %rsi mov $13, %rdx # 字符串长度 syscall mov $60, %rax # exit 系统调用号 mov $0, %rdi # 返回值 0 syscall .section .rodata msg: .ascii "Hello World\n" ``` - 编译命令(禁用标准库): ```bash as -o program.o program.s ld -o program program.o ``` - **关键点**: - 必须自行处理系统调用(如 `write`, `exit`) - 不依赖任何库函数 #### 2. **通过编译选项指定入口函数** 使用 `-e` 参数指定自定义入口(如 `my_entry`): ```c void my_entry() { // 手动初始化 const char msg[] = "Custom Entry\n"; syscall(1, 1, msg, sizeof(msg)-1); // write syscall(60, 0); // exit } ``` - 编译命令: ```bash gcc -nostdlib -e my_entry -o program program.c ``` - **注意事项**: - 需手动调用系统调用(`syscall`) - 无法使用标准库函数(如 `printf`) --- ### 三、特殊场景:动态链接库的入口 共享库(`.so`)可通过 `__attribute__((constructor))` 定义**加载时自动执行的函数**: ```c __attribute__((constructor)) void init_library() { printf("库被加载时自动执行\n"); } ``` - 此函数在库被加载时(如 `dlopen`)执行,但并非传统意义的“入口函数”。 --- ### 四、总结 | 场景 | 入口函数 | 依赖 | 适用场景 | |---------------------|-------------------|--------------------|-------------------------| | 标准 C 程序 | `main`(间接入口)| C 运行时库(glibc)| 普通用户程序 | | 裸金属编程 | `_start` | 无 | 系统级开发、内核模块 | | 自定义入口 | 任意函数名 | 手动处理系统调用 | 极简程序或特殊需求 | | 动态链接库初始化 | `constructor`属性 | 动态链接器 | 共享库初始化代码 | **核心原则**: - 默认情况下**用户只需关注 `main` 函数**,C 库处理底层初始化。 - 若需完全控制程序行为(如内核开发),可自定义入口函数,但需手动实现系统调用和初始化逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值