Assembler with LCC-Win32

SelDelNT

#include // for Win32 api's

int main(void)
{

    // get the address that this module is mapped too
    HINSTANCE hModule = GetModuleHandle(NULL);

    // get the name of this module for passing to DeleteFileA
    char buf[MAX_PATH];
    GetModuleFileNameA(hModule, buf, sizeof(buf));

    // provide an exit code for ExitProcess
    UINT uExitCode = 0;

    // get the direct addresses of the functions that go on the stack
    HINSTANCE hKernel32 = GetModuleHandle("KERNEL32");
    DWORD pExitProcess = (DWORD)GetProcAddress(hKernel32,"ExitProcess");
    DWORD pDeleteFileA = (DWORD)GetProcAddress(hKernel32,"DeleteFileA");

    // for this function the IAT address is ok
    DWORD pUnmapViewOfFile = (DWORD)UnmapViewOfFile;

    // handle to the file mapping object for this program
    HANDLE hFileMapping = (HANDLE)(0x00000004);

    // close the handle to the file mapping object for this program
    CloseHandle(hFileMapping);

    _asm("leal %buf, %eax");	        // load the address of the file name buffer into eax
    _asm("pushl %uExitCode);            // push the parameter for ExitProces onto the stack
    _asm("pushl $0x0");                 // return address for ExitProcess
    _asm("pushl %eax");                 // eax provides address to parameter for DeleteFileA
    _asm("pushl %pExitProcess");        // push direct address of ExitProcess onto the stack
    _asm("pushl %hModule");             // push the parameter to UnmapViewOfFile onto the stack
    _asm("pushl %pDeleteFileA");        // push direct address of DeleteFileA onto the stack
    _asm("pushl %pUnmapViewOfFile");    // push IAT address of UnmapViewOfFile onto the stack
    _asm("ret");                        // pop the stack fomenting the cascade of function calls

    return 0;
}

Writing Self Deleting Executable Files on Windows NT/2000 Using LCC-Win32

by Mike Caetano

The above code is based on code written by Gary Nebbett, author of the "Windows NT/2000 Native API Reference". Nebbett first published a variation of this routine in March of 1999 in response to a question on comp.os.ms-windows.programmer.win32 regarding how to create a self deleting executable for Intel based systems running Windows NT. [1]

Nebbett's routine was written for use with Microsoft Visual C++ and will not work with LCC-Win32 even after accounting for the differences in assembler syntax. I wasn't able to account for the failure of Nebbett's code to work with LCC-Win32 until I found another example using a slightly different approach. [2] I was able to replicate this example using LCC-Win32. What follows is an explanation of how the above code works it's magic.

To fully understand what this code does I think a few basics regarding program execution under Windows should be reviewed. A program and a process are different items. A program is a static sequence of instructions that is stored in a file on disk. A process is a container for a set of resources used by the threads that execute an instance of a program. The distinction is important because this makes it possible to delete the program from disk without hindering the viability of the process.

When a program is launched the operating system prepares a process object before creating the threads that execute the instructions stored in the program. A process object has two primary components, a list of handles to various system resources that a given process might require and a private virtual address space spanning the range of memory that can be addressed using a 32 bit pointer. This address space contains all of the instructions and data stored in the program, as well as the stack, the heap and all of the instructions and data stored in any library files linked to the program.

A process itself is inert. It must have at least one thread in it's context in order to execute the instructions stored in the program. Before the primary thread of a process can begin executing a program's code, the operating system must first load that program into the address space of the process. The system uses file mapping to accomplish this. The system maps the program and then any libraries linked to the program into the address space of the process. The routines the system uses to perform this mapping are similar enough to the Win32 API functions involved in file mapping that they are sufficient for the purposes of this explanation. Therefore a quick review is in order.

To use a memory-mapped file three steps are needed. First the file to be mapped must be created or opened:

    HANDLE hFile = CreateFile(szFileName,...);

Then a file-mapping object must be created using the handle to that file:

    HANDLE hFileMapping = CreateFileMapping(hFile,...);

Then the file-mapping object must be mapped into the address space of the process:

    LPVOID lpVoid = MapViewOfFile(hFileMapping,...);

Cleaning up a memory mapped file also involves three steps.

First the file-mapping object must be unmapped from the address space of the process:

    UnmapViewOfFile(lpVoid);

Then the file-mapping and file handle objects must be closed:

    CloseHandle(hFileMapping);
    CloseHandle(hFile);

Note that the file-mapping handle can be closed before the file is unmapped. This fact will become important later on. For details regarding the use of these functions consult the relevant documentation. [3]

Mapping the program into the address space of the process holds the file open for the lifetime of the process. This locks the file and prevents it from being deleted while it is running. Before a process can delete its executable file it must unmap that file from its address space and close the handle to the associated file mapping object.

Ordinarily, unmapping a view on a file requires passing the address returned from MapViewOfFile to UnmapViewOfFile. In the case of a running program, however, the system performs the mapping on behalf of the process.

So how can a process obtain this value? As it turns out, the base address of the program also happens to be the address at which the file is mapped into the address space of the process.

The value of this address is 0x00400000 and is passed as the HINSTANCE parameter to WinMain. It can also be obtained by passing a NULL value to GetModuleHandle as shown in this code.

Passing this value to UnmapViewOfFile will successfully unmap the program from the process address space. However, once this call completes, the memory addresses that previously contained the program's instructions are no longer valid. This makes it impossible to access any statically stored values in the program (eg string literals) or call any functions in the executable. It also means that functions imported from external libraries such as Kernel32.dll can not be called in the ordinary fashion. This eventuality must be prepared for before unmapping the program from the address space of the process.

If you are not familiar with the PE-file ("portable executable") format, you might want to acquaint yourself with it before continueing. [4]

A pe-file contains a section of code known as the import address table (IAT). The import address table correlates an offset into the program file with an offset into an external library that the program links to where the code for a function used by the program can be found. When the operating system maps the program and and any libraries it requires into the address space of the process, it adjusts these offsets so that they point to the correct addresses in the address space of the process. This means that a call to a function imported from an external library, such as CreateFile, references an address in the IAT that points to the address where the actual code for that function was mapped.

PE-files are designed this way because there is no guarantee that the system will always load a dll at it's prefered address in any given process. This design provides an efficient method for resolving conflicts that arise when different libraries have the same preferred load address.

This design also means that any calls to functions imported from external libraries that are needed to continue the deletion routine must be accessed by other means after the program has been unmapped from memory. Fortunately, using GetProcAddress provides a way to bypass the IAT and store the direct address of any needed functions in local variables.

This is advantageous because local variables reside on the stack and the stack is independent of the file mapping. This means that function addresses stored in variables on the stack remain available for use after the program has been unmapped. Unmapping the executable file does not effect external libraries mapped into the address space of the process. The code in those libraries remains available for the process to use.

So any import function calls that are needed to prepare for the deletion sequence should be made before the executable is unmapped. This includes GetModuleHandle, GetModuleFileNameA, GetProcAddress and CloseHandle. How those functions are used should be self evident from the code.

To briefly touch on the remaining import functions:

ExitProcess
The preferred means for ending a process. Provides a clean process shutdown. This includes calling the entry-point function of all attached dynamic-link libraries (DLLs) with a value indicating that the process is detaching from the DLL.

DeleteFileA
Deletes an existing file. This function fails if an application attempts to delete a file that is open for normal I/O or as a memory-mapped file.

UnmapViewOfFile
Unmaps a mapped view from the address space of the calling process. The call to this function can be made through the IAT address because at the point where this function is executed the IAT remains valid. However, subsequent calls to import functions must be made using the direct address of the functions obtained using GetProcAddress.

Nebbett's original code does not use GetProcessAddress to acquire addresses to be stored on the stack. My assumption is that Microsoft's linker bypasses the IAT all together for functions exported from Kernel32.dll and instead replaces the names of those functions with their the direct address. This is reasonable as Kernel32.dll always loads at it's preffered address.

Recall from the file mapping review above that the handle to the file mapping object must be closed before the file can be deleted. Also recall that this handle can be closed before the program is unmapped from memory.

This allows for closing the handle to the file-mapping object as the last step before setting up the inline assembler instructions that perform the true magic of this self-deletion routine.

    HANDLE hFileMapping = (HANDLE)0x4;
    CloseHandle(hFileMapping);

This call decrements the reference count to the file-mapping handle. DeleteFileA will fail if there is an outstanding reference to this handle.

Ok - So where does (HANDLE)(0x4) come from?

According to Nebbett, when the system creates a new process the first handle added to the handle table for that process is the handle to the file mapping object used to map the program file into the address space of that process.

Nebbet relied on empirical evidence to determine that this handle always has the value 4. This can be confirmed by calling MapViewOfFile using (HANDLE)(0x4) as the file-mapping handle and then mapping the returned pointer to the pe-file header structures.

The values provided by this overlay can be compared to the same values obtained by mapping the hinstance variable for the program to the pe-file header structures as well and then comparing the results. The timedatestamp found using both addresses matches exactly. [5]

There is no account given for why the handle to the file itself doesn't have to be closed. My guess is that the system does not add the handle to the program file to the handle table for the process and so there is no outstanding reference to the file that would otherwise prevent DeleteFileA from succeeding.

Now that all the setup has been completed the most interesting elements of the routine can be examined - the inline assembly language statements that finish up the job.

Recall that a stack operates on a "last in first out" basis, ie. the last item pushed on the stack will be the first item popped off the stack. This means that the push sequence becomes very important to the success of the routine. The items must be pushed in reverse order to the order in which they are to be accessed. Complicating matters is the fact that pushing an address onto the stack doesn't mean the function stored at that address will be called. However, it's easier to explain what happens as it happens then to explain the push order ahead of time.

Nebbett employs a crafty assembly language trick to cascade the needed function calls. Putting a "ret" instruction on the stack pops the instruction pointer off the stack and returns program control to the address at the top of the stack, ie pUnmapViewOfFile.

pUnmapViewofFile finds two items on the stack: an address to return to (ie. pDeleteFileA) and the value of hModule. The value of hModule provides the argument to UnmapViewOfFile and that function is executed.

It's ok to use the IAT address of UnmapViewOfFile here as the program file remains mapped into the address space of the process. This call unmaps the program file making it possible for the call to DeleteFileA to succeed but also making the IAT useless for subsequent calls.

UnmapViewOfFile returns program control to the address on the top of the stack, ie. pDeleteFileA.

pDeleteFileA finds two items on the stack: the address to return to (ie. pExitProcess) and the register eax. The address of the buffer containing the name of the file to delete was previously stored in eax, thus eax provides the argument to DeleteFileA and that function is executed.

DeleteFileA deletes the program file making the continued execution of the process moot.

DeleteFileA returns to the address on the top of the stack, ie. pExitProcess. pExitProcess finds two items on the stack: the address to return to - 0x0 - which is never used - and the value to use as the exit code for the process - in this case also 0.

ExitProcess informs the operating system to detach external libraries and close the process. When the process closes the last vestiges of the program file cease to exist.

The remarkable element of Nebbett's code is that the stack order is laid out in such a way that the 'ret' instruction pops the instruction pointer off the stack which positions it on the function address at the top of the stack. This sets off a chain reaction as the 'ret' instruction at the end of each of function begets a call to the next function address lower in the stack until there are no additional functions to call.

A rather nifty trick indeed.


References:

"Programming Applications for Microsoft Windows" by Jeffrey Richter.

"Inside Microsoft Windows 2000" by David Solomon and Mark Russinovich

[1] Gary Nebbett's original example

[2] Nick Repin's version for bcc

[3] CreateFileMapping

[4] "Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format, Part 1" by Matt Pietrek

// confirm.c
// confirms Nebbett's determination that the file mapping handle
// for the program belonging to the current process has the value 4.

#include <windows.h>
#include <stdio.h>

DWORD _stdcall GetTimeDateStamp(LPVOID lpVoid)
{
    PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)lpVoid;

    PIMAGE_NT_HEADERS pFileHeader = (PIMAGE_NT_HEADERS)((DWORD)lpVoid + pDOSHeader->e_lfanew);

    return pFileHeader->FileHeader.TimeDateStamp;
}

int main(void)
{
    // get the address that this module is mapped to
    HINSTANCE hModule = GetModuleHandle(NULL);

    // handle to the file mapping object for this program
    HANDLE hFileMapping = (HANDLE)(0x00000004);

    // map a view of this program
    LPVOID pThis = MapViewOfFile(hFileMapping, FILE_MAP_READ,0,0,0);

    // dump the timestamp
    printf("DWORD TimeDateStamp;  %08X\n", GetTimeDateStamp(hModule) );
    printf("DWORD TimeDateStamp;  %08X\n", GetTimeDateStamp(pThis) );

    if ( GetTimeDateStamp(hModule) != GetTimeDateStamp(pThis) )
    {
        printf("ack! no match!\n");
    }

    UnmapViewOfFile(pThis);

    return 0;

}

Back to main page