Make executable harder to recover from memory

Modern forensics tools are capable of recovering your executable from RAM even when you wiped it out from the hard-disk or other non volatile memory. As you may already know that when a program is run, it gets loaded into memory by os’s loader. It’s a complex process of it’s own. After all the process specific initialization is done, program starts to execute and stays in memory until stopped. Many malware, to prevent sample acquisition wipe their image from the disk, thus making recovery a little harder if not impossible. Forensics investigators often try to circumvent this by imaging the RAM and then recovering the malware image from it but there are ways to prevent that. Let’s try to figure that out. But before that Let’s study a little bit about PE (Portable Executable)

What is PE?

Portable Executable (PE) is a file format used in Windows executable files. This file format is a specification for an executable file. Other popular executable file formats are ELF used in in Linux and Mach-o in OSX.

The PE file format is a data structure that contains the information necessary for the Windows OS loader to manage the wrapped executable code. Before PE there was a format called COFF used in Windows NT systems.

Structure of PE

header

First portion of PE is MS-DOS header. This is to ensure backwards compatibility, Microsoft has written a series of machine instructions into the head of each PE file. When a 32-bit Windows file is run in a 16-bit DOS environment, the program will display the error message: “This program cannot be run in DOS mode.”, then terminate. It can be described as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct DOS_Header {
char signature[2] = "MZ"; //Magic Number
short lastsize;
short nblocks;
short nreloc;
short hdrsize;
short minalloc;
short maxalloc;
void *ss;
void *sp;
short checksum;
void *ip;
void *cs;
short relocpos;
short noverlay;
short reserved1[4];
short oem_id;
short oem_info;
short reserved2[10];
long e_lfanew; //Address to NT header

It’s a complicated structure so, I commented out stuff relevant for this discussion.

After the DOS header there is a stub program that as discussed above.

e_magic is ASCII set to “MZ” which stands for Mark Zibikowski who developed MS-DOS. We care only about e_lfanew which is a pointer to PE header. Let’s take a brief look at NT Header.

NT Header can be described with following structure

1
2
3
4
5
struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

We have Signature which contains some well known value to identify the structure. FileHeader and Optional Header is embedded in structure and are not a pointer.

Let’s take a little bit look at IMAGE_FILE_HEADER

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
}

Machine refers to type of architecture (x86-64 or x86).

TimeDateStamp is the unix timestamp when the file was linked.

and so on.

You can read about other field here..aspx)

Interesting member is SizeOfOptionalHeader which contains a field called SizeOfHeaders which refers to t he combined size of the following items,

1
2
3
4
5
e_lfanew member of IMAGE_DOS_HEADER
4 byte signature
size of IMAGE_FILE_HEADER
size of optional header
size of all section headers

Something more interesting now

Now what can we do from above information to hide our malware? Answer is to erase the header from memory and recovery of executable will become almost impossible. Let’s try it out.

This is the simple code whose executable we’ll try to recover

1
2
3
4
5
6
7
8
#include<iostream>
int main() {
while(true) {
std::cout << "Hello this is dog" << std::endl;
Sleep(2000);
}
}

I will be using rekall tools, you can grab it from here. First step is to compile and run the above program. Then run winpmem (download from rekall link) and run this command as administrator from cmd prompt.

1
winpmem.exe -o dump

first

Now we should start rekall with this dumped image, we can do this by

1
rekall.exe -f <path-to-dump>

now to dump executable image

1
procdump prog_regex="badboy*", dump_dir=<path-to-dump-executable>

second

Opening it in IDA, looks like regular binary, with everything intact , it even works on double clicking it.

third

Now Let’s try by erasing the header and see what happens.

#include "stdafx.h"

void badboy() {
    while(true) {
        std::cout << "Hello this is dog" << std::endl;
        Sleep(2000);
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    BOOL pFailure = FALSE;
    DWORD sizeOfHeader = 0;
    DWORD lpOldProtect = 0;
    HANDLE imgAddress = GetModuleHandle(NULL);
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)imgAddress;
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + (DWORD)pDosHeader->e_lfanew);

    printf("NT Header at : %p\n", pNtHeader);
    printf("DOS Header at : %p\n", pDosHeader);
    printf("Offset : %p\n", (DWORD)pNtHeader - (DWORD)pDosHeader);

    if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
        puts("We got some problems, DOS Signature mismatch\n");
        pFailure = TRUE;
    }
    if(pNtHeader->Signature != IMAGE_NT_SIGNATURE){
        puts("We got some problems, NT Signature mismatch\n");
        pFailure = TRUE;
    }

    sizeOfHeader = pNtHeader->OptionalHeader.SizeOfCode;

    printf("Header Size : %d\n", sizeOfHeader);

    if(!pFailure) {
        BOOL pChangePerm = VirtualProtect((LPVOID)imgAddress, sizeOfHeader,PAGE_READWRITE,&lpOldProtect);
        if(pChangePerm) {
            puts("Modified page permission to RW\n");
            RtlZeroMemory((PVOID)imgAddress,sizeOfHeader);
        }
        pChangePerm = VirtualProtect((LPVOID)imgAddress,sizeOfHeader,lpOldProtect,&lpOldProtect);

        if(pChangePerm){
            puts("Page Permission restored\n");
        }
    }

    badboy();

    return 0;
}

Note that we also need to make page writable using VirtualProtect() and then nulled out memory using RtlZeroMemory() which works like memset()

This is the all the theory put into code. Now let’s try to run this process

fourth

Let’s acquire the image and try to extract the executable using command save as above. Dumping out the process and viewing it in IDA

fifth

And BAM!! IDA Can’t read anything from it. Neither can any other debugger since we corrupted header which contains all the meta data about a program.

All the credit goes to int0x80 for showing this technique.

References

Anti-Forensics AF (PDF)

DEF CON 24 - int0x80 - Anti Forensics AF (Video)

x86 Disassembly/Windows Executable Files

Peering Inside the PE: A Tour of the Win32 Portable Executable File Format

Life of Binaries 2013