Created
January 9, 2024 10:23
-
-
Save marcan/727947e7dd4ac283f2522a0ed63957c6 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
import struct, time | |
## This experiment demonstrates that the claimed "hash" (that is not a hash) used | |
## by the L2 cache ECC debug feature used by Operation Triangulation is not secure, | |
## and can be trivially reverse engineered by anyone who owns one of the machines | |
## with the hardware (such as any M1 Mac), in seconds to days. Therefore, this proves | |
## that no "insider" access or leak is necessary to obtain this table, and that the | |
## attackers most likely did exactly the same thing. | |
## This is the "black box", i.e. the hardware: The table is not exposed to the caller. | |
class BlackBox: | |
__table = [ | |
0x007, 0x00B, 0x00D, 0x013, 0x00E, 0x015, 0x01F, 0x016, | |
0x019, 0x023, 0x02F, 0x037, 0x04F, 0x01A, 0x025, 0x043, | |
0x03B, 0x057, 0x08F, 0x01C, 0x026, 0x029, 0x03D, 0x045, | |
0x05B, 0x083, 0x097, 0x03E, 0x05D, 0x09B, 0x067, 0x117, | |
0x02A, 0x031, 0x046, 0x049, 0x085, 0x103, 0x05E, 0x09D, | |
0x06B, 0x0A7, 0x11B, 0x217, 0x09E, 0x06D, 0x0AB, 0x0C7, | |
0x127, 0x02C, 0x032, 0x04A, 0x051, 0x086, 0x089, 0x105, | |
0x203, 0x06E, 0x0AD, 0x12B, 0x147, 0x227, 0x034, 0x04C, | |
0x052, 0x076, 0x08A, 0x091, 0x0AE, 0x106, 0x109, 0x0D3, | |
0x12D, 0x205, 0x22B, 0x247, 0x07A, 0x0D5, 0x153, 0x22D, | |
0x038, 0x054, 0x08C, 0x092, 0x061, 0x10A, 0x111, 0x206, | |
0x209, 0x07C, 0x0BA, 0x0D6, 0x155, 0x193, 0x253, 0x28B, | |
0x307, 0x0BC, 0x0DA, 0x156, 0x255, 0x293, 0x30B, 0x058, | |
0x094, 0x062, 0x10C, 0x112, 0x0A1, 0x20A, 0x211, 0x0DC, | |
0x196, 0x199, 0x256, 0x165, 0x259, 0x263, 0x30D, 0x313, | |
0x098, 0x064, 0x114, 0x0A2, 0x15C, 0x0EA, 0x20C, 0x0C1, | |
0x121, 0x212, 0x166, 0x19A, 0x299, 0x265, 0x2A3, 0x315, | |
0x0EC, 0x1A6, 0x29A, 0x266, 0x1A9, 0x269, 0x319, 0x2C3, | |
0x323, 0x068, 0x0A4, 0x118, 0x0C2, 0x122, 0x214, 0x141, | |
0x221, 0x0F4, 0x16C, 0x1AA, 0x2A9, 0x325, 0x343, 0x0F8, | |
0x174, 0x1AC, 0x2AA, 0x326, 0x329, 0x345, 0x383, 0x070, | |
0x0A8, 0x0C4, 0x124, 0x218, 0x142, 0x222, 0x181, 0x241, | |
0x178, 0x2AC, 0x32A, 0x2D1, 0x0B0, 0x0C8, 0x128, 0x144, | |
0x1B8, 0x224, 0x1D4, 0x182, 0x242, 0x2D2, 0x32C, 0x281, | |
0x351, 0x389, 0x1D8, 0x2D4, 0x352, 0x38A, 0x391, 0x0D0, | |
0x130, 0x148, 0x228, 0x184, 0x244, 0x282, 0x301, 0x1E4, | |
0x2D8, 0x354, 0x38C, 0x392, 0x1E8, 0x2E4, 0x358, 0x394, | |
0x362, 0x3A1, 0x150, 0x230, 0x188, 0x248, 0x284, 0x302, | |
0x1F0, 0x2E8, 0x364, 0x398, 0x3A2, 0x0E0, 0x190, 0x250, | |
0x2F0, 0x288, 0x368, 0x304, 0x3A4, 0x370, 0x3A8, 0x3C4, | |
0x160, 0x290, 0x308, 0x3B0, 0x3C8, 0x3D0, 0x1A0, 0x260, | |
0x310, 0x1C0, 0x2A0, 0x3E0, 0x2C0, 0x320, 0x340, 0x380 | |
] | |
def __calculate_hash(self, buffer): | |
acc = 0 | |
for i in range(8): | |
pos = i * 4 | |
value = struct.unpack("<I", buffer[pos:pos+4])[0] | |
for j in range(32): | |
if (((value >> j) & 1) != 0): | |
acc ^= self.__table[32 * i + j] | |
return acc | |
def is_hash_valid(self, buffer, hash): | |
return hash == self.__calculate_hash(buffer) | |
# Now we reverse engineer the table "black box", just by trying data and hash values and seeing if they work | |
tries = 0 | |
reverse_engineered_table = [] | |
b = BlackBox() | |
start = time.time() | |
for databit in range(256): | |
buffer = (1 << databit).to_bytes(length=32, byteorder="little") | |
for hash in range(0x400): | |
tries += 1 | |
if b.is_hash_valid(buffer, hash): | |
reverse_engineered_table.append(hash) | |
break | |
else: | |
assert False | |
t = time.time() - start | |
print(f"Extracted {len(reverse_engineered_table)} table entries in {tries} tries ({t:.02f} seconds).") | |
# Assume 100 microseconds per trial (this is very, very generous) | |
t1 = tries * 100e-6 | |
print(f"Time to do this on real hardware, without reboots: {t1:.02f} seconds (conservatively)") | |
# Approximately 7 seconds to reboot an M1 Mac Mini into m1n1 and run an experiment | |
t2 = tries * 7 / 86400 | |
print(f"Time to do this on real hardware, with reboots: {t2:.02f} days") | |
print() | |
print("Table:") | |
for i, v in enumerate(reverse_engineered_table): | |
print(f"{v:03x} ", end="") | |
if i % 16 == 15: | |
print() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Man you're a genius at reverse engineering. How I wish I had your knowledge!