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自定义函数进行初始化设置
  • 获取argcargv作为参数准备调用main函数

4. Rust特有的启动流程

4.1 main函数调用

  • main函数会调用std::rt::lang_start::h09171374b7c116e9
  • 参数说明:
    • 第一个参数:用户编写的fn main()函数地址(被重命名为"文件名__main"格式)
    • 第二、三个参数:argcargv
    • 第四个参数: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函数

  1. 将参数保存到局部变量var_40var_48
  2. std__sys__windows__stack_overflow__vectored_handler添加到SEH链表(处理栈溢出异常)
  3. 设置最大堆栈大小
  4. 如果上述操作失败,跳转到错误处理代码
  5. 调用Rust库函数进行更多设置

5.2 函数调用链

  1. 从函数表中获取偏移0x28处的函数地址:_ZN3std2rt10lang_start28_$u7b$u7bclosure$u7d$u7d17h37c065078fbc660eE
  2. 调用该函数时,参数为args数组地址

6. 最终调用用户main函数

6.1 调用路径

  1. _ZN3std2rt10lang_start28_$u7b$u7bclosure$u7d$u7d17h37c065078fbc660eE

    • args数组取出第一个元素(用户main函数地址)
    • 调用std::sys_common::backtrace::__rust_begin_short_backtrace::h3cd2d30df04669c3
  2. __rust_begin_short_backtrace

    • 调用core::ops::function::FnOnce::call_once::h7a03834
  3. call_once

    • 最终调用用户编写的test_pro__main函数(即Rust中的fn main()

7. 动态调试验证

7.1 main函数调用

  • 前两个参数为argcargv
  • 第三个参数指向公共文件夹

7.2 lang_start调用

  • 用户main函数地址作为第一个参数
  • argcargv和0作为后续参数

7.3 内部函数调用

  • 可以看到函数表访问和偏移0x28处的函数调用
  • 最终通过多层调用到达用户main函数

8. 总结

Rust程序的启动流程比传统C/C++程序更为复杂,主要特点包括:

  1. 使用TLS回调函数机制
  2. 多层封装调用用户main函数
  3. 包含栈溢出保护等安全机制
  4. 使用函数表实现灵活的调用机制
  5. 通过多个中间函数实现backtrace等功能

理解这些流程对于Rust逆向分析和调试至关重要,特别是在处理复杂程序或进行安全审计时。

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逆向分析和调试至关重要,特别是在处理复杂程序或进行安全审计时。