Self deleting executables

I read an interesting article the other day that spoke about the various mechanisms a Win32 application can employ for deleting itself from the disk once execution completes. The basic issue is of course that while the module is being executed the operating system has the file locked. So something like this will just not work:

    TCHAR szModule[MAX_PATH];
    GetModuleFileName( NULL, szModule, MAX_PATH );
    DeleteFile( szModule );

Of the various options available, the author of the said article had suggested the following approach as being the definitive one as it has the added benefit of functioning correctly on all versions of Microsoft Windows (starting with '95).

Now would be a good time to hop over to the article and see what it's about (and while you're there make sure you look at some of the other articles - pretty neat). Here's the link:

http://www.catch22.net/tuts/self-deleting-executables

And here's the approach in brief:

  • When it's time to delete ourselves we first spawn an external process that is guaranteed to exist on all Windows computers (explorer.exe for example) in the suspended state. We do this by calling CreateProcess passing CREATE_SUSPENDED for the dwCreationFlags parameter. Note that when a process is launched this way there's really no telling at what point the primary thread of the process will get suspended. But it does appear to get suspended long before the entry point gets invoked and in fact it occurs even before the Win32 environment for the process has been fully initialized.

  • After this we get the CONTEXT data (basically, the CPU register state) for the suspended primary thread (in the remote process) via GetThreadContext.

  • We then manipulate the stack pointer (ESP) to allocate some space on the remote stack for storing some of our data (like the path to the executable to be deleted). After this we plonk the binary code for a local routine that we've written for deleting files over to the remote process (along with the data it needs) by calling WriteProcessMemory.

  • Next we mess around with the instruction pointer (EIP) so that it points to the binary code we've copied to the remote process and update the suspended thread's context (via SetThreadContext).

  • And finally, we resume execution of the remote process (via ResumeThread). Since the EIP in the remote thread is now pointing to our code, it executes it; which of course, happily deletes the original executable. And that's it!

While this approach does get the job done, the fact that our deletion code executes in the remote process even before Windows has had a chance to initialize it fully places some restrictions on the kind of APIs that we can invoke. It so turns out that APIs like DeleteFile and ExitProcess do work while the process is in this half-baked state. So I figured I'll modify the approach somewhat so that it allows us to call any API we want from our injected code. Here's what I did:

  • As before we launch the external process in a suspended state. However, instead of plonking our code at the location that ESP happens to be pointing at when it got suspended, we put it over the executable's entry-point routine, i.e., we replace the remote process's entry point with our own injected code. And when the entry-point code executes we can be pretty sure that the Win32 environment is fully initialized and primed for use!

  • Figuring out where the entry point of a module lives requires us to parse PE file format structures. In your own program for example, the following code would give you a pointer to the entry point routine in the process's executable image:

#pragma pack( push, 1 )

struct coff_header
{
    unsigned short machine;
    unsigned short sections;
    unsigned int timestamp;
    unsigned int symboltable;
    unsigned int symbols;
    unsigned short size_of_opt_header;
    unsigned short characteristics;
};

struct optional_header
{
    unsigned short magic;
    char linker_version_major;
    char linker_version_minor;
    unsigned int code_size;
    unsigned int idata_size;
    unsigned int udata_size;
    unsigned int entry_point;
    unsigned int code_base;
};

#pragma pack( pop )

//
// get the module address
//
char *module = (char *)GetModuleHandle( NULL );

//
// get the sig
//
int *offset = (int*)( module + 0x3c );
char *sig = module + *offset;

//
// get the coff header
//
coff_header *coff = (coff_header *)( sig + 4 );

//
// get the optional header
//
optional_header *opt = (optional_header *)( (char *)coff +
   sizeof( coff_header ) );

//
// get the entry point
//
char *entry_point = (char *)module + opt->entry_point;
  • The entry point that you define by the way - main or WinMain - isn't the actual entry point routine. The compiler inserts its own entry point which in turn calls our function. This entry point typically does stuff like CRT initialization and cleanup. In an ANSI console app for instance the actual entry point routine is something called mainCRTStartup.

  • It appears logical that we should be able to find the entry point routine in remote processes also in a similar fashion using ReadProcessMemory. While that is so, finding the equivalent of the module variable in the code given above for remote processes turned out to be trickier than anticipated. The problem is that there is no convenient GetModuleHandle routine that'll work for remote processes.

  • As it turns out GetModuleHandle returns a virtual address that is valid only within the relevant process's address space. ReadProcessMemory however requires real addresses to work with. So the question is, how do we get to know the remote process's base address in memory? The solution as it turned out requires us to dig deep into the OS's internals! The credit for this solution goes to Ashkbiz Danehkar whose article called Injective Code inside Import Table on Code Project outlines a method for finding this.

  • In brief, the operating system maintains a user-mode data structure for every thread in the system called the Thread Environment Block (TEB) which describes pretty much everything you'd want to know about the thread including a pointer to another data structure called the Process Environment Block (PEB) which, as may be apparent describes processes including, happily for us, a pointer to the image's base address in memory! These structures are not however documented (by Microsoft that is ;). But some very very clever folks at http://undocumented.ntinternals.net/ managed to figure out the layout for these structures all by themselves!

  • So all we need to do is:

    • Figure out where the TEB for the primary thread lives in the remote process. This information is stored in the thread's FS register which is accessible via the GetThreadSelectorEntry API.
    • Read the PEB using the pointer to it in the thread's TEB via ReadProcessMemory.
    • Use the pointer to the image's base address in the PEB and parse the PE structures till we are left with a reference to the remote process's entry point routine.
    • Phew!

    Here's the code that achieves this:

//
// Gets the address of the entry point routine given a
// handle to a process and its primary thread.
//
DWORD GetProcessEntryPointAddress( HANDLE hProcess, HANDLE hThread )
{
    CONTEXT             context;
    LDT_ENTRY           entry;
    TEB                 teb;
    PEB                 peb;
    DWORD               read;
    DWORD               dwFSBase;
    DWORD               dwImageBase, dwOffset;
    DWORD               dwOptHeaderOffset;
    optional_header     opt;

    //
    // get the current thread context
    //
    context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    GetThreadContext( hThread, &context );

    //
    // use the segment register value to get a pointer to
    // the TEB
    //
    GetThreadSelectorEntry( hThread, context.SegFs, &entry );
    dwFSBase = ( entry.HighWord.Bits.BaseHi << 24 ) |
                     ( entry.HighWord.Bits.BaseMid << 16 ) |
                     ( entry.BaseLow );

    //
    // read the teb
    //
    ReadProcessMemory( hProcess, (LPCVOID)dwFSBase,
                       &teb, sizeof( TEB ), &read );

    //
    // read the peb from the location pointed at by the teb
    //
    ReadProcessMemory( hProcess, (LPCVOID)teb.Peb,
                       &peb, sizeof( PEB ), &read );

    //
    // figure out where the entry point is located;
    //
    dwImageBase = (DWORD)peb.ImageBaseAddress;
    ReadProcessMemory( hProcess, (LPCVOID)( dwImageBase + 0x3c ),
                       &dwOffset, sizeof( DWORD ), &read );

    dwOptHeaderOffset = ( dwImageBase + dwOffset + 4 +
                            sizeof( coff_header ) );
    ReadProcessMemory( hProcess, (LPCVOID)dwOptHeaderOffset,
                       &opt, sizeof( optional_header ), &read );

    return ( dwImageBase + opt.entry_point );
}
  • If you're wondering what the weird code initializing dwFSBase means all I can do is direct you to the documentation for the LDT_ENTRY data structure in MSDN. Structures of this kind are partly the reason why system programmers tend to go bald early in life.

  • Now that we know where the entry point lives in the remote process it should be really straightforward right? Wrong! There still is that itsy bitsy problem of figuring out how we are to pass data to the remote process!

    The routine that deletes our executable looks like this:

#pragma pack(push, 1)

//
//  Structure to inject into remote process. Contains 
//  function pointers and code to execute.
//
typedef struct _SELFDEL
{
    HANDLE  hParent;                // parent process handle
    FARPROC fnWaitForSingleObject;
    FARPROC fnCloseHandle;
    FARPROC fnDeleteFile;
    FARPROC fnSleep;
    FARPROC fnExitProcess;
    FARPROC fnRemoveDirectory;
    FARPROC fnGetLastError;
    FARPROC fnLoadLibrary;
    FARPROC fnGetProcAddress;
    BOOL    fRemDir;
    TCHAR   szFileName[MAX_PATH];   // file to delete
} SELFDEL;

#pragma pack(pop)

//
//  Routine to execute in remote process. 
//
static void remote_thread(SELFDEL *remote)
{
    // wait for parent process to terminate
    remote->fnWaitForSingleObject(remote->hParent, INFINITE);
    remote->fnCloseHandle(remote->hParent);

    // try to delete the executable file 
    while(!remote->fnDeleteFile(remote->szFileName))
    {
        // failed - try again in one second's time
        remote->fnSleep(1000);
    }

    // finished! exit so that we don't execute garbage code
    remote->fnExitProcess(0);
}
  • As you might have noticed the function remote_thread makes all system calls via function pointers instead of calling them directly. This is done because, in the normal course, the compiler generates tiny stubs whenever calls to routines in dynamically loaded DLLs are made from a program. This stub jumps to a function pointer stored in a table initialized by the operating system's loader at runtime. Since we don't want these fancy stubs generated for code that is meant to be injected into a remote process, we deal exclusively with function pointers.

    Fortunately for us, the system APIs (in kernel32, user32 etc.) always get loaded at the same virtual address in all processes. So all we need to do is initialize a data structure with pointers to all the system calls we want to make from the remote process and pass this structure along also. With our entry-point overwrite strategy of course, how are we to do this? To make a long story short, I settled for the following approach.

  • First, I modified remote_thread to look like this:

//
//  Routine to execute in remote process. 
//
static void remote_thread()
{
    //
    // this will get replaced with a
    // real pointer to the data when it
    // gets injected into the remote
    // process
    //
    SELFDEL *remote = (SELFDEL *)0xFFFFFFFF;

    //
    // wait for parent process to terminate
    //
    remote->fnWaitForSingleObject(remote->hParent, INFINITE);
    remote->fnCloseHandle(remote->hParent);

    //
    // try to delete the executable file 
    //
    while(!remote->fnDeleteFile(remote->szFileName))
    {
        //
        // failed - try again in one second's time
        //
        remote->fnSleep(1000);
    }

    //
    // finished! exit so that we don't execute garbage code
    //
    remote->fnExitProcess(0);
}
  • I then converted this into shellcode (the exact mechanics of which I'll outline in another post) to arrive at what looks like this (this is just representative shellcode and not the one that got generated for the routine shown above):
char shellcode[] = {
    '\x55', '\x8B', '\xEC', '\x83', '\xEC', 
    '\x10', '\x53', '\xC7', '\x45', '\xF0',
    '\xFF', '\xFF', '\xFF', '\xFF',   // replace these 4 bytes
                                      // with actual address
    '\x8B', '\x45', '\xF0', '\x8B', '\x48',
    '\x20', '\x89', '\x4D', '\xF4', '\x8B',
    '\x55', '\xF0', '\x8B', '\x42', '\x24',
    '\x89', '\x45', '\xFC', '\x6A', '\xFF', ... more shell code here
  • shellcode, if you didn't know, is the technical term used (in security circles) to refer to binary machine code that is typically used in exploits as the payload. As it turns out in our case the value 0xFFFFFFFF that we initialized the pointer remote with in remote_thread shows up the exact same way in the shellcode also. Since we know where the entry point lives in the remote process, all we need to do is to first replace 0xFFFFFFFF in the shellcode with the actual pointer to the data before over-writing the entry point. Here's how this looks:
STARTUPINFO             si = { sizeof(si) };
PROCESS_INFORMATION     pi;
SELFDEL                 local;
DWORD                   data;
TCHAR                   szExe[MAX_PATH] = _T( "explorer.exe" );
DWORD                   process_entry;

//
// this shellcode self-deletes and then shows a messagebox
//
char shellcode[] = {
    '\x55', '\x8B', '\xEC', '\x83',
    '\xEC', '\x10', '\x53', '\xC7',
    '\xFF', '\xFF', '\xFF', '\xFF',   // replace these 4 bytes
                                      // with actual address
    '\x8B', '\x45', '\xF0', '\x8B',
    '\x48', '\x20', '\x89', '\x4D',

    ... snipped lots of meaningless shellcode here! ...

    '\xFF', '\xD0', '\x5B', '\x8B',
    '\xE5', '\x5D', '\xC3'
};

//
// initialize the SELFDEL object
//
local.fnWaitForSingleObject     = (FARPROC)WaitForSingleObject;
local.fnCloseHandle             = (FARPROC)CloseHandle;
local.fnDeleteFile              = (FARPROC)DeleteFile;
local.fnSleep                   = (FARPROC)Sleep;
local.fnExitProcess             = (FARPROC)ExitProcess;
local.fnRemoveDirectory         = (FARPROC)RemoveDirectory;
local.fnGetLastError            = (FARPROC)GetLastError;
local.fnLoadLibrary             = (FARPROC)LoadLibrary;
local.fnGetProcAddress          = (FARPROC)GetProcAddress;

//
// Give remote process a copy of our own process handle
//
DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), 
    pi.hProcess, &local.hParent, 0, FALSE, 0);
GetModuleFileName(0, local.szFileName, MAX_PATH);

//
// get the process's entry point address
//
process_entry = GetProcessEntryPointAddress( pi.hProcess, pi.hThread );

//
// replace the address of the data inside the
// shellcode (bytes 10 to 13)
//
data = process_entry + sizeof( shellcode );
shellcode[13] = (char)( data >> 24 );
shellcode[12] = (char)( ( data >> 16 ) & 0xFF );
shellcode[11] = (char)( ( data >> 8 ) & 0xFF );
shellcode[10] = (char)( data & 0xFF );

//
// copy our code+data at the exe's entry-point
//
VirtualProtectEx( pi.hProcess,
                  (PVOID)process_entry,
                  sizeof( local ) + sizeof( shellcode ),
                  PAGE_EXECUTE_READWRITE,
                  &oldProt );
WriteProcessMemory( pi.hProcess,
                    (PVOID)process_entry,
                    shellcode,
                    sizeof( shellcode ), 0);
WriteProcessMemory( pi.hProcess,
                    (PVOID)data,
                    &local,
                    sizeof( local ), 0);

//
// Let the process continue
//
ResumeThread(pi.hThread);

There! That's all there is to it. Please find the code for a self-deleting executable (that among other things also displays a message box from the remote process's hijacked entry point) here:

myselfdel.c
ntundoc.h

comments powered by Disqus