PE 自身数据写入

有时候需要我们程序向自身写入一些数据,以便于下次运行可以直接读取。当不使用配置文件(外部存储)时,应该怎么做?原理很简单:向 PE 新增一个节,然后向这个特殊的节写入数据。这也是很多奇奇怪怪的程序会用的玩意。花了点时间写了一个例子。代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>

#define DATA_SIZE 0x600  // 新节的虚拟大小
#define MARKER "MYDATA"  // 标记
#define MARKER_SIZE 6  // 标记的长度

// 元数据头结构
struct MetadataHeader {
    char marker[MARKER_SIZE];  // 标记
    DWORD dataSize;  // 数据长度
};

// 获取当前 PE 文件路径
std::string GetPEFilePath() {
    char filePath[MAX_PATH] = { 0 };
    GetModuleFileNameA(NULL, filePath, MAX_PATH);
    return std::string(filePath);
}

// 复制当前文件到副本
std::string CopyCurrentFile() {
    std::string srcFilePath = GetPEFilePath();
    std::string destFilePath = srcFilePath + ".copy.exe";

    if (CopyFileA(srcFilePath.c_str(), destFilePath.c_str(), FALSE)) {
        std::cout << "成功复制文件到: " << destFilePath << std::endl;
        return destFilePath;
    }
    else {
        std::cerr << "文件复制失败: " << GetLastError() << std::endl;
        return "";
    }
}

// 创建批处理文件以替换原文件
void CreateBatchFileToReplaceSelf(const std::string& originalFile, const std::string& copiedFile) {
    std::string batchFilePath = originalFile + "_replace.bat";

    // 批处理文件内容
    std::string batchContent =
        "@echo off\n"
        ":loop\n"
        "tasklist | find \"" + originalFile.substr(originalFile.find_last_of("\\") + 1) + "\" >nul\n"
        "if not errorlevel 1 (\n"
        "    timeout /t 1 >nul\n"
        "    goto loop\n"
        ")\n"
        "copy /y \"" + copiedFile + "\" \"" + originalFile + "\"\n"
        "del \"" + copiedFile + "\"\n"
        "del \"%~f0\"\n";  // 删除自身

    // 创建批处理文件
    FILE* batchFile = fopen(batchFilePath.c_str(), "w");
    if (batchFile) {
        fwrite(batchContent.c_str(), 1, batchContent.size(), batchFile);
        fclose(batchFile);

        // 使用 CreateProcess 以隐藏模式启动批处理文件
        STARTUPINFOA si = { 0 };
        PROCESS_INFORMATION pi = { 0 };
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_HIDE;  // 隐藏窗口

        // 启动批处理文件
        if (CreateProcessA(NULL, (LPSTR)batchFilePath.c_str(), NULL, NULL, FALSE,
            CREATE_NO_WINDOW | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
            std::cout << "已创建并启动隐藏的批处理文件: " << batchFilePath << std::endl;
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }
        else {
            std::cerr << "无法启动批处理文件: " << GetLastError() << std::endl;
        }
    }
    else {
        std::cerr << "无法创建批处理文件。" << std::endl;
    }
}

// 随机生成节名(最多8个字符)
void GenerateRandomSectionName(char* sectionName) {
    const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for (int i = 0; i < 7; ++i) {
        sectionName[i] = charset[rand() % (sizeof(charset) - 1)];
    }
    sectionName[7] = '\0';  // 确保字符串以 '\0' 结尾
}

// 检查节中是否存在特定标记
bool CheckMarker(char* buffer, DWORD offset) {
    MetadataHeader* header = reinterpret_cast<MetadataHeader*>(buffer + offset);
    return memcmp(header->marker, MARKER, MARKER_SIZE) == 0;
}

// 查找包含特定标记的节
PIMAGE_SECTION_HEADER FindSectionWithMarker(PIMAGE_NT_HEADERS nt, char* fileBuffer) {
    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);
    for (int i = 0; i < nt->FileHeader.NumberOfSections; i++, section++) {
        // 检查该节的起始位置是否有标记
        if (CheckMarker(fileBuffer, section->PointerToRawData)) {
            return section;
        }
    }
    return NULL;
}

// 读取数据并打印
void ReadDataFromSection(char* buffer, DWORD offset) {
    MetadataHeader* header = reinterpret_cast<MetadataHeader*>(buffer + offset);
    char* data = buffer + offset + sizeof(MetadataHeader);
    std::cout << "读取的数据长度: " << header->dataSize << std::endl;
    std::cout << "数据: " << std::string(data, header->dataSize) << std::endl;
}

// 添加新节到 PE 文件
BOOL AddSection(char* FileBuffer, LPCSTR NewFileName, const char* newData, DWORD dataSize) {
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)FileBuffer;
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(FileBuffer + dos->e_lfanew);
    PIMAGE_FILE_HEADER file_header = &(nt->FileHeader);
    PIMAGE_OPTIONAL_HEADER option_header = &(nt->OptionalHeader);
    PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(nt);

    // 计算节表的偏移量
    DWORD dwNumSections = file_header->NumberOfSections;
    DWORD dwSectionTableSize = sizeof(IMAGE_SECTION_HEADER) * dwNumSections;
    PIMAGE_SECTION_HEADER lastSection = &section_header[dwNumSections - 1];

    // 创建随机节名
    char randomSectionName[8] = { 0 };
    GenerateRandomSectionName(randomSectionName);
    std::cout << "生成的随机节名: " << randomSectionName << std::endl;

    // 创建新节
    IMAGE_SECTION_HEADER newSection = { 0 };
    memcpy(newSection.Name, randomSectionName, strlen(randomSectionName));
    newSection.Misc.VirtualSize = DATA_SIZE;
    newSection.VirtualAddress = (lastSection->VirtualAddress + ((lastSection->Misc.VirtualSize + 0xFFF) & 0xFFFFF000));
    newSection.SizeOfRawData = 0x1000;  // 文件对齐后的大小
    newSection.PointerToRawData = (lastSection->PointerToRawData + lastSection->SizeOfRawData);
    newSection.Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;

    // 将新节添加到节表
    PIMAGE_SECTION_HEADER newSectionHeader = &section_header[dwNumSections];
    memcpy(newSectionHeader, &newSection, sizeof(IMAGE_SECTION_HEADER));

    // 更新节数量和镜像大小
    file_header->NumberOfSections += 1;
    option_header->SizeOfImage = (newSection.VirtualAddress + ((newSection.Misc.VirtualSize + 0xFFF) & 0xFFFFF000));

    // 填充元数据头和数据
    MetadataHeader metadata = { 0 };
    memcpy(metadata.marker, MARKER, MARKER_SIZE);
    metadata.dataSize = dataSize;
    memcpy(FileBuffer + newSection.PointerToRawData, &metadata, sizeof(MetadataHeader));
    memcpy(FileBuffer + newSection.PointerToRawData + sizeof(MetadataHeader), newData, dataSize);

    // 复制到新文件
    DWORD fileSize = option_header->SizeOfHeaders + lastSection->PointerToRawData + lastSection->SizeOfRawData;
    FILE* newFile = fopen(NewFileName, "wb");
    if (newFile) {
        fwrite(FileBuffer, 1, fileSize, newFile);
        char padding[0x1000] = { 0 };
        fwrite(padding, 1, 0x1000, newFile);  // 新节的数据区填充
        fclose(newFile);
        std::cout << "成功添加随机节并写入到文件: " << NewFileName << std::endl;
        return TRUE;
    }
    else {
        std::cerr << "无法创建新文件: " << GetLastError() << std::endl;
        return FALSE;
    }
}

int main() {
    // 初始化随机数种子
    srand(static_cast<unsigned int>(time(NULL)));

    // 获取当前文件路径
    std::string originalFile = GetPEFilePath();

    // 打开当前文件并检查是否存在指定的节
    HANDLE hFile = CreateFileA(originalFile.c_str(), GENERIC_READ, 0, NULL, 
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "无法打开当前文件: " << GetLastError() << std::endl;
        return 1;
    }

    // 获取文件大小
    DWORD fileSize = GetFileSize(hFile, NULL);
    char* fileBuffer = (char*)malloc(fileSize);
    if (!fileBuffer) {
        std::cerr << "内存分配失败。" << std::endl;
        CloseHandle(hFile);
        return 1;
    }
    memset(fileBuffer, 0, fileSize);

    // 读取文件内容到缓冲区
    DWORD bytesRead = 0;
    if (!ReadFile(hFile, fileBuffer, fileSize, &bytesRead, NULL) || bytesRead != fileSize) {
        std::cerr << "读取文件失败: " << GetLastError() << std::endl;
        free(fileBuffer);
        CloseHandle(hFile);
        return 1;
    }
    CloseHandle(hFile);

    // 解析 PE 头
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileBuffer;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(fileBuffer + dosHeader->e_lfanew);

    // 检查是否存在指定的节
    PIMAGE_SECTION_HEADER existingSection = FindSectionWithMarker(ntHeaders, fileBuffer);
    if (existingSection) {
        std::cout << "已找到带标记的节,读取数据..." << std::endl;
        ReadDataFromSection(fileBuffer, existingSection->PointerToRawData);
        free(fileBuffer);
        return 0;  // 不进行文件复制,直接退出
    }

    // 没有找到指定的节,进行文件复制
    std::string copiedFilePath = CopyCurrentFile();
    if (copiedFilePath.empty()) {
        free(fileBuffer);
        return 1;  // 文件复制失败
    }

    // 打开副本文件
    hFile = CreateFileA(copiedFilePath.c_str(), GENERIC_READ | GENERIC_WRITE, 
        0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "无法打开副本文件: " << GetLastError() << std::endl;
        return 1;
    }

    // 获取文件大小
    fileSize = GetFileSize(hFile, NULL);
    fileBuffer = (char*)malloc(fileSize + 0x1000);  // 额外分配 0x1000 字节用于新节
    if (!fileBuffer) {
        std::cerr << "内存分配失败。" << std::endl;
        CloseHandle(hFile);
        return 1;
    }
    memset(fileBuffer, 0, fileSize + 0x1000);

    // 读取文件内容到缓冲区
    bytesRead = 0;
    if (!ReadFile(hFile, fileBuffer, fileSize, &bytesRead, NULL) || bytesRead != fileSize) {
        std::cerr << "读取文件失败: " << GetLastError() << std::endl;
        free(fileBuffer);
        CloseHandle(hFile);
        return 1;
    }
    CloseHandle(hFile);

    // 添加新节或读取已有数据
    const char* newData = "Hello World.";
    DWORD dataSize = strlen(newData) + 1;
    if (!AddSection(fileBuffer, copiedFilePath.c_str(), newData, dataSize))
    {
        std::cerr << "添加新节或读取数据失败。" << std::endl;
        free(fileBuffer);
        return 1;
    }

    free(fileBuffer);

    // 创建并运行批处理文件以替换自身
    CreateBatchFileToReplaceSelf(originalFile, copiedFilePath);

    std::cout << "操作完成。" << std::endl;
    return 0;
}

运行后,程序首先检查是否具有特殊节区,如果有,直接读取数据。如果没有,则执行写入逻辑“AddSection” 最后执行 CreateBatchFileToReplaceSelf 使用隐身的批处理替换自身并删除副本。

效果如下:

【警告】

此示例仅作为参考示例,不保证稳健性和可移植性。


本文出处链接:[https://blog.youkuaiyun.com/qq_59075481/article/details/143271992]。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值