自删除程序的研究及实现

这是一个非常有意思的话题, 一个EXE程序如何在运行结束以后从电脑中删除. 网上一搜就会找到各种各样的介绍. 看完这篇文章或许可以帮助你不必继续Google了, 因为我相信读完这篇文章你对这个话题已经有了足够的认识. 而且据我经验, 网上太多的拷贝/粘贴都没有实践过, 甚至有误导的嫌疑. 或者这篇文章对初学者有一定的难度, 但是我还是建议你多花点时间理解一下, 结合代码能让你有更深刻的体会. 而且有一些经验,教训可以避免你犯同样的错误. 更何况我也是一个初学者...如有不当之处敬请谅解...

 

 

声明一下, 本文中所有的方法都非原创, 我所作的只是把文字的东西变成代码, 同时我会对每一种方法的利弊进行分析.

本文一共阐述了7种方法, 其中5种能成功工作. 另外一种颇为经典的方法只能在NT下运行, 另外一种也是网上广为流传的方法未能成功. 原因不解. 

 

接下来正式开始我们的介绍. 如果你是第一次碰到这个话题, 下面的代码可能是最先出现在你脑中的:

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

当然, 这是不可行的. 要不然也没有讨论的必要. 因为这个时候的文件是被锁住的. 至于为什么这里不便深究. 有兴趣的读者可以研究一些文件映射对象及程序的加载过程. 当然这是一个更加复杂的话题. 其实接下来有一个方法会利用到这个知识, 所以还是会提到一点. 当然也只是一点而已, 因为我也就知道这么一点...

 

 

下面的链接是一篇自删除问题的介绍, 其中列举了一些方法. 你可以看也可以不看. 我放在这里的原因只是想告诉你, 有一些比较'搓'的办法我就不研究了. 我们需要完成的任务是在程序运行结束之后立刻删除, 注意是立刻. 而我所谓'搓'的办法是要在重启计算机后才会删除. 这显然不是我们满意的. MoveFileEx和WININIT.INI就是两种我提到的比较'搓'的办法. 如果你明白了我的意图, 那你可以直接跳过这个链接继续往下了.

http://www.catch22.net/tuts/selfdel

 

 

另外有必要提一下的是我的测试环境, 我使用的机器是win7 x86和win7 x64. 而在win7 64下又分两种情况, 程序以x86及x64方式编译/运行. 每一种环境下都可能导致不同的测试结果. 在具体介绍每一种方法的时候我还会单独提到这个问题.

  

1. Batch File

第一种我介绍的是batch file. batch file运行的时候是不会被lock的, 这就是意味着我们可以临时产生一个batch file, 由这个batch file负责EXE文件及自身的删除:

/* Author: Junwei Han Data: 11/29/2010 Description: Use batch file for self delete. */ #include #pragma comment(lib, "shlwapi.lib") ////////////////////////////////////////////////////////////////////// // use batch file for the delete like this: // :Repeat // del "C:/MYDIR/SelfDel.EXE" // if exist "C:/MYDIR/SelfDel.EXE" goto Repeat // del "C:/MYDIR/tmp.bat" ////////////////////////////////////////////////////////////////////// BOOL SelfDel_1() { TCHAR* szStrings[] = { ":Repeat/ndel /"", "/"/n/rif exist /"", "/" goto Repeat/n/rdel /"", "/"" }; TCHAR szFile[MAX_PATH], szBat[MAX_PATH]; GetModuleFileName(0, szFile, MAX_PATH); lstrcpy(szBat, szStrings[0]); lstrcat(szBat, szFile); lstrcat(szBat, szStrings[1]); lstrcat(szBat, szFile); lstrcat(szBat, szStrings[2]); // extract the path of the full name PathRemoveFileSpec(szFile); lstrcat(szFile, "//tmp.bat"); lstrcat(szBat, szFile); lstrcat(szBat, szStrings[3]); // create a temp batch file which used for the delete HANDLE hBatFile = CreateFile(szFile, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); DWORD dwNum; WriteFile(hBatFile, szBat, lstrlen(szBat) + 1, &dwNum, NULL); CloseHandle(hBatFile); if(((INT)ShellExecute(0, 0, szFile, 0, 0, SW_HIDE) > 32)) { // We don't have to end our process immediately since our batch // file will try to delete the exe file repeatly until success. Sleep(1000); return TRUE; } return FALSE; }

代码很简单, 不多解释. 代码中有个Sleep只是让你知道在你启动了BAT文件以后原程序不必立刻退出, 因为在BAT文件中我们会重复的进行删除直到成功为止. 但是总是不是那么的舒服, 毕竟有个BAT文件一直在那边等会让你觉得浪费CPU. 这个代码应该可以运行在所有支持BAT文件的windows系统下(本人只在win7 x86和x64下测试通过).

   

2. ComSpec

这是一种接近Batch File的方法, 也很简单:

/* Author: Junwei Han Data: 11/29/2010 Description: Use ComSpec for self delete. */ #include ////////////////////////////////////////////////////////////////////// // use ComSpec for the delete like this: // "C:/WINDOWS/system32/cmd.exe" /c del ".../SelfDel.exe >> NUL" ////////////////////////////////////////////////////////////////////// BOOL SelfDel_2() { 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)) // Must exit as soon as possible. return TRUE; } return FALSE; }

代码也再简单不过, 没有解释的必要. 跟BAT的区别只是这里我们需要程序立刻返回.

       

3. NT_ONLY

这个我都不知道用什么名字形容比较好, 因为它实在太经典了. 唯一的不足之处是只能在windows NT下运行, 所以我们暂且就称这种方法为: NT_ONLY. 还记得之前我们提过EXE不能删除是因为被锁住了. 这个锁住是因为这个EXE文件被当做一个文件映射, 而有一个函数叫做UnmapViewOfFile可以解除这个映射. 你是不是又在动脑筋了?于是一气呵成:

HMODULE hMod = GetModuleHandle(0); UnmapViewOfFile(hMod); TCHAR szFilePath[_MAX_PATH]; GetModuleFileName(NULL, szFilePath, _MAX_PATH); DeleteFile(szFilePath);

很可惜, 不但EXE没有删除, 程序还崩溃了(NT, 未验证). UnmapViewOfFile意味着取消文件映射, 具体来说, 是取消用户空间某一段内存到文件的映射, 或者说, 是把该用户空间的内存区域标识为没有映射, 直接导致的后果是把这个内存区域标识为无效. 而UnmapViewOfFile之后的代码正是在这个区域中的, 这下可好, 当运行到GetMouduleFileName的时候内存访问无效, 程序崩溃.

 

接下来让我们整理一下, 有没有办法可以避免这个问题. 在调用完UnmapViewOfFile之后, DeleteFile也是我们必须调用的. 但是这样的话存在两个问题: 1. DeleteFile的调用者不能是我们自己的代码. 2. 既然UnmapViewOfFile之后我们的代码已经无效了, 那程序如何返回?对于第二个问题, ExitProcess可以帮助我们退出程序. 但是ExitProcess又有谁去调呢?另外这些函数的参数我们怎么传递给它们?带着这些疑问, 我们来欣赏一下<>的作者Gary Nebbett的天才之作:

////////////////////////////////////////////////////////////////////// // Only work for windows NT and 2000, not 9x/xp/win7... // // Remove CloseHandle() and update UnmapViewOfFile to FreeLibrary will // make it working under windows 9x. ////////////////////////////////////////////////////////////////////// /*void SelfDel_NTOnly() { TCHAR szFile[MAX_PATH]; HMODULE hMod = GetModuleHandle(0); GetModuleFileName(hMod, szFile, MAX_PATH); CloseHandle((HANDLE)4); __asm { lea eax, szFile push 0 push 0 push eax push ExitProcess push hMod push DeleteFile push UnmapViewOfFile ret } }*/

在研究这段汇编代码之前我们注意到有个CloseHandle((HANDLE)4)的调用. 这是因为在NT/2000中保存了一个用户空间的对这个文件映射对象的引用的句柄, 这个句柄是个硬编码:4. 在UnmapViewOfFile之前我们需要先关闭这个句柄. 接下来的一段汇编是整个程序的核心, 在研究这个代码之前请确保你已经充分理解了retn这个返回指令的意义, 如果你对下面的问题没有搞明白, 那我保证你是不会理解这个代码的.

 

void B(int i) { } void A(...) { ... B(0) ... }

调用B(0)的前后堆栈发生了什么变化? 特别是在B函数返回retn 4这条指令做了什么?你可以这么理解这个指令: EIP=[esp], esp+=8. 当然这个是一气呵成的, 这么写只是便于你理解. 有了这个基础, 在结合下面的堆栈变化图, 我相信理解这个代码对你来说已经不在话下了:

 

 

4. Using a DLL

第三种方法虽然精妙但是却只能在NT/2000下工作, 在此基础上有人想出了更加完美的解决方案. 一个动态链接库总是以LoadLibrary, FreeLibrary的方式加载/卸载. 卸载?不就是从内存中撤掉一个DLL的映射么?那我们是不是把UnmapViewOfFile替换成FreeLibrary实现对一个DLL的自我删除呢?事实证明是完全可行的. 那么谁来加载这个DLL, 如果由我们的EXE加载肯定不行, 因为EXE的生命周期肯定长于DLL, EXE还是没办法删除. 微软提供了我们一个工具rundll32.exe允许我们以命令行的形式加载一个DLL并且调用DLL内部的函数, 当然这个函数的原型有一定的规范. 使用方法如下:rundll32.exe XXX.dll,_Func@16 AnyStringYouWantToPassIntoFunc

接下来让我们整理一下, 如何用DLL实现这个目标:

1. 首先我们肯定需要两个文件: EXE+DLL.

2. EXE负责启动rundll32进程, 同时以参数的形式将EXE的路径名传给rundll32, 确切的说是传给DLL中的函数.

3. EXE退出, rundll32开始运行. DLL中的函数被调用, 首先删除EXE文件, 再通过FreeLibrary的手法完成自己的删除.

 

一切都很完美, 但是我们可以做的更完美:

1. 我们只需要提供一个EXE文件, DLL以二进制资源的形式嵌入EXE资源段. EXE运行的时候动态生成这个DLL.

2. EXE必须在启动rundll32之后立刻退出么?答案是否定的, 我们可以在DLL中WaitForSingleObject等待EXE退出. 那么如何在DLL中获得EXE的进程句柄呢?方法当然有很多, 但是有个最直截了当的: 我们还是以参数的形式与EXE路径一起传到DLL的函数中.

 

有了以上的基础, 下面的代码就不难理解了. EXE:

/* Author: Junwei Han Data: 11/29/2010 Description: Use Dll for self delete. */ #include "resource.h" #include #include #include using namespace std; #include #pragma comment(lib, "shlwapi.lib") #pragma comment(lib, "../Debug/SelfDelDll.lib") // The Dll is embeded into the exe file as binary resource. We need to read the // binary resource and write it into a Dll file first. This is not necessary if // you would like to distribute both exe file and dll file. BOOL ExtractResourceToFile(HINSTANCE hInst, int dwResourceId, char const *szName) { HRSRC hResInfo = FindResource(hInst, MAKEINTRESOURCE(dwResourceId), MAKEINTRESOURCE(RC_BINARYTYPE)); HGLOBAL hgRes = LoadResource(hInst, hResInfo); void *pResData = LockResource(hgRes); DWORD dwSize = SizeofResource(hInst, hResInfo); HANDLE hFile = CreateFile(szName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); DWORD dwWritten; if(!WriteFile(hFile, pResData, dwSize, &dwWritten, 0)) { CloseHandle(hFile); return FALSE; } CloseHandle(hFile); return TRUE; } /////////////////////////////////////////////////////////////////////////////////////// // Thank you for the smart guy who found this wonderful solution. Following are some // basic information: // // 1. rundll32.exe is installed for all windows system. This can be used as follows: // rundll32.exe , // In our sample, it looks like this: // rundll32.exe ,<_SelfDel@16> // 2. A new rundll32 process will be created to run the code in the Dll. // 3. Our exe is supposed to exit after CreateProcess and will be delete in the Dll. // 4. The left work for the Dll is to delete itself. Since the dll is always loaded // using LoadLibrary by rundll32.exe, so we can use FreeLibrary to Free it from // memory, after that the dll can be deleted. However the code page for the dll // is invalid now so we can't execute any code from the library itself, it will // cause 'access violation'. Following code can be used to avoid that: // lea eax, szFileName // push 0 // push 0 // push eax // dll name // push ExitProcess // push g_hModDll // dll module // push DeleteFile // push FreeLibrary // ret // 5. The dll is embeded into our host exe as resource. So we don't need to post the // dll separately. A temp dll will be created from the rsrc when used. /////////////////////////////////////////////////////////////////////////////////////// BOOL SelfDel_3() { char szExeName[MAX_PATH], szDllName[MAX_PATH]; HMODULE hModule = GetModuleHandle(0); GetModuleFileName(hModule, szExeName, sizeof(szExeName)); // The Dll will be created in the same folder with the exe file. // This is not necessary if SelfDel.exe is run by double click. // However, if the we use visual studio or cmd to start SelfDel, // the temp Dll will be created in other places. Another choice // is the %tmp% path, however it's not convenience for test. lstrcpy(szDllName, szExeName); PathRemoveFileSpec(szDllName); lstrcat(szDllName, "//SelfDel.dll"); if(!ExtractResourceToFile(hModule, ID_SELFDEL_DLL, szDllName)) return FALSE; char szCmd[MAX_PATH * 2]; GetWindowsDirectory(szCmd, sizeof(szCmd)); lstrcat(szCmd, "//rundll32.exe"); if (GetFileAttributes(szCmd) == INVALID_FILE_ATTRIBUTES) { GetSystemDirectory(szCmd, sizeof(szCmd)); lstrcat(szCmd, "//rundll32.exe"); } DWORD dwProcessId = GetCurrentProcessId(); sprintf_s(szCmd, "%s %s,_SelfDel@16 %s+%d",szCmd, szDllName, szExeName, dwProcessId); PROCESS_INFORMATION procInfo; STARTUPINFO startInfo; memset(&startInfo, 0, sizeof(startInfo)); startInfo.dwFlags = STARTF_FORCEOFFFEEDBACK; BOOL bRet = CreateProcess(0, szCmd, 0, 0, FALSE, NORMAL_PRIORITY_CLASS, 0, 0, &startInfo, &procInfo); if(bRet) { system("Pause"); // You can do what ever you want... return TRUE; } else { return FALSE; } }

 

DLL:

/* Author: Junwei Han Data: 11/29/2010 Description: This Dll is for SelfDel_3. */ #include HMODULE g_hModDll; extern "C" BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) g_hModDll = hInstance; return TRUE; } extern "C" __declspec(dllexport) void CALLBACK SelfDel(HWND, HINSTANCE, LPTSTR lpCmdLine, int) { DWORD dwProcessId = 0; DWORD dwFlagIndex = lstrlen(lpCmdLine); while(lpCmdLine[dwFlagIndex] != '+') dwFlagIndex--; for(int i = dwFlagIndex + 1; i < lstrlen(lpCmdLine); i++) { dwProcessId = dwProcessId * 10 + (lpCmdLine[i] - (int)'0'); } lpCmdLine[dwFlagIndex] = '/0'; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); WaitForSingleObject(hProcess, INFINITE); CloseHandle(hProcess); DeleteFile(lpCmdLine); char szFileName[MAX_PATH]; GetModuleFileName(g_hModDll, szFileName, sizeof(szFileName)); __asm { lea eax, szFileName push 0 push 0 push eax push ExitProcess push g_hModDll push DeleteFile push FreeLibrary ret } }

代码虽然都已给出, 但是要运行起来还是不够的. 因为在我们会把DLL已资源的形式嵌入到EXE中, 所以我们还需要一个resource.h和一个rc文件来完成这个事情, 另外还需要设置一下项目依赖关系, 因为编译EXE的时候需要保证DLL以后生成. 这些工作就交给读者自己了.

读到这里, 你有没有一个疑问: 这个代码在x64下编译运行么?答案是100%否定的. 因为x64根本不支持嵌入式汇编. 那么你可能会继续问: 如果单独用一个asm文件呢?其实我也有这个问题, 甚至还打算动手写一个x64版本的DLL. 刚动手不久, 猛然发现这个技术在x64下面是不可行的, 原因在于x64下函数的前四个参数是用寄存器(rcx,rdx,r8,r9)传递的, 并非堆栈. 如果你还不能理解这一点, 那说明你对之前的那段汇编代码理解不够.

 

 

5. CreateRemoteThread

这也是一个惯用的手法, 编写代码期间也遇到了不少问题. 之前我有一篇文章就是比较详细得记录了用这个方法时遇到的一些问题或现象. 现在你只需关心下面的内容, 在适当的时候我会提醒你可以翻翻那一篇文章. 用CreateRemoteThread的基本想法也很简单: 找到一个比较common的进程, 然后在其中创建一个远程线程, 这个线程就是用来删除我们的EXE的. 当然, 如果不考虑通用性的话, 你可以简简单单的打开QQ, 然后再任务管理器找到进程ID, 注入完事. 估计代码不超过100行. 但是由于我当初的想法就是写一个'比较'通用的程序, 而不是说不打开QQ就不能运行. 那么下面的问题接踵而至:

1. 如何选择进程保证能在不同机器上运行. 当然如果lsass/csrss这样的系统进程能注入的话最好不过, 因为基本上现在的windows平台都会有这些系统进程. 很可惜, 这样进程是不允许你注入的. 这个时候你不妨看看我之前的一篇文章, 其中列举了win 7下CreateRemoteThread所有的情况.

2. 既然系统进程不能使用, 那么我们只能用非系统进程, 但是每个用户的非系统进程都不一样, 怎么样?遍历吧. 问题又来了, 遍历下来以后如何删选, 可能是系统进程可能是用户进程, 在win7x64还可能是x64也可能是x86的. 什么样的进程符合我们的需求?也许你想说, 找到一个进程就注入, 总有一个会成功. 但是事实证明这是一个馊主意, 特别是用x64注入x86进程的时候会导致目标进程崩溃. 所以我们还是老老实实挑选一个我们认为OK的吧. 这个时候如果你还没有读过我之前一篇文章<<远程线程注入时遇到的问题>>, 我还是建议你回去读一下. 通过测试结果, 你可以很清楚地看出什么样的目标是我们需要的: 用户进程/相同位宽(都是x86或者x64).

 

为了实现这个目标, 有不少的工作需要我们完成, 代码量也迅速膨胀:

/* Author: Junwei Han Data: 11/29/2010 Description: Use CreateRemoteThread technical for self delete. NT platform only. Please make sure following setting are as expected before compliering: 1. Properties->Configuration Properties->C/C++->Code Generation->Basic Runtime Checks(Default) 2. Properties->Configuration Properties->C/C++->Code Generation->Buffer Security Check(No) 3. Properties->Configuration Properties->Linker->General->Enable Incremental Linking(No) More details about these settings, please refer to following link: http://blog.csdn.net/panda1987/archive/2010/10/19/5952262.aspx */ #include #include #include #include #include // For GetModuleFileNameEx #include #include "ntdll.h" using namespace std; #pragma comment(lib, "Psapi.lib") // Functions will be used for the remote thread. typedef BOOL (__stdcall* PFN_DeleteFile)(LPCTSTR); typedef DWORD (__stdcall* PFN_WaitForSingleObject)(HANDLE, DWORD); typedef HANDLE (__stdcall* PFN_OpenProcess)(DWORD, BOOL, DWORD); typedef BOOL (__stdcall* PFN_CloseHandle)(HANDLE); // Data will be used for the remote thread typedef struct tagRmtData { TCHAR m_szData[MAX_PATH]; // path of file to be deleted. HANDLE m_processHandle; void* m_pfnDeleteFile; void* m_pfnWaitForSingleObject; void* m_pfnOpenProcess; void* m_pfnCloseHandle; } RMTDATA, *PRMTDATA; // Function for the remote thread. void __stdcall RmtFunc(PRMTDATA pData) { PFN_WaitForSingleObject pfnWaitForSingleObject = (PFN_WaitForSingleObject)pData->m_pfnWaitForSingleObject; pfnWaitForSingleObject(pData->m_processHandle, INFINITE); PFN_CloseHandle pfnCloseHandle = (PFN_CloseHandle)pData->m_pfnCloseHandle; pfnCloseHandle(pData->m_processHandle); PFN_DeleteFile pfnDeleteFile = (PFN_DeleteFile)pData->m_pfnDeleteFile; pfnDeleteFile(pData->m_szData); } // Endmark is used to get size of RpcFunc. Incremental Linking must be disabled void __stdcall Endmark() {} BOOL FillRPCData(PRMTDATA pData) { TCHAR szFile[MAX_PATH]; GetModuleFileName(0, szFile, MAX_PATH); lstrcpy(pData->m_szData, szFile); HMODULE hInst = ::LoadLibrary("kernel32.dll"); if(hInst != NULL) { pData->m_pfnDeleteFile = GetProcAddress(hInst, "DeleteFileA"); pData->m_pfnWaitForSingleObject = GetProcAddress(hInst, "WaitForSingleObject"); pData->m_pfnOpenProcess = GetProcAddress(hInst, "OpenProcess"); pData->m_pfnCloseHandle = GetProcAddress(hInst, "CloseHandle"); FreeLibrary(hInst); } if(!pData->m_pfnDeleteFile || !pData->m_pfnWaitForSingleObject || !pData->m_pfnOpenProcess || !pData->m_pfnCloseHandle) return FALSE; else return TRUE; } // Get the user name for the process. Following are some comment user names // which can be found from taskmgr:SYSTEM | LOCAL SERVICE | NETWORK SERVICE BOOL ProcessUserName(DWORD dwProcessID, TCHAR* szUserName) { BOOL bSuccess = FALSE; HANDLE hProcess = NULL; HANDLE hToken = NULL; TOKEN_USER *pTokenUser = NULL; __try { hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessID); if(NULL == hProcess) __leave; if(!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { __leave; } DWORD dwNeedLen = 0; GetTokenInformation(hToken, TokenUser, NULL, 0L, &dwNeedLen); if(dwNeedLen == 0) { __leave; } pTokenUser = (TOKEN_USER*)new BYTE[dwNeedLen]; if(!GetTokenInformation(hToken, TokenUser, pTokenUser, dwNeedLen, &dwNeedLen)) { __leave; } SID_NAME_USE sn; DWORD dwDmLen = MAX_PATH; DWORD dwNameLen = MAX_PATH; TCHAR szDomainName[MAX_PATH]; if(!LookupAccountSid(NULL, pTokenUser->User.Sid, szUserName, &dwNameLen, szDomainName, &dwDmLen, &sn)) { __leave; } bSuccess = TRUE; } __finally { if(hProcess != NULL) CloseHandle(hProcess); if(hToken != NULL) CloseHandle(hToken); if(pTokenUser) delete [](BYTE*)pTokenUser; } return bSuccess; } // Actually it's not necessary to raise the privilege of the current process here. // If we don't do this, OpenProcess will fail when the target process is a system // process.Since CreateRemoteThread can't work as expected for system process, so // we only use none-system process as our target process. Therefore this function // can be removed If you want. BOOL EnableDebugPrivilege() { HANDLE hToken; if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { return FALSE; } LUID Luid; if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid)) { return FALSE; } TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; tp.Privileges[0].Luid = Luid; if(!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { return FALSE; } return TRUE; } // Convert the last error code to description string. LPTSTR ConvertErrorCodeToString(DWORD dwErrorCode) { HLOCAL hLocalAddress = NULL; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErrorCode, 0, (LPTSTR)&hLocalAddress, 0, NULL); return (LPTSTR)hLocalAddress; } BOOL NewRemoteThreadForDelete(DWORD dwProcessID, TCHAR* szProcessName) { BOOL bRmtSuccess = FALSE; HANDLE hRmtProcess = NULL; LPVOID pRmtFuncAddr = NULL; LPVOID pRmtDataAddr = NULL; HANDLE hRmtThread = NULL; RMTDATA stRmtData = {0}; DWORD dwRmtFunSize = (DWORD)Endmark - (DWORD)RmtFunc; __try { if(!FillRPCData(&stRmtData)) __leave; hRmtProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); if(!hRmtProcess) __leave; if(!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), hRmtProcess, &stRmtData.m_processHandle, PROCESS_ALL_ACCESS, FALSE, 0)) { return FALSE; } LPVOID pRmtFuncAddr = VirtualAllocEx(hRmtProcess, NULL, dwRmtFunSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(!pRmtFuncAddr) __leave; if(!WriteProcessMemory(hRmtProcess, pRmtFuncAddr, RmtFunc, dwRmtFunSize, NULL)) __leave; LPVOID pRmtDataAddr = VirtualAllocEx(hRmtProcess, NULL, sizeof(RMTDATA), MEM_COMMIT, PAGE_READWRITE); if(!pRmtDataAddr) __leave; if(!WriteProcessMemory(hRmtProcess, pRmtDataAddr, &stRmtData, sizeof(RMTDATA), 0)) __leave; hRmtThread = CreateRemoteThread(hRmtProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRmtFuncAddr, pRmtDataAddr, 0, 0); if(!hRmtThread) { // We should never get here, print out some error message if this happens. // The exe will still be able to deleted if a target process can be found. LPTSTR lpStr = ConvertErrorCodeToString(GetLastError()); cout<

还是老样子, 代码我不打算过多的解释. 通过之前的介绍加上代码注释我相信不存在什么困难了. 重点还是介绍一下期间遇到的问题已经应该注意的地方:

1. 首先需要提醒的地方是有些代码可能不是必须的, 在这个例子中EnableDebugPrivilege和GetModuleFileNameEx就是如此. 有些函数是我在不断尝试过程中加入的, 为了方便以后查阅而没有删除(当然是无害的), 一般我会加上注释说明.

2. 为了使CreateRemoteThread这个方法正常工作, 光有代码是不顶用的. 还需要一些配置, 具体做法在代码上方已经给出. 至于为什么要这样设置是另外一个话题, 请参考我之前的另外一篇文章<<运行时和编译时的安全性检查>>:

http://blog.csdn.net/panda1987/archive/2010/10/19/5952262.aspx

3. ntdll.h这个头文件不是公开的, 需要自己网上下载.  很多数据结构比如PROCESS_BASIC_INFORMATION, PEB_LDR_DATA, PEB, LDR_MODULE都是在这个头文件中定义的. 碰到了一个不解的问题是有一个winternl.h的头文件也有这些数据结构的定义, 但是有些定义(比如PEB_LDR_DATA)却与ntdll.h不同, 这个头文件是不能用的. 困惑中...

4. 有一部分API会在这样的环境中失败: OS: win7x64. Self.exe:x86. Target.exe:x64. 比如GetModuleFileNameEx就是一个例子. 简单分析一下原因: 在使用GetModuleFileNameEx的时候系统会访问进程的PEB, PEB的指针存放在一个叫PROCESS_BASIC_INFORMATION的结构体中. 那么当我们用x86的进程读取一个x64进程的PEB时会遇到什么问题? 按照正常的思维, x86进程无法装下64位指针会发生截断. 但是我还不清楚WOW64下windows做了什么处理, 更深入的说WOW64是如何实现的. 这个希望以后有足够的知识去研究. 这里只是提出有这么一个问题, 确实有部分API是在WOW64下不能正常工作的.

5. 在判断程序是否是32位还是64的问题上遇到了一点问题, 我能想到的唯一的办法就是找到可执行映像的基址, 然后逐步找到IMAGE_OPTIONAL_HEADER中Magic这个字段. 理解这个代码需要一定的PE基础, 网上有很多相关资料, 这里就不多解释了. 我遇到的问题是这样的, 在x64+x64+x64(OS:x64/本进程:x64/目标进程:x64)环境下IMAGE_DOS_HEADER都能正常得到, 但是大部分目标进程的IMAGE_NT_HEADERS却不能获得. ReadProcessMemory在读取x64进程的IMAGE_NT_HEADERS的时候出现三种情况:

1) 如果目标进程的基址<0x0000 0001 0000 0000. ReadProcessMemory失败. GetLastError()==0x000003E6. 错误信息: Invalid access to memory location.

2) 如果目标进程的基址>0x0000 0001 0000 0000. ReadProcessMemory失败. GetLastError()==0x0000012B. 错误信息: Only part of a Read ProcessMemory or WriteProcessMemory request was completed.

3) 如果目标进程的基址==0x0000 0000 0040 0000. ReadProcessMemory成功. 可惜的是这样的64位程序很少. cmd就是一个例子.

至于为什么, 我也很不解. 如果有知晓的朋友务必指教. 感激不尽!

 

6. CreateProcess

这个方法跟之前的远程线程相当类似. 比起CreateRemoteThread有一个优点就是进程的选择更加灵活, 我们可以选择explorer这样的进程作为我们的目标进程, 不需要遍历等等操作. 基本的想法是这样的:

1. 用CreateProcess创建一个进程, 比如explorer. 创建的时候挂起.

2. 找到该进程的基址, 注入代码.

3. 恢复进程的执行.

 

我们可以通过与CreateRemoteThread的比较更具体的了解这个方法, 我们知道CreateRemoteThread提供两个参数, 一个是远程函数地址, 一个是远程数据地址. 这样我们可以通过两次VirtualAllocEx得到的指针分别作为这两个参数. 这样远程函数就可以方便的访问这些数据. 可是CreateProcess就不同了, 我们是把映像的基址所在的代码替换了, 而基址通常是由CRT调用的. 这样的话数据怎么办? 最简单的例子就是EXE的路径, 放在哪里? 如何在目标代码中正确的访问? 当然方法有很多, 我们这里提供一个比较简单的:

1. 代码还是正好放在基址位置. 要不然我们还要计算、更新基址.

2. 数据紧跟着代码放. 这里的数据无非是一个RMTDATA结构.

那么问题又来了, 在代码中如何快速的定位到这个数据结构的起点? 如果我们能找到映像的基址当然一切不成问题, 只要基址+函数长度就是数据的起点. 虽然我们在代码中已经得到了映像的基址, 但是你别忘了这是在将要被删除的EXE内完成的. 如今代码已经注入到explorer, 如果要获得映像的基址, 那还是要大动干戈重新来一边. 我们有更快捷的方法, 首先请读者试想一下, 如何在程序中获得当前EIP的值, 也就是当前运行代码的地址. 我提供两种办法:

/////////// 方法1 ///////////// CALL @f @@: pop ebx /////////// 方法2 ///////////// DWORD __stdcall current_address(DWORD dwDummy) { return *((DWORD*)((DWORD)&dwDummy - 4)); } void XXX() { ... DWORD dwDummy = 0; DWORD dwCurrentAddress = current_address(dwDummy); ... }

如果你对这个代码还不理解, 建议你补补汇编知识. 这里获得的是当前指令的地址, 显然不是基址. 但是我们可以肯定的是这个地址肯定大于基址, 而且相距不远. 这样的话, 我们可以利用一些标志字节来搜索数据结构的起始地址.

 

接下来的代码对你来说肯定小菜一碟了:)

/* Author: Junwei Han Data: 12/6/2010 Description: Use New Process for self delete. */ #include #include #include "ntdll.h" DWORD __stdcall current_address(DWORD dwDummy); DWORD __stdcall NewEntryPoint_End(); BOOL GetPEBFromHandle(HANDLE hProcess, PEB* ppeb); // defined in SelfDel_5.cpp typedef BOOL (__stdcall* PFN_DeleteFile)(LPCTSTR); typedef DWORD (__stdcall* PFN_WaitForSingleObject)(HANDLE, DWORD); typedef BOOL (__stdcall* PFN_CloseHandle)(HANDLE); typedef VOID (__stdcall* PFN_ExitProcess)(UINT); typedef struct tagRmtData { DWORD m_dwDataLable; // we use this lable to find the beginning of the data TCHAR m_szData[MAX_PATH]; HANDLE m_processHandle; void* m_pfnDeleteFile; void* m_pfnWaitForSingleObject; void* m_pfnExitProcess; void* m_pfnCloseHandle; } RMTDATA, *PRMTDATA; void __stdcall NewEntryPoint() { DWORD dwDummy = 0; DWORD dwCurrentAddress = current_address(dwDummy); DWORD dwNewEntrySize = (DWORD)NewEntryPoint_End - (DWORD)NewEntryPoint; DWORD dwStartSearchAddr = dwCurrentAddress + dwNewEntrySize; // Find the data next to the code. If we know the entry point here, the start // address of the data is easy to know.Of course we can find a way to get the // entry point, however, there is a easier way to do this. while(true) { if(*(DWORD*)dwStartSearchAddr == 0xCCCCCCCC) break; else dwStartSearchAddr--; } PRMTDATA pData = (PRMTDATA)dwStartSearchAddr; PFN_WaitForSingleObject pfnWaitForSingleObject = (PFN_WaitForSingleObject)pData->m_pfnWaitForSingleObject; pfnWaitForSingleObject(pData->m_processHandle, INFINITE); PFN_CloseHandle pfnCloseHandle = (PFN_CloseHandle)pData->m_pfnCloseHandle; pfnCloseHandle(pData->m_processHandle); PFN_DeleteFile pfnDeleteFile = (PFN_DeleteFile)pData->m_pfnDeleteFile; pfnDeleteFile(pData->m_szData); PFN_ExitProcess pfnExitProcess = (PFN_ExitProcess)pData->m_pfnExitProcess; pfnExitProcess(0); } DWORD __stdcall current_address(DWORD dwDummy) { return *((DWORD*)((DWORD)&dwDummy - 4)); } DWORD __stdcall NewEntryPoint_End() { return 0; } // Get the entry point address for the process. DWORD GetProcessEntryPointAddress(HANDLE hProcess/*, HANDLE hThread*/) { SIZE_T read = 0; PEB peb; if(!GetPEBFromHandle(hProcess, &peb)) return 0; // // Another way to get PEB. x86 only. // /*CONTEXT context; context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext(hThread, &context); LDT_ENTRY ldt_entry; GetThreadSelectorEntry(hThread, context.SegFs, &ldt_entry); DWORD dwFSBase = (ldt_entry.HighWord.Bits.BaseHi << 24) | (ldt_entry.HighWord.Bits.BaseMid << 16) | (ldt_entry.BaseLow); TEB teb; ReadProcessMemory(hProcess, (LPCVOID)dwFSBase, &teb, sizeof(TEB), &read); ReadProcessMemory(hProcess, (LPCVOID)teb.ProcessEnvironmentBlock, &peb, sizeof(PEB), &read);*/ IMAGE_DOS_HEADER dos_header; if(!ReadProcessMemory(hProcess, peb.ImageBaseAddress, &dos_header, sizeof(IMAGE_DOS_HEADER), &read)) return 0; if(0x5a4d != dos_header.e_magic) // 'MZ' return 0; IMAGE_OPTIONAL_HEADER opt; DWORD dwOptHeaderAddr = ((DWORD)peb.ImageBaseAddress + dos_header.e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER)); if(!ReadProcessMemory(hProcess, (PVOID)dwOptHeaderAddr, &opt, sizeof(IMAGE_OPTIONAL_HEADER), &read)) return 0; return (DWORD)peb.ImageBaseAddress + opt.AddressOfEntryPoint; } BOOL UpdateProcessImage(PRMTDATA pData, HANDLE hProcess) { pData->m_dwDataLable = 0xCCCCCCCC; pData->m_pfnWaitForSingleObject = (FARPROC)WaitForSingleObject; pData->m_pfnCloseHandle = (FARPROC)CloseHandle; pData->m_pfnDeleteFile = (FARPROC)DeleteFile; pData->m_pfnExitProcess = (FARPROC)ExitProcess; GetModuleFileName(0, pData->m_szData, MAX_PATH); if(!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), hProcess, &pData->m_processHandle, PROCESS_ALL_ACCESS, FALSE, 0)) { return FALSE; } DWORD dwOldProtect = 0; DWORD dwEntryAddress = GetProcessEntryPointAddress(hProcess/*, pi.hThread*/); DWORD dwNewEntrySize = (DWORD)NewEntryPoint_End - (DWORD)NewEntryPoint; DWORD dwDataAddress = dwEntryAddress + dwNewEntrySize; DWORD dwTotalSize = dwNewEntrySize + sizeof(RMTDATA); if(!VirtualProtectEx(hProcess, (PVOID)dwEntryAddress, dwTotalSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { return FALSE; } if(!WriteProcessMemory(hProcess, (PVOID)dwEntryAddress, NewEntryPoint, dwNewEntrySize, 0)) { return FALSE; } if(!WriteProcessMemory(hProcess, (PVOID)dwDataAddress, pData, sizeof(RMTDATA), 0)) { return FALSE; } return TRUE; } BOOL SelfDel_4() { BOOL bSuccess = FALSE; STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; TCHAR szExe[MAX_PATH] = _T("explorer.exe"); if(CreateProcess(0, szExe, 0, 0, 0, CREATE_SUSPENDED | IDLE_PRIORITY_CLASS, 0, 0, &si, &pi)) { RMTDATA stRmtData; if(UpdateProcessImage(&stRmtData, pi.hProcess)) { ResumeThread(pi.hThread); CloseHandle(pi.hThread); system("Pause"); bSuccess = TRUE; } CloseHandle(pi.hProcess); } return bSuccess; }

这个程序可以以x86的形式在x86和x64下运行, 但是却不能以x64的形式运行. 因为explorer是32位的, 而注入函数却是以x64编译的, 不出问题才怪. 解决的方法应该不难, 可以用shellcode实现, 直接将x86下编译的二进制代码写入目标进程. 这里我就不试了, 有兴趣的读者不妨自己一试.

 

这次为止, 已经介绍了6种自删除程序的实现方法, 除第三种只能在NT/2000下运行以外, 其他程序都能在win7x86,win7x64下面运行. 第四种方法需要读者自己添加一个resource.h和rc文件, 第五第六种方法需要读者自己下载一个ntdll.h文件. 另外要对vs进行一定的设置. 在结束之前, 还有一种方法也是网上广为流传的, 有兴趣的读者不妨搜索一下'DeleteMe.cpp'. 可惜的是99%都是copy/paste. 我按照同样的方法重新写了一份代码, 经测试这个方法不能正常工作.

 

 

7. FILE_FLAG_DELETE_ON_CLOSE(Not Working)

////////////////////////////////////////////////////////////////////////////////////////////// // This is writen by Jeffrey Richter, we can find lot of descriptions for this technique from // internet when talking about self delete. However, they are all 'cheater'... I feel sad for // this, most of them just copy and paste. Actually it can't work as expected. I tested on xp // and win7, both failed. The original exe can be deleted while the cloned one can't. I tried // to find the reason why this is happening, however there are so few topics I can found from // google discussing about this. One possible reason is that: // The file deletion does only work if the last file handle to be closed // is the one that was opened with the "FILE_FLAG_DELETE_ON_CLOSE" flag. // In this sample, the last handle opened for the file is by CreateProcess, we have no chance // to add a FILE_FLAG_DELETE_ON_CLOSE flag, so the file can't be deleted when closed. However // that was merely a guess of mine. If so, Jeffrey Richter had played a big joke with us... ////////////////////////////////////////////////////////////////////////////////////////////// /*void SelfDel_Jeffrey() { if (__argc == 1) { TCHAR szPathOrig[MAX_PATH], szPathClone[MAX_PATH]; GetModuleFileName(NULL, szPathOrig, MAX_PATH); lstrcpy(szPathClone, szPathOrig); PathRemoveFileSpec(szPathClone); lstrcat(szPathClone, "//SelfDel.tmp"); CopyFile(szPathOrig, szPathClone, FALSE); HANDLE hFile = CreateFile(szPathClone, 0, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); TCHAR szCmdLine[MAX_PATH * 2]; HANDLE hProcessOrig = OpenProcess(SYNCHRONIZE, TRUE, GetCurrentProcessId()); wsprintf(szCmdLine, __TEXT("%s %d /"%s/""), szPathClone, hProcessOrig, szPathOrig); STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); PROCESS_INFORMATION pi; CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); CloseHandle(hProcessOrig); CloseHandle(hFile); } else { HANDLE hProcessOrig = (HANDLE)_ttoi(__targv[1]); WaitForSingleObject(hProcessOrig, INFINITE); CloseHandle(hProcessOrig); DeleteFile(__targv[2]); } }*/

我不能确定这个代码为什么广为流传, 或许它能再win9x上工作. 只可惜过时了...

 

 

THE END...Thank you for reading:)

你可能感兴趣的:(exe,path,null,dll,image,header)