Dumping Windows Executables From Memory

In the world of Windows binaries, there are times when we need to extract an executable from memory and save it back to disk. This process serves a crucial purpose; some executables have the ability to transform their sections upon execution. This transformation is often orchestrated by Thread Local Storage (TLS) callbacks, which occur during various stages, including the creation of a process and before the main entry point is executed.


Why Dump from Memory?

Understanding the purpose behind this operation is key. Consider scenarios involving packers, which aim to obfuscate the code on disk, and unpack it upon execution. These operations can happen in the realm of TLS callbacks, making it essential to grasp the process of dumping an executable from memory back to disk.


The Process Unveiled

Let's dive into the fascinating process of taking a packed executable, unpacking it, and writing it back to disk for further analysis. We'll walk you through each step of the journey, demystifying the techniques involved.


The code and method described below can be seen in the following source code: memdump - Windows x64 PE process memory dumper to disk

Step 1: Creating a Child Process

We kick off by creating a new child process in a suspended state. Windows provides a handy function for this, CreateProcessA (or CreateProcessW for unicode). The critical part here is the dwCreationFlags. To prevent TLS callbacks from executing prematurely, we utilize the CREATE_SUSPENDED flag.

// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
WINBASEAPI
BOOL
WINAPI
CreateProcessA(
    _In_opt_ LPCSTR lpApplicationName,
    _Inout_opt_ LPSTR lpCommandLine,
    _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ BOOL bInheritHandles,
    _In_ DWORD dwCreationFlags,
    _In_opt_ LPVOID lpEnvironment,
    _In_opt_ LPCSTR lpCurrentDirectory,
    _In_ LPSTARTUPINFOA lpStartupInfo,
    _Out_ LPPROCESS_INFORMATION lpProcessInformation
    );
// In use
PROCESS_INFORMATION process_information{};
STARTUPINFO startup_info{};
startup_info.cb = sizeof(startup_info);
CreateProcessA(nullptr, "C:\\temp\\something.exe",
               nullptr, nullptr, TRUE, CREATE_SUSPENDED,
               nullptr, nullptr, &startup_info, &process_info);

Step 2: Triggering TLS Callbacks

Next, we need to trigger the TLS callbacks. This can be achieved by either resuming the process or creating a remote thread to execute them. In the memdump application, an alternative method is employed, which involves reading and subsequently overwriting the TLS callbacks address to nullptr and injecting code that manually calls them with the address. This approach provides finer control over the process.


Step 3: Writing Module Headers to Disk

Once the process is ready for writing back to disk, we ensure it's packed in the format stored on disk. Understanding the Windows PE format is crucial. To swiftly obtain the size of the headers, we utilize the SizeOfHeaders field in the IMAGE_OPTIONAL_HEADER.

std::ofstream ofs("process_dumped.exe", std::ios::binary);
// Internal code to get SizeOfHeaders
auto const base = (uint8_t*)GetModuleHandle(NULL);
auto const dos_header = (IMAGE_DOS_HEADER*)base;
auto const nt_headers = (IMAGE_NT_HEADERS*)(base +
  dos_header->e_lfanew);
auto const size = nt_headers->OptionalHeader.SizeOfHeaders;
ofs.write(base, size);
Note: Adjustments may be necessary if the target process requires adding new sections.

Step 4: Writing Sections to Disk

With the headers securely written to the stream, we move on to writing the sections. The IMAGE_SECTION_HEADER structure is pivotal for this step, providing vital information about the location of data in both the running process and on disk.


/* 
* Image section headers follow the IMAGE_NT_HEADERS so we can
* simply increment the nt_headers to the start by adding 1
*/
auto image_section = (IMAGE_SECTION_HEADER*)(nt_headers + 1);
for (auto i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i) {
  // VirtualAddress is the relative address, so we must add it to the module base
  auto const section_start = base + image_section->VirtualAddress;
  auto const section_size = image_section->SizeOfRawData;
  ofs.write(section_start, section_size);
  ++image_section;
}

The stream contains the running process, packed back up and ready for analysis. For a more in-depth look at each step, refer to the commented code in the memdump application.


Conclusion: Unleashing the Power of Dumping

Dumping Windows executables from memory is a crucial skill in the world of reverse engineering, debugging, and security analysis. Understanding the inner workings of executables, the Windows PE format, and the intricate dance of TLS callbacks opens up a world of possibilities for analysis and investigation.

Armed with the knowledge and techniques outlined in this post, you're ready to tackle the challenges of analyzing and dissecting Windows binaries with confidence. Remember, this field is constantly evolving, so there's always more to explore. Happy dumping!