首页
社区
课程
招聘
[原创]删除杀软回调 bypass EDR 研究
发表于: 2023-10-20 12:20 14805

[原创]删除杀软回调 bypass EDR 研究

2023-10-20 12:20
14805

原文链接:删除杀软回调 bypass EDR 研究 (qq.com)


 通过删除杀软或EDR的“创建进程通知回调”、“创建线程通知回调”、“加载镜像通知回调”、“注册表通知回调”,极大的消弱杀软或EDR的动态查杀能力。


本文属于以下文章和项目的复现和升华。

文章:

对抗临近 | 红队大佬的私人秘籍:EDR绕过技术曝光!(https://mp.weixin.qq.com/s/jva2d8nLz6ti8fLTR0O-wQ)

https://br-sn.github.io/Removing-Kernel-Callbacks-Using-Signed-Drivers/

项目:

https://github.com/br-sn/CheekyBlinder

https://github.com/lawiet47/STFUEDR


01


杀软或EDR内核回调简介


Windows x64 系统中,由于 PatchGuard 的限制,杀软或EDR正常情况下,几乎不能通过 hook 的方式,完成其对恶意软件的监控和查杀。那怎么办呢?别急,微软为我们提供了其他的方法,完成此类功能,那就是系统回调机制。比如本文提到的“创建进程通知回调”、“创建线程通知回调”、“加载镜像通知回调”、“注册表通知回调”等等。

在恶意软件和杀软 攻与防的对抗中,二者经过激烈的较量,完成了螺旋式的上升变革,给我们的感觉是,杀软越来越强大了,我们的网络环境越来越安全了。


02

删除杀软回调项目简介


github 上有两个比较经典的项目,可以完成删除杀软回调的功能,项目如下所示:

https://github.com/br-sn/CheekyBlinder

https://github.com/lawiet47/STFUEDR

这些项目主要完成了三大功能:

利用合法驱动读取或修改内核数据;

寻找“创建进程通知回调”、“创建线程通知回调”、“加载镜像通知回调”、“注册表通知回调”内核数组地址;

将杀软或EDR驱动对应的回调数组中的某个元素,置 0 或删除;

这里需要注意的是,“创建进程通知”、“创建线程通知回调”、“加载镜像通知回调”是正常的数组,而“注册表通知回调”是一个双向循环链表。

下面简单介绍一下,上述四大回调数组内核地址的寻找方法和删除杀软回调的方法。

使用工具:windbg preview

系统环境:Windows 1809 x64


03

创建进程回调数组定位



04

创建线程回调数组定位

方法一:


方法二:


05

加载镜像回调数组定位

方法一:


方法二:


06

注册表通知回调数组定位


07

删除杀软回调的方法


删除上述四大系统回调的方法被分为两类,“创建进程通知”、“创建线程通知回调”、“加载镜像通知回调”被分为一类,为数组类;“注册表通知回调”被分为一类,为双向循环链表类,以下简称链表类。

数组类删除方法是:找到驱动对应数组中的元素,将该元素内核地址赋值为 0;


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 4
支持
分享
最新回复 (12)
雪    币: 3004
活跃值: (30861)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-10-20 16:17
1
雪    币: 3664
活跃值: (3065)
能力值: ( LV8,RANK:147 )
在线值:
发帖
回帖
粉丝
3
mark
2023-10-20 16:53
0
雪    币: 9687
活跃值: (2491)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
mark
2023-10-20 20:46
0
雪    币: 1396
活跃值: (1453)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2023-10-20 22:20
0
雪    币: 6061
活跃值: (12614)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
6
发文不易,先点赞。
上升到驱动级别对抗风险极大,部分Edr创建回调成功后,会保存了一份记录。利用上述同样的方式,启动线程检测是否被删除,不友好的可能直接上报蓝屏了。
基于r3程序策略变种来对抗edr回调也是不错的方式,比如基于Process Herpaderping,Process Reimaging新的变种研究。
2023-10-23 08:29
0
雪    币: 420
活跃值: (579)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
一半人生 发文不易,先点赞。 上升到驱动级别对抗风险极大,部分Edr创建回调成功后,会保存了一份记录。利用上述同样的方式,启动线程检测是否被删除,不友好的可能直接上报蓝屏了。 基于r3程序策略变种来对抗ed ...
感谢大佬指点,如果在EDR回调函数地址开始处直接 ret,或 直接在回调数组某回调元素结构中替换掉 EDR 原始回调函数地址,会不会能绕过 EDR 的这种 "记录" 保护呢?
2023-10-23 17:02
0
雪    币: 852
活跃值: (9821)
能力值: ( LV13,RANK:385 )
在线值:
发帖
回帖
粉丝
8
bigbang95 感谢大佬指点,如果在EDR回调函数地址开始处直接 ret,或 直接在回调数组某回调元素结构中替换掉 EDR 原始回调函数地址,会不会能绕过 EDR 的这种 "记录" 保护呢?
你能想到的EDR也能想到. 对抗无止境. 比如保存得 "记录" 可否就是回调函数地址头部得x个字节. 看看自己有没有被改过. 这块反正挺麻烦得.就看有没有保存这块所谓得"记录" 其实摘钩啥的很简单. 难得是绕过. 就是让你在我系统上运行.我不动你得情况下.如何让你检测不到. 这个很麻烦. 
2023-10-23 17:21
0
雪    币: 420
活跃值: (579)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
TkBinary 你能想到的EDR也能想到. 对抗无止境. 比如保存得 "记录" 可否就是回调函数地址头部得x个字节. 看看自己有没有被改过. 这块反正挺麻烦得.就看有没有保存这块所谓得" ...
是啊,哈哈,像”一半人生“大佬说的,得研究新变种
2023-10-24 10:10
0
雪    币: 405
活跃值: (2260)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
早都有保护了。
_int64 __fastcall RestoreSelf(char a1)
{
  unsigned __int64 v1; // rax
  unsigned __int64 v2; // rax
  __int64 result; // rax

  *(_QWORD *)(qword_30CE8 + 240) = ShutDownDispatch;
  if ( a1 )
    IoRegisterShutdownNotification(gDeviceObject);
  _disable();
  v1 = __readcr0();
  __writecr0(v1 & 0xFFFFFFFFFFFEFFFFui64);
  *(__m128i *)CreateProcessNotifyRoutine = _mm_loadu_si128(&CodeCreateProcessNotifyRoutine);
  *(__m128i *)LoadImageNotifyRoutine = _mm_loadu_si128(&CodeLoadImageNotifyRoutine);
  *(__m128i *)RegisterCallback = _mm_loadu_si128((const __m128i *)&CodeRegisterCallback);
  *(__m128i *)ShutDownDispatch = _mm_loadu_si128(CodeShutDownDispatch);
  v2 = __readcr0();
  result = v2 ^ 0x10000;
  __writecr0(result);
  _enable();
  return result;
}

char __fastcall DoSelfProtect(__int64 a1, char a2)
{
  void **v2; // rax
  __int64 v4; // rbp
  int v5; // eax
  void (__fastcall *v6)(PVOID *, __int64, __int64); // rcx
  char v7; // di
  _QWORD *v8; // rbx
  __int64 v9; // rsi
  unsigned int v10; // eax
  unsigned __int64 SystemRoutineAddress; // rbx
  unsigned __int64 v12; // rax
  _DWORD *v13; // r8
  __int64 v14; // r9
  unsigned int v15; // ecx
  __int64 v16; // rdx
  __int64 v17; // rax
  char v18; // di
  _QWORD *v19; // rbx
  __int64 v20; // rsi
  __int64 v21; // rdx
  __int64 v22; // rcx
  unsigned int v23; // esi
  char v24; // r12
  __int64 v25; // rdi
  _QWORD *v26; // rbx
  unsigned int v27; // edx
  __int64 v28; // r8
  unsigned __int64 v29; // rax
  unsigned __int64 v30; // rax
  unsigned int v31; // edx
  __int64 v32; // r8
  unsigned __int64 v33; // rax
  unsigned __int64 v34; // rax
  unsigned int v35; // edx
  __int64 v36; // r8
  unsigned __int64 v37; // rax
  unsigned __int64 v38; // rax
  __int64 v39; // rdx
  __int64 v40; // r8
  unsigned __int64 v41; // rax
  unsigned __int64 v42; // rax
  _QWORD *v43; // rbx
  unsigned int v44; // esi
  __int64 v45; // rdi
  void ***v46; // r12
  char v47; // bl
  __int64 v48; // rdi
  __int64 v49; // r10
  void **v50; // rsi
  char Dst[8]; // [rsp+30h] [rbp-58h] BYREF
  char v53[16]; // [rsp+38h] [rbp-50h] BYREF
  char v54[16]; // [rsp+48h] [rbp-40h] BYREF
  __int64 v55; // [rsp+58h] [rbp-30h]
  __int64 v56; // [rsp+60h] [rbp-28h]
  int v57; // [rsp+68h] [rbp-20h]
  void *retaddr; // [rsp+88h] [rbp+0h] BYREF
  __int64 v59; // [rsp+90h] [rbp+8h] BYREF

  v2 = &retaddr;
  v4 = a1;
  if ( byte_3541F != 1 )
  {
    LODWORD(v59) = 0;
    v5 = sub_1EB94(a1, &v59);
    v6 = LoadImageNotifyRoutine;
    if ( v5 >= 0 )
    {
      if ( !(_DWORD)v59 )
        goto LABEL_13;
      v7 = 0;
      if ( !(_DWORD)v59 )
        goto LABEL_13;
      v8 = (_QWORD *)(v4 + 8);
      v9 = (unsigned int)v59;
      do
      {
        if ( (void (__fastcall *)(PVOID *, __int64, __int64))*v8 == LoadImageNotifyRoutine )
        {
          v7 = 1;
        }
        else
        {
          if ( sub_1B6A0(*v8) )
          {
            PsRemoveLoadImageNotifyRoutine(*v8);
            sub_1B598(*v8);
          }
          v6 = LoadImageNotifyRoutine;
        }
        v8 += 3;
        --v9;
      }
      while ( v9 );
      if ( !v7 )
LABEL_13:
        PsSetLoadImageNotifyRoutine(LoadImageNotifyRoutine);
    }
    v10 = IsAppServer(v6);
    if ( v10 != 13 )
    {
      memmove(Dst, (const void *)(((unsigned __int64)v10 << 6) + 156960), 0x40ui64);
      SystemRoutineAddress = MmGetSystemRoutineAddress(v53);
      v12 = MmGetSystemRoutineAddress(v54);
      if ( SystemRoutineAddress >= v12 )
        LODWORD(v12) = SystemRoutineAddress + 512;
      v59 = 0i64;
      if ( (int)sub_1E998(SystemRoutineAddress, v12, v55, v56, (__int64)&v59) >= 0 )
      {
        v13 = (_DWORD *)v4;
        v14 = v57 + v59 + *(int *)(v57 + v59) + 4;
        v15 = 0;
        v16 = 0i64;
        while ( 1 )
        {
          v17 = *(_QWORD *)(v14 + 8 * v16);
          if ( !v17 )
            break;
          *v13 = 2;
          ++v16;
          v13 += 6;
          ++v15;
          *((_QWORD *)v13 - 2) = *(_QWORD *)(v17 & 0xFFFFFFFFFFFFFFF8ui64);
          if ( v16 >= 64 )
            goto LABEL_30;
        }
        if ( !v15 )
          goto LABEL_30;
        v18 = 0;
        v19 = (_QWORD *)(v4 + 8);
        v20 = v15;
        do
        {
          if ( (NTSTATUS (__fastcall *)(__int64, __int64, char))*v19 == CreateProcessNotifyRoutine )
          {
            v18 = 1;
          }
          else if ( sub_1B6A0(*v19) )
          {
            LOBYTE(v21) = 1;
            PsSetCreateProcessNotifyRoutine(*v19, v21);
            sub_1B598(*v19);
          }
          v19 += 3;
          --v20;
        }
        while ( v20 );
        if ( !v18 )
LABEL_30:
          PsSetCreateProcessNotifyRoutine(CreateProcessNotifyRoutine, 0i64);
      }
    }
    LODWORD(v59) = 0;
    if ( (int)sub_1EC9C(v4, &v59) >= 0 )
    {
      v23 = v59;
      if ( (_DWORD)v59 )
      {
        v24 = 0;
        v25 = 0i64;
        if ( !(_DWORD)v59 )
          goto LABEL_41;
        v26 = (_QWORD *)(v4 + 8);
        do
        {
          if ( (__int64 (__fastcall *)(__int64, int, _QWORD *))*v26 == RegisterCallback )
          {
            v24 = 1;
          }
          else if ( sub_1B6A0(*v26) )
          {
            CmUnRegisterCallback(*(_QWORD *)(v4 + 24 * v25 + 16));
            sub_1B598(*v26);
          }
          v25 = (unsigned int)(v25 + 1);
          v26 += 3;
        }
        while ( (unsigned int)v25 < v23 );
        if ( !v24 )
        {
LABEL_41:
          byte_35C49 = 1;
          RegRegisterCallback();
        }
      }
      else
      {
        RegRegisterCallback();
      }
    }
    v27 = 0;
    v28 = 0i64;
    do
    {
      if ( *((_BYTE *)RegisterCallback + v28) != byte_34868[v28 + 0x10000] )
        break;
      ++v27;
      ++v28;
    }
    while ( v27 < 0x10 );
    if ( v27 != 16 )
    {
      byte_35C49 = 1;
      _disable();
      v29 = __readcr0();
      __writecr0(v29 & 0xFFFFFFFFFFFEFFFFui64);
      *(__m128i *)RegisterCallback = _mm_loadu_si128((const __m128i *)&CodeRegisterCallback);
      v30 = __readcr0();
      __writecr0(v30 ^ 0x10000);
      _enable();
    }
    v31 = 0;
    v32 = 0i64;
    do
    {
      if ( *((_BYTE *)CreateProcessNotifyRoutine + v32) != byte_25C38[v32 + 0x10000] )
        break;
      ++v31;
      ++v32;
    }
    while ( v31 < 0x10 );
    if ( v31 != 16 )
    {
      byte_35C49 = 1;
      _disable();
      v33 = __readcr0();
      __writecr0(v33 & 0xFFFFFFFFFFFEFFFFui64);
      *(__m128i *)CreateProcessNotifyRoutine = _mm_loadu_si128(&CodeCreateProcessNotifyRoutine);
      v34 = __readcr0();
      __writecr0(v34 ^ 0x10000);
      _enable();
    }
    v35 = 0;
    v36 = 0i64;
    do
    {
      if ( *((_BYTE *)LoadImageNotifyRoutine + v36) != byte_34850[v36 + 0x10000] )
        break;
      ++v35;
      ++v36;
    }
    while ( v35 < 0x10 );
    if ( v35 != 16 )
    {
      byte_35C49 = 1;
      _disable();
      v37 = __readcr0();
      __writecr0(v37 & 0xFFFFFFFFFFFEFFFFui64);
      *(__m128i *)LoadImageNotifyRoutine = _mm_loadu_si128(&CodeLoadImageNotifyRoutine);
      v38 = __readcr0();
      __writecr0(v38 ^ 0x10000);
      _enable();
    }
    if ( *(__int64 (__fastcall **)(__int64, __int64))(qword_30CE8 + 240) != ShutDownDispatch )
    {
      *(_QWORD *)(qword_30CE8 + 240) = ShutDownDispatch;
      byte_35C49 = 1;
    }
    v39 = 0i64;
    v40 = 0i64;
    do
    {
      LOBYTE(v2) = byte_34880[v40 + 0x10000];
      if ( *((_BYTE *)ShutDownDispatch + v40) != (_BYTE)v2 )
        break;
      v39 = (unsigned int)(v39 + 1);
      ++v40;
    }
    while ( (unsigned int)v39 < 0x10 );
    if ( (_DWORD)v39 != 16 )
    {
      byte_35C49 = 1;
      _disable();
      v41 = __readcr0();
      __writecr0(v41 & 0xFFFFFFFFFFFEFFFFui64);
      *(__m128i *)ShutDownDispatch = _mm_loadu_si128(CodeShutDownDispatch);
      v42 = __readcr0();
      v2 = (void **)(v42 ^ 0x10000);
      __writecr0((unsigned __int64)v2);
      _enable();
    }
    if ( gDeviceObject )
    {
      v43 = (_QWORD *)qword_31CE0;
      v44 = 0;
      if ( !qword_31CE0 )
      {
        v2 = (void **)sub_17544(v22, v39, v40, 16i64);
        qword_31CE0 = (__int64)v2;
        if ( !v2 )
          return (char)v2;
        LOBYTE(v2) = MmIsAddressValid(v2);
        if ( !(_BYTE)v2 )
          return (char)v2;
        v43 = (_QWORD *)qword_31CE0;
      }
      if ( (_QWORD *)*v43 != v43 )
      {
        v45 = v43[1];
        if ( (_QWORD *)v45 != v43 )
        {
          v46 = (void ***)v4;
          do
          {
            LOBYTE(v2) = MmIsAddressValid(v45);
            if ( !(_BYTE)v2 )
              break;
            LOBYTE(v2) = MmIsAddressValid(*(_QWORD *)(v45 + 16));
            if ( !(_BYTE)v2 )
              break;
            LOBYTE(v2) = MmIsAddressValid(*(_QWORD *)(*(_QWORD *)(v45 + 16) + 8i64));
            if ( !(_BYTE)v2 )
              break;
            v2 = *(void ***)(v45 + 16);
            ++v44;
            *v46 = v2;
            v45 = *(_QWORD *)(v45 + 8);
            ++v46;
          }
          while ( (_QWORD *)v45 != v43 );
          if ( v44 )
          {
            v47 = 0;
            v48 = v44;
            do
            {
              if ( *(_QWORD *)v4 == gDeviceObject )
              {
                v47 = 1;
              }
              else if ( a2 )
              {
                v2 = (void **)sub_1B6A0(*(_QWORD *)(*(_QWORD *)(*(_QWORD *)v4 + 8i64) + 24i64));
                v50 = v2;
                if ( v2 )
                {
                  LOBYTE(v2) = IoUnregisterShutdownNotification(v49);
                  v50[24] = *(void **)(*(_QWORD *)(*(_QWORD *)v4 + 8i64) + 240i64);
                }
              }
              v4 += 8i64;
              --v48;
            }
            while ( v48 );
            if ( !v47 )
              LOBYTE(v2) = IoRegisterShutdownNotification(gDeviceObject);
          }
        }
      }
    }
  }
  return (char)v2;
}
2023-10-24 10:52
0
雪    币: 405
活跃值: (2260)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
保护调用时机也是五花八门,线程,DPC,每次磁盘访问,每次网络访问,各种调度切换==。反正你很难同时干掉。
2023-10-24 10:57
0
雪    币: 420
活跃值: (579)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
wowocock 保护调用时机也是五花八门,线程,DPC,每次磁盘访问,每次网络访问,各种调度切换==。反正你很难同时干掉。
嗯嗯,看来只有新技术、新变种,才行啊
2023-10-24 11:31
0
雪    币: 0
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
2023-10-25 11:07
0
游客
登录 | 注册 方可回帖
返回
//