Hackvent 2024: Day 2

Hackvent 202450

[HV24.02] Meet me at the bar

December. Nightshift at Santa's gift factory. Gifts still to be wrapped are starting to pile up as they cannot be processed any further. One elve still on duty sent Santa some pictures asking for help as all the other elves are already celebrating at the bar. The elve has problems decrypting the pictures as it is still learning the alphabet.

Analyze the images and get the flag.
Flag format: HV24{}
sha256sum of the attachment: cdf6da7570730dfeed9eabb21bfc438cf594b54b24599a67c7b6f41718098fd8

This challenge was written by lu161. He stole a lot of chocolate bars and ended up behind bars.
Hackvent 2024 - Day 2 - Help zip

Solution

We are presented with a ZIP file containing 9 PNG images of barcodes.

Hackvent 2024 - Day 2 - Example Barcode Files

The files are named example1, example 2, etc.

Notice how some files contain 2 barcodes but others contain only 1 barcode.

By doing some light research and visual comparisons, we quickly discover that each barcode is an EAN-8 barcode.

We attempt to scan a barcode, but the scan either fails or produces inconsistent results because each barcode has a section of lines missing. The EAN-8 standard consists of a starting guard zone (101), followed by 4 digits (each represented as a 7-bit binary), a central guard zone (01010), another 4 digits, and finally an ending guard zone (101).

Upon manual inspection, we observe that all guard zones are intact. Additionally, only one digit is missing or omitted from each barcode. Furthermore, by manually scanning some of the codes or parsing the binary, we observe that each barcode starts with the digits 1337 (or 'leet').

At this stage, we decide to identify the digits in each barcode. We pre-process our image files to make processing via script easier. Notably, we crop each image so there is one barcode per file and rename the files appropriately.

Hackvent 2024 - Day 2 - Pre-processing EAN8 barcodes

Now, we must write a script to determine each EAN-8 code. The standard specifies that a checksum exists (the last digit of the code). This checksum allows us to deterministically determine the other missing digit regardless of where it is missing from.

Therefore, we write the following Python script to help us solve the problem.

# Hackvent 2024 - Day 2
# Mo Beigi
#
# Scan, validate and correct EAN-8 barcodes

import os
from PIL import Image

def parse_ean8_binary(binary_string):
    missing_segment = "0" * 7

    # Encoding table
    left_table = {
        "0001101": "0", "0011001": "1", "0010011": "2",
        "0111101": "3", "0100011": "4", "0110001": "5",
        "0101111": "6", "0111011": "7", "0110111": "8",
        "0001011": "9"
    }
    right_table = {
        "1110010": "0", "1100110": "1", "1101100": "2",
        "1000010": "3", "1011100": "4", "1001110": "5",
        "1010000": "6", "1000100": "7", "1001000": "8",
        "1110100": "9"
    }

    # Ignore the prefix guard zone (101)
    if not binary_string.startswith("101"):
        raise ValueError("Invalid EAN-8 barcode: missing prefix guard zone.")
    binary_string = binary_string[3:]

    # Read the first 4 digits using the left encoding table
    left_digits = ""
    for i in range(4):
        segment = binary_string[i * 7:(i + 1) * 7]
        if segment == missing_segment:
            left_digits += "?"
        elif segment in left_table:
            left_digits += left_table[segment]
        else:
            raise ValueError(f"Invalid left binary segment: {segment}")

    # Ignore the middle guard zone (01010)
    if not binary_string[28:33] == "01010":
        raise ValueError("Invalid EAN-8 barcode: missing middle guard zone.")
    binary_string = binary_string[33:]

    # Read the last 4 digits using the right encoding table
    right_digits = ""
    for i in range(4):
        segment = binary_string[i * 7:(i + 1) * 7]
        if segment == missing_segment:
            right_digits += "?"
        elif segment in right_table:
            right_digits += right_table[segment]
        else:
            raise ValueError(f"Invalid right binary segment: {segment}")

    # Ignore the suffix guard zone (101)
    if not binary_string[28:] == "101":
        raise ValueError("Invalid EAN-8 barcode: missing suffix guard zone.")

    return left_digits + right_digits  

def is_black(pixel):
    return pixel == (0, 0, 0, 255) or pixel == 0

def barcode_to_binary(file_path):
    img = Image.open(file_path)
    pixels = img.load()

    barcode_binary = ""

    width, height = img.size
    start_index = 0
    height_index = 10 # some offset to ensure we read a valid row
    pixel_width = 2  # Each line in the barcode is this wide
    ean8_width = 67

    # Increment pixel by pixel to find the first black pixel 
    # marking the initial '101' gaurd zone
    for x in range(width):
        pixel = pixels[x, height_index]

        if is_black(pixel):
            start_index = x
            break
    
    # Process rest of barcode
    end_index = start_index + (ean8_width * pixel_width)
    for x in range(start_index, end_index, pixel_width):
        pixel = pixels[x, height_index]

        if is_black(pixel):
            barcode_binary += "1"
        else:
            barcode_binary += "0"
    return barcode_binary

def brute_force_missing_digit(ean8_code):
    """
    Brute-force the missing digit in the barcode 
    and validate using checksum.
    """
    for digit in range(10):
        potential_code = ean8_code.replace("?", str(digit))
        if validate_ean8_checksum(potential_code):
            return potential_code, digit
    return None

def validate_ean8_checksum(barcode):
    """
    Validate the EAN-8 checksum for a given barcode.
    """
    if len(barcode) != 8 or not barcode.isdigit():
        return False
    odd_sum = sum(int(barcode[i]) for i in range(0, 7, 2))
    even_sum = sum(int(barcode[i]) for i in range(1, 7, 2))
    checksum = (odd_sum * 3 + even_sum) % 10
    checksum = (10 - checksum) % 10
    return checksum == int(barcode[-1])

def process_all_barcodes():
    for file_name in os.listdir():
        if file_name.endswith(".png"):
            example, part = file_name.split("_")[0], file_name.split("_")[1].split(".")[0]
            barcode_binary = barcode_to_binary(file_name)
            ean8_code = parse_ean8_binary(barcode_binary)
            bruteforced_ean8_code, missing_digit = brute_force_missing_digit(ean8_code)
            
            print("-" * 40)
            print(f"File: {file_name}")
            print(f"Example: {example}, Part: {part}")
            print(f"Barcode binary: {barcode_binary}")
            print(f"EAN-8 Code: {ean8_code}")
            print(f"Bruteforced final EAN-8 Code: {bruteforced_ean8_code}")
            print(f"Missing digit: {missing_digit}")
            print("-" * 40)

# Run the script
process_all_barcodes()

Running this code produces the following output:

----------------------------------------
File: 1_1.png
Example: 1, Part: 1
Barcode binary: 1010000000011110101111010111011010101100110100001010000101000010101
EAN-8 Code: ?3371333
Bruteforced final EAN-8 Code: 13371333
Missing digit: 1
----------------------------------------
----------------------------------------
File: 1_2.png
Example: 1, Part: 2
Barcode binary: 1010011001011110101111010111011010101001000000000011011001010000101
EAN-8 Code: 13378?26
Bruteforced final EAN-8 Code: 13378226
Missing digit: 2
----------------------------------------
----------------------------------------
File: 2_1.png
Example: 2, Part: 1
Barcode binary: 1010011001011110101111010111011010101001110101110010010000000000101
EAN-8 Code: 1337548?
Bruteforced final EAN-8 Code: 13375485
Missing digit: 5
----------------------------------------
----------------------------------------
File: 3_1.png
Example: 3, Part: 1
Barcode binary: 1010011001011110101111010111011010101001000110110000000001010000101
EAN-8 Code: 133782?6
Bruteforced final EAN-8 Code: 13378226
Missing digit: 2
----------------------------------------
----------------------------------------
File: 3_2.png
Example: 3, Part: 2
Barcode binary: 1010011001011110101111010111011010101101100100001010000100000000101
EAN-8 Code: 1337233?
Bruteforced final EAN-8 Code: 13372330
Missing digit: 0
----------------------------------------
----------------------------------------
File: 4_1.png
Example: 4, Part: 1
Barcode binary: 1010011001011110101111010111011010101110010111001010000100000000101
EAN-8 Code: 1337003?
Bruteforced final EAN-8 Code: 13370039
Missing digit: 9
----------------------------------------
----------------------------------------
File: 5_1.png
Example: 5, Part: 1
Barcode binary: 1010011001011110101111010111011010100000000100111010111001001110101
EAN-8 Code: 1337?545
Bruteforced final EAN-8 Code: 13372545
Missing digit: 2
----------------------------------------
----------------------------------------
File: 5_2.png
Example: 5, Part: 2
Barcode binary: 1010011001011110101111010111011010101110010000000011100101001000101
EAN-8 Code: 13370?08
Bruteforced final EAN-8 Code: 13370008
Missing digit: 0
----------------------------------------
----------------------------------------
File: 6_1.png
Example: 6, Part: 1
Barcode binary: 1010011001011110101111010111011010100000000110110010000101011100101
EAN-8 Code: 1337?234
Bruteforced final EAN-8 Code: 13371234
Missing digit: 1
----------------------------------------
----------------------------------------
File: 6_2.png
Example: 6, Part: 2
Barcode binary: 1010011001011110101111010111011010101000100100010010001000000000101
EAN-8 Code: 1337777?
Bruteforced final EAN-8 Code: 13377779
Missing digit: 9
----------------------------------------
----------------------------------------
File: 7_1.png
Example: 7, Part: 1
Barcode binary: 1010011001011110101111010111011010101001110110011010001000000000101
EAN-8 Code: 1337517?
Bruteforced final EAN-8 Code: 13375171
Missing digit: 1
----------------------------------------
----------------------------------------
File: 7_2.png
Example: 7, Part: 2
Barcode binary: 1010011001011110101111010111011010100000000100001011011001000100101
EAN-8 Code: 1337?327
Bruteforced final EAN-8 Code: 13374327
Missing digit: 4
----------------------------------------
----------------------------------------
File: 8_1.png
Example: 8, Part: 1
Barcode binary: 1010011001011110101111010111011010101001110110011010001000000000101
EAN-8 Code: 1337517?
Bruteforced final EAN-8 Code: 13375171
Missing digit: 1
----------------------------------------
----------------------------------------
File: 8_2.png
Example: 8, Part: 2
Barcode binary: 1010011001011110101111010111011010101110100111010011101000000000101
EAN-8 Code: 1337999?
Bruteforced final EAN-8 Code: 13379995
Missing digit: 5
----------------------------------------
----------------------------------------
File: 9_1.png
Example: 9, Part: 1
Barcode binary: 1010011001011110101111010111011010100000000100111010111001001110101
EAN-8 Code: 1337?545
Bruteforced final EAN-8 Code: 13372545
Missing digit: 2
----------------------------------------
----------------------------------------
File: 9_2.png
Example: 9, Part: 2
Barcode binary: 1010011001011110101111010111011010101011100000000011011001000100101
EAN-8 Code: 13374?27
Bruteforced final EAN-8 Code: 13374327
Missing digit: 3
----------------------------------------

We decide to collect the missing digits for each original image and put them together. This gives us:

1) 12
2) 5
3) 20
4) 9
5) 20
6) 19
7) 14
8) 15
9) 23

Knowing that we need to generate a flag, we try and map each digit to an alpha character. For example, 1 = A, 2 = B etc. This produces the string LETITSNOW. We put this into the flag format to solve todays challenge.

Flag:

HV24{LETITSNOW}

Leave a comment

(required)(will not be published)(required)


Comments

There are no comments yet. Be the first to add one!