Self-Deleting Executables

Self-Deleting Executables引用并回复

This is a subject that tends to come up every so often in the newsgroups, so I thought I'd write an article about the techniques I've collected to enable an executable to delete itself from disk (whilst running, that is). There is very little information on the web, and what information there is is also hard to find.

Why would you want a program to delete itself? The only good reason I know of is an un-install program that needs to remove an application, as well as itself in order to completely remove the application from disk. I'm sure there are other good reasons for self-deleting executables, but most other cases would probably be viruses or trojans trying to hide themselves.

Altogether I know of five different methods, each of which I will describe shortly. I must take this opportunity to mention that only one of these techniques has been developed by myself. Apart from the last method, I am presenting the same material described by Jeffrey Richter in his January 1996 MSJ column, titled "Win32 Q&A". Click here to read the original article. So, I hope no-one thinks I am ripping off other people's ideas or work.

Why the obvious doesn't work

If you try to run the following code, nothing will happen.

TCHAR szEXEPathname[_MAX_PATH];
GetModuleFileName(NULL, szEXEPathname, _MAX_PATH);
DeleteFile(szEXEPathname);

The code above retrieves the full path to the current executable, and then tries to delete the file whilst running. This code will fail because all 32bit versions of Windows (95, 98, ME, NT, 2000, XP etc) use a mechanism called memory-mapped files to load an executable image into memory.

When the Windows loader runs an executable, it opens the executable's disk file and maps that region of disk into memory, effectively loading the executable into memory. This disk file is kept open during the lifetime of the process, and is only closed when the process terminates. Because of this lock on the file, it is normally impossible to delete an executable file whilst it is running. Just run notepad.exe, then try to delete the notepad executable - it won't work.

The MoveFileEx method

I'm going to mention this technique even though it doesn't really solve our problem, because it's quite useful to know and can be handy in other situations.

BOOL MoveFileEx(LPCTSTR lpExistingFileName,
LPCTSTR lpNewFileName, DWORD dwFlags);

This API call moves a file to a new location. When you pass NULL as the second parameter, this causes the file to be moved "nowhere", effectively deleting the file. Now, ordinarily this would fail if you tried this with the path to the current executable. However, if we specify MOVEFILE_DELAY_UNTIL_REBOOT in the dwFlags parameter, this tells Windows not to move (or delete) the file until the system is shutdown or rebooted.

There are a few problems with this technique. Firstly, you cannot remove the directory that the executable resides in. Second, the file is not deleted immediately - if your system doesn't get rebooted very often, then the file will stay around. But the biggest problem is that MoveFileEx is not implemented on Windows 95/98/ME.

The WININIT.INI method

Under Windows 95/98/ME, an application called WININIT.EXE runs each time the system is started. This application looks for a file called WININIT.INI. If this file exists, WININIT.EXE looks for a section called [Rename]. Each entry in the [Rename] section specifies a file rename operation which will occur (once) when time the system starts. This method is obviously very similar to the MoveFileEx method described above.

[Rename]
NUL=c:/dir/myapp.exe
c:/dir/new.exe=c:/dir/old.exe

The filename to the left of the equal sign specifies the new name of the filename on the right. When NUL is used as the new filename, the file is deleted. This means that an application can write an entry into WININIT.INI, specifying NUL and the applications own full path.

You must be careful when writing an entry to the [Rename] section. You cannot use WritePrivateProfileString API call, because this function prevents any duplicate entries from occuring under the same section. This restriction would prevent there from being more than one "NUL=" entry. Therefore you must manually write any entry if you want to use this technique.

The Self-Deleting Batch File method

This is quite a well known method, and was documented in MSDN some time ago. This technique works on both Windows 95 and Windows NT. It works because MS-DOS batch files are able to delete themselves. To test this technique, create a small batch file containing the single command:

del %0.bat

The batch file, when run, deletes itself and issues an error "The batch file cannot be found". This error is just a simple message, so it can be safely ignored. By itself this isn't too useful, but when modified to delete our executable it solves our problem, albeit in a rather forceful manner. Our executable will create a batch file (called DelUs.bat) with the following content:

:Repeat
del "C:/MYDIR/MYPROG.EXE"
if exist "MYPROG.EXE" goto Repeat
rmdir "C:/MYDIR"
del "/DelUS.bat"

This batch file repeatedly attempts to delete the specified file, and will run continuously consuming CPU until it succeeds. When the execuable has been deleted, the batch file then deletes itself.

The executable needs to spawn off the batch file using CreateProcess, and then should exit immediately. It would be a good idea to give the batch file's thread of execution a low priority so that it doesn't get much execution time until the original executable has terminated.

The COMSPEC method

This is a method kindly shared by Tony Varnas, who recently emailed me this snippet:

BOOL SelfDelete()
{
TCHAR szFile[MAX_PATH], szCmd[MAX_PATH];

if((GetModuleFileName(0,szFile,MAX_PATH)!=0) &&
(GetShortPathName(szFile,szFile,MAX_PATH)!=0))
{
lstrcpy(szCmd,"/c del ");
lstrcat(szCmd,szFile);
lstrcat(szCmd," >> NUL");

if((GetEnvironmentVariable("ComSpec",szFile,MAX_PATH)!=0) &&
((INT)ShellExecute(0,0,szFile,szCmd,0,SW_HIDE)>32))
return TRUE;
}
return FALSE;
}

This method is very similar to the batch-file method (above) but is alot neater in its implementation. It works under all 32bit versions of Windows (95,98,ME,NT,2000,XP), as long as the COMSPEC environment variable is defined. This is always defined (by default) to be the full path to the operating system's command interpreter. For Windows 95, this is "command.exe". For Windows NT, this is "cmd.exe".

The function will only work if the executable has exited, so it is important to call this function and then exit immediately. It works by spawning a copy of the system's command interpreter, and asking it to perform the following command:

del <executable-path> >> NUL

This deletes the current executable, and pipes the output to NUL (no output). The shell process is created with a hidden window as well, so the whole process is invisible.

The DELETE_ON_CLOSE method

The CreateFile API call accepts several flags which affect how a file is created or opened. One of these flags, FILE_FLAG_DELETE_ON_CLOSE, specifies that the file will be deleted when the last handle to it is closed. The basis to this technique will be to run an executable with this flag set, so that when it exits, it is deleted automatically.

The first step is to create an empty file with the DELETE_ON_CLOSE flag specified. The exact binary content of the current executable file is then copied into this new file, in effect duplicating the executable on disk. A new process is then created (using the new executable file). This has the effect that the duplicate file's handle count is incremented. Also, when the new process was created, the full path of the current process was passed through the command-line argument.

Next, the current executable (which wants to delete itself) closes the file handle used to create the new process, and then exits. Now, the duplicate's file-handle count is decremented, but because CreateProcess incremented its handle count when it started, the file is not deleted.

At this point, the duplicate executable has started running. The PID specified on the command-line is used to open a handle to the original process. The duplicate waits for the original process to terminate, using WaitForSingleObject. When this call returns, the duplicate can call DeleteFile on the filename also specified through its command-line argument. The original executable (the one that wanted to delete itself) has been successfully deleted. This just leaves the duplicate copy, which exits normally. The duplicate's file-handle count drops to zero, the DELETE_ON_CLOSE flag comes into effect, and the duplicate file is deleted also.

It sounds a bit complicated, but it's not too difficult. Here's the steps one more time:

[ Current process ]
1. Create a new file with FILE_FLAG_DELETE_ON_CLOSE.
2. Copy the current executable's content into the new file.
3. Create a new process with the duplicate executable:
4. Pass the current executable's full path and PID in the call to CreateFile.
5. Sleep for a short time to give the new process time to start.
6. Close the new file.
7. Exit current process.

[ Duplicate process ]
8. Wait for the process specified on command-line to die.
9. Delete file specified on command-line.
10. Exit duplicate process.

There are just a couple of technicalities to mention. First, when the "new" process is spawned, the "old" process must sleep for a short period, enough to let the Windows loader open the file and create the process (thus incrementing it's file count).

Second, the new process must wait until the old process terminates, which releases its file count.

Third, when the duplicate executable is created, it must also have the FILE_SHARE_DELETE flag specified, otherwise CreateProcess will fail, because it won't be able to open the file whilst we have it open with the DELETE_ON_CLOSE flag set.

Obviously this method will require careful coding, because the program must be written in such a way so that it can perform these dual tasks. The "new" executable must know that it's job is to delete the file specified on the command line, for instance.

It's a little messy, but it does work very well. In fact, the uninstall program that I wrote, which is included with the software you can download from this site, uses this very method. I've included an example program which demonstrates this technique.

An alternative method is to write a very small stand-alone executable, which it's sole task is to delete the file-name specified on it's command-line. This executable could then be imbedded as a "payload" to the executable which wants to delete itself. This payload would be created and executed in the same way as described above.

The Ultimate Self-Deleting Executable!

I thought I'd save the best until last. This inline assembly snippet is short and simple. I can't claim credit for this code - I found it posted on usenet some time ago. The author's name is Gary Nebbett, also known as supreme King Of Coding, and the author of "Windows NT Native API Reference" and other similar amazing feats. Thanks Gary :)

#include <windows.h>

int main(int argc, char *argv[])
{
char buf[MAX_PATH];
HMODULE module;

module = GetModuleHandle(0);
GetModuleFileName(module, buf, MAX_PATH);
CloseHandle((HANDLE)4);

__asm
{
lea eax, buf
push 0
push 0
push eax
push ExitProcess
push module
push DeleteFile
push UnmapViewOfFile
ret
}

return 0;
}

This snippet ONLY works under Windows NT, but it works like a dream. As soon as you compile and run the above program, it just disappears from disk! Note: unfortunately this gem only seems to work under NT/2000, not XP or .NET Server.

Now for Windows 9x!

Thanks must go to Tony Varnas again for some great detective work. He managed to unearth the following assembler snippet which works exactly like the snippet above, but this time for Windows 95,98,ME (tested on all three).


int main(int argc, char *argv[])
{
char buf[MAX_PATH];
HMODULE module;

module = GetModuleHandle(0);
GetModuleFileName(module, buf, MAX_PATH);

__asm
{
lea eax, buf
push 0
push 0
push eax
push ExitProcess
push module
push DeleteFile
push FreeLibrary
ret
}

return 0;
}

Combined Windows NT and Windows 9X version

void DeleteMyself()
{
char buf[MAX_PATH];
HMODULE module;
DWORD fnFreeOrUnmap;

module = GetModuleHandle(0);
GetModuleFileName(module, buf, MAX_PATH);

// Check for Win9x Kernel
if(0x80000000 & GetVersion())
{
fnFreeOrUnmap = FreeLibrary;
}
// do for WinNT kernel
else
{
fnFreeOrUnmap = UnmapViewOfFile;
CloseHandle((HANDLE)4);
}

__asm
{
lea eax, buf
push 0
push 0
push eax
push ExitProcess
push module
push DeleteFile
push fnFreeOrUnmap
ret
}
}

int main(int argc, char *argv[])
{
DeleteMyself();
return 0;
}
The Definitive Self Deleting Executable

I am pleased to present what I believe is the definitive self-deleting executable, for all versions of Windows. This technique is quite involved but

//
// Delete currently running executable and exit
//
BOOL SelfDelete(BOOL fRemoveDirectory)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;

CONTEXT context;
DWORD oldProt;
SELFDEL local;
DWORD entrypoint;

TCHAR szExe[MAX_PATH] = _T("explorer.exe");

// PreSelfDelete(szExe);

// return 0;


//
// Create executable suspended
//
if(CreateProcess(0, szExe, 0, 0, 0, CREATE_SUSPENDED|IDLE_PRIORITY_CLASS, 0, 0, &si, π))
{
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.fRemDir = fRemoveDirectory;

// 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);

// copy in binary code
memcpy(local.opCodes, FUNC_ADDR(remote_thread), CODESIZE);

//
// Allocate some space on process's stack and place
// our SELFDEL structure there. Then set the instruction pointer
// to this location and let the process resume
//
context.ContextFlags = CONTEXT_INTEGER|CONTEXT_CONTROL;
GetThreadContext(pi.hThread, &context);

// Allocate space on stack (aligned to cache-line boundary)
entrypoint = (context.Esp - sizeof(SELFDEL)) & ~0x1F;

//
// Place a pointer to the structure at the bottom-of-stack
// this pointer is located in such a way that it becomes
// the remote_thread's first argument!!
//
local.Arg0 = (SELFDEL *)entrypoint;

context.Esp = entrypoint - 4; // create dummy return address
context.Eip = entrypoint + 4; // offset of opCodes within structure

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

FlushInstructionCache(pi.hProcess, (PVOID)entrypoint, sizeof(local));

SetThreadContext(pi.hThread, &context);

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

return TRUE;
}

return FALSE;
}
Conclusion

I've used this article to describe all the methods I know of to delete an executable whilst it is running. You can take your pick of the techniques, but the last method must be by far the simplest and best solution I have come across
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值