Last active
June 14, 2019 16:55
-
-
Save Danukeru/eff6b00b0785fc982404 to your computer and use it in GitHub Desktop.
Credit: http://www.nayuki.io/page/forcing-a-files-crc-to-any-value [Here for Fair Use Archiving/Reference]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# CRC-32 forcer (Python) | |
# Compatible with Python 2 and 3. | |
# | |
# Copyright (c) 2015 Project Nayuki | |
# All rights reserved. Contact Nayuki for licensing. | |
# http://www.nayuki.io/page/forcing-a-files-crc-to-any-value | |
# | |
import os, sys, zlib | |
# ---- Main function ---- | |
def main(args): | |
# Handle arguments | |
if len(args) != 3: | |
return "Usage: python forcecrc32.py FileName ByteOffset NewCrc32Value" | |
try: | |
offset = int(args[1]) | |
except ValueError: | |
return "Error: Invalid byte offset" | |
if offset < 0: | |
return "Error: Negative byte offset" | |
try: | |
if len(args[2]) != 8 or args[2].startswith(("+", "-")): | |
return "Error: Invalid new CRC-32 value" | |
temp = int(args[2], 16) | |
if temp & MASK != temp: | |
return "Error: Invalid new CRC-32 value" | |
new_crc = reverse32(temp) | |
except ValueError: | |
return "Error: Invalid new CRC-32 value" | |
# Process the file | |
try: | |
raf = open(args[0], "r+b") | |
try: | |
raf.seek(0, os.SEEK_END) | |
length = raf.tell() | |
if offset + 4 > length: | |
return "Error: Byte offset plus 4 exceeds file length" | |
# Read entire file and calculate original CRC-32 value | |
crc = get_crc32(raf) | |
print("Original CRC-32: {:08X}".format(reverse32(crc))) | |
# Compute the change to make | |
delta = crc ^ new_crc | |
delta = multiply_mod(reciprocal_mod(pow_mod(2, (length - offset) * 8)), delta) | |
# Patch 4 bytes in the file | |
raf.seek(offset) | |
bytes4 = bytearray(raf.read(4)) | |
if len(bytes4) != 4: | |
return "Error: Cannot read 4 bytes at offset" | |
for i in range(4): | |
bytes4[i] ^= (reverse32(delta) >> (i * 8)) & 0xFF | |
raf.seek(offset) | |
raf.write(bytes4) | |
print("Computed and wrote patch") | |
# Recheck entire file | |
if get_crc32(raf) == new_crc: | |
print("New CRC-32 successfully verified") | |
else: | |
return "Error: Failed to update CRC-32 to desired value" | |
except IOError as e: | |
return "Error: I/O error" | |
finally: | |
raf.close() | |
except IOError: | |
return "Error: Cannot open file " + args[0] | |
return None | |
# ---- Utilities ---- | |
POLYNOMIAL = 0x104C11DB7 # Generator polynomial. Do not modify, because there are many dependencies | |
MASK = (1 << 32) - 1 | |
def get_crc32(raf): | |
raf.seek(0) | |
crc = 0 | |
while True: | |
buffer = raf.read(128 * 1024) | |
if len(buffer) == 0: | |
return reverse32(crc & MASK) | |
else: | |
crc = zlib.crc32(buffer, crc) | |
def reverse32(x): | |
y = 0 | |
for i in range(32): | |
y = (y << 1) | (x & 1) | |
x >>= 1 | |
return y | |
# ---- Polynomial arithmetic ---- | |
# Returns polynomial x multiplied by polynomial y modulo the generator polynomial. | |
def multiply_mod(x, y): | |
# Russian peasant multiplication algorithm | |
z = 0 | |
while y != 0: | |
z ^= x * (y & 1) | |
y >>= 1 | |
x <<= 1 | |
if x & (1 << 32) != 0: | |
x ^= POLYNOMIAL | |
return z | |
# Returns polynomial x to the power of natural number y modulo the generator polynomial. | |
def pow_mod(x, y): | |
# Exponentiation by squaring | |
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. | |
def divide_and_remainder(x, y): | |
if y == 0: | |
raise ValueError("Division by zero") | |
if x == 0: | |
return (0, 0) | |
ydeg = get_degree(y) | |
z = 0 | |
for i in range(get_degree(x) - ydeg, -1, -1): | |
if (x & (1 << (i + ydeg)) != 0): | |
x ^= y << i | |
z |= 1 << i | |
return (z, x) | |
# Returns the reciprocal of polynomial x with respect to the modulus polynomial m. | |
def reciprocal_mod(x): | |
# Based on a simplification of the extended Euclidean algorithm | |
y = x | |
x = POLYNOMIAL | |
a = 0 | |
b = 1 | |
while (y != 0): | |
divrem = divide_and_remainder(x, y) | |
c = a ^ multiply_mod(divrem[0], b) | |
x = y | |
y = divrem[1] | |
a = b | |
b = c | |
if x == 1: | |
return a | |
else: | |
raise ValueError("Reciprocal does not exist") | |
def get_degree(x): | |
if x == 0: | |
return -1 | |
i = 0 | |
while True: | |
if x >> i == 1: | |
return i | |
i += 1 | |
# ---- Miscellaneous ---- | |
if __name__ == "__main__": | |
errmsg = main(sys.argv[1:]) | |
if errmsg is not None: | |
sys.stderr.write(errmsg + "\n") | |
sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment