有时候需要我们程序向自身写入一些数据,以便于下次运行可以直接读取。当不使用配置文件(外部存储)时,应该怎么做?原理很简单:向 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 = §ion_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 = §ion_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]。