Simplest way:
Create process as suspended
-> Overwrite ProcessStartStub、ModuleOEP/TLS or anywhere else, with a loader stub
-> Resume the main thread.
-> Your loader stub is executed. Do any thing as your wish here.
-> Execute the original code and process started.
DT ways
1、LoadImageNotify.
When the notify of the main module trigered(right after CreateProcesNotify), the ntdll and main module is already mapped, you can hook the main module here. This is almost the earlest inject point.
2、CreateProcessNotify
This notify is trigered by the first thread of the process. In fact, when you got this, the process is just “Started” yet.
*The EPROCESS is allocated, but partial initialized.
*The PEB is allocated, but almost empty.
*Ntdll is mapped, but not regiester to ldr list. The main module is not loaded yet.
But you can still inject your code, if you know how.
3)CreateThreadNotify
Maybe this is the earlest and "graceful" way to got known a process "will" start. In fact, this notify is called by the thread which calling CreateProcess, and even the first instruction of the created process is not got executed yet.
You can hook here, if you are the most DT man