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 ...
随机推荐
- 8,Phaser__并发且多阶段任务
使用场景 考选武状元 10 个 武生 参加考试 ,第一个关 靠耐力, 坚持最久的5个人进入第二关, 第二关考 力气,力气最大的 3个人进入第二关,第三关考兵法,兵法最好的当选武状元
- Lecture notes of Mathematical analysis
Lecture notes of Mathematical analysis Preliminary theory Teaching purpose: Mathematical analysis is ...
- Pycharm与github的秘密
GIT介绍 GIT文章请看老男孩教育-银角大王的博客: http://www.cnblogs.com/wupeiqi/articles/7295372.html Git 是一个开源的分布式版本控制软件 ...
- PIE SDK Command&&Tool工具命令一览表
PIE SDK Command&&Tool工具命令一览表 编号 模板 名称(中文) Command&Tool 程序集 备注 1 数据管理 加载栅格数据 PIE.Controls ...
- 卸载Windows,安装纯Linux
操作步骤 下载安装一键GHOST优盘版 参考帮助文档,安装U盘启动 重启电脑,F12进入模式选择界面,选择USB device 选择DOS 工具 进入Disk Genius 删除掉除主分区(0)外的其 ...
- TomCat端口被占用问题
TomCat在使用时,有时会爆出端口被占用的问题 解决方法: 1.快捷键win+R,输入cmd,点击回车后再输入netstat -ano|findstr 8005 注:什么端口被占用就输入什么端口号 ...
- flow类型检查
https://blog.csdn.net/tangxiujiang/article/details/80778627 Flow的简单介绍 2018年06月22日 21:54:25 tangxiuji ...
- Python"由于目标计算机积极拒绝,无法连接。"错误解决
出现这种情况的原因是电脑使用了代理服务器,在设置中,将代理服务关闭就行 这时候通过GET访问界面就能成功了.测试代码: import requests r=requests.get('https:// ...
- selenium+Python(文件上传)
文件上传操作也比较常见功能之一,上传功能没有用到新有方法或函数,上传过程一般要打开一个本地窗口,从窗口选择本地文件添加.所以,一般会卡在如何操作本地窗口添加上传文件只要定位上传按钮,通send_key ...
- android viewpager 拿到当前显示的 fragment 的实例
一个 ViewPager 通过 FragmentPagerAdapter 绑定了 3 个 fragment 可以通过 Fragment fragment = getSupportFragmentMan ...