diff --git a/xld.py b/xld.py index 4aa1424..685d00d 100644 --- a/xld.py +++ b/xld.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys -import copy +import base64 import struct import argparse @@ -15,21 +15,24 @@ DIGEST_LENGTH = 64 + len('\nVersion=0001') def bit_concat32(high, low): return ((high & 0xFFFFFFFF) << 32) | (low & 0xFFFFFFFF) -def byte_swap(bits, n): - n = n & (1 << bits) - 1 - return int.from_bytes(n.to_bytes(bits // 8, 'little')[::-1], 'little') +def reverse_bytes32(n): + as_bytes = n.to_bytes(4, 'little')[::-1] + return int.from_bytes(as_bytes, 'little') -def LODWORD(n): +def concat_reverse_bytes32(high, low): + return bit_concat32(reverse_bytes32(high), reverse_bytes32(low)) + +def LOW32(n): return n & 0x00000000FFFFFFFF -def HIDWORD(n): +def HIGH32(n): return (n & 0xFFFFFFFF00000000) >> 32 -def set_LODWORD(n, v): +def set_LOW32(n, v): return (n & 0xFFFFFFFF00000000) | (v & 0xFFFFFFFF) -def set_HIDWORD(n, v): - return (n & 0x00000000FFFFFFFF) | ((v & 0xFFFFFFFF) << 32) +def set_HIGH32(n, v): + return ((v & 0xFFFFFFFF) << 32) | (n & 0x00000000FFFFFFFF) def rotate_left(n, k): return ((n << k) & 0xFFFFFFFF) | (n >> (32 - k)) @@ -38,9 +41,8 @@ def rotate_right(n, k): return ((n >> k) | (n << (32 - k))) & 0xFFFFFFFF -def almost_sha256(data): - # Non-standard initial state - state = (0x1D95E3A4, 0x06520EF5, 0x3A9CFB75, 0x6104BCAE, 0x09CEDA82, 0xBA55E60B, 0xEAEC16C6, 0xEB19AF15) +def sha256(data, initial_state): + state = initial_state # Standard round constants round_constants = (0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2) @@ -52,11 +54,13 @@ def almost_sha256(data): data += b'\x80' + (b'\x00' * ((K - 7) // 8)) + L.to_bytes(8, 'big') for start in range(0, len(data), 64): + # Process chunks of 64 bytes chunk = data[start:start + 64] - round_state = [0] * 64 - round_state[0:16] = struct.unpack('!16L', chunk) - + + for i in range(0, len(chunk), 4): + round_state[i // 4] = int.from_bytes(chunk[i:i + 4], 'big') + for i in range(16, 64): s0 = rotate_right(round_state[i - 15], 7) ^ rotate_right(round_state[i - 15], 18) ^ (round_state[i - 15] >> 3) s1 = rotate_right(round_state[i - 2], 17) ^ rotate_right(round_state[i - 2], 19) ^ (round_state[i - 2] >> 10) @@ -89,80 +93,57 @@ def almost_sha256(data): def scramble(data): - previous = MAGIC_INITIAL_STATE - mod_current = 0 + X = LOW32(MAGIC_INITIAL_STATE) + Y = HIGH32(MAGIC_INITIAL_STATE) - output = b'' + output = [] - for size in range(DIGEST_LENGTH, 0, -8): - current = 0 + for offset in range(0, len(data), 8): + size = len(data) - offset - needs_padding = (size < 8) # We will always need padding in the end + # If we can read off two 32-bit integers, do it. Otherwise, reuse the last state + if size >= 8: + a = int.from_bytes(data[offset:offset + 4], 'little') + b = int.from_bytes(data[offset + 4:offset + 8], 'little') - if not needs_padding: - offset = DIGEST_LENGTH - size - chunk1 = int.from_bytes(data[offset:offset + 4], 'little') - chunk2 = int.from_bytes(data[offset + 4:offset + 8], 'little') - - current = previous ^ bit_concat32(byte_swap(32, chunk2), byte_swap(32, chunk1)) - else: - current = byte_swap(64, bit_concat32(mod_current, HIDWORD(mod_current))) + X ^= reverse_bytes32(a) + Y ^= reverse_bytes32(b) + # Do some kind of 8-round scramble on the state four times for i in range(4): for j in range(2): - current = set_HIDWORD(current, HIDWORD(current) ^ current) + Y ^= X - a = (MAGIC_CONSTANTS[4*j + 0] + HIDWORD(current)) & 0xFFFFFFFF - b = a - a = rotate_left(a, 1) - c = (b - 1 + a) & 0xFFFFFFFF - d = c - c = rotate_left(c, 4) - current = set_LODWORD(current, d ^ c ^ current) + a = (MAGIC_CONSTANTS[4*j + 0] + Y) & 0xFFFFFFFF + b = (a - 1 + rotate_left(a, 1)) & 0xFFFFFFFF - e = (MAGIC_CONSTANTS[4*j + 1] + current) & 0xFFFFFFFF - f = e - e = rotate_left(e, 2) - g = (f + 1 + e) & 0xFFFFFFFF - h = g - g = rotate_left(g, 8) - i = (MAGIC_CONSTANTS[4*j + 2] + (h ^ g)) & 0xFFFFFFFF - p = i - i = rotate_left(i, 1) - k = (i - p) & 0xFFFFFFFF - l = k - k = rotate_left(k, 16) + X ^= b ^ rotate_left(b, 4) - current = set_HIDWORD(current, HIDWORD(current) ^ (current | l) ^ k) - m = (MAGIC_CONSTANTS[4*j + 3] + HIDWORD(current)) & 0xFFFFFFFF - n = m - m = rotate_left(m, 2) + c = (MAGIC_CONSTANTS[4*j + 1] + X) & 0xFFFFFFFF + d = (c + 1 + rotate_left(c, 2)) & 0xFFFFFFFF - current = set_LODWORD(current, ((n + 1 + m) ^ current) & 0xFFFFFFFF) + e = (MAGIC_CONSTANTS[4*j + 2] + (d ^ rotate_left(d, 8))) & 0xFFFFFFFF + f = (rotate_left(e, 1) - e) & 0xFFFFFFFF - previous = current - mod_current = byte_swap(64, (current << 32) | HIDWORD(current)) - if needs_padding: - remaining = bytearray(data[len(output):]) + Y ^= (X | f) ^ rotate_left(f, 16) - for i in range(size): - remaining[i] ^= mod_current & 0xFF - mod_current >>= 8 - output += remaining - break + g = (MAGIC_CONSTANTS[4*j + 3] + Y) & 0xFFFFFFFF + X ^= (g + 1 + rotate_left(g, 2)) & 0xFFFFFFFF - output += mod_current.to_bytes(8, 'little') + output.append(concat_reverse_bytes32(Y, X).to_bytes(8, 'little')) - return output + if size > 0: + last_chunk = output.pop() + output.append(bytearray(data[8 * len(output) + i] ^ last_chunk[i] for i in range(size))) + + return b''.join(output) def encode(data): - import base64 - # Non-standard base64 alphabet mapping = str.maketrans( 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', @@ -193,7 +174,9 @@ def extract_info(data): def xld_verify(data): data, version, old_signature = extract_info(data) - hashed_data = (almost_sha256(data.encode('utf-8')) + '\nVersion=0001').encode('ascii') + initial_state = (0x1D95E3A4, 0x06520EF5, 0x3A9CFB75, 0x6104BCAE, 0x09CEDA82, 0xBA55E60B, 0xEAEC16C6, 0xEB19AF15) + hashed_data = (sha256(data.encode('utf-8'), initial_state) + '\nVersion=0001').encode('ascii') + scrambled_data = scramble(hashed_data) signature = encode(scrambled_data)