Skip to content

Instantly share code, notes, and snippets.

@BillyDoesDev
Last active September 8, 2022 08:32
Show Gist options
  • Save BillyDoesDev/f13e4f9fa3ae57fc58be72e8e1b64671 to your computer and use it in GitHub Desktop.
Save BillyDoesDev/f13e4f9fa3ae57fc58be72e8e1b64671 to your computer and use it in GitHub Desktop.
ctf-challenge1

Dependencies

numpy==1.23.2
Pillow==9.2.0
pyzbar==0.1.9
qrcode==7.3.1

Note

  • Depending on your system, you might need to have zbar installed on your system to get the scripts to work.
  • You must also have hashcat installed.

Getting started

Once you have the encrypted image ready, you can extract the qr grid hidden within it using any tool of your choice. There are a lot of great websites to get this done as well, such as https://www.aperisolve.com/.

Finally, run decode.py on the qr grid that you just extracted and enjoy!

##NOTE: run this script only after you've extracted the qr codes from the encoded image
import subprocess
import os
from PIL import Image, ImageOps
from pyzbar.pyzbar import decode # you also need to have zbar itself installed
def decode_(qr_grid_path: str, qr_codes_on_each_side: int, wordlist_path: str, inverted=True) -> tuple:
with Image.open(qr_grid_path).convert("L") as im:
side, logs = im.height // qr_codes_on_each_side, []
## put all the extracted qr data inside `logs`
for y in range(0, im.height, side):
for x in range(0, im.width, side):
logs.append(
decode(
ImageOps.scale(
image=ImageOps.invert(im.crop((x, y, side + x, side + y))) if inverted else im.crop((x,y, side+x,side+y)),
factor=512 / side,
resample=Image.Resampling.NEAREST,
)
)[0].data.decode("ascii")
)
data = {} # coords : hash
for line in logs:
raw = line.split(") = ")
data[tuple(int(_) for _ in raw[0].strip("(").split(","))] = raw[1].strip()
try:
os.mkdir("./temp")
except FileExistsError:
pass
with open("./temp/hashes.txt", mode="w", encoding="utf-8") as hashes_, open(
"./temp/0sN1s.rule", mode="w", encoding="utf-8"
) as rule_:
for hash_ in data.values():
hashes_.write(hash_ + "\n")
rule_.write("^ ^1\n^ ^0")
crack_hashes_command = [
"hashcat",
"-O", # Optimised Kernel
"-m", # type of hash
"1400", # SHA2-256
os.path.join("temp", "hashes.txt"), # file containing all the hashes
wordlist_path, # path to wordlist
"-r", # use rule
os.path.join("temp", "0sN1s.rule"), # path to rule
]
subprocess.run(crack_hashes_command, check=None) # crack the hashes using `hashcat` (make sure it's in your PATH)
data_inv = {} # hash : cracked stuff
for line in subprocess.run(
crack_hashes_command + ["--show"],
check=None,
capture_output=True,
text=True,
).stdout.splitlines():
raw = line.split(":")
if raw[1][0] in "01":
data_inv[raw[0]] = raw[1][0]
else:
data_inv[raw[0]] = bytes.fromhex(raw[1][5:-1]).decode()[0]
with Image.new(
mode="RGB", size=(qr_codes_on_each_side, qr_codes_on_each_side)
) as im:
pixels = []
for y in range(im.height):
for x in range(im.width):
color = 255 * int(data_inv[data[(x, y)]])
pixels.append((color, color, color))
im.putdata(pixels)
__import__("shutil").rmtree("temp") # remove the `temp` dir
return (
(
decode(
ImageOps.scale(
image=im,
factor=512 / qr_codes_on_each_side,
resample=Image.Resampling.NEAREST,
)
)[0].data.decode("ascii")
),
im,
)
if __name__ == "__main__":
decoded_text, final_qr = decode_(
qr_grid_path="qr_codes.png",
qr_codes_on_each_side=25,
wordlist_path="rockyou.txt",
inverted=True
)
print(f"\n\nDecoded Text:\n{decoded_text}")
import hashlib
import random
import subprocess
import qrcode
from PIL import Image, ImageOps
def create_qr(data: str, box_size=10, border=4) -> Image: # returns in BnW
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L, # About 7% or less errors can be corrected.
box_size=box_size,
border=border,
)
qr.add_data(data)
qr.make(fit=True)
return qr.make_image(fill_color="black", back_color="white").convert("L")
def generate_qr_grid(password, wordlist_path, invert=False, verify_hashes=True) -> Image: # returns in BnW
with open(wordlist_path, mode="rb") as f:
password_qr = create_qr(data=password, box_size=1, border=2)
passwords = f.readlines()
hashes_with_coordinates = []
with open(".0sN1s.rule", mode="w", encoding="utf-8") as rule_:
rule_.write("^ ^1\n^ ^0")
for y in range(password_qr.height):
for x in range(password_qr.width):
while True:
data_to_be_hashed = (
f"{password_qr.getpixel((x, y)) // 255} "
+ random.choice(passwords)[:-1].decode()
)
if not verify_hashes:
break
crack_hashes_command = [
"hashcat",
"-O", # Optimised Kernel
"-m", # type of hash
"1400", # SHA2-256
hashlib.sha256(data_to_be_hashed.encode()).hexdigest(), # hash to crack
wordlist_path, # path to wordlist
"-r", # use rule
".0sN1s.rule", # path to rule
]
# crack the hashes using `hashcat` (make sure it's in your PATH)
# see if the hash is actually crackable or not
try:
subprocess.run(
crack_hashes_command,
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
break
except subprocess.CalledProcessError:
continue
hashes_with_coordinates.append(
f"({x}, {y}) = "
+ hashlib.sha256(data_to_be_hashed.encode()).hexdigest()
# + f":{data_to_be_hashed}"
)
print(f"Passed ({x}, {y})")
__import__("os").remove(".0sN1s.rule")
side = create_qr(data=hashes_with_coordinates[-1], box_size=1, border=2).width
random.shuffle(hashes_with_coordinates)
with Image.new(
mode="L",
color=255,
size=(side * password_qr.width, side * password_qr.width),
) as im:
index = 0
for y in range(0, im.height, side):
for x in range(0, im.width, side):
im.paste(
create_qr(
data=hashes_with_coordinates[index],
box_size=1,
border=2,
),
(x, y),
)
index += 1
return ImageOps.invert(im) if invert else im
def encrypt(password, src_img_path: str, wordlist_path: str, invert=False, verify_hashes=True) -> Image:
# hides the qr code grid inside the red channel
# of the source image
qr_grid = generate_qr_grid(
password=password,
wordlist_path=wordlist_path,
invert=invert,
verify_hashes=verify_hashes,
)
src_img = Image.open(src_img_path).convert("RGB")
if src_img.width < qr_grid.width or src_img.height < qr_grid.height:
src_img = src_img.resize((qr_grid.width, qr_grid.height))
pixels = []
for y in range(src_img.height):
for x in range(src_img.width):
try:
red_channel = int(
bin(src_img.getpixel((x, y))[0])[:-1]
+ str(qr_grid.getpixel((x, y)) // 255),
base=2,
)
except IndexError:
red_channel = src_img.getpixel((x, y))[0]
pixels.append(
(
red_channel,
src_img.getpixel((x, y))[1],
src_img.getpixel((x, y))[2],
)
)
src_img.putdata(pixels)
return src_img
if __name__ == "__main__":
generate_qr_grid(
password="<<<spam>>>",
wordlist_path="../rockyou.txt",
invert=True,
verify_hashes=False,
).save("qr_codes.png")
# encrypt(
# password="<<<spam>>>",
# src_img_path="lol.png",
# wordlist_path="rockyou.txt",
# invert=True,
# verify_hashes=False,
# ).save("encr.png")
## ~$ hashcat -O -m 1400 hashes.txt rockyou.txt -r 0sN1s.rule # Do this to crack the hashes effectively
##0sN1s.rule:
##^ ^1
##^ ^0
##well.. these rules exist in dive.rule, but that's just too many rules and this custom ruleset really speeds things up (duh)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment