Skip to content

Commit

Permalink
LZ4/Zstd collisions
Browse files Browse the repository at this point in the history
  • Loading branch information
angea committed Mar 25, 2023
1 parent 144eb7e commit 9680603
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/free/README.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ <h2 id="unicoll">UniColl</h2>
<li>specific header <a href="md5-s1.mp4" class="uri">md5-s1.mp4</a> / <a href="md5-s2.mp4" class="uri">md5-s2.mp4</a></li>
</ul></li>
<li><p>ZIP <a href="md5-1.zip" class="uri">md5-1.zip</a> / <a href="md5-2.zip" class="uri">md5-2.zip</a></p></li>
<li><p>LZ4 archives <a href="md5-1.lz4" class="uri">md5-1.lz4</a> / <a href="md5-2.lz4" class="uri">md5-2.lz4</a></p></li>
<li><p>Zstandard archives <a href="md5-1.zstd" class="uri">md5-1.zstd</a> / <a href="md5-2.zstd" class="uri">md5-2.zstd</a></p></li>
</ul>
<h2 id="hashclash">HashClash</h2>
<ul>
Expand Down
3 changes: 3 additions & 0 deletions examples/free/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Tiny, copyright-free, PII-free collision PoCs

- ZIP [md5-1.zip](md5-1.zip) / [md5-2.zip](md5-2.zip)

- LZ4 archives [md5-1.lz4](md5-1.lz4) / [md5-2.lz4](md5-2.lz4)
- Zstandard archives [md5-1.zstd](md5-1.zstd) / [md5-2.zstd](md5-2.zstd)


## HashClash

Expand Down
4 changes: 4 additions & 0 deletions examples/free/free.md5
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ f838cf5d58e4d52f9912d465a0789146 *md5-s2.png
5a5d453477ecce413a48a7f4dd60675b *md5-2.xps
2b980a3708ff9edfdd6c8dfbb42e4f8d *md5-1.zip
2b980a3708ff9edfdd6c8dfbb42e4f8d *md5-2.zip
2cabfd771228c648d97c505a8b184476 *md5-2.lz4
2cabfd771228c648d97c505a8b184476 *md5-1.lz4
f17b6da9f5ba8e9e04510fff93a67aec *md5-1.zstd
f17b6da9f5ba8e9e04510fff93a67aec *md5-2.zstd
Binary file added examples/free/md5-1.lz4
Binary file not shown.
Binary file added examples/free/md5-1.zstd
Binary file not shown.
Binary file added examples/free/md5-2.lz4
Binary file not shown.
Binary file added examples/free/md5-2.zstd
Binary file not shown.
105 changes: 105 additions & 0 deletions scripts/zstd-lz4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env python3

# Reusable MD5 collision for Zstd/Lz4 files
# via pre-computed UniColl prefixes

# Ange Albertini 2023

import argparse
import hashlib
import sys

parser = argparse.ArgumentParser(
description="Generate Zstd/lz4 MD5 collisions.")
parser.add_argument('file1', help="first 'top' input file.")
parser.add_argument('file2', help="second 'bottom' input file.")

args = parser.parse_args()
filename_a = args.file1
filename_b = args.file2


def check_magic(contents):
magics = [
[0x28, 0xB5, 0x2F, 0xFD], # Zstd Magic
[0x04, 0x22, 0x4D, 0x18], # LZ4 Magic
[0x50, 0x2A, 0x4D, 0x18], # Skippable Frame 0
[0x51, 0x2A, 0x4D, 0x18], # Skippable Frame 1
[0x52, 0x2A, 0x4D, 0x18], # Skippable Frame 2
[0x53, 0x2A, 0x4D, 0x18], # Skippable Frame 3
[0x54, 0x2A, 0x4D, 0x18], # Skippable Frame 4
[0x55, 0x2A, 0x4D, 0x18], # Skippable Frame 5
[0x56, 0x2A, 0x4D, 0x18], # Skippable Frame 6
[0x57, 0x2A, 0x4D, 0x18], # Skippable Frame 7
[0x58, 0x2A, 0x4D, 0x18], # Skippable Frame 8
[0x59, 0x2A, 0x4D, 0x18], # Skippable Frame 9
[0x5A, 0x2A, 0x4D, 0x18], # Skippable Frame A
[0x5B, 0x2A, 0x4D, 0x18], # Skippable Frame B
[0x5C, 0x2A, 0x4D, 0x18], # Skippable Frame C
[0x5D, 0x2A, 0x4D, 0x18], # Skippable Frame D
[0x5E, 0x2A, 0x4D, 0x18], # Skippable Frame E
[0x5F, 0x2A, 0x4D, 0x18], # Skippable Frame F
]
for magic in magics:
if contents.startswith(bytearray(magic)):
return True
else:
return False


def makeSkipFrameHdr(l):
return bytearray([0x50, 0x2A, 0x4D, 0x18]) + l.to_bytes(4, "little")


def makeSkipFrame(contents):
return makeSkipFrameHdr(len(contents)) + contents


with open(filename_a, "rb") as f:
contents_a = f.read()

if check_magic(contents_a) == False:
print("Error: File B (%s) is not a valid Zstandard/LZ4 file." %
(filename_b))
sys.exit(1)

with open(filename_b, "rb") as f:
contents_b = f.read()

if check_magic(contents_a) == False:
print("Error: File B (%s) is not a valid Zstandard/LZ4 file." %
(filename_b))
sys.exit(1)

with open('zstdlz4-s.bin', "rb") as f:
prefix_s = f.read()
with open('zstdlz4-l.bin', "rb") as f:
prefix_l = f.read()
assert hashlib.md5(prefix_s).digest() == hashlib.md5(prefix_l).digest()
assert hashlib.sha1(prefix_s).digest() != hashlib.sha1(prefix_l).digest()

## 0C0: MMMM 1111 .... ....
## 1C0: AA..AA MMMM 2222 BB..BB

suffix = b"".join([
makeSkipFrameHdr(0xF8 + len(contents_a) + 8),
b"\0" * (0xF8),
contents_a,
makeSkipFrame(contents_b),
])

coll_s = prefix_s + suffix
coll_l = prefix_l + suffix
assert hashlib.md5(coll_s).digest() == hashlib.md5(coll_l).digest()
assert hashlib.sha1(coll_s).digest() != hashlib.sha1(coll_l).digest()

hash = hashlib.md5(coll_s).hexdigest()[:8]
cn1 = "coll1-%s.zstd" % hash
cn2 = "coll2-%s.zstd" % hash

with open(cn1, "wb") as f:
f.write(coll_s)
with open(cn2, "wb") as f:
f.write(coll_l)

print("Collision successful: %s / %s" % (cn1, cn2))
Binary file added scripts/zstdlz4-l.bin
Binary file not shown.
Binary file added scripts/zstdlz4-s.bin
Binary file not shown.

0 comments on commit 9680603

Please sign in to comment.