Rust逆向之程序启动
字数 2077 2025-08-22 12:23:30
Rust逆向分析:程序启动过程详解
1. 基本PE信息分析
1.1 编译器信息
- Rust编译器可能内置了VC++8.0的编译器
- 文件对齐(FileAlignment)为0x200,比传统C/C++编译器的0x400或0x1000更小,这是Rust的优化以缩小文件体积
- 基址随机化(ASLR)默认开启,可以关闭以方便调试
1.2 节区特点
- Rust不像Go会增加额外的节区
- 存在TLS(线程本地存储)表,表明程序可能在执行main函数前会执行TLS回调函数
1.3 导入表特点
- 会导入一些Rust专有的DLL和函数
2. TLS回调函数分析
2.1 TLS回调函数地址
- 通过TLS表可以找到回调函数地址:
_ZN3std3sys7windows16thread_local_key15on_tls_callback17hd83a98723c50bb8aE
2.2 回调函数行为
- 经过条件判断后,可能会执行
std__sys__windows__thread_local_key__run_keyless_dtors函数 - 实际调试中发现条件不满足,跳过该函数执行
3. 程序启动流程
3.1 入口函数
- 程序入口点为
mainCRTStartup - 首先调用
__security_init_cookie函数,基于时间、进程PID和线程PID设置安全cookie - 然后跳转到
__scrt_common_main_seh函数
3.2 __scrt_common_main_seh函数
- 调用Rust自定义函数进行初始化设置
- 获取
argc和argv作为参数准备调用main函数
4. Rust特有的启动流程
4.1 main函数调用
- main函数会调用
std::rt::lang_start::h09171374b7c116e9 - 参数说明:
- 第一个参数:用户编写的
fn main()函数地址(被重命名为"文件名__main"格式) - 第二、三个参数:
argc和argv - 第四个参数:0
- 第一个参数:用户编写的
4.2 lang_start函数内部
- 将参数赋值到
args数组中 - 调用
std::rt::lang_start_internal::h68f55995b3e4e811函数 - 注意:用户main函数地址会被赋值两次到
args数组
4.3 函数表结构
impl__std::rt::lang_start::closure_env_0_tuple_______core::ops::function::Fn_tuple_vtable_数组保存了关键函数地址- 特别注意数组最后一个函数,后续会执行
5. 关键内部函数分析
5.1 lang_start_internal函数
- 将参数保存到局部变量
var_40和var_48 - 将
std__sys__windows__stack_overflow__vectored_handler添加到SEH链表(处理栈溢出异常) - 设置最大堆栈大小
- 如果上述操作失败,跳转到错误处理代码
- 调用Rust库函数进行更多设置
5.2 函数调用链
- 从函数表中获取偏移0x28处的函数地址:
_ZN3std2rt10lang_start28_$u7b$u7bclosure$u7d$u7d17h37c065078fbc660eE - 调用该函数时,参数为
args数组地址
6. 最终调用用户main函数
6.1 调用路径
-
_ZN3std2rt10lang_start28_$u7b$u7bclosure$u7d$u7d17h37c065078fbc660eE- 从
args数组取出第一个元素(用户main函数地址) - 调用
std::sys_common::backtrace::__rust_begin_short_backtrace::h3cd2d30df04669c3
- 从
-
__rust_begin_short_backtrace- 调用
core::ops::function::FnOnce::call_once::h7a03834
- 调用
-
call_once- 最终调用用户编写的
test_pro__main函数(即Rust中的fn main())
- 最终调用用户编写的
7. 动态调试验证
7.1 main函数调用
- 前两个参数为
argc和argv - 第三个参数指向公共文件夹
7.2 lang_start调用
- 用户main函数地址作为第一个参数
argc、argv和0作为后续参数
7.3 内部函数调用
- 可以看到函数表访问和偏移0x28处的函数调用
- 最终通过多层调用到达用户main函数
8. 总结
Rust程序的启动流程比传统C/C++程序更为复杂,主要特点包括:
- 使用TLS回调函数机制
- 多层封装调用用户main函数
- 包含栈溢出保护等安全机制
- 使用函数表实现灵活的调用机制
- 通过多个中间函数实现backtrace等功能
理解这些流程对于Rust逆向分析和调试至关重要,特别是在处理复杂程序或进行安全审计时。