You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++实现JPEG二进制文件带掩码模式扫描与修改的问题排查

JPEG二进制文件模式扫描与修改故障排查(含掩码匹配问题)

需求与问题

需求

使用C++对JPEG格式二进制文件进行模式扫描并修改指定字节,需保证文件不损坏;由于存在未知字节,必须采用模式+掩码系统(掩码中x表示需严格匹配对应字节,?表示该位置字节任意)。

问题

当掩码为纯x(如xxx)时代码运行正常,但掩码包含未知字节标识?(如x?x)时,无法按预期完成匹配与修改。


原代码存在的核心问题

1. 掩码匹配时的指针移动错误

原代码中,当掩码位置为?时,未执行fs.get(c),导致文件读取指针未移动,后续字节读取位置错位。例如掩码x?x,匹配第一个字节后,第二个字节(掩码为?)未被读取,指针停留在第一个字节后的位置,第三个字节的匹配实际读取的是第二个字节的内容,导致匹配失败。

2. 二进制数据长度计算错误

使用strlen()计算二进制数据长度,但二进制数据可能包含\0(如示例中的"\x90\x00\x90"),strlen()会在第一个\0处停止,导致生成的vector长度远小于实际需要的模式长度。

3. Map键类型错误

使用char*作为std::map的键,实际比较的是指针地址而非字符串内容,导致不同的字符串字面量可能被视为不同的键,无法正确遍历匹配模式。

4. 结构体成员与代码调用不匹配

PatternMap结构体定义的成员为old和bypass,但后续代码中使用了it->second.new,属于笔误,导致编译错误或逻辑错误。

5. 递归调用的栈溢出风险

文件被占用时的递归重试未限制深度,可能导致栈溢出,且未正确返回递归调用的结果。


修复方案与完整代码

修复要点

  • 修正掩码匹配逻辑:无论掩码是否为?,都需读取对应字节并移动指针,仅在掩码为x时进行匹配校验。
  • 使用固定长度而非strlen()计算二进制数据的长度,避免\0截断。
  • 将std::map的键改为std::string,确保按字符串内容匹配。
  • 修正结构体成员名与代码调用的一致性。
  • 替换递归重试为循环重试,避免栈溢出。

完整修复代码

#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <thread>
#include <chrono>
#include <cstring>
#include <windows.h> // 仅用于GetLastError(),跨平台可替换为对应错误处理

// 定义模式映射结构体:old为需匹配的二进制数据,newData为替换后的二进制数据
struct PatternMap {
    const char* old;
    const char* newData;
};

bool run(const std::string& fileName, const std::vector<char>& toSearch, const std::vector<char>& newData, const std::string& mask) {
    // 检查模式、掩码与替换数据长度是否一致
    if (toSearch.size() != mask.size() || newData.size() != mask.size()) {
        std::cerr << "模式、掩码与替换数据长度必须一致" << std::endl;
        return false;
    }

    const int MAX_RETRIES = 5; // 最大重试次数
    int retryCount = 0;

    while (retryCount < MAX_RETRIES) {
        std::fstream fs(fileName, std::ios::binary | std::ios::in | std::ios::out);
        if (fs.is_open()) {
            char c;
            while (fs.get(c)) {
                // 匹配模式的第一个字节
                if (c == toSearch.front()) {
                    std::streamoff matchStartPos = fs.tellg() - 1; // 记录匹配起始位置(当前指针已在第一个字节后)
                    bool match = true;

                    for (size_t i = 1; i < toSearch.size(); ++i) {
                        // 无论掩码是否为?,都读取下一个字节
                        if (!fs.get(c)) {
                            match = false;
                            break;
                        }
                        // 仅当掩码为x时,校验字节是否匹配
                        if (mask[i] == 'x' && c != toSearch[i]) {
                            match = false;
                            break;
                        }
                    }

                    if (match) {
                        std::cout << "找到完整匹配,执行替换" << std::endl;
                        // 将写入指针移到匹配起始位置
                        fs.seekp(matchStartPos);
                        // 写入替换数据
                        fs.write(newData.data(), newData.size());
                        fs.flush();
                    }
                    // 将读取指针移回匹配第一个字节后的位置,继续后续扫描
                    fs.seekg(matchStartPos + 1);
                }
            }
            fs.close();
            return true;
        } else {
            DWORD err = GetLastError();
            if (err == 32) { // 文件被占用
                std::this_thread::sleep_for(std::chrono::milliseconds(1000));
                retryCount++;
            } else {
                std::cerr << "打开文件失败,错误码:" << err << std::endl;
                return false;
            }
        }
    }
    std::cerr << "文件被占用,重试" << MAX_RETRIES << "次后仍无法打开" << std::endl;
    return false;
}

int main() {
    std::string filePath = "test.jpg"; // 替换为你的JPEG文件路径

    // 模式映射表:键为掩码,值为匹配数据和替换数据,注意二进制数据用\x前缀,长度与掩码一致
    std::map<std::string, PatternMap> patternMap = {
        {"xxx", {"\x90\x90\x90", "\xAA\xAA\xAA"}},
        {"x?x", {"\x90\x00\x90", "\xBB\x00\xBB"}}
    };

    int patternIndex = 1;
    for (const auto& entry : patternMap) {
        const std::string& mask = entry.first;
        const PatternMap& pm = entry.second;

        std::cout << "正在处理模式:" << patternIndex << ",掩码:" << mask << std::endl;

        // 按掩码长度构造匹配和替换的vector,避免strlen截断二进制数据
        std::vector<char> oldDataVec(pm.old, pm.old + mask.size());
        std::vector<char> newDataVec(pm.newData, pm.newData + mask.size());

        bool result = run(filePath, oldDataVec, newDataVec, mask);
        if (!result) {
            std::cerr << "模式" << patternIndex << "处理失败" << std::endl;
        }

        patternIndex++;
    }

    return 0;
}

额外注意事项

  • 处理JPEG文件时,需注意JPEG的结构特征(如SOI、EOI标记),避免修改关键结构导致文件损坏,建议仅修改非关键的自定义数据段。
  • 二进制数据的表示需使用\x前缀(而非原代码中的\0x),\0x是错误的转义方式,会被解析为\0加x字符。
  • 若需跨平台使用,需替换Windows.h中的GetLastError()为对应平台的错误处理函数(如Linux下的errno)。

内容的提问来源于stack exchange,提问作者ayya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 15:15:45