Last active
March 10, 2025 16:51
-
-
Save danny8376/fd7104396827adeabb8c13780a6f65c6 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
Put corrupted OTP (named otp.mem, same as GodMode9 name) in the same location, run it, then the fixed OTP will be named otp_fixed.mem (will still write it even nothing is fixed) |
This file contains hidden or 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
from pyctr.crypto import CryptoEngine | |
from Cryptodome.Cipher import AES | |
from hashlib import sha256 | |
import itertools | |
a = CryptoEngine() | |
def print_diff(ori, enc, dec, postxt, *suffix): | |
if postxt == None: | |
return | |
if type(postxt) is tuple: | |
s = len(enc) // len(postxt) | |
for x in range(len(enc) // s): | |
b, e = x * s, (x + 1) * s | |
xori, xenc, xdec = ori[b:e], enc[b:e], dec[b:e] | |
if xori != xenc: | |
print(f"{postxt[x]} ori:", xori.hex(), *suffix) | |
print(f"{postxt[x]} fix:", xenc.hex(), xdec.hex(), *suffix) | |
elif ori != enc: | |
print(f"{postxt} ori:", ori.hex(), *suffix) | |
print(f"{postxt} fix:", enc.hex(), dec.hex(), *suffix) | |
def decrypt_otp_block(data, iv): | |
cipher = AES.new(a.otp_key, AES.MODE_CBC, iv) | |
return cipher.decrypt(data) | |
def crack_singlebit(indata, iv, check, postxt): | |
#if check(decrypt_otp_block(indata, iv)): | |
# return indata | |
ret = None | |
ints = bytearray(indata) | |
for x in range(len(indata)): | |
orig = ints[x] | |
for bit in range(8): | |
new = orig ^ (1 << bit) | |
ints[x] = new | |
data = decrypt_otp_block(ints, iv) | |
if check(data): | |
print_diff(indata, ints, data, postxt, "(single bit)") | |
ret = ints | |
return ret | |
ints[x] = orig | |
return ret | |
def crack_dataline(indata, iv, bit, check, postxt): | |
#if check(decrypt_otp_block(indata, iv)): | |
# return indata | |
set_size = len(indata) // 4 | |
ret = None | |
for combi in range(pow(2, set_size)): | |
ints = bytearray(indata) | |
for addr in range(set_size): | |
if (combi >> addr) & 1: | |
byte = 4 * addr + bit // 8 | |
orig = ints[byte] | |
ints[byte] = orig ^ (1 << (bit % 8)) | |
cipher = AES.new(a.otp_key, AES.MODE_CBC, iv) | |
data = cipher.decrypt(ints) | |
if check(data): | |
print_diff(indata, ints, data, postxt, "(data line)") | |
ret = ints | |
return ret | |
return ret | |
def crack_singlebit_dataline(indata, extract, check, postxt, bit = -1): | |
indata, iv = extract(indata) | |
ret = (-1, b"\x00" * len(indata)) | |
if check(decrypt_otp_block(indata, iv)): | |
if bit == -1: | |
bit = -2 | |
return (bit, indata) | |
if bit == -1 or bit == 32: | |
res = crack_singlebit(indata, iv, check, postxt) | |
if res != None: | |
ret = (32, res) | |
return ret | |
#if bit == 32: # force sigle bit -> fail | |
# return ret | |
if bit < 0: # -1: default, -2: no need to fix previously | |
for bit in range(32): | |
res = crack_dataline(indata, iv, bit, check, postxt) | |
if res != None: | |
ret = (bit, res) | |
return ret | |
else: | |
res = crack_dataline(indata, iv, bit, check, postxt) | |
if res != None: | |
ret = (bit, res) | |
return ret | |
return ret | |
def crack0x00(otp2fix): | |
def extract(data): | |
return data[0:0x10], a.otp_iv | |
def check(data): | |
return data[0:4] == b"\x0f\xb0\xad\xde" and data[0xD:0x10] == b"\x00\x00\x00" and data[0xC] in (0, 2) | |
ret = crack_singlebit_dataline(otp2fix, extract, check, "0x00") | |
if ret[0] == -1: | |
print("Can't fix 0x00") | |
exit(1) | |
return ret | |
def crack0x10(otp2fix, bit): | |
def extract(data): | |
return data[0x10:0x20], data[0:0x10] | |
def check(data): | |
return data[0x8:0xA] == b"\x05\x00" | |
ret = crack_singlebit_dataline(otp2fix, extract, check, "0x10", bit) | |
if ret[0] == -1: | |
print("Can't fix 0x10") | |
exit(1) | |
return ret | |
def crack0x70x80(otp2fix, bit): | |
def extract(data): | |
return data[0x70:0x90], data[0x60:0x70] | |
def check(data): | |
return data[0x10:0x20] == b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" | |
ret = crack_singlebit_dataline(otp2fix, extract, check, ("0x70", "0x80"), bit) | |
if ret[0] == -1: | |
print("can't fix 0x70, 0x80") | |
#exit(1) | |
return ret | |
def showbits(bit): | |
for addr in range(0x100 // 4): | |
byte = 4 * addr + bit // 8 | |
orig = otp[byte] | |
print((orig >> (bit % 8)) & 1, end="\t") | |
if addr % 4 == 3: | |
print() | |
def verify(ints): | |
cipher = AES.new(a.otp_key, AES.MODE_CBC, a.otp_iv) | |
data = cipher.decrypt(ints) | |
ohash = data[0xE0:] | |
before_hash = data[0:0xE0] | |
hash_before_hash = sha256(before_hash).hexdigest() | |
return data[0:4].hex() == "0fb0adde" and hash_before_hash == ohash.hex() | |
def crack_bits_by_hash(otp2fix, byte_range, bit_count=1): | |
#[forked_iter] = itertools.tee(byte_range, 1) | |
#for bytei in forked_iter: | |
for bytei in byte_range: | |
ogbyte=otp2fix[bytei] | |
for biti in range(8): | |
otp2fix[bytei] = ogbyte ^ (1 << biti) | |
if bit_count > 1: | |
crack_bits_by_hash(otp2fix, byte_range, bit_count - 1) | |
if verify(otp2fix): | |
return otp2fix | |
otp2fix[bytei] = ogbyte | |
with open('otp.mem', 'rb') as f: | |
content = f.read(0x100) | |
otp = bytearray(content) | |
bit, otp[0:0x10] = crack0x00(otp) | |
#showbits(bit) | |
bit, otp[0x10:0x20] = crack0x10(otp, bit) | |
bit, otp[0x70:0x90] = crack0x70x80(otp, bit) | |
#showbits(bit) | |
#remaining_range = itertools.chain(range(0x20, 0x70), range(0x90, 0x100)) | |
remaining_range = list(range(0x20, 0x70)) + list(range(0x90, 0x100)) | |
bit_count = 0 | |
while not verify(otp): | |
bit_count += 1 | |
if bit_count <= 1: | |
print(f"Can't be fixed by just pattern.") | |
print(f"Brute forcing non-patterned part for all possible {bit_count} bit flip...") | |
crack_bits_by_hash(otp, remaining_range, bit_count) | |
for x in range(0x10): | |
print("--------------------------------") | |
start = 0x10 * x | |
end = start + 0x10 | |
og=content[start:end] | |
inmem=otp[start:end] | |
print(og.hex()) | |
print(inmem.hex()) | |
print("same" if og == inmem else "fixed") | |
with open('otp_fixed.mem', 'wb') as f: | |
f.write(otp) | |
#for bit in range(32): | |
# ints = bytearray(content[0:0x10]) | |
# for combi in range(pow(2, 0x10 // 4)): | |
# for addr in range(0, 0x10, 4): | |
# if (combi >> addr) & 1: | |
# byte = addr + bit // 8 | |
# orig = ints[byte] | |
# ints[byte] = orig ^ (1 << (bit % 8)) | |
# cipher = AES.new(a.otp_key, AES.MODE_CBC, a.otp_iv) | |
# data = cipher.decrypt(ints) | |
# ohash = data[0xE0:] | |
# before_hash = data[0:0xE0] | |
# hash_before_hash = sha256(before_hash).hexdigest() | |
# if data[0:4].hex() == "0fb0adde": | |
# print(data[0:4].hex(), bit, combi) | |
# #print(bit, hash_before_hash, ohash.hex(), hash_before_hash == ohash.hex()) | |
# if hash_before_hash == ohash.hex(): | |
# print(beginning + before_hash + ohash) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment