这是我在研究程序自我删除(关于程序自我删除相关的技术我会在之后的文章中详细介绍)技术中碰到的问题, 暂时没有找到原因及解决办法. 暂且记录下来方便日后查阅、分析.
CreateRemoteThread经常在木马病毒中使用, 最常见的做法就是把LoadLibrary当作线程函数在目标进程中动态加载自己的Dll, 这种方法比通过HOOK加载Dll的优点在于不需要目标进程依赖于User32.dll. 但是在实际使用过程中确会遇到不少问题.
除了使用LoadLibrary作为线程函数我们可以使用自定义的线程函数, 当然前提是这个线程函数的二进制代码已经正确无误地插入到目标进程. 具体方法这里不便展开.
AdjustTokenPrivileges:
首先碰到的问题是关于进程权限问题, 确切的来说也不是问题, 只是在开发过程中遇到的比较奇怪的现象, 仅仅记录下来方便日后研究. 在此之前一直没有搞清楚提高进程权限是什么意思, 难道是提高系统级? 当然不可能, 微软不傻. 经过一些测试我有了一些发现. 代码如下:
BOOL EnableDebugPrivilege() { HANDLE hToken; if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken) != TRUE) { return FALSE; } LUID Luid; if(LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid) != TRUE) { 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) != TRUE) { return FALSE; } return TRUE; }
测试结果如下:
1. 使用该函数->枚举进程. 结果显示枚举到进程67个, 但是任务管理器显示当前进程73个. 未解.
2. 不使用该函数->OpenProcess打开系统进程失败.
这里有两个例外:
1. 在我电脑上(win7 x86)有一个进程名为SearchFilterHost.exe在任务管理器中显示为系统进程, 但是OpenProcess还是能成功.
2. 如果从vs启动, 结果同1.
在win7 x64上再次测试, 暂时没有发现SearchFilterHost这样的例外. 这里我说的系统进程暂时理解为任务管理器显示用户名为SYSTEM的进程. 不知是否正确.
枚举进程使用了CreateToolhelp32Snapshot/EnumProcesses两种方法, 情况相同.
CreateRemoteThread:
接下来遇到的问题是CreateRemoteThread时遇到的问题, 测试环境:win7 x86 + win7 x64. 首先我把CreateRemoteThread成功的例子整理一下(我们自己的进程名字暂时叫self.exe):
1. 平台:x86. self.exe:x86. 目标进程:非系统进程.
2. 平台:x64. self.exe:x86. 目标进程:32位非系统进程.
3. 平台:x64. self.exe:x64. 目标进程:64位非系统进程.
接下来是CreateRemoteThread调用失败的一些情况:
1. 平台:x86. self.exe:x86. 目标进程:系统进程.
GetLastError()==8. 描述: Not enough storage is available to process this command.
2. 平台:x64. self.exe:x86. 目标进程:64位进程. (包括用户进程和系统进程)
GetLastError()==5. 描述: Access Denied.
3. 平台:x64. self.exe:x86. 目标进程:32位系统进程.
GetLastError()==8. 描述: Not enough storage is available to process this command.
4. 平台:x64. self.exe:x64. 目标进程:64位系统进程.
GetLastError()==8. 描述: Not enough storage is available to process this command.
5. 平台:x64. self.exe:x64. 目标进程:32位系统进程.
GetLastError()==8. 描述: Not enough storage is available to process this command.
6. 平台:x64. self.exe:x64. 目标进程:32位非系统进程.
成功!但是直接导致目标进程崩溃.
为了跟使我们的结果更加清晰, 我们将它汇总到下面的表格中:
平台(x86/x64) | Self.exe(x86/x64) | 目标进程(x86/x64) | System Process | GetLastError | Descirption | Exception |
x86 | x86 | x86 | Y | 8 | Not enough... | winlogon.exe |
x86 | x86 | x86 | N | S_OK | ||
x64 | x86 | x86 | Y | 8 | Not enough... | |
x64 | x86 | x86 | N | S_OK | ||
x64 | x86 | x64 | Both | 5 | Access Denied | |
x64 | x64 | x64 | Y | 8 | Not enough... | winlogon.exe |
x64 | x64 | x64 | N | S_OK | ||
x64 | x64 | x86 | Y | 8 | Not enough... | |
x64 | x64 | x86 | N | S_OK | Crash!!! |
汇总结果如下:
1. 在x64平台上用x86进程注入x64进程. Access Denied.
2. 除1外, 任何情况只要是注入系统进程的. Not enough storage is available to process this command. 这里有个例外就是winlogon.exe
3. 剩下情况如果是注入进程与目标进程相容(都是x86或者都是x64), 注入成功. 这个是我们意料之中的.
4. 如果是x64进程注入x86进程. 注入成功但是目标进程崩溃. 这个想来也合理, 因为x64位编译出来的注入函数也是64位的, 必崩溃无疑. 理论上只要直接注入的函数/数据没问题就可以成功, 有兴趣的话可以用shellcode尝试一下, 也就是先用x86编译出来相关的数据和代码, 然后以二进制的方式提取出来, 再以二进制的方式写入远程线程. 这样就不必关心当前进程是x86的还是x64的. 这里还有一个问题就是为什么第一种情况下(x86进程注入x64进程(用户进程))会失败, 没有给我们任何使用shellcode的机会.
写到这儿, 我已经打算动手下一篇文章: 自删除问题的研究。 这是一个很有趣的话题, 其中也不乏一些堪称'天才'的杰作. 有兴趣的读者千万不要错过.