首页
社区
课程
招聘
[原创][推荐]windows 下 exe 用户层面启动过程分析带源码
发表于: 2018-4-22 23:23 4546

[原创][推荐]windows 下 exe 用户层面启动过程分析带源码

2018-4-22 23:23
4546

【+】本次研究windows的exe的启动过程(用户层面完成,系统层面未完成)

1.创建一个简单的exe项目作为我们观察的对象。

    

#include<windows.h>

intmain(intargc,char*argv[]) {

return0;

}


设置debug模式下的运行库为/MTD,  release模式下的运行库为/MT (c/c++的代码生成), x64平台的也配置一下

关于/MT 等的介绍运行库模式


然后我们使用x86 的windbg来调试这个程序 (windbg 在C:\Program Files (x86)\Windows Kits\10\Debuggers ), 选择对应版本


2.调试过程

    这里我们选择x86 debug模式下的 exe来测试


bp exestartup!main                    //这里我们下个断点在main函数,
g                                       //运行程序
                                           //发现ntdll的符号没有完全解析,所以我们重新加载一下符号
.reload
kn                                         //查看函数的调用栈
    得到调用栈如下
# ChildEBP RetAddr  
00 008ffce4 00e11b4e exestartup!main [c:\users\binlmmhc\desktop\exe启动过程\exestartup\exestartup\exestartup.cpp @ 3]
01 008ffcf8 00e119c0 exestartup!invoke_main+0x1e [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
02 008ffd50 00e1185d exestartup!__scrt_common_main_seh+0x150 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 283]
03 008ffd58 00e11bc8 exestartup!__scrt_common_main+0xd [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 326]
04 008ffd60 76fb8654 exestartup!mainCRTStartup+0x8 [f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17]
05 008ffd74 779a4b17 KERNEL32!BaseThreadInitThunk+0x24
06 008ffdbc 779a4ae7 ntdll!__RtlUserThreadStart+0x2f
07 008ffdcc 00000000 ntdll!_RtlUserThreadStart+0x1



3.源码查找

    既然已经看到了调用栈了,我们就从上而下,从深到浅的找到运行库中的代码, CRT源码在安装VS的时候可以选择安装源码的。

    我的安装在D:\Vs2017Pro\VC\Tools\MSVC\14.11.25503\crt\src\vcruntime


exestartup!invoke_main+0x1e [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]            //这里我们要找到invoke_main函数


我们找到exe_common.inl这个内联文件中,找到invoke_main函数,我们发现invoke_main有4中调用模式,

分别对应main, wmian, WinMain, wWinMain这4中入口函数的调用。


因为我们这里的是main的,所以我们找到了这里  exe_common.inl @ 69


#ifdefined_SCRT_STARTUP_MAIN

usingmain_policy=__scrt_main_policy;
usingfile_policy=__scrt_file_policy;
usingargv_policy=__scrt_narrow_argv_policy;
usingenvironment_policy=__scrt_narrow_environment_policy;

staticint__cdecl invoke_main()throw()
{
returnmain(__argc, __argv,_get_initial_narrow_environment());
}


   然后我们看一下是谁调用了invoke_main, 发现是__scrt_common_main_seh 这个函数,所以我们继续寻找

02 008ffd50 00e1185d exestartup!__scrt_common_main_seh+0x150 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 283]

这个函数定义在exe_common.inl @ 230行, 函数有点长, 我这里省略一下

static __declspec(noinline)  int __cdecl __scrt_common_main_seh()throw()
{
    .    .    .    .    .    .
    intconstmain_result=invoke_main();
    .    .    .    .    .    .
   returnmain_result;
}
        我们在继续找__scrt_common_main这个函数

03 008ffd58 00e11bc8 exestartup!__scrt_common_main+0xd [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 326]



// 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()throw()
{
    // 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();
}

        然后我们发现__scrt_common_main这个函数的是 mainCRTStartup这个启动函数,还有其他3中,分别与wmain, WinMain, wWinMain对应

所以我们在exe_main.cpp中找到了mainCRTStartup这个函数

extern"C"intmainCRTStartup()
{
    return__scrt_common_main();
}


        在KERNEL32和ntdll中的函数我这里就不去找了(没有源码的和没有文档化的)



4.聚焦invoke_main

    我们发现invoke_main完成我们的main的调用, 我们再对__argc, __argv, _get_initial_narrow_environment 分析一下


staticint__cdecl invoke_main()throw()
{
    returnmain(__argc, __argv,_get_initial_narrow_environment());
}

    所以继续拿起我们的windbg

.restart                       //重新启动程序

bp exestartup!invoke_main        //断在invoke_main 体会到了符号的好处没有


    接下来我们看一下invoke_main的汇编代码

exestartup!invoke_main:
00e11b30 55              push    ebp
00e11b31 8bec            mov     ebp,esp
00e11b33 e8faf4ffff        call    exestartup!ILT+45(__get_initial_narrow_environment) (00e11032)
00e11b38 50             push    eax
00e11b39 e815f7ffff       call    exestartup!ILT+590(___p___argv) (00e11253)
00e11b3e 8b00           mov     eax,dword ptr [eax]
00e11b40 50              push    eax
00e11b41 e808f7ffff       call    exestartup!ILT+585(___p___argc) (00e1124e)
00e11b46 8b08           mov     ecx,dword ptr [eax]
00e11b48 51              push    ecx
00e11b49 e832f7ffff       call    exestartup!ILT+635(_main) (00e11280)
00e11b4e 83c40c           add     esp,0Ch
00e11b51 5d                  pop     ebp
00e11b52 c3                  ret


因为我们看main的参数最终是怎么获得的,所以我们分别在下面三个地址下断点,然后分别跟入分析

00e11b33 e8faf4ffff        call    exestartup!ILT+45(__get_initial_narrow_environment) (00e11032)

00e11b39 e815f7ffff       call    exestartup!ILT+590(___p___argv) (00e11253)

00e11b41 e808f7ffff       call    exestartup!ILT+585(___p___argc) (00e1124e)


    获取环境变量

bp 00e11b33

bp 00e11b39

bp 00e11b41

g //运行起来,直到获取环境变量这个函数断点

t

p

p                    //看到如下的汇编代码



ucrtbased!_get_initial_narrow_environment:

687db750 a1e0328e68      mov     eax,dword ptr [ucrtbased!__dcrt_initial_narrow_environment (688e32e0)] ds:002b:688e32e0=03435c38

687db755 85c0               test    eax,eax

687db757 750a                jne     ucrtbased!_get_initial_narrow_environment+0x13 (687db763)

687db759 e85e010000      call    ucrtbased!common_get_or_create_environment_nolock<char> (687db8bc)

687db75e a3e0328e68      mov     dword ptr [ucrtbased!__dcrt_initial_narrow_environment (688e32e0)],eax



kn                   //看一下调用栈,发现有符号并没有解析完全,所以我们重新加载一下符号

.reload              //然后得到了如下的调用栈



0:000> kn

# ChildEBP RetAddr  

00 (Inline) -------- ucrtbased!common_get_initial_environment [minkernel\crts\ucrt\src\desktopcrt\env\environment_initialization.cpp @ 320]

01 012ff8c4 00e11b38 ucrtbased!_get_initial_narrow_environment [minkernel\crts\ucrt\src\desktopcrt\env\environment_initialization.cpp @ 329]

02 012ff8cc 00e119c0 exestartup!invoke_main+0x8 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]

03 012ff924 00e1185d exestartup!__scrt_common_main_seh+0x150 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 283]

04 012ff92c 00e11bc8 exestartup!__scrt_common_main+0xd [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 326]

05 012ff934 76fb8654 exestartup!mainCRTStartup+0x8 [f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17]

06 012ff948 779a4b17 KERNEL32!BaseThreadInitThunk+0x24

07 012ff990 779a4ae7 ntdll!__RtlUserThreadStart+0x2f

08 012ff9a0 00000000 ntdll!_RtlUserThreadStart+0x1b



我们发现了minkernel\crts\ucrt\src\desktopcrt\env\environment_initialization.cpp @ 320]这个东西,然后查资料,最后在WindowsKits里找到了源码

我的在C:\Program Files (x86)\Windows Kits\10\Source\10.0.15063.0\ucrt  这里,所以我们继续寻找    ucrtbased!common_get_initial_environment 代码



得到如下的代码 C:\Program Files (x86)\Windows Kits\10\Source\10.0.15063.0\ucrt\env\environment_initialization.cpp @ 311

template<typenameCharacter>

static Character** __cdecl common_get_initial_environment()throw()
{
    Character**&initial_environment=get_initial_environment(Character());

    if(!initial_environment)
    {
           initial_environment=common_get_or_create_environment_nolock<Character>();
    }
    returninitial_environment;
}

其他的就不继续深入了关于环境变量


        同理,获取命令行参数,到获取命令行参数那里的断点

t
p        
p            //得到如下汇编代码

ucrtbased!get_argv:
6881faf0 8bff                    mov     edi,edi
6881faf2 55                      push    ebp
6881faf3 8bec                    mov     ebp,esp
6881faf5 b834278e68        mov     eax,offset ucrtbased!__argv (688e2734)
6881fafa 5d                      pop     ebp
6881fafb c3                      ret



看到它获取的是一个全局变量

kn        //看调用栈

0:000> kn

# ChildEBP RetAddr  

00 012ff8c0 00e11b3e ucrtbased!get_argv [minkernel\crts\ucrt\src\appcrt\startup\argv_parsing.cpp @ 27]

01 012ff8cc 00e119c0 exestartup!invoke_main+0xe [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]

02 012ff924 00e1185d exestartup!__scrt_common_main_seh+0x150 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 283]

03 012ff92c 00e11bc8 exestartup!__scrt_common_main+0xd [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 326]

04 012ff934 76fb8654 exestartup!mainCRTStartup+0x8 [f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17]

05 012ff948 779a4b17 KERNEL32!BaseThreadInitThunk+0x24

06 012ff990 779a4ae7 ntdll!__RtlUserThreadStart+0x2f

07 012ff9a0 00000000 ntdll!_RtlUserThreadStart+0x1b



所以得到如下代码C:\Program Files (x86)\Windows Kits\10\Source\10.0.15063.0\ucrt\startup\argv_parsing.cpp @ 22

int__argc                  =0;// The number of arguments in __argv or __wargv

char**__argv    =nullptr;// The arguments as narrow strings

wchar_t**__wargv=nullptr;// The arguments as wide strings

char*_pgmptr    =nullptr;// The name of the program as a narrow string

wchar_t*_wpgmptr=nullptr;// The name of the program as a wide string

char*_acmdln    =nullptr;// The raw command line as a narrow string

wchar_t*_wcmdln=nullptr;// The raw command line as a wide string



_BEGIN_SECURE_CRT_DEPRECATION_DISABLE



int*__cdecl__p___argc() {return&__argc; }

char***__cdecl__p___argv() {return&__argv; }

wchar_t***__cdecl__p___wargv() {return&__wargv; }

char**__cdecl__p__pgmptr() {return&_pgmptr; }

wchar_t**__cdecl__p__wpgmptr() {return&_wpgmptr; }

char**__cdecl__p__acmdln() {return&_acmdln; }

wchar_t**__cdecl__p__wcmdln() {return&_wcmdln; }


其详细解析的代码在argv_parsing.cpp中, 这里我也不深入了

argc 和argv是差不多的




5.入口点问题


我们知道默认入口点有4个,就是mainCRTStartup, wmainCRTStartup, WinMainCRTStartup, wWinMainCRTStartup

然后我们看一下我们的这个程序的入口点, 

bp exestartup!mainCRTStartup

得到如下 且 程序加载模块基地址是 00e00000

2 e Disable Clear  00e11bc0     0001 (0001)  0:**** exestartup!mainCRTStartup

然后我用studyPE 查看了exe的入口点偏移

发现entry = 11050

所以我们反汇编看一下 00e11050 , 得到如下汇编

exestartup!ILT+75(_mainCRTStartup):

00e11050 e96b0b0000      jmp     exestartup!mainCRTStartup (00e11bc0)

看来入口点就是一个跳转 到入口函数


注:Windows Kits 下的东西,安装SDK


系统层面的接下来有时间会继续写的。。。。



[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回