hasherezade / pe_to_shellcode

Converts PE into a shellcode
https://www.youtube.com/watch?v=WQCiM0X11TA
BSD 2-Clause "Simplified" License
2.27k stars 423 forks source link

access violation adding std #29

Open phiber0 opened 1 year ago

phiber0 commented 1 year ago

Hi hasherezade!

I have found a weird problem adding some std headers. for example if you add < string > nothing happens and the dll is loaded 10/10 times, but if you add < thread > < mutex > or < condition_variable > the dll mostly crash before injection is complited. (works 1 of 10 times).

I have tested the dll compiled on VS 2013,2015 both /MT. works just fine without < mutex >

basic dll code taked from ShellCodeRDI for testing.

#include <Windows.h>
#include <stdio.h>
#include <string>
#include <mutex>

DWORD threadID;

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBoxA(NULL, "DLLMain :D!", "We've started.", 0);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

//extern "C" to prevent C++ name mangling
extern "C" __declspec(dllexport) BOOL SayGoodbye(LPVOID lpUserdata, DWORD nUserdataLen)
{
    std::mutex testMutex;
    try {
        int i = 0, j = 1;
        j /= i;   // This will throw a SE (divide by zero).
    }
    catch (...) {
        MessageBoxA(NULL, "C++ Exception Thrown!", "Caught it", 0);
    }

    MessageBoxA(NULL, "I'm Leaving!", "Goodbye", 0);

    return TRUE;
}

can you share some light about this. thank you!

edit: forgot to add my test steps.

1) pe2shc.exe TestDLL_x64.dll 2) runshc64.exe TestDLL_x64.shc.dll

hasherezade commented 1 year ago

hi @phiber0 ! I tried to reproduce the problem you described, but for some reason everything worked fine for me. This is what I used to build the project: https://gist.github.com/hasherezade/21d3938a3f846d988c1b030c3a713872 - your snippet + my CMakeLists.txt from which I generated a VisualStudio project, used VS2019. I am attaching the binaries generated from my build, both converted and before conversion: test_case2_bin.zip

example_working

Please let me know if my binaries work for you. Also, please share the binaries from your builds, and I will check them.

P.S. Although the DllMain worked fine, and the DLL got loaded, I see a problem with the SayGoodbye function: this function throws and exception, which won't be handled if the binary is loaded manually by the stub. So it not gonna work from the shellcode, but crash instead. But I guess this was not your question.

phiber0 commented 1 year ago

Hi!, Thank you for taking the time to check this one.

I have used your CMakeLists.txt to create my VS project and you are right, the dll is loaded if the compiled is set to 2019/2017, but crashed if we use 2015/2013. (tested on win10 and win7, same results).

I have attached 4 compilations, one for each version if you can take a look. I didn't change anything on the code/options, just the toolset version and compiled release.

let me know if I can do anything else.

Ps, SayGoodbye was in the original code, I just use it to have a place to insert the std::mutex to trigger the error.

test_cases.zip

hasherezade commented 1 year ago

I see, you are right - the binaries created with the older Visual Studio indeed crash. I will check them in more details and let you know what I found.

hasherezade commented 1 year ago

@phiber0 - I investigated it, and now I understand what causes it.

The binaries compiled in older versions of Visual Studio have one more initialization function that they run in _initterm. This is from VS2015: init_vs2015

And from VS2017: init_vs2017

And the problem with that function is, that it makes use of the API function RtlPcToFileHeader to fetch the base of the current module.

init_error

Due to the fact that the shellcodified PE is loaded manually, its base will not be retrieved. The function returns a NULL pointer, which will be further used as a base. And finally it causes exception:

access_violation

I tested it with a libPeConv-based manual loader, where I hooked the function RtlPcToFileHeader, and finally it works:

This PoC confirms that this is where the problem is located.

However, in the stub that I use in pe2shc, I don't want to use any function hooking, so I can't solve it the same way. Looking at the implementation of this function from ReactOS:

 PVOID NTAPI
 RtlPcToFileHeader(IN PVOID PcValue,
                   PVOID* BaseOfImage)
 {
     PLIST_ENTRY ModuleListHead;
     PLIST_ENTRY Entry;
     PLDR_DATA_TABLE_ENTRY Module;
     PVOID ImageBase = NULL;

     RtlEnterCriticalSection (NtCurrentPeb()->LoaderLock);
     ModuleListHead = &NtCurrentPeb()->Ldr->InLoadOrderModuleList;
     Entry = ModuleListHead->Flink;
     while (Entry != ModuleListHead)
     {
         Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

         if ((ULONG_PTR)PcValue >= (ULONG_PTR)Module->DllBase &&
                 (ULONG_PTR)PcValue < (ULONG_PTR)Module->DllBase + Module->SizeOfImage)
         {
             ImageBase = Module->DllBase;
             break;
         }
         Entry = Entry->Flink;
     }
     RtlLeaveCriticalSection (NtCurrentPeb()->LoaderLock);

     *BaseOfImage = ImageBase;
     return ImageBase;
 }

It seems the only way to solve this would be to add the shellcode to the list of loaded modules. But doing so will make the injected shellcode to be less stealthy. I will probably make it optional.

phiber0 commented 1 year ago

wow, you are amazing!

I will wait for your pe2shc update!.

this problem is also happening if you use ReflectiveDLLInjection so your solution could help a loot of people.

thank you

hasherezade commented 1 year ago

thank you! at least now we know the reason! but solving it seems not so easy as I thought earlier:

I tried to add the shellcode to the list of the loaded modules, and it seemed to work fine (the module got added) - yet RtlPcToFileHeader still didn't work as expected.

I was doing my experiments on Windows 10, and I ended up going down the function RtlPcToFileHeader to see what is wrong. It turns out that this function evolved quite a lot, and is no longer that simple as in the ReactOS version (the decompiled version of the new one is here).

So, adding the module to the InLoadOrderLinks list is no longer the solution. I need to analyze it in more details, and then will see what else I can do.