Search code examples
c++windowswinapicode-injection

PE injection fails if injector gets launched by specific application?


Short disclaimer: As this question includes topics regarding hacking/pentesting, I'd like to state that this question is only asked for educational purposes as part of a school project. To prevent possible abuse, I will only post code that is necessary for understanding the problem.

To demonstrate dangers and vulnerabilities of Windows 10, I'm currently writing a small C++/WinAPI application that utilizes two common techniques:

  • A UAC bypass using the "fodhelper technique" (this works by simply setting a specific registry value to the path of the executable which is supposed to be elevated and then launching an automatically elevated Windows executable called "fodhelper.exe", which will then read the registry value and execute it as command/launch the specified application).
  • Performing PE injection, i.e. running a PE file from the address space of the current process (based on this example from github). The PE that gets injected in my program is a simple C++ Console Application (x86) that prints a message box. The shellcode is hardcoded in the injector binary (x86).

I managed to perform both of these techniques successfully in independent files. However, once I combine these two methods (i.e. first elevating, then injecting), a weird error appears.

Description of the problem

When the injector gets started manually (by double clicking), everything works fine, but when the injector is launched by System32\fodhelper.exe (x64) as a result of the UAC bypass, the following happens: After the injection has finished, the console window of the injected application appears, but instead of continuing the execution, I receive a bunch of error messages stating "The code execution cannot proceed because [garbage characters].dll was not found". This indicates that something went wrong with the offsets, and the Windows loader is trying to read the imports at a wrong position.

To summarize: The code injection works fine, unless the injector was started by fodhelper.exe. In this case the injected PE file is unable to run.

Things I have tried so far to find the origin of the issue

  • Debugging the injection using GetLastError and printing the various memory addresses used during the injection. There is no difference if the file is manually started (and the injection is successful) or if it gets started by fodhelper.exe (and the injection fails).
  • Replace the WriteProcessMemory calls with WriteFile to compare the output file when the injector gets manually launched or by fodhelper.exe. Both output files are exactly the same and runnable. This indicates that the injection itself is not the problem, but the Windows loader seems to act differently.
  • Manually elevating the injector using UAC or by using an elevated command prompt. In both cases, the injection is successful.
  • Copying fodhelper.exe to another location (for example to the desktop) and launching this copy. In this case, the injection is successful. The injection only fails if the injector gets started by the original fodhelper.exe in the System32 folder.

It seems that the injection behaves completely identical, but the indicators show that due to some unknown impact of fodhelper.exe that gets passed down to the injector, the Windows loader seems to behave differently.

I appreciate any explanation or assumption! Feel free to ask if you require more information.

Minimal reproducible example

(with limited debug info and comments): https://0bin.net/paste/UPRIg12n#6nJvBok72UcDvIa56c-XEss7AibIh1Zrs+c3sUzvQMj

Note: See how the injection works if you exclude the elevateProcess function or manually elevate the exe with UAC, and how it fails when including said function.

Edit

According to the answer by user RbMm, this error is a result of a specific exploit protection attribute (PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY with the EnableModuleTamperingProtection value) that gets automatically applied onto fodhelper.exe and seemingly gets inherited by all child processes. According to this, removing/resetting this attribute when launching the target process should fix the error. So far I've tried the following, but couldn't achieve any change in the outcome:

DWORD resetPolicy = 0;
STARTUPINFOEXA siEx = { 0 };
SIZE_T AttributeListSize = 0;
siEx.StartupInfo.cb = sizeof(siEx);
InitializeProcThreadAttributeList(NULL, 1, 0, &AttributeListSize);
siEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, AttributeListSize);
InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &AttributeListSize);
UpdateProcThreadAttribute(siEx.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &resetPolicy, sizeof(resetPolicy), NULL, NULL);
...
CreateProcessA(CurrentFilePath, NULL, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED, NULL, NULL, (LPSTARTUPINFOA)&siEx, &PI);


Solution

  • when process created via RunAs with elevation - the appinfo.dll call RAiLaunchAdminProcess function (this is in some svchost.exe) and this function, pass STARTUPINFOEX (and EXTENDED_STARTUPINFO_PRESENT flag) to CreateProcessAsUser. and here - lpAttributeList, in particular PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY attribute key is used for set several exploit mitigation policy for the child process (fodhelper.exe in your case). and here EnableModuleTamperingProtection is set for child process tree. effect of this - when system resolve import descriptor, it check (inside LdrpGetImportDescriptorForSnap) for this mitigation flag, and if it enabled - call LdrpCheckPagesForTampering api, it return true, if SharedOriginal is 0, this means this is a copy-on-write private copy of the EXE/IAT -- hence 'tampered' with. after this LdrpMapCleanModuleView is called. at this point your try begin breaking

    possible first public info about this, from Alex Ionescu -

    LdrpCheckPagesForTampering/LdrpMapCleanModuleView (RS3) are pretty cool antihollowing mitigations (EPROCESS.EnableModuleTamperingProtection)

    if you by self launch new process, you of course not call UpdateProcThreadAttribute for set PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY and in this case, your code sometime work. really only random and sometime - here exist many other errors and bad codding