known last state extension attack test works

This commit is contained in:
Christoph J. Scherr 2023-04-27 15:38:14 +02:00
parent 6129b1f9e8
commit afc7f1b76c
Signed by: PlexSheep
GPG Key ID: 25B4ACF7D88186CC
2 changed files with 102 additions and 41 deletions

View File

@ -0,0 +1,46 @@
current buffer: 5441
loading buffer
current in_byte: K
current buffer: 544143
loading buffer
internal state after the bytes were read: c2b50599
buffer pre last fill: 5441434b
buffer after last fill: 5441434b
last internal state: 1b516b3a
Hacked MIC: cd255d98
current in_byte:
current buffer: 4e
loading buffer
current in_byte: A
current buffer: 4e20
loading buffer
current in_byte: T
current buffer: 4e2041
loading buffer
current in_byte: T
current buffer: 4e204154
current in_byte: A
current buffer: 54
loading buffer
current in_byte: C
current buffer: 5441
loading buffer
current in_byte: K
current buffer: 544143
loading buffer
internal state after the bytes were read: 0e986369
buffer pre last fill: 5441434b
buffer after last fill: 5441434b
last internal state: 1a9d9590
Hacked MIC: 31bda0ab
=========
working extension attack against a basic mic:
=========
>>> authur1.authur1(bytearray(4), False, bytearray(0x33a9cfff.to_bytes(4))).hex()
'fd0ef003'
>>> authur1.keyed_hash(bytearray(8), bytearray(16), False).hex()
'fd0ef003'
>>>

View File

@ -41,11 +41,15 @@ def inner_authur1(input: int) -> int:
return output
def authur1(input: bytearray, verbose: bool = False) -> bytearray:
if verbose:
print("input: %s" % input)
def authur1(input: bytearray,
verbose: bool = False,
first_state: bytearray = DEFINED_INITIAL,
return_last_state: bool = False) -> bytearray:
internal_buffer: bytearray = bytearray()
accumulator: bytearray = DEFINED_INITIAL
accumulator: bytearray = first_state
if verbose:
print("input: %s" % input.hex())
print("first internal state: %s" % accumulator.hex())
for in_byte in input:
if verbose:
print("current in_byte: %s" % chr(in_byte))
@ -85,38 +89,17 @@ def authur1(input: bytearray, verbose: bool = False) -> bytearray:
print("last internal state: %s" % accumulator.hex())
# now Q the accumulator and return
# if input = "" this step breaks things, just remove it.
if len(input) != 0:
if len(input) != 0 and not return_last_state:
accuint: int = int.from_bytes(accumulator)
accuint: int = inner_authur1(accuint)
accumulator: bytearray = bytearray(accuint.to_bytes(4))
return accumulator
def keyed_hash(message: bytearray, key: bytearray) -> bytearray:
def keyed_hash(message: bytearray, key: bytearray, verbose: bool = False, return_last_state: bool = False) -> bytearray:
assert len(key) == 16, "key is not 16 Byte long: %s" % len(key)
# prepend only
input: bytearray = key + message
mic: bytearray = authur1(input)
return mic
def reverse_inner_authur1(output: int) -> int:
assert output.bit_length() <= 32, "output length is <= 32: %d" % output.bit_length()
# plexcryptool.binary uses u32 for shifting
#output: int = input ^ (binary.rotl32(input, SHIFT_LENGTH))
# -> output ^ input = binary.rotl32(input, SHIFT_LENGTH)
# -> input = binary.rotl32(input, SHIFT_LENGTH) ^ output
input: int = output ^ (binary.rotr32(output, SHIFT_LENGTH))
assert False, "inner_authur1 can not be reversed!"
assert input.bit_length() <= 32, "input length is <= 32: %d" % input.bit_length()
return input
def find_last_internal_state(mic: bytearray) -> bytearray:
# reverse the mic to get the last internal state
# TODO
raise NotImplementedError("find last internal state is still TODO")
mic: bytearray = authur1(input, verbose, return_last_state=return_last_state)
return mic
def extension_attack(valid_pairs: list):
@ -175,8 +158,37 @@ def extension_attack(valid_pairs: list):
print("The given originals were not sufficient to perform an extension attack.\n"+
"We need a message, which has a length that is a multiple of 16 (Bytes).")
return
last_internal: bytearray = find_last_internal_state(target_pair[1])
print("Found a fitting target pair: (%s,%s)" % target_pair)
# now find the last internal state.
# inner_authur1 cannot be reversed, so we need to brute force it
# the key space is only 2**32, so it should be possible
# we need to check everyone of these 2**32 against the valid hashes to find a working one
found_the_state = False
KEY_SPACE = 2**32
extension_msg = bytearray("ef".encode())
# given mic for "abcdef" 0f6b8802
looking_for = bytearray(0x0f6b8802.to_bytes(4))
last_internal_state = None
print("=" * 120)
print("Bruteforcing the internal state, this might take a while")
print("=" * 120)
for i in range(0, KEY_SPACE):
mic = authur1(extension_msg, False, bytearray(i.to_bytes(4)))
if found_the_state:
break
if not mic is None and mic == looking_for:
print("=" * 120)
print("FOUND THE THING")
found_the_state = True
last_internal_state = i
print("IT IS %s" % hex(last_internal_state))
extension_text = "EXTENSION ATTACK"
hacked_mic = authur1(bytearray(extension_text.encode()), True, bytearray(last_internal_state.to_bytes(4)))
print("Hacked MIC: %s" % hacked_mic.hex())
def test():
init: int = int.from_bytes(DEFINED_INITIAL)
@ -204,25 +216,28 @@ def test():
print("H aka authur1 passed the test")
test_reverse_inner_authur1()
#test_reverse_inner_authur1()
test_extension_attack()
print("All tests passed!")
def test_extension_attack():
"""
Test the attack against a known key
Test the attack against a known last state
"""
# TODO
raise(NotImplementedError("Extension attack is still TODO"))
key = bytearray(0x13377331.to_bytes(16))
message = bytearray("1234".encode())
ext_message = bytearray("EXT".encode())
# we need to bruteforce this, skip for the test
mic = keyed_hash(message, key)
last_internal_state = keyed_hash(message, key, return_last_state=True)
forged_mic = authur1(ext_message, False, last_internal_state)
message.extend(ext_message)
validated_mic = keyed_hash(message, key)
assert validated_mic == forged_mic, "forged mic\n%sis not valid\n%s" % ( forged_mic.hex(), validated_mic.hex() )
print("Manual extension attack with known last internal state works\n(%s == %s)" % (forged_mic.hex(), validated_mic.hex()))
def test_reverse_inner_authur1():
a = 0x1337
aq = inner_authur1(a)
assert reverse_inner_authur1(aq) == a, "reverse_inner_authur1 does not work:\n%s\nis not\n%s" % (
bin(a),
bin(reverse_inner_authur1(a))
)
def main():
parser = argparse.ArgumentParser(prog="authur1", description='Implementation and attack for the custom authur1 hash. Don\'t actually use this hash!')