首页 > 编程语言 >C++ 逆向之 main 函数的查找

C++ 逆向之 main 函数的查找

时间:2024-09-19 11:03:23浏览次数:1  
标签:__ 逆向 scrt 函数 C++ callback 入口 main

在整个程序的逆向分析过程中,寻找 main 函数是逆向分析过程的第一步,程序的主要逻辑从这里展开。

这里面涉及到两个概念:用户入口(User Entry Point)应用程序入口(Application Entry Point)

用户入口

用户入口是开发者编写的用于程序开始的函数。对于大多数 C/C++ 程序而言,这个入口函数通常是 main,也可以是 WinMain(在 Windows GUI 程序中)或其他用户定义的入口函数。

应用程序入口

应用程序入口是操作系统在加载可执行文件时调用的第一个代码位置,当我们将程序拖入 x64dbg 后第一个断下的地方就是应用程序入口点。这个位置通常是由编译器或连接器自动生成的,它负责初始化运行时环境,初始化完成后就会跳到用户入口(main 函数或 WinMain 函数)

在逆向工程中,通过理解和识别这两个不同的入口点,可以更好地分析程序的结构和执行流程。例如,通过定位应用程序入口,你可以看到如何设置和调用用户入口函数;而通过分析用户入口函数,可以理解程序的主要逻辑和功能。

接下来我们会分析,如何分别在 32 位 和 64 位程序的 Debug 和 Release 版本中找到 main 函数入口点,我们的实验环境是 VS2019。

一、对用户入口(main 函数)进行深入理解

在进行实验之前,首先我们需要重新认识一下 main 函数,知己知彼方能百战百胜。

我们都知道,C/C++ 程序的 main 函数其实归根到底也是一个函数,那么这个函数有没有参数呢?其实是有的,大家会发现我们可以在 main 函数中填入参数,也可以不填入参数,其实都是可以编译通过的,在 main 函数中填入参数我们可以在命令行中进行调用,更加的方便灵活。

其实,不管我们有没有填入参数,在我们对 main 函数的逆向过程中都会发现,在执行 main 函数之前,都会压入 3 个参数,那么我们是不是可以通过这个特点来定位 main 函数的位置呢?答案是肯定的

其实 main 函数传递的三个参数分别是:int argc(参数个数)char *argv[](参数)char *envp[](环境变量)

前面两个大家用的比较多,最后一个环境变量参数大家可能不是很了解,我们可以通过下面这段代码对 main 函数的参数有一个直观的了解:

#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[], char *envp[])
{
	printf("参数个数:%d\r\n\n", argc);

	for (int i = 0; i < argc; i++)
	{
		printf("Argument%d:%s\r\n", i, argv[i]);
	}
	printf("\r\n");

	printf("环境变量:\r\n");
	int i = 0;
	for (char** env = envp; *env != 0; env++, i++)
	{
		char* curEnv = *env;
		printf("Enviroment Variable%d:%s\r\n", i, curEnv);
	}

	system("pause");

	return 0;
}

我们通过命令行进行调用,输入 3 个参数:

得到的结果如下:

二、对应用程序入口进行深入理解

有过逆向经验的朋友都知道,在执行 main 函数之前,其实是有一段用于负责初始化运行时环境的代码,当我们将程序拖入 x64dbg 中,会在应用程序入口断下,那么我们可以通过 VS2019 自己随便编写一个程序,此时会在程序目录生成 exe 可执行文件和符号文件,正常情况下我们在分析别人程序的时候是不会带有符号文件的,但是如果我们自己编写一个程序,在符号文件的帮助下,大大降低我们的逆向难度。比如没有符号文件,我们无法在 x64dbg 中直接跳转到 main 函数入口,但是有符号文件就可以,而且很多的 call 都会标明对应的函数名,而不是一个冰冷的地址,感兴趣的朋友可以去对比一下有符号文件和没有符号文件逆向过程的区别。

当然我们今天要介绍的不是带着符号文件逆向,而是从正向开发的角度,看看在执行 main 函数之前到底进行了什么操作,对这段代码有一个直观的了解,才能更胸有成竹的找到 main 函数入口。

首先,我们来看一下 main 函数的调用栈:

我们可以看到,main 函数的调用栈是:mainCRTStartup() -> __scrt_common_main() -> __scrt_common_main_seg() -> invoke_main()

对 main 函数的调用栈有一个大概的流程了解后,我们来看一下它对应的正向开发代码:
mainCRTStartup()

// The implementation of the common executable entry point code.  There are four
// executable entry points defined by the CRT, one for each of the user-definable
// entry points:
//
//  * mainCRTStartup     => main
//  * wmainCRTStartup    => wmain
//  * WinMainCRTStartup  => WinMain
//  * wWinMainCRTStartup => wWinMain
//
// These functions all behave the same, except for which user-definable main
// function they call and whether they accumulate and pass narrow or wide string
// arguments.  This file contains the common code shared by all four of those
// entry points.
//
// The actual entry points are defined in four .cpp files alongside this .inl
// file.  At most one of these .cpp files will be linked into the resulting
// executable, so we can treat this .inl file as if its contents are only linked
// into the executable once as well.
extern "C" int mainCRTStartup()
{
    return __scrt_common_main();
}

__scrt_common_main()

// This is the common main implementation to which all of the CRT main functions
// delegate (for executables; DLLs are handled separately).
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:
    __security_init_cookie();

    return __scrt_common_main_seh();
}

关于 __security_init_cookie() 函数可以参照微软官方文档:

全局安全 Cookie 用于使用 /GS (缓冲区安全检查) 编译的代码和使用异常处理的代码中的缓冲区溢出保护。在进入受溢出保护的函数时,cookie 被放在堆栈上,在退出时,堆栈上的值与全局 cookie 进行比较。它们之间的任何差异都表明发生了缓冲区溢出,并导致程序立即终止。
通常,__security_init_cookie 在初始化时由 CRT 调用。如果绕过 CRT 初始化(例如,如果使用 /ENTRY 指定入口点),则必须自行调用__security_init_cookie。如果未调用 __security_init_cookie,则全局安全 Cookie 将设置为默认值,并且缓冲区溢出保护会受到损害。由于攻击者可以利用此默认 Cookie 值来破坏缓冲区溢出检查,因此我们建议您在定义自己的入口点时始终调用 __security_init_cookie。
对 __security_init_cookie 的调用必须在输入任何 overrun protected 函数之前进行;否则将检测到虚假的缓冲区溢出。

__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();//tls init
        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();//tls destructor
        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;
    }
}

invoke_main()

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

三、在 32 位和 64 位程序的 Debug 版本中寻找 main 函数

通常来说,Debug 版本会最大程度保留和原始代码一样的结构,因此对照上面的代码,结合调用 main 函数之前必定传入 3 个参数这个特点,我们可以很快找到 main 函数的入口点,下面是 Debug 版本下应用程序入口的调用图:

标签:__,逆向,scrt,函数,C++,callback,入口,main
From: https://www.cnblogs.com/lostin9772/p/18419730

相关文章

  • 小程序上传代码出错,Error: 代码包大小超过限制,main package source size 2662KB...
    上传代码问题uniapp小程序上传代码时提示:Error:代码包大小超过限制,mainpackagesourcesize2662KB...解决方案使用HbuilderX发行,打开微信开发者工具,再次上传代码可解决此问题填写小程序appid与小程序名称: 执行发行时遇到的问题此应用DCloudappid......
  • c++1095: 时间间隔(多实例测试) (字符串和字符以及数字的转换)
    问题描述:题目描述从键盘输入两个时间点(24小时制),输出两个时间点之间的时间间隔,时间间隔用“小时:分钟:秒”表示。要求程序定义如下两个函数,并在main()中调用这两个函数实现相应的功能/*三个形参分别为为用于表示一个时间点的时、分、秒,函数返回对应的秒。*/int HmsToS(int......
  • C++-练习-41
    题目:编写一个程序,它打开一个文本文件,逐个字符地读取该文件,知道到达文件末尾,然后指出该文件中包含多少个字符。(包含空格)源代码:#include<iostream>#include<fstream>intmain(){ usingnamespacestd; charch; intch_num=0; ifstreamfin; fin.open("people.......
  • C++-练习-42
    题目:编写一个程序,记录捐献给"维护合法权利团队"的资金。该程序要求用户输入捐献者数目,然后要求用户输入每一个捐献者的姓名和款项。这些信息被存在一个动态分配的结构数组中。每个结构有两个成员:用来存储姓名的字符数组和用力啊存储款项的double成员。读取所有的数据后,程序将......
  • C++之move函数的使用
    在C++中,std::move 是一个标准库函数,用于实现“移动语义”(MoveSemantics),这是C++11引入的一个重要特性。std::move 允许你将对象的资源“转移”到另一个对象,从而避免不必要的复制操作,提高效率。什么是 std::move?std::move 是一个类型转换函数,它将其参数转换为右值引......
  • C++学习笔记(28)
    十四、实现strchr()和strrchr()函数示例:#define_CRT_SECURE_NO_WARNINGS//使用C风格字符串操作的函数需要定义这个宏#include<iostream>usingnamespacestd;//返回在字符串s中第一次出现c的位置,如果找不到,返回0。//babcddefaeaconstchar*mystrchr(co......
  • C++学习笔记(一、预备知识)
    C++简介C++融合了三种不同的编程方式:-C语言代表的过程性语言-C++在C语言基础上添加的类代表的面向对象语言-C++模板支持的泛型编程。值得一提的是,不要把C语言与C++混为一谈,他们可以说是两个不同的语言。C++简史2.1C语言20世纪70年代,贝尔实验室的DennisRi......
  • C/C++语言基础--C++面向对象、类、对象概念讲解
    本专栏目的更新C/C++的基础语法,包括C++的一些新特性前言今天更新的比较晚了,主要一直用是谷歌Colab训练模型,访问国内csdn反而不好使了,请大家见谅;C++是面向对象的语言,本文将介绍什么是面向对象、什么是类、什么是对象、类和对象的关系是什么?欢迎大家点赞+收藏+关注;C语......
  • C++基于select和epoll的TCP服务器
    select版本服务器#include<arpa/inet.h>#include<stdlib.h>#include<stdio.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<string>#include<pthread.h>#include<sys/select.h>......
  • C++入门基础知识75(高级)——【关于C++ Web 编程】
    成长路上不孤单......