3

These are two examples of the same code. The same code in IDA looks different. Why? For compilation this C++ code was used VS2019.

.text:004E27A0 ; =============== S U B R O U T I N E =======================================
.text:004E27A0
.text:004E27A0 ; Attributes: bp-based frame
.text:004E27A0
.text:004E27A0 ; int __cdecl WinMainCRTStartup()
.text:004E27A0                 public _WinMainCRTStartup
.text:004E27A0 _WinMainCRTStartup proc near
.text:004E27A0                 push    ebp
.text:004E27A1                 mov     ebp, esp
.text:004E27A3                 call    __scrt_common_main
.text:004E27A8                 pop     ebp
.text:004E27A9                 retn
.text:004E27A9 _WinMainCRTStartup endp
.text:004E27A9
.text:004E27A9 ; ---------------------------------------------------------------------------
.text:005AC230 ; =============== S U B R O U T I N E =======================================
.text:005AC230
.text:005AC230 ; Attributes: bp-based frame
.text:005AC230
.text:005AC230 ; int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
.text:005AC230 WinMain         proc near               ; CODE XREF: j_WinMainj
.text:005AC230
.text:005AC230 var_C           = dword ptr -0Ch
.text:005AC230 var_8           = dword ptr -8
.text:005AC230 var_4           = dword ptr -4
.text:005AC230 hInstance       = dword ptr  8
.text:005AC230 hPrevInstance   = dword ptr  0Ch
.text:005AC230 lpCmdLine       = dword ptr  10h
.text:005AC230 nShowCmd        = dword ptr  14h
.text:005AC230
.text:005AC230                 push    ebp
.text:005AC231                 mov     ebp, esp
.text:005AC233                 sub     esp, 4Ch
.text:005AC236                 push    ebx
.text:005AC237                 push    esi
.text:005AC238                 push    edi
.text:005AC239                 mov     eax, [ebp+lpCmdLine]
.text:005AC23C                 mov     GpCmdLine, eax
.text:005AC241                 push    498h            ; size_t
.text:005AC246                 push    0               ; int
.text:005AC248                 push    offset App      ; void *
.text:005AC24D                 call    j__memset
.text:005AC252                 add     esp, 0Ch
.text:005AC255                 mov     eax, [ebp+hInstance]
.text:005AC258                 mov     hinst, eax
3
  • 4
    What do you mean "same code"? Those are two very different functions. WinMainCRTStartup Is provided by the CRT and gives the CRT a chance to do it's initialization; the CRT will then call your WinMain when it is ready.
    – josh poley
    Jan 8 at 15:43
  • +1, that is not the same code. One is part of the CRT, the other is the main function written by the application developer. Jan 9 at 0:06
  • I compiled the same C++ code with different project settings, and got that in the first code example, I can't find the WinMain function. Why it's not there I don't understand.
    – black4
    Jan 9 at 6:54

1 Answer 1

1

TL;DR: the gist is roughly the same as commented before on the question. WinMainCRTStartup is the function which performs some boilerplate initialization for the C/C++ runtime ("CRT") and eventually calls your WinMain. So the former isn't your own code, but code supplied by the compiler toolchain and the latter gets eventually called by the former. The leading underscore(s) originate from name mangling.


Frankly you are giving too little context, but based on experience I will fill the gaps with assumptions 😉

You may want to have a look at this answer first and this answer second, I'll wait.

Okay, so not assuming too much prior knowledge (i.e. in case you skipped the reading part), you should know that we have several levels here.

  1. The PE optional header has a field IMAGE_OPTIONAL_HEADER::AddressOfEntryPoint which contains the RVA of the entry point to the software
  2. in your C/C++ program -- and further assuming MSVC based on the names from your question -- the main or wmain (but I'll subsume them under "main") function for console programs or WinMain/wWinMain for GUI programs, respectively, is what you perceive as the entry point to your program, but it's not prior to your the C/C++ runtime (msvcrt.dll or the newer msvcr1xx.dll) takes control (in case of statically linked C/C++ runtime, this boilerplate code gets embedded into your binary).

We can prove this easily. Suppose we take a trivial C++ program like this (go ahead, follow along):

// File is named test.cpp
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow)
{
    return MessageBox(NULL, "Hello, world", "Hellow Windows world", 0);
}

And compile it from a Visual C++/Studio "Developer Command Prompt" (32-bit/x86 default) with:

cl /nologo /permissive- /Zi /Fe:test.exe test.cpp /link /opt:ref /opt:icf user32.lib

/Zi removes optimizations as it implies /debug, so we counter with /opt:ref and /opt:icf passed directly to the linker.

Looking at the resulting test.exe first with dumpbin /nologo /headers test.exe from the same prompt you should be seeing something along the lines of (excerpt):

OPTIONAL HEADER VALUES
             10B magic # (PE32)
           14.34 linker version
            C600 size of code
            8400 size of initialized data
               0 size of uninitialized data
            125C entry point (0040125C) _WinMainCRTStartup
            1000 base of code

The penultimate line contains the reference to _WinMainCRTStartup which is set as entry point for the PE file. Exactly what you are seeing, right?

Well, turns out if you have the Professional edition or higher of Visual Studio, you have the source code to this stuff probably installed already (it's still proprietary code!). It's inside $(VCToolsInstallDir)\crt ($(VCToolsInstallDir) can be retrieved by invoking "%Program Files(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -nologo -property installationPath and drilling down into VC\Tools\MSVC\*).

Now you can find the files for the entry point(s):

  • Console subsystem, _UNICODE not defined:$(VCToolsInstallDir)\crt\src\vcruntime\exe_main.cpp
  • Console subsystem, _UNICODE defined: $(VCToolsInstallDir)\crt\src\vcruntime\exe_wmain.cpp
  • GUI ("windows") subsystem, _UNICODE not defined: $(VCToolsInstallDir)\crt\src\vcruntime\exe_winmain.cpp
  • GUI ("windows") subsystem, _UNICODE defined: $(VCToolsInstallDir)\crt\src\vcruntime\exe_wwinmain.cpp

So in your case we're looking at a GUI program without _UNICODE defined ("ANSI entry point").

Loading this file into IDA we will land right at _WinMain@16:

IDA View-A and Exports

But pay attention to the Exports view which I added here as well. We can see the _WinMainCRTStartup. That function is defined by Visual Studio and the linker is hardcoded to "know" about a bunch of names for entry points, this one included, by default.

The code (also seen in exe_winmain.cpp) amounts to:

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

And the extern "C" is what causes the name to be mangled and prepended an underscore.

It's worthwhile peeking at the code for the C/C++ runtime startup code yourself and getting to know it. Potentially the thread local storage (TLS) callbacks also get to run prior to your entry point.


Bonus exercise

NB: A prior look at The Old New Thing can't hurt 😉

Alright, let's swap the contents of the test.cpp for this one:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

DWORD CALLBACK TrueEntryPoint(void)
{
    (void)::MessageBeep(0xFFFFFFFFU);
    return 42;
}

(Note: the choice of MessageBeep was deliberate so we could rebuild still with only importing from user32.dll.)

... and rebuild, this time telling the linker to use TrueEntryPoint as the PE entry point:

cl /nologo /permissive- /Zi /Fe:test.exe test.cpp /link /opt:ref /opt:icf user32.lib /entry:TrueEntryPoint

Executing the code you should now hear a beep (speaker) or some short sound which you also get otherwise in the GUI on occasion.

But wait, the binary is now 2.5 KiB in size, as compared to a staggering 81.0 KiB for the original binary.

That size difference is owed to all the C/C++ runtime functionality such as printf, but also some default exception handling, buffer overrun checks etc. Some may consider it bloat, but if you don't have it you aren't allowed to call any of the C/C++ runtime functionality without first performing the initialization steps yourself.

Note that if we hadn't statically linked the C/C++ runtime but instead used it dynamically, things would be slightly different, as all of that "bloat" would now be in the C/C++ runtime DLLs which hopefully only exist once (per version) on a given system. But it requires you to install the "Runtime Redistriutables" in order to run your application.

4
  • I'm just wondering why I enabled Incremental:NO in the linker settings of Visual Studio 2019 and the look of the code in IDA has changed.
    – black4
    Jan 20 at 9:57
  • @black4 makes sense as a question, but it wasn't the question you asked. The mention of incremental:no in your comment is the only mention in this whole Q&A.
    – 0xC0000022L
    Jan 20 at 10:15
  • I'm sorry, I didn't ask the question objectively. I have a Visual Studio 2019 project. I compiled it. Opened the exe file in IDA. I see the WinMain function. Then I changed the project settings Linker->Incremental No, compiled it, and again opened the exe file in IDA. I didn't see the WinMain function. Instead, the IDA opened the _WinMainCRTStartup function. The question is why is this happening? That is, now you need to look for WinMain yourself. I don't know what project settings to enable to always have the WinMain function in IDA. I can open a new topic with this question if needed.
    – black4
    Jan 20 at 15:38
  • 1
    @black4 I know this may be annoying, but you should actually write that up as a separate question. You may very well refer to this one and that you understand the difference between entry point and main function, but that your question is about the code differences you see from toggling that switch. I think this could even be a rather interesting Q&A (although I'll admit I'd have to research for the answer). But since the idea behind incremental linking is to lower link times, I have at least a guess where to look and what for ...
    – 0xC0000022L
    Jan 20 at 21:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.