I checked in MSVC 2019 CRT - it does not call ExitProcess.
WinMainCRTStartup is in "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\crt\src\vcruntime\exe_winmain.cpp" file - that simply calls __scrt_common_main() and returns whatever this function returns.
__scrt_common_main is in "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\crt\src\vcruntime\exe_common.inl" file - and that simply calls invoke_main in same file (which calls your main) and uses its return value to return back to caller.
So CRT does not call ExitProcess. Maybe exe loader (one that calls WinMainCRTStartup) calls ExitProcess for you - but that is not part of CRT.
// Initialization is complete; invoke main...
int const main_result = invoke_main();
// main has returned; exit somehow...
Isn't that it right there, the CRT exit function?
"[...]the C runtime library automatically calls ExitProcess when you exit the main thread, regardless of whether there are any worker threads still active." - https://devblogs.microsoft.com/oldnewthing/20100827-00/?p=13023
I stepped through the code to look. After returning from WinMain it checks if it's a managed app (__scrt_is_managed_app), it's not, and so it calls the exit function.
Which in my case, is either in the same executable (-MT, app.exit) or ucrtbase.dll (-MD, ucrtbase.exit).
Inside the exit function it eventually called ExitProcess. It looks like it could also call TerminateProcess depending on the "process end policy"