x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点

实验程序下载

问题:程序的入口真的是 main 吗

几乎所有 C/C++ 语言正向开发的视频教学包括学校老师都会说,程序的入口点或者启动函数是 main 函数,是最先执行的地方。浅显的基于表面的现象,的确如此,main 函数处于所有代码的起始位置并最先执行。

图片[1]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

但是如果要深入研究其底层工作原理,会发现其最开始执行的位置,并非 main,而是另有其他函数,接下来的内容我们一起探讨,并通过 x32dbg + IDA Pro 结合的方式了解其奥秘

使用 x32dbg 分析

直接拖到 x32dbg 中,就会断在入口断点的位置(可以在选项中只勾选入口断点)

图片[2]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

F7 跟进

图片[3]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

执行 call 下面的 jmp 会来到下面的位置

图片[4]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

这里的 call F7 跟进去,就看到 main 函数了

图片[5]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

由此可得,main 函数并不是最先开始执行的那个函数

最先开始被执行的,应该是所谓的 _mainCRTStartup

使用 IDA Pro 去分析

发现 _main 函数是最开始被 IDA 显示出来的

图片[6]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

里面调用了 _main_0函数,点击进入发现就是 C/C++ 正向开发中的 main 函数

图片[7]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

通过分析是这个位置在调用

图片[8]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

最顶层的调用位置

图片[9]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

查看调用层次(快捷键:减号键)

图片[10]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

图片[11]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

图片[12]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

VC++控制台程序 调用结构(逆向层)

图片[13]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

VC++控制台程序 调用结构(正向层)

图片[14]-x86/x64汇编基础34.逆向实战01:揭秘真正VC++控制台程序入口点-汇编语言社区论坛-技术社区-学技术网

相关源代码整理,可双击右边调用堆栈查看源代码

extern "C" DWORD mainCRTStartup(LPVOID)
{
    return __scrt_common_main();
}

// This is the common main implementation to which all of the CRT main functions
// delegate (for executables; DLLs are handled separately).
//// 这是所有 CRT 主函数(针对可执行文件;动态链接库单独处理)委托的通用主实现。
static __forceinline int __cdecl __scrt_common_main()
{
    // The /GS security cookie must be initialized before any exception handling
    // targeting the current image is registered.  No function using exception
    // handling can be called in the current image until after this call:
    ///GS 安全 Cookie 必须在针对当前映像注册任何异常处理程序之前进行初始化。
    //在执行此调用之前,当前映像中任何使用异常处理的函数都不能被调用:
    __security_init_cookie();

    return __scrt_common_main_seh();
}

static __declspec(noinline) int __cdecl __scrt_common_main_seh()
{
    if (!__scrt_initialize_crt(__scrt_module_type::exe))
        __scrt_fastfail(FAST_FAIL_FATAL_APP_EXIT);

    bool has_cctor = false;
    __try
    {
        bool const is_nested = __scrt_acquire_startup_lock();

        if (__scrt_current_native_startup_state == __scrt_native_startup_state::initializing)
        {
            __scrt_fastfail(FAST_FAIL_FATAL_APP_EXIT);
        }
        else if (__scrt_current_native_startup_state == __scrt_native_startup_state::uninitialized)
        {
            __scrt_current_native_startup_state = __scrt_native_startup_state::initializing;

            if (_initterm_e(__xi_a, __xi_z) != 0)
                return 255;

            _initterm(__xc_a, __xc_z);

            __scrt_current_native_startup_state = __scrt_native_startup_state::initialized;
        }
        else
        {
            has_cctor = true;
        }

        __scrt_release_startup_lock(is_nested);

        // If this module has any dynamically initialized __declspec(thread)
        // variables, then we invoke their initialization for the primary thread
        // used to start the process:
        _tls_callback_type const* const tls_init_callback = __scrt_get_dyn_tls_init_callback();
        if (*tls_init_callback != nullptr && __scrt_is_nonwritable_in_current_image(tls_init_callback))
        {
            (*tls_init_callback)(nullptr, DLL_THREAD_ATTACH, nullptr);
        }

        // If this module has any thread-local destructors, register the
        // callback function with the Unified CRT to run on exit.
        _tls_callback_type const * const tls_dtor_callback = __scrt_get_dyn_tls_dtor_callback();
        if (*tls_dtor_callback != nullptr && __scrt_is_nonwritable_in_current_image(tls_dtor_callback))
        {
            _register_thread_local_exe_atexit_callback(*tls_dtor_callback);
        }

        //
        // Initialization is complete; invoke main...
        //

        int const main_result = invoke_main();

        //
        // main has returned; exit somehow...
        //

        if (!__scrt_is_managed_app())
            exit(main_result);

        if (!has_cctor)
            _cexit();

        // Finally, we terminate the CRT:
        __scrt_uninitialize_crt(true, false);
        return main_result;
    }
    __except (_seh_filter_exe(GetExceptionCode(), GetExceptionInformation()))
    {
        // Note:  We should never reach this except clause.
        int const main_result = GetExceptionCode();

        if (!__scrt_is_managed_app())
            _exit(main_result);

        if (!has_cctor)
            _c_exit();

        return main_result;
    }
}

static int __cdecl invoke_main()
{
    return main(__argc, __argv, _get_initial_narrow_environment());
}

int main()
{
    return 0;
}

(*tls_init_callback)(nullptr, DLL_THREAD_ATTACH, nullptr);
  • 作用:手动触发 TLS 初始化函数,模拟系统调用时 DLL_THREAD_ATTACH 事件。
  • 用于初始化线程本地变量。

__scrt_get_dyn_tls_dtor_callback()
  • 作用:获取线程本地变量的析构函数指针,用于注册析构回调函数。

_register_thread_local_exe_atexit_callback(*tls_dtor_callback)
  • 作用:注册 TLS 析构函数,使得线程退出时可以调用它。

invoke_main()
  • 作用:真正调用用户写的 main() 函数。
  • 是对 main(argc, argv, envp) 的封装,支持 wmainWinMain 等。

__scrt_is_managed_app()
  • 作用:判断是否是 .NET 托管程序。
  • 如果是,则使用 _cexit/_c_exit,否则用 exit/_exit

exit(main_result)
  • 作用:调用标准 C 函数,执行 atexit() 注册的回调,然后终止进程。

_cexit()
  • 作用:有序地清理 CRT(调用全局析构函数、清理资源)。

__scrt_uninitialize_crt(true, false)
  • 作用:卸载 CRT,释放资源,清理堆栈和运行库状态。
  • true 表示是正常退出;false 表示不是 DLL 卸载。

_seh_filter_exe(GetExceptionCode(), GetExceptionInformation())
  • 作用在 SEH 捕获时调用的异常过滤器
  • 通常用于生成 minidump、记录日志或决定是否继续执行。
  • SEH,全称是 Structured Exception Handling,即 结构化异常处理,是 Windows 操作系统的一种底层异常处理机制。
  • SEH 是 Windows 用来处理“异常”(比如访问非法内存、除以零等)的机制,支持 __try / __except 这样的语法结构。
🔧 SEH 机制原理(简略版):
  1. 当线程执行到 __try 块时,SEH 会把一个异常处理器加入线程的 SEH 链表(记录在 FS 段中)。
  2. 一旦异常发生,操作系统会遍历这些处理器,直到某个处理器“愿意”处理异常。
  3. 如果没人处理,就调用默认处理器,通常是程序崩溃(显示崩溃窗口)。

_exit(main_result) / _c_exit()
  • _exit()立刻终止进程,不调用全局析构器和 atexit() 注册函数
  • _c_exit():清理 CRT 但不执行全局析构函数
核心流程总结
__scrt_common_main_seh
 ├── __scrt_initialize_crt
 ├── _initterm_e (__xi range)
 ├── _initterm (__xc range)
 ├── TLS 初始化回调
 ├── invoke_main (你的 main 函数)
 └── exit / _cexit / _exit

关于 main 函数的参数

函数完整原型,IDA 逆向时可以看到 3 个参数

尽管我们自己开发的时候不会写,但是编译器会自动给我们加上

int main(int argc, const char** argv, const char** envp);

参数名

类型

含义

argc

int

参数数量(argument count),含程序本身路径, 或者说包括程序名本身,所以永远 >=1

argv

const char**

参数数组(argument vector),每个元素是一个字符串 (C风格字符串)

envp

const char**

环境变量数组(environment pointer),每个元素是一个环境变量的字符串

示例代码

#include <iostream>

int main(int argc, const char** argv, const char** envp)
{
    std::cout << "参数个数 argc = " << argc << "\n";

    // 打印 argv 内容
    std::cout << "\n【命令行参数】argv:\n";
    for (int i = 0; i < argc; ++i)
    {
        std::cout << "argv[" << i << "] = " << argv[i] << "\n";
    }

    // 打印环境变量 envp 内容(最多打印10个)
    std::cout << "\n【环境变量】envp:\n";
    for (int i = 0; envp[i] != nullptr && i < 10; ++i)
    {
        std::cout << "envp[" << i << "] = " << envp[i] << "\n";
    }

    return 0;
}

执行测试

your_program.exe hello world 123

输出结果

参数个数 argc = 4

【命令行参数】argv:
argv[0] = your_program.exe
argv[1] = hello
argv[2] = world
argv[3] = 123

【环境变量】envp:
envp[0] = PATH=...
envp[1] = TEMP=...
envp[2] = USER=...
...

总结

1.main 函数并不是最先被执行的函数,最先被执行的是mainCRTStartup

  • mainCRTStartup 是入口点(IDA会重命名为 start
  • __scrt_common_main_seh 是初始化和异常保护层
  • invoke_main → main(argc, argv, envp) 是真正的用户代码执行点

2.流程逻辑总结如下所示

+---------------------------+
          | 操作系统加载 EXE 文件      |
          +---------------------------+
                       │
                       ▼
          +---------------------------+
          | mainCRTStartup             |
          +---------------------------+
                       │
                       ▼
          +---------------------------+
          | __scrt_common_main_seh     |
          | ├─ __security_init_cookie  |
          | ├─ __scrt_initialize_crt   |
          | ├─ _initterm_e / _initterm |
          | ├─ TLS 初始化(如有)       |
          +---------------------------+
                       │
                       ▼
          +---------------------------+
          | invoke_main()             |
          +---------------------------+
                       │
                       ▼
          +---------------------------+
          | int main(argc, argv, envp)|
          +---------------------------+
                       │
                       ▼
          +---------------------------+
          | exit / _cexit / _exit     |
          +---------------------------+
                       │
                       ▼
          +---------------------------+
          | __scrt_uninitialize_crt   |
          +---------------------------+
                       │
                       ▼
          +---------------------------+
          | 返回操作系统,进程结束    |
          +---------------------------+
[操作系统加载 EXE]
        │
        ▼
[入口点:NtProcessStartup / kernel32!BaseProcessStartup]
        │
        ▼
[调用编译器生成的 EXE 入口点函数:mainCRTStartup]
        │
        ▼
[调用 CRT 框架内部初始化函数:__scrt_common_main_seh()]
        │
        ├─► 初始化 Security Cookie: __security_init_cookie()
        ├─► 初始化 CRT 运行时环境:__scrt_initialize_crt()
        ├─► 执行 .CRT$XIA ~ .CRT$XIZ 区段中的全局构造函数(_initterm_e)
        ├─► 执行 .CRT$XCA ~ .CRT$XCZ 中的静态构造函数(_initterm)
        ├─► TLS 初始化回调(如有): __scrt_get_dyn_tls_init_callback()
        │
        ▼
[调用 invoke_main()]
        │
        ▼
[调用你的 main(argc, argv, envp)]
        │
        ▼
[你的程序逻辑运行结束,返回 int 值]
        │
        ▼
[处理退出逻辑:exit() / _cexit() / _exit()]
        │
        ▼
[CRT 释放资源:__scrt_uninitialize_crt()]
        │
        ▼
[进程退出:返回给操作系统]
请登录后发表评论