VC 在调用main函数之前的操作
title: VC 在调用main函数之前的操作
tags: [VC++, 反汇编, C++实现原理]
date: 2018-09-16 10:36:23
categories: VC++反汇编分析
keywords: VC++, 反汇编, C++实现原理, main函数调用, VC 运行环境初始化
在C/C++语言中规定,程序是从main函数开始,也就是C/C++语言中以main函数作为程序的入口,但是操作系统是如何加载这个main函数的呢,程序真正的入口是否是main函数呢?本文主要围绕这个主题,通过逆向的方式来探讨这个问题。本文的所有环境都是在xp上的,IDE主要使用IDA 与 VC++ 6.0。为何不选更高版本的编译器,为何不在Windows 7或者更高版本的Windows上实验呢?我觉得主要是VC6更能体现程序的原始行为,想一些更高版本的VS 它可能会做一些优化与检查,从而造成反汇编生成的代码过于复杂不利于学习,当逆向的功力更深之后肯定得去分析新版本VS 生成的代码,至于现在,我的水平不够只能看看VC6 生成的代码
首先通过VC 6编写这么一个简单的程序
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
int main()
{
wchar_t str[] = L"hello world";
size_t s = wcslen(str);
return 0;
}
通过单步调试,打开VC6 的调用堆栈界面,发现在调用main函数之前还调用了mainCRTStartup 函数:
在VC6 的反汇编窗口中好像不太好找到mainCRTStartup函数的代码,因此在这里改用IDA pro来打开生成的exe,在IDA的 export窗口中双击 mainCRTStartup 函数,代码就会跳转到函数对应的位置。
它的代码比较长,刚开始也是进行函数的堆栈初始化操作,这个初始化主要是保存原始的ebp,保存重要寄存器的值,并且改变ESP的指针值初始化函数堆栈,这些就不详细说明了,感兴趣的可以去看看我之前写的关于函数反汇编分析的内容:
C函数原理
在初始化完成之后,它有这样的汇编代码
.text:004010EA push offset __except_handler3
.text:004010EF mov eax, large fs:0
.text:004010F5 push eax
.text:004010F6 mov large fs:0, esp
这段代码主要是用来注册主线程的的异常处理函数的,为什么它这里的4行代码就可以设置线程的异常处理函数呢?这得从SEH的结构说起。
每个线程都有自己的SEH链,当发生异常的时候会调用链中存储的处理函数,然后根据处理函数的返回来确定是继续运行原先的代码,还是停止程序还是继续将异常传递下去。这个链表信息保存在每个线程的NT_TIB结构中,这个结构每个线程都有,用来记录当前线程的相关内容,以便在进行线程切换的时候做数据备份和恢复。当然不是所有的线程数据都保存在这个结构中,它只保留部分。该结构的定义如下:
typedef struct _NT_TIB
{
PEXCEPTION_REGISTRATION_RECORD ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
union
{
PVOID FiberData;
ULONG Version;
};
PVOID ArbitraryUserPointer;
PNT_TIB Self;
} NT_TIB, *PNT_TIB;
这个结构的第一个参数是一个异常处理链的链表头指针,链表结构的定义如下:
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
这个结构很简单的定义了一个链表,第一个成员是指向下一个节点的指针,第二个参数是一个异常处理函数的指针,当发生异常的时候会去调用这个函数。而这个链表的头指针被存到fs寄存器中
知道了这点之后再来看这段代码,首先将异常函数入栈,然后将之前的链表头指针入栈,这样就组成了一个EXCEPTION_REGISTRATION_RECORD结构的节点而这个节点的指针现在就是ESP中保存的值,之后再将链表的头指针更新,也就是最后一句对fs的重新赋值,这是一个典型的使用头插法新增链表节点的操作。通过这样的几句代码就向主线程中注入了一个新的异常处理函数。
之后就是进行各种初始化的操作,调用GetVersion 获取版本号,调用 __heap_init 函数初始化C运行时的堆栈,这个函数后面有一个 esp + 4的操作,这里可以看出这个函数是由调用者来做堆栈平衡的,也就是说它并不是Windows提供的api函数(API函数一般都是stdcall的方式调用,并且命名采用驼峰的方式命名)。调用GetCommandLineA函数获取命令行参数,调用 GetEnvironmentStringsA 函数获取系统环境变量,最后有这么几句话:
.text:004011B0 mov edx, __environ
.text:004011B6 push edx ; envp
.text:004011B7 mov eax, ___argv
.text:004011BC push eax ; argv
.text:004011BD mov ecx, ___argc
.text:004011C3 push ecx ; argc
.text:004011C4 call _main_0
这段代码将环境变量、命令行参数和参数个数作为参数传入main函数中。 在C语言中规定了main函数的三种形式,但是从这段代码上看,不管使用哪种形式,这三个参数都会被传入,程序员使用哪种形式的main函数并不影响在VC环境在调用main函数时的传参。只是我们代码中不使用这些变量罢了。
到此,这篇博文简单的介绍了下在调用main函数之前执行的相关操作,这些汇编代码其实很容易理解,只是在注册异常的代码有点难懂。最后总结一下在调用main函数之前的相关操作
- 注册异常处理函数
- 调用GetVersion 获取版本信息
- 调用函数 __heap_init初始化堆栈
- 调用 __ioinit函数初始化啊IO环境,这个函数主要在初始化控制台信息,在未调用这个函数之前是不能进行printf的
- 调用 GetCommandLineA函数获取命令行参数
- 调用 GetEnvironmentStringsA 函数获取环境变量
- 调用main函数
VC 在调用main函数之前的操作的更多相关文章
- 多玩YY语音的面试题:C++中如何在main()函数之前执行操作?
多玩YY语音的面试题:C++中如何在main()函数之前执行操作? 第一反应main()函数是所有函数执行的开始.但是问题是main()函数执行之前如何执行呢? 联想到MFC里面的 C**App类的t ...
- WCF 服务器调用回调函数 单程-双程操作模式:(待补充Demo)
服务器端Server 实现回调接口Interface定义.客户端实现回调接口Interface实现,从而实现服务器端通过 var channel = OperationContent.Current ...
- main函数之后的调用
main函数代表进程的主线程.程序开始执行时,系统为程序创建一个进程,main函数其实并不是首先被调用的函数,而是操作系统调用了C/C++运行期启动函数,该函数负责对C/C++ 运行期库进行初始化.它 ...
- C语言中的main函数以及main函数是如何被调用的
main函数是C语言中比较特殊的函数,C程序总是从main函数开始执行,main函数的原型是: int main(int argc, char *argv[]); 其中argc是命令行参数的个数,ar ...
- [汇编与C语言关系]2. main函数与启动例程
为什么汇编程序的入口是_start,而C程序的入口是main函数呢?以下就来解释这个问题 在<x86汇编程序基础(AT&T语法)>一文中我们汇编和链接的步骤是: $ as hell ...
- c/c++ main函数执行之前/后
转载自:http://bbs.csdn.net/topics/300103318#r_78088969 main函数之前--真正的函数执行入口或开始 一种解释: 实际上,在可执行文件被加载之后,控制权 ...
- main函数执行前、后再执行的代码
一.main结束 不代表整个进程结束 (1)全局对象的构造函数会在main 函数之前执行, 全局对象的析构函数会在main函数之后执行: 用atexit注册的函数 ...
- C 语言main 函数终极探秘(&& 的含义是:如果 && 前面的程序正常退出,则继续执行 && 后面的程序,否则不执行)
所有的C程序必须定义一个称之为main的外部函数,这个函数是程序的入口,也就是当程序启动时所执行的第一个函数,当这个函数返回时,程序也将终止,并且这个函数的返回值被看成是程序成功或失败的 ...
- C++main函数与命令行参数,退出程序
本文翻译自:https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=vs-2019 (除动态链接库d ...
随机推荐
- centos6装python3,并安装requests, lxml和beautifulsoup模块
一. 安装python3并设为默认版本,与python2共存 1.下载Python3.4安装包 wget https://www.python.org/ftp/python/3.4.4/Pytho ...
- 集成 jpush-react-native 常见问题汇总 (iOS 篇)
给 iOS 应用添加推送功能是一件比较麻烦的事情,本篇文章收集了集成 jpush-react-native 的常见问题,目的是为了帮助用户更好地排查问题 1. 收不到推送 确保是在真机上测试,而不是在 ...
- 2019.2.15 t3 平均值
#include <cstdio> #include <iostream> #include <cstring> #include <cmath> #i ...
- 方法引用(Method reference)和invokedynamic指令详细分析
方法引用(Method reference)和invokedynamic指令详细分析 invokedynamic是jvm指令集里面最复杂的一条.本文将详细分析invokedynamic指令是如何实现方 ...
- SD与SE的关系,以及异常值
很多刚进入实验室的同学对实验数据的标准差(SD)与标准误(SE)的含义搞不清,不知道自己的数据报告到底该用SD还是SE.这里对这两个概念进行一些介绍. 标准差(SD)强调raw data的Variat ...
- mybatis的入门(一)
一.mybatis的介绍 mybatis是Apache的一个开源项目ibatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为M ...
- dp--hdu1171(01背包)
hdu1171 题目 Problem Description Nowadays, we all know that Computer College is the biggest department ...
- ruby中的\z与\Z区别
s = "this is\nthe name\n" puts "--------------" puts s.match(/name\Z/) puts s.ma ...
- Android规划周期任务
问题:应用总要周期性的执行某项任务,例如检查服务器上的更新或者提醒用户做某些事情. 解决方案:用AlarmManager来管理和执行任务.AlarmManager可用于计划未来的单次或重复操作,甚至在 ...
- ubuntu下安装Sublime Text2
1.下载Sublime Text2官网下载地址:http://www.sublimetext.com 2.安装Sublime Text2解压即可使用,如linjiqin@ubuntu:~$ sudo ...