/* * CRC-32 forcer (C) * * Copyright (c) 2022 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 /* Forward declarations */ const char *modify_file_crc32(const char *path, uint64_t offset, uint32_t newcrc, bool printstatus); static uint32_t get_crc32_and_length(FILE f[static 1], uint64_t length[static 1]); static const char *fseek64(FILE f[static 1], uint64_t offset); static uint32_t reverse_bits(uint32_t x); static uint64_t multiply_mod(uint64_t x, uint64_t y); static uint64_t pow_mod(uint64_t x, uint64_t y); static void divide_and_remainder(uint64_t x, uint64_t y, uint64_t q[static 1], uint64_t r[static 1]); static uint64_t reciprocal_mod(uint64_t x); static int get_degree(uint64_t x); /*---- Main application ----*/ int main(int argc, char *argv[]) { // Handle arguments if (argc != 4) { fprintf(stderr, "Usage: %s FileName ByteOffset NewCrc32Value\n", argv[0]); return EXIT_FAILURE; } // Parse and check file offset argument uint64_t offset; if (sscanf(argv[2], "%" SCNu64, &offset) != 1) { fprintf(stderr, "Error: Invalid byte offset\n"); return EXIT_FAILURE; } char temp[21] = {0}; sprintf(temp, "%" PRIu64, offset); if (strcmp(temp, argv[2]) != 0) { fprintf(stderr, "Error: Invalid byte offset\n"); return EXIT_FAILURE; } // Parse and check new CRC argument uint32_t newcrc; if (strlen(argv[3]) != 8 || argv[3][0] == '+' || argv[3][0] == '-' || sscanf(argv[3], "%" SCNx32, &newcrc) != 1) { fprintf(stderr, "Error: Invalid new CRC-32 value\n"); return EXIT_FAILURE; } newcrc = reverse_bits(newcrc); // Process the file const char *errmsg = modify_file_crc32(argv[1], offset, newcrc, true); if (errmsg == NULL) return EXIT_SUCCESS; else if (0 < strcmp(errmsg, "I/O error: ") && strcmp(errmsg, "I/O error:!") < 0) // Prefix test perror(errmsg); else fprintf(stderr, "%s\n", errmsg); return EXIT_FAILURE; } /*---- Main function ----*/ // Public library function. Returns NULL if successful, a string starting with "I/O error: " // if an I/O error occurred (please see perror()), or a string if some other error occurred. const char *modify_file_crc32(const char *path, uint64_t offset, uint32_t newcrc, bool printstatus) { FILE *f = fopen(path, "r+b"); if (f == NULL) return "I/O error: fopen"; // Read entire file and calculate original CRC-32 value. // Note: We can't use fseek(f, 0, SEEK_END) + ftell(f) to determine the length of the file, due to undefined behavior. // To be portable, we also avoid using POSIX fseeko()+ftello() or Windows GetFileSizeEx()/_filelength(). uint64_t length; uint32_t crc = get_crc32_and_length(f, &length); if (length < 4 || offset > length - 4) { fclose(f); return "Error: Byte offset plus 4 exceeds file length"; } if (printstatus) fprintf(stdout, "Original CRC-32: %08" PRIX32 "\n", reverse_bits(crc)); // Compute the change to make uint32_t delta = crc ^ newcrc; delta = (uint32_t)multiply_mod(reciprocal_mod(pow_mod(2, (length - offset) * 8)), delta); // Patch 4 bytes in the file const char *errmsg = fseek64(f, offset); if (errmsg != NULL) return errmsg; for (int i = 0; i < 4; i++) { int b = fgetc(f); if (b == EOF) { fclose(f); return "I/O error: fgetc"; } b ^= (int)((reverse_bits(delta) >> (i * 8)) & 0xFF); if (fseek(f, -1, SEEK_CUR) != 0) { fclose(f); return "I/O error: fseek"; } if (fputc(b, f) == EOF) { fclose(f); return "I/O error: fputc"; } if (fflush(f) == EOF) { fclose(f); return "I/O error: fflush"; } } if (printstatus) fprintf(stdout, "Computed and wrote patch\n"); // Recheck entire file bool match = get_crc32_and_length(f, &length) == newcrc; fclose(f); if (match) { if (printstatus) fprintf(stdout, "New CRC-32 successfully verified\n"); return NULL; // Success } else return "Assertion error: Failed to update CRC-32 to desired value"; } /*---- Utilities ----*/ // Generator polynomial. Do not modify, because there are many dependencies static const uint64_t POLYNOMIAL = UINT64_C(0x104C11DB7); static uint32_t get_crc32_and_length(FILE f[static 1], uint64_t length[static 1]) { rewind(f); uint32_t crc = UINT32_C(0xFFFFFFFF); *length = 0; while (true) { char buffer[32 * 1024]; size_t n = fread(buffer, sizeof(buffer[0]), sizeof(buffer) / sizeof(buffer[0]), f); if (ferror(f) != 0) { perror("fread"); exit(EXIT_FAILURE); } for (size_t i = 0; i < n; i++) { for (int j = 0; j < 8; j++) { uint32_t bit = ((uint8_t)buffer[i] >> j) & 1; crc ^= bit << 31; bool xor = (crc >> 31) != 0; crc = (crc & UINT32_C(0x7FFFFFFF)) << 1; if (xor) crc ^= (uint32_t)POLYNOMIAL; } } *length = 0U + *length + n; if (feof(f) != 0) return ~crc; } } static const char *fseek64(FILE f[static 1], uint64_t offset) { rewind(f); while (offset > 0) { unsigned long n = LONG_MAX; if (offset < n) n = (unsigned long)offset; if (fseek(f, (long)n, SEEK_CUR) != 0) return "I/O error: fseek"; offset -= n; } return NULL; } static uint32_t reverse_bits(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 multiply_mod(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 pow_mod(uint64_t x, uint64_t y) { // Exponentiation by squaring uint64_t z = 1; while (y != 0) { if ((y & 1) != 0) z = multiply_mod(z, x); x = multiply_mod(x, x); y >>= 1; } return z; } // Computes polynomial x divided by polynomial y, returning the quotient and remainder. static void divide_and_remainder(uint64_t x, uint64_t y, uint64_t q[static 1], uint64_t r[static 1]) { if (y == 0) { fprintf(stderr, "Division by zero\n"); exit(EXIT_FAILURE); } if (x == 0) { *q = 0; *r = 0; return; } int ydeg = get_degree(y); uint64_t z = 0; for (int i = get_degree(x) - ydeg; i >= 0; i--) { if (((x >> (i + ydeg)) & 1) != 0) { x ^= y << i; z |= (uint64_t)1 << i; } } *q = z; *r = x; } // Returns the reciprocal of polynomial x with respect to the generator polynomial. static uint64_t reciprocal_mod(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) { uint64_t q, r; divide_and_remainder(x, y, &q, &r); uint64_t c = a ^ multiply_mod(q, b); x = y; y = r; a = b; b = c; } if (x == 1) return a; else { fprintf(stderr, "Reciprocal does not exist\n"); exit(EXIT_FAILURE); } } static int get_degree(uint64_t x) { int result = -1; for (; x != 0; x >>= 1) result++; return result; }