#include        "defs.h"

unsigned long   do_child_inject(__in HANDLE     hProcess){
        OBJECT_ATTRIBUTES       oa;
        UNICODE_STRING          us;
        NTSTATUS                status;
        HANDLE                  hFile = INVALID_HANDLE_VALUE;
        IO_STATUS_BLOCK         IoStatus;
        unsigned long           b_ret = FALSE;
        FILE_STANDARD_INFORMATION fsi;
        ULONG                   cbNeeded;
        ULONG                   dwFileSize;
        PVOID                   lpBuffer = NULL;
        PVOID                   lpRemoteBuffer = NULL;
        PVOID                   lpRemoteParam  = NULL;
        PVOID                   lpRemoteStack  = NULL;
        PIMAGE_DOS_HEADER       pmz;
        PPEHEADER32             pe32;
        PSECTION_HEADER         psection;
        ULONG                   index;
        ULONG_PTR               pLocalBase = 0;
        PIMAGE_IMPORT_DESCRIPTOR p_import;
        PULONG_PTR              pOriginalFirstThunk;
        PULONG_PTR              pFirstThunk;
        PIMAGE_BASE_RELOCATION  p_reloc;    
        ULONG_PTR               delta, apply_reloc;
        ULONG                   dwRelocSize, dwRelocChunkSize;
        PUSHORT                 preloc;
        SIZE_T                  size;
        inject_struct           inject;
        PVOID                   lpRemoteEntryPoint;
        UCHAR                   hook[5];
        DWORD                   dwOldProt;
        CONTEXT                 ctx;
        INITIAL_TEB             init_teb;
        CLIENT_ID               cid;
        HANDLE                  hThread = NULL;
        HANDLE                  hEvent  = NULL;
        LARGE_INTEGER           delay;
        UCHAR                   old_bytes[2];
        HANDLE                  hHandles[2];

        RtlInitUnicodeString(&us, g_wsTraceNtPath);
        InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE, NULL, NULL);
        
        status = NtCreateFile(&hFile, 
                               FILE_GENERIC_READ,
                               &oa,
                               &IoStatus,
                               NULL,
                               0,
                               FILE_SHARE_READ,
                               FILE_OPEN,
                               FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
                               NULL,
                               0);
        if (status != STATUS_SUCCESS) goto __Exit0;
                                               
        status = NtQueryInformationFile(hFile,
                                        &IoStatus,
                                        &fsi,
                                        sizeof(fsi),
                                        FileStandardInformation);                        
        if (status != STATUS_SUCCESS) goto __Exit0;
        
        dwFileSize = fsi.EndOfFile.LowPart;
        
        lpBuffer = dlmalloc(dwFileSize);
        
        status = NtReadFile(hFile,
                            NULL,
                            NULL,
                            NULL,
                            &IoStatus,
                            lpBuffer,
                            dwFileSize,
                            NULL,
                            NULL);
        if (status != STATUS_SUCCESS) goto __Exit0;
        
        pmz = (PIMAGE_DOS_HEADER)lpBuffer;
        pe32= (PPEHEADER32)((ULONG_PTR)lpBuffer + pmz->e_lfanew);
        psection = (PSECTION_HEADER)((ULONG_PTR)pe32 + 4 + sizeof(IMAGE_FILE_HEADER) + pe32->pe_sizeofoptionalheader);
        lpRemoteBuffer = ntVirtualAllocEx(hProcess,
                                          0,
                                          pe32->pe_sizeofimage,
                                          MEM_COMMIT,
                                          PAGE_EXECUTE_READWRITE);
        if (!lpRemoteBuffer) goto __Exit0;
        
        pLocalBase = (ULONG_PTR)ntVirtualAlloc(0, pe32->pe_sizeofimage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (!pLocalBase) goto __Exit0;        
        
        memcpy((void *)pLocalBase, lpBuffer, pe32->pe_sizeofheaders);
        for (index = 0; index < pe32->pe_numberofsections; index++)
                memcpy((void *)(pLocalBase + psection[index].sh_virtualaddress),
                       (void *)((ULONG_PTR)lpBuffer + psection[index].sh_pointertorawdata),
                       psection[index].sh_sizeofrawdata);
        
        pmz = (PIMAGE_DOS_HEADER)pLocalBase;
        pe32= (PPEHEADER32)(pLocalBase + pmz->e_lfanew);
        
        p_import = (PIMAGE_IMPORT_DESCRIPTOR)(pLocalBase + pe32->pe_import);
        
        //we only process imports from ntdll.dll !!! Others are not of my interest
        //as those will not be there in final version
        while (p_import->Name){
                pFirstThunk = (PULONG_PTR)(p_import->FirstThunk + pLocalBase);
                pOriginalFirstThunk = pFirstThunk;
                
                if (p_import->OriginalFirstThunk)
                        pOriginalFirstThunk = (PULONG_PTR)(p_import->OriginalFirstThunk + pLocalBase);
                
                while (*pOriginalFirstThunk){
                        *pFirstThunk = (ULONG_PTR)getprocaddress((void *)g_ntdllbase, (LPSTR)(*pOriginalFirstThunk + 2 + pLocalBase));
                        pOriginalFirstThunk++;
                        pFirstThunk++;
                }       
                
                p_import++;
        }  
        
        //process relocation...
        if (pe32->pe_reloc){
                p_reloc     = (PIMAGE_BASE_RELOCATION)(pe32->pe_reloc + pLocalBase);
                dwRelocSize = pe32->pe_relocsize;
                delta = (ULONG_PTR)lpRemoteBuffer - pe32->pe_imagebase;
                
                while (dwRelocSize){
                        preloc      = (PUSHORT)((ULONG_PTR)p_reloc + sizeof(IMAGE_BASE_RELOCATION));
                        dwRelocChunkSize = p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION);
                        apply_reloc = pLocalBase + p_reloc->VirtualAddress;
                        while (dwRelocChunkSize){
                                if (((*preloc & 0xF000) >> 12) == 0x3)
                                        *(PULONG_PTR)(apply_reloc + (*preloc & 0xFFF)) += delta;
                                
                                preloc++;
                                dwRelocChunkSize -= sizeof(USHORT);        
                        }                
                        
                        dwRelocSize -= p_reloc->SizeOfBlock;
                        p_reloc     = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)p_reloc + p_reloc->SizeOfBlock);
                }
        }
        
        
        status = NtWriteVirtualMemory(hProcess,
                                      lpRemoteBuffer,
                                      (PVOID)pLocalBase,
                                      pe32->pe_sizeofimage,
                                      &size);  
        if (status != STATUS_SUCCESS) goto __Exit0;
        
        lpRemoteEntryPoint = (PVOID)((ULONG_PTR)getprocaddress((PVOID)pLocalBase, "DllMainInject") - pLocalBase + (ULONG_PTR)lpRemoteBuffer);
        
        if (trace_base != 0)
                lpRemoteEntryPoint = (PVOID)((ULONG_PTR)DllMainInject - (ULONG_PTR)trace_base + (ULONG_PTR)lpRemoteBuffer);
        else
                lpRemoteEntryPoint = (PVOID)((ULONG_PTR)DllMainInject - (ULONG_PTR)get_imagebase() + (ULONG_PTR)lpRemoteBuffer);        
        memset(&inject, 0, sizeof(inject));
        
        inject.ntdllbase = (PVOID)g_ntdllbase;
        inject.tracebase = lpRemoteBuffer;
        inject.LdrInitializeThunk = (PVOID)pLdrInitializeThunk;
        memcpy(inject.wsTraceNtPath, g_wsTraceNtPath, sizeof(inject.wsTraceNtPath));    
        memcpy(inject.wsNtdllNtPath, g_wsNtdllNtPath, sizeof(inject.wsNtdllNtPath));                           
            
        InitializeObjectAttributes(&oa, NULL, 0, NULL, NULL);
        status = NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, &oa, NotificationEvent, FALSE);
        status = NtDuplicateObject(CURRENT_PROCESS,
                                   hEvent,
                                   hProcess,
                                   &inject.hEvent,
                                   0,
                                   0,
                                   DUPLICATE_SAME_ACCESS);
                                   
        inject.LdrInitializeThunk_hooklen = 5;
        status = NtReadVirtualMemory(hProcess,
                                    (PVOID)pLdrInitializeThunk,
                                    inject.LdrInitializeThunk_oldbytes,
                                    5,
                                    &size);
        if (status != STATUS_SUCCESS) goto __Exit0;
        
        lpRemoteParam = ntVirtualAllocEx(hProcess, 0, sizeof(inject_struct), MEM_COMMIT, PAGE_READWRITE);
        NtWriteVirtualMemory(hProcess,
                             lpRemoteParam,
                             &inject,
                             sizeof(inject),
                             &size);
                             
        
        hook[0]            = 0xE9;
        *(DWORD *)&hook[1] = (ULONG_PTR)lpRemoteEntryPoint - pLdrInitializeThunk - 5;
        ntVirtualProtectEx(hProcess, (PVOID)pLdrInitializeThunk, 5, PAGE_EXECUTE_READWRITE, &dwOldProt);
        status = NtWriteVirtualMemory(hProcess,
                                      (PVOID)pLdrInitializeThunk,
                                      hook,
                                      5,
                                      &size);
        ntVirtualProtectEx(hProcess, (PVOID)pLdrInitializeThunk, 5, dwOldProt, &dwOldProt);
        if (status != STATUS_SUCCESS) goto __Exit0;
                        
        lpRemoteStack = ntVirtualAllocEx(hProcess, 0, 0x10000, MEM_COMMIT, PAGE_READWRITE);
        
        memset(&ctx, 0, sizeof(CONTEXT));
        memset(&cid, 0, sizeof(CLIENT_ID));
        memset(&init_teb, 0, sizeof(INITIAL_TEB));
        
        /************************************************************
         * Context is later fixed by NtSet/GetContextThread, as we 
         * want to stop at LdrInitializeThunk, and not actually to
         * execute it, in such way we bypass initialization of a 
         * proess, and initialization is done by primary thread, thus
         * whole LdrInitializeProcess can be instrumented...
         ************************************************************/
        ctx.ContextFlags = CONTEXT_FULL;
        ctx.SegCs        = 0x1b;
        ctx.SegEs        = 0x23;
        ctx.SegDs        = 0x23;
        ctx.SegSs        = 0x23;
        ctx.SegFs        = 0x3b;
        ctx.Esp          = (ULONG_PTR)lpRemoteStack + 0xFFFC;
        
        init_teb.StackBase = (PVOID)((ULONG_PTR)lpRemoteStack + 0x10000);
        init_teb.StackLimit= lpRemoteStack;
        init_teb.StackAllocationBase = lpRemoteStack;
        status = NtCreateThread(&hThread, 
                                THREAD_ALL_ACCESS,
                                NULL,
                                hProcess,
                                &cid,
                                &ctx,
                                &init_teb,
                                TRUE);                     
        if (status != STATUS_SUCCESS) goto __Exit0;
        if (REBASED_NTDLL_BASE != (ULONG_PTR)peRemapNtdllProcess(hProcess, (void *)REBASED_NTDLL_BASE)){
                DbgPrint(("%s -- failed to add ntdll shadow to sub process... fuck..."));
        }
        
        ntVirtualProtectEx(hProcess, (PVOID)pLdrInitializeThunk, 2, PAGE_EXECUTE_READWRITE, &dwOldProt);
        NtReadVirtualMemory(hProcess, (PVOID)pLdrInitializeThunk, &old_bytes, 2, &size);
        NtWriteVirtualMemory(hProcess, (PVOID)pLdrInitializeThunk, "\xeb\xfe", 2, &size);
        
        NtResumeThread(hThread, NULL);
        memset(&ctx, 0, sizeof(&ctx));
        ctx.ContextFlags = CONTEXT_FULL;
               
        while (ctx.Eip != pLdrInitializeThunk){
                DbgPrint(("%s -- waiting for child LdrInitializeThunk to do injection...", __FUNCTION__));
                delay.QuadPart = RELATIVE(MILLISECONDS(50));
                status = NtWaitForSingleObject(hProcess, FALSE, &delay);
                if (status == STATUS_WAIT_0){
                        DbgPrint(("%s -- child process died during Get/SetThreadContext loop", __FUNCTION__));
                        goto __Exit0;
                }
                NtGetContextThread(hThread, &ctx);
        }
        
        NtSuspendThread(hThread, NULL);
        ctx.Eip = (ULONG_PTR)lpRemoteEntryPoint;
        ctx.Ecx = (ULONG_PTR)lpRemoteParam;
        NtSetContextThread(hThread, &ctx);
        
        NtWriteVirtualMemory(hProcess, (PVOID)pLdrInitializeThunk, &old_bytes, 2, &size);
        ntVirtualProtectEx(hProcess, (PVOID)pLdrInitializeThunk, 2, dwOldProt, &dwOldProt);
        NtResumeThread(hThread, NULL);
        
        hHandles[0] = hEvent;
        hHandles[1] = hProcess;
        
        status = NtWaitForMultipleObjects(2, hHandles, WaitAny, FALSE, NULL);
        NtClose(hEvent);
        if (status == STATUS_WAIT_0){
                DbgPrint(("%s -- child signaled event... all good...", __FUNCTION__));
        }else if (status == STATUS_WAIT_0 + 1){
                DbgPrint(("%s -- child process died... Abort...", __FUNCTION__));
                goto __Exit0;
        }
        delay.QuadPart = RELATIVE(SECONDS(1));
        NtDelayExecution(FALSE, &delay);
        b_ret = TRUE;
        //prepare hook of LdrInitializeThunk...
__Exit0:
        if (b_ret == FALSE && lpRemoteBuffer){
                ntVirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_DECOMMIT);
                ntVirtualFreeEx(hProcess, lpRemoteBuffer, 0, MEM_RELEASE);
        }
        
        if (lpRemoteParam){
                ntVirtualFreeEx(hProcess, lpRemoteParam, 0, MEM_DECOMMIT);
                ntVirtualFreeEx(hProcess, lpRemoteParam, 0, MEM_RELEASE);
        }
        
        if (lpRemoteStack){
                ntVirtualFreeEx(hProcess, lpRemoteStack, 0, MEM_DECOMMIT);
                ntVirtualFreeEx(hProcess, lpRemoteStack, 0, MEM_RELEASE);
        }
        
        if (pLocalBase){
                ntVirtualFree((PVOID)pLocalBase, 0, MEM_DECOMMIT);
                ntVirtualFree((PVOID)pLocalBase, 0, MEM_RELEASE);
        }
        
        if (hThread)
                NtClose(hThread);
        
        if (hFile != INVALID_HANDLE_VALUE)
                NtClose(hFile);
        if (lpBuffer)
                dlfree(lpBuffer);
        return b_ret;       
}