/* * CRC-32 forcer (C++) * * Copyright (c) 2024 Project Nayuki * https://www.nayuki.io/page/forcing-a-files-crc-to-any-value * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see COPYING.txt). * If not, see . */ #include #include #include #include #include #include #include #include #include #include #include using std::uint32_t; using std::uint64_t; /* Forward declarations */ void modifyFileCrc32(const std::string &path, uint64_t offset, uint32_t newcrc, bool printstatus); static std::pair getCrc32AndLength(std::istream &f); static uint32_t reverseBits(uint32_t x); static uint64_t multiplyMod(uint64_t x, uint64_t y); static uint64_t powMod(uint64_t x, uint64_t y); static std::pair divideAndRemainder(uint64_t x, uint64_t y); static uint64_t reciprocalMod(uint64_t x); static int getDegree(uint64_t x); /*---- Main application ----*/ int main(int argc, char *argv[]) { // Handle arguments if (argc != 4) { std::cerr << "Usage: " << argv[0] << " FileName ByteOffset NewCrc32Value" << std::endl; return EXIT_FAILURE; } // Parse and check file offset argument uint64_t offset; { std::istringstream ss(argv[2]); ss >> offset; if (ss.fail() || !ss.eof()) { std::cerr << "Error: Invalid byte offset" << std::endl; return EXIT_FAILURE; } } // Parse and check new CRC argument uint32_t newcrc; { std::string s(argv[3]); if (s.size() != 8 || s[0] == '+' || s[0] == '-') { std::cerr << "Error: Invalid new CRC-32 value" << std::endl; return EXIT_FAILURE; } std::istringstream ss(s); ss >> std::hex >> newcrc; if (ss.fail() || !ss.eof()) { std::cerr << "Error: Invalid new CRC-32 value" << std::endl; return EXIT_FAILURE; } } newcrc = reverseBits(newcrc); // Process the file modifyFileCrc32(std::string(argv[1]), offset, newcrc, true); return EXIT_SUCCESS; } /*---- Main function ----*/ // Public library function. void modifyFileCrc32(const std::string &path, uint64_t offset, uint32_t newcrc, bool printstatus) { std::fstream f(path, std::ios::in | std::ios::out | std::ios::binary); // Read entire file and calculate original CRC-32 value uint64_t length; uint32_t crc; { std::pair temp = getCrc32AndLength(f); crc = temp.first; length = temp.second; } if (length < 4 || offset > length - 4) throw std::domain_error("Error: Byte offset plus 4 exceeds file length"); if (printstatus) { std::ostringstream ss; ss << std::hex << std::uppercase << std::setfill('0') << std::setw(8) << reverseBits(crc); std::cout << "Original CRC-32: " << ss.str() << std::endl; } // Compute the change to make uint32_t delta = crc ^ newcrc; delta = static_cast(multiplyMod(reciprocalMod(powMod(2, (length - offset) * 8)), delta)); // Patch 4 bytes in the file if (offset > std::numeric_limits::max()) throw std::domain_error("Offset too large"); f.clear(); for (int i = 0; i < 4; i++) { f.seekg(offset + i, std::ios::beg); int b = f.get(); if (b == std::char_traits::eof()) throw std::runtime_error("Unexpected end of file"); b ^= static_cast((reverseBits(delta) >> (i * 8)) & 0xFF); f.seekp(offset + i, std::ios::beg); f.put(b); f.flush(); } if (printstatus) std::cout << "Computed and wrote patch" << std::endl; // Recheck entire file bool match = getCrc32AndLength(f).first == newcrc; f.close(); if (!match) throw std::logic_error("Assertion error: Failed to update CRC-32 to desired value"); if (printstatus) std::cout << "New CRC-32 successfully verified" << std::endl; } /*---- Utilities ----*/ // Generator polynomial. Do not modify, because there are many dependencies static const uint64_t POLYNOMIAL = UINT64_C(0x104C11DB7); static std::pair getCrc32AndLength(std::istream &f) { f.clear(); f.seekg(0, std::ios::beg); uint32_t crc = UINT32_C(0xFFFFFFFF); uint64_t length = 0; while (!f.eof()) { std::array buffer; f.read(buffer.data(), buffer.size()); std::streamsize n = f.gcount(); for (std::streamsize i = 0; i < n; i++) { for (int j = 0; j < 8; j++) { uint32_t bit = (static_cast(buffer[i]) >> j) & 1; crc ^= bit << 31; bool doXor = (crc >> 31) != 0; crc = (crc & UINT32_C(0x7FFFFFFF)) << 1; if (doXor) crc ^= static_cast(POLYNOMIAL); } } length = 0U + length + n; } return std::pair(~crc, length); } static uint32_t reverseBits(uint32_t x) { uint32_t result = 0; for (int i = 0; i < 32; i++, x >>= 1) result = (result << 1) | (x & 1U); return result; } /*---- Polynomial arithmetic ----*/ // Returns polynomial x multiplied by polynomial y modulo the generator polynomial. static uint64_t multiplyMod(uint64_t x, uint64_t y) { // Russian peasant multiplication algorithm uint64_t z = 0; while (y != 0) { z ^= x * (y & 1); y >>= 1; x <<= 1; if (((x >> 32) & 1) != 0) x ^= POLYNOMIAL; } return z; } // Returns polynomial x to the power of natural number y modulo the generator polynomial. static uint64_t powMod(uint64_t x, uint64_t y) { // Exponentiation by squaring uint64_t z = 1; while (y != 0) { if ((y & 1) != 0) z = multiplyMod(z, x); x = multiplyMod(x, x); y >>= 1; } return z; } // Computes polynomial x divided by polynomial y, returning the quotient and remainder. static std::pair divideAndRemainder(uint64_t x, uint64_t y) { if (y == 0) throw std::domain_error("Division by zero"); if (x == 0) return std::pair(0, 0); int ydeg = getDegree(y); uint64_t z = 0; for (int i = getDegree(x) - ydeg; i >= 0; i--) { if (((x >> (i + ydeg)) & 1) != 0) { x ^= y << i; z |= static_cast(1 << i); } } return std::pair(z, x); } // Returns the reciprocal of polynomial x with respect to the generator polynomial. static uint64_t reciprocalMod(uint64_t x) { // Based on a simplification of the extended Euclidean algorithm uint64_t y = x; x = POLYNOMIAL; uint64_t a = 0; uint64_t b = 1; while (y != 0) { std::pair qr = divideAndRemainder(x, y); uint64_t c = a ^ multiplyMod(qr.first, b); x = y; y = qr.second; a = b; b = c; } if (x == 1) return a; else throw std::domain_error("Reciprocal does not exist"); } static int getDegree(uint64_t x) { int result = -1; for (; x != 0; x >>= 1) result++; return result; }