Hackvent 2024: Day 15

Hackvent 202460

[HV24.15] Rudolph's Symphony

Rudolph has decided to organise a music concert as a special surprise for Santa this Christmas!

Four of the other reindeer have eagerly signed up to perform, but things aren't going as smoothly as planned. Unfortunately, the reindeer have been pranking each other and as a result they are having issues preparing their material.

Can you step in and help each reindeer get ready in time for the big event?


Analyze the resource and get the flag.
Flag format: HV24{}
sha256sum of rudolphs-symphony.zip: 8b1d38d37e8172e2c2694d52afc29de66ddb8fef760224cd6de4a4b39d8ee673

This challenge was written by mobeigi. Big effort for the big challenge.

File:

Hackvent 2024 - Day 15 - rudolphs-symphony.zip

This challenge was also written by myself!

Solution

Initial Analysis

We are presented with a zip file that contains four folders, one for each of the reindeer that have signed up to perform. The reindeer are Blitzen, Prancer, Comet, and Dancer.

Let's list all the files in all folders:

$ ls -LR

.:
Blitzen  Comet  Dancer  Prancer

./Blitzen:
blitzen-passwords.kdbx  flag.txt  update.txt

./Comet:
update.jpg

./Dancer:
update.pdf

./Prancer:
Chrome.zip  update.md

Note that each reindeer has an update document (in varying file formats). Some reindeer also have additional files.

Blitzen

We begin the challenge by looking at Blitzen's files. There are other potential starting points, but this one is hinted at as the intended starting point by the CTF author.

To begin, we read Blitzen's update.txt:

Greetings Rudolph,

I am excited to sing for everyone at the concert! I've been working on my vocals all year round. I feel like it will really bring all the performances together. It's going to be a really fun party!

I have hidden my top secret lyrics in my military strength password manager but I forgot the master password which unlocks it :(

Luckily, I thought something like this might happen so I keep my master password in my flag.txt file. However, some jokster of a Reindeer has come along and filled this file with lots and lot of fakes. To make things worse, it looks like they've also somehow changed the real master password because I tried every single password I found in the file and all of them were incorrect.

Once I get back into my password manager, I'll do one final vocal training session before the big day!

Take care,
- Blitzen

We need to access their KeePass 2 safe (blitzen-passwords.kdbx), but it is password-encrypted, and we don't know the password. Blitzen tells us that they keep their master password in their flag.txt file. Upon inspection, we see that the file contains many fake flags in the expected HV24{...} format. We also notice that some flags appear to be mutated.

The mutations we observe in the flag contents are:

  • Base64 encoding
  • Hex encoding
  • Reversed string

There are two methods to solve this part of the challenge.

Method 1: Brute-forcing the password

Since we know the real flag exists in flag.txt and has simply been mutated, we can write a brute-force script to try each flag candidate. For each flag candidate, we would also attempt to reverse each mutation. There are 1672 candidates and 3 mutations, giving us a total of 4 * 1672 = 6688 passwords to try.

This method is left as an exercise for the reader.

Method 2: Hidden QR code

The other approach is to find the hidden QR code in the file. Notice that the file contains 57 lines. If you open the text file in a hex editor, you will observe the presence of many NUL characters on each line, which is not normal for a text file. Furthermore, some of the flags mention QR as a hint that there could be a QR code hidden in this data.

As it turns out, this file is hiding a 57x57 QR code. If we treat every flag in the HV24{...} format as a 1 and every NUL character as a 0, we get a valid 57x57 QR code.

We use the following script to build the QR code image:

from PIL import Image

def generate_qr_code(input_file, output_file):
    """
    Generate a QR code image directly from a binary representation where '1' = black pixel, '0' = white pixel.

    :param input_file: Path to the input file containing the binary representation (57x57 matrix).
    :param output_file: Path to save the generated QR code image.
    """
    qr_matrix = []

    # Read the binary matrix from the input file
    with open(input_file, "rb") as file:
        for line in file:
            binary_row = ""
            i = 0
            while i < len(line):
                # Detect flags
                if line[i:i+5] == b"HV24{":
                    # Read until the closing '}'
                    end_idx = line.find(b"}", i)
                    if end_idx != -1:
                        binary_row += "1"
                        i = end_idx + 1  # Move past the '}'
                    else:
                        raise ValueError("Malformed flag detected")
                elif line[i:i+1] == b"\x00":
                    binary_row += "0"
                    i += 1
                else:
                    # Skip unknown characters
                    i += 1
            if len(binary_row) != 57:
                raise ValueError(f"Line length {len(binary_row)} does not match 57 binary digits.")
            qr_matrix.append(list(map(int, binary_row)))

    if len(qr_matrix) != 57:
        raise ValueError(f"File does not contain 57 rows for a 57x57 QR code.")

    # Create an image from the binary matrix
    scale = 10  # Scale factor for enlarging the QR code
    img_size = 57 * scale
    img = Image.new("1", (img_size, img_size), 1)  # "1" mode for 1-bit pixels, default to white

    pixels = img.load()

    for y, row in enumerate(qr_matrix):
        for x, value in enumerate(row):
            color = 0 if value == 1 else 1  # 0 = black, 1 = white
            for dy in range(scale):
                for dx in range(scale):
                    pixels[x * scale + dx, y * scale + dy] = color

    # Save the QR code as an image
    img.save(output_file)
    print(f"QR code saved to {output_file}")


# Input and output paths
input_file = "flag.txt"  # The input file containing 'HV24{...}' and '\x00'
output_file = "qr-code.png"

# Generate the QR code
generate_qr_code(input_file, output_file)

This gives us the following QR code:

Hackvent 2024 - Day 15 - Blitzen QR Code

Scanning this QR codes gives us the text:

gur znfgre cnffjbeq lbh ner ybbxvat sbe vf gur 1336gu bar ohg jvgu pbagragf erirefrq! Nyjnlf rkcrpg gur bss-ol-bar reebe!

Using character analysis, you might be able to tell that this is English. This text decodes using ROT13 to:

the master password you are looking for is the 1336th one but with contents reversed! Always expect the off-by-one error!

We'll use another script to retrieve the 1336th flag and reverse its contents:

def get_1336th_flag(file_path):
    """
    Extracts the 1336th 'HV24{...}' flag from the file, reverses its contents, and prints it.

    :param file_path: Path to the input file containing the flags.
    """
    flags = []

    with open(file_path, "rb") as file:
        for line in file:
            # Find all occurrences of 'HV24{...}' in the line
            start = 0
            while (start := line.find(b"HV24{", start)) != -1:
                end = line.find(b"}", start)
                if end != -1:
                    flag = line[start:end + 1]  # Extract the full flag
                    flags.append(flag)
                    start = end + 1  # Move past the current flag
                else:
                    break

    # Ensure there are enough flags in the file
    if len(flags) < 1336:
        raise ValueError(f"The file contains only {len(flags)} flags, less than the required 1336th flag.")

    # Get the 1336th flag
    flag_1336 = flags[1335]  # Index is 1335 because lists are 0-based
    reversed_content = flag_1336[5:-1][::-1]  # Reverse the content inside 'HV24{...}'

    # Print the result
    print("HV24{" + reversed_content.decode("utf-8") + "}")


# Input file path
input_file = "flag.txt"  # Replace with the actual file name

# Run the function
get_1336th_flag(input_file)

This prints out: HV24{#?#?#?#ToG3th3r#}

This provides us with the password for the KeePass safe and Blitzen's flag component.

KeePass Safe

We open the KeePass safe using the password HV24{#?#?#?#ToG3th3r#}:

Hackvent 2024 - Day 15 - Blitzen KeyPass Safe

There are various folders in the safe with a few entries.

Notably, we study the following entries:

  • Generalmaster_password
    • Password: HV24{#?#?#?#ToG3th3r#}
    • Note: My personal secure password. Rumour has it all of the other Reindeer have similar ones for themselves.
  • WindowsWindows 11
    • Username: blitzen
    • Password: Blitzen1994
    • Note: Santa tells me he often finds many of the reindeers passwords used online by other people. I guess its because some of the reindeer like to include their own names in their passwords.
  • Recycle Binprancers keys:
    • File: Prancers-keys.zip
    • Note: There can only be one great vocalist this year and its going to be me.

We observe that each reindeer seems to have their own flag components in the format: HV24{#?#?#?#?#}, where one of the question marks is replaced by a secret word. We predict that the final flag for this challenge will be a combination of all four flag components.

Another interesting note is that Blitzen used their own name as part of their Windows password. They also mention that other reindeer like to do the same. If you are especially observant, you will notice that Blitzen1994 is a password found in the rockyou.txt wordlist.

Finally, we notice a very suspicious prancers-keys.zip file, which we save for later.

Prancer

Let's begin by reading Prancer's update:

## Prancer's Update

YO YO RUDOLPH!

How's that famous red nose! Still shining bright I hope!

For my performance, in your honour, I am going to rap this masterpiece:
https://www.youtube.com/watch?v=B4W_XYI4AN0

All my practice was going well! Until I went to visit the vet because when I came back my web browser was broken!@#$
My browser preferences were completely gone! Luckily, I found the browser related files they tried to delete in my recycle bin and I restored everything. Everything seems to work again now expect my saved passwords...I can't seem to figure out why. I checked the security tapes and it showed Blitzen leaving the scene of the crime! Trying to interfere with my practice so they can outshine me haha.

Oh and one more thing, I found out that one of the other Reindeer had a suspicious file on their computer. After Santa told us about that nasty Ransomware that infected his computer, I am taking no chances. I scanned the file for viruses but all of the anti-virus softwares think it is clean. But you should never risk things like this so I deleted the file permanently to keep everyone safe. :)

Anyway, this will be the best event ever thanks to you!
YOU'LL GO DOOOOWN IN HISTA, YOU'LL GO DOOOOWN IN HISTA, YOU'LL GO DOOOOWN IN HISTOOORY!

Rock on,
Prancer

Prancer tells us they saw Blitzen messing with them by trying to break their web browser. We are given a Chrome.zip file containing Prancer's Chrome user data directory. Prancer mentions that everything seems to work again now, except for their saved passwords.

Cracking Chrome Passwords protected using Windows DPAPI

The ZIP file (prancers-keys.zip) from earlier is a DPAPI archive of Prancer's master key on a Windows-based machine. It contains a file at:
C\Users\prancer\AppData\Roaming\Microsoft\Protect\S-1-5-21-3152064623-1017805262-467371474-1001\c790ec71-1c98-404a-9dab-4bfe8f6871a5,
which is the directory used to store DPAPI master keys.

Blitzen must have deleted these files from Prancer's machine, causing Prancer's saved passwords on Chrome to fail the decryption process. We decide to use DB Browser for SQLite with the Chrome/Default/Login Data file to explore saved passwords. We find one entry with an encrypted password for Prancer's email. We have a hunch that we need to crack this.

We use the tool mimikatz to crack the Windows DPAPI master password, which is encrypting the Chrome passwords. We use the dpapi::masterkey command, ensuring that we pass in the c790ec71-1c98-404a-9dab-4bfe8f6871a5 file:

$ mimikatz # dpapi::masterkey /in:c790ec71-1c98-404a-9dab-4bfe8f6871a5

**MASTERKEYS**
  dwVersion          : 00000002 - 2
  szGuid             : {c790ec71-1c98-404a-9dab-4bfe8f6871a5}
  dwFlags            : 00000005 - 5
  dwMasterKeyLen     : 000000b0 - 176
  dwBackupKeyLen     : 00000090 - 144
  dwCredHistLen      : 00000014 - 20
  dwDomainKeyLen     : 00000000 - 0
[masterkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : dd9910a8f9dbed99caf0eb98527e4bff
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : 024ed8b5eef41f45a96d52eaeb98c06e75054c5369076328ac49abbc5e956ce6a043adbdd63899646afc1c5820b3d2021d5230846f26250ea92ee8dc7a4691e6fbc6c5c3abae38c0a0b47ae10d146b5a20171cfdb4a8556edb53c4a4cdeeab33ada65f67c3f3131e5c4c057297487d60e85e2de5123daf63171c6a2f5aac00afbd19efa9ec9f3232ed307d7a66f3ed3e

[backupkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : 29f8e374eedee9b6f5d2a7ae8c0b77bc
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : f0aa63b9dc61a1da66de3137d9830bd1996a0bfa9942deb3f16d66d07949cd6070dfdebe3aa7a3e08393e2ec438f28fce6e28380f7f37b656ba5fcd174215bb2ee07850b263b520a0f15637b0fe1ce2863068a68236030ae1d98c0793ade307aa20d136429e48ee0b1bded7fb26bb545

[credhist]
  **CREDHIST INFO**
    dwVersion        : 00000003 - 3
    guid             : {a163913c-885c-42aa-b243-4581bc47fd80}

At this stage, we need to brute-force the password using a popular tool like John or Hashcat. I decided to use Hashcat.

First, we create a Hashcat hash from the available data using the DPAPI example hashes as a guide: Hashcat Example Hashes.

In our case, we want to use mode 15900 (DPAPI masterkey file v2 + local context).

The format will look something like this:

$DPAPImk$<version>*<context>*<SID>*<encryption_algorithm>*<hash_algorithm>*<iterations>*<salt>*<encrypted_masterkey_length>*<encrypted_masterkey>

This is the data we extracted from our dpapi::masterkey results from earlier:

Version: 2
Context: 1 (local)
SID: S-1-5-21-3152064623-1017805262-467371474-1001
encryption_algorithm: aes256
hash_algorithm: sha512
iterations: 8000
salt: dd9910a8f9dbed99caf0eb98527e4bff
encrypted_masterkey_length: 288
encrypted_masterkey: 024ed8b5eef41f45a96d52eaeb98c06e75054c5369076328ac49abbc5e956ce6a043adbdd63899646afc1c5820b3d2021d5230846f26250ea92ee8dc7a4691e6fbc6c5c3abae38c0a0b47ae10d146b5a20171cfdb4a8556edb53c4a4cdeeab33ada65f67c3f3131e5c4c057297487d60e85e2de5123daf63171c6a2f5aac00afbd19efa9ec9f3232ed307d7a66f3ed3e

Substituting our values into the hash format, we obtain the following hash:

$DPAPImk$2*1*S-1-5-21-3152064623-1017805262-467371474-1001*aes256*sha512*8000*dd9910a8f9dbed99caf0eb98527e4bff*288*024ed8b5eef41f45a96d52eaeb98c06e75054c5369076328ac49abbc5e956ce6a043adbdd63899646afc1c5820b3d2021d5230846f26250ea92ee8dc7a4691e6fbc6c5c3abae38c0a0b47ae10d146b5a20171cfdb4a8556edb53c4a4cdeeab33ada65f67c3f3131e5c4c057297487d60e85e2de5123daf63171c6a2f5aac00afbd19efa9ec9f3232ed307d7a66f3ed3e

To effectively brute-force this hash, we use the rockyou.txt wordlist and filter out any password that does not contain the word prancer (case-insensitive), based on our earlier hint about reindeer liking to use their own names in their passwords:

$ cat rockyou.txt | grep -i 'prancer' > wordlist.txt


$ cat wordlist.txt | wc -l
60

This results in 60 potential passwords, making it very fast to brute-force.

We then begin our brute-force using our wordlist.txt in DPAPI masterkey file v2 (context 1 & 2) (15900) mode:

$ hashcat -m 15900 -a 0 hash.txt wordlist.txt

After a few seconds, we successfully crack the password! The Windows password for Prancer is prancer2.

Now, we rerun dpapi::masterkey and include the /password parameter for the Windows password:

$ mimikatz # dpapi::masterkey /in:c790ec71-1c98-404a-9dab-4bfe8f6871a5 /password:prancer2 /sid:S-1-5-21-3152064623-1017805
262-467371474-1001
**MASTERKEYS**
  dwVersion          : 00000002 - 2
  szGuid             : {c790ec71-1c98-404a-9dab-4bfe8f6871a5}
  dwFlags            : 00000005 - 5
  dwMasterKeyLen     : 000000b0 - 176
  dwBackupKeyLen     : 00000090 - 144
  dwCredHistLen      : 00000014 - 20
  dwDomainKeyLen     : 00000000 - 0
[masterkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : dd9910a8f9dbed99caf0eb98527e4bff
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : 024ed8b5eef41f45a96d52eaeb98c06e75054c5369076328ac49abbc5e956ce6a043adbdd63899646afc1c5820b3d2021d5230846f26250ea92ee8dc7a4691e6fbc6c5c3abae38c0a0b47ae10d146b5a20171cfdb4a8556edb53c4a4cdeeab33ada65f67c3f3131e5c4c057297487d60e85e2de5123daf63171c6a2f5aac00afbd19efa9ec9f3232ed307d7a66f3ed3e

[backupkey]
  **MASTERKEY**
    dwVersion        : 00000002 - 2
    salt             : 29f8e374eedee9b6f5d2a7ae8c0b77bc
    rounds           : 00001f40 - 8000
    algHash          : 0000800e - 32782 (CALG_SHA_512)
    algCrypt         : 00006610 - 26128 (CALG_AES_256)
    pbKey            : f0aa63b9dc61a1da66de3137d9830bd1996a0bfa9942deb3f16d66d07949cd6070dfdebe3aa7a3e08393e2ec438f28fce6e28380f7f37b656ba5fcd174215bb2ee07850b263b520a0f15637b0fe1ce2863068a68236030ae1d98c0793ade307aa20d136429e48ee0b1bded7fb26bb545

[credhist]
  **CREDHIST INFO**
    dwVersion        : 00000003 - 3
    guid             : {a163913c-885c-42aa-b243-4581bc47fd80}



[masterkey] with volatile cache: SID:S-1-5-21-3152064623-1017805262-467371474-1001;GUID:{a163913c-885c-42aa-b243-4581bc47fd80};MD4:1e413b40a2ba3a786b1a0361a1eb6822;SHA1:ca183909baa0edd8c03e4bf794c2c7df21586388;
  key : a0b912995883940fac279a75f575c3ead6b037de200e99b52ae2749218568c80a54c602d52a65a991ca93ee830f3d2856c27c7c18ee9f12c657f3e94b6755afd
  sha1: 0b1601b9711202469e94a2188eb40005d26849db

[masterkey] with password: prancer2 (normal user)
  key : a0b912995883940fac279a75f575c3ead6b037de200e99b52ae2749218568c80a54c602d52a65a991ca93ee830f3d2856c27c7c18ee9f12c657f3e94b6755afd
  sha1: 0b1601b9711202469e94a2188eb40005d26849db

This provides us with the master key in hex format: a0b912995883940fac279a75f575c3ead6b037de200e99b52ae2749218568c80a54c602d52a65a991ca93ee830f3d2856c27c7c18ee9f12c657f3e94b6755afd

In the Chrome data directory, we also have access to the Local State files. The Local State file exposes the encrypted_key, which is: RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAABx7JDHmBxKQJ2rS/6PaHGlEAAAABwAAABHAG8AbwBnAGwAZQAgAEMAaAByAG8AbQBlAAAAEGYAAAABAAAgAAAA83EeusVzPjsnBVv3kOCD/WKv+vPupZfVCEgS3AnHVV4AAAAADoAAAAACAAAgAAAAcOiLSJLvHXKfCWrte7WoT1LDS6MibMnPMJphcKpyKIcwAAAAXDXiwe0JEFrieifkUUh/6KGGvIJjel63WP6cE4S34UZvJ2O3ddz1dkrmXi6s88ntQAAAACdkCr/EYzTDN+gooyH/0LhTdOIbmFN+YzIozk+MAc6NvlansMQvrfygFCrRuellkLGskRmnT7qAOAmnb2k9iCE=

We can use the mimikatz dpapi::chrome plugin to crack Chrome passwords.

At a minimum, we must pass in the following parameters:

  • /in - The Default/Login Data file.
  • /state - The Login State file.
  • /encryptedkey - The encrypted key (found in the Local State file).

Since we are not running on Prancer's original Windows system, we must also provide the master key using the /masterkey parameter.

$ mimikatz # dpapi::chrome /in:"C:\Users\Mo\Documents\CTF\Hacking-Lab\Hackvent\2024\day 15\Chrome\Default\Login Data" /state:"C:\Users\Mo\Documents\CTF\Hacking-Lab\Hackvent\2024\day 15\Chrome\Local State" /encryptedkey:RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAABx7JDHmBxKQJ2rS/6PaHGlEAAAABwAAABHAG8AbwBnAGwAZQAgAEMAaAByAG8AbQBlAAAAEGYAAAABAAAgAAAA83EeusVzPjsnBVv3kOCD/WKv+vPupZfVCEgS3AnHVV4AAAAADoAAAAACAAAgAAAAcOiLSJLvHXKfCWrte7WoT1LDS6MibMnPMJphcKpyKIcwAAAAXDXiwe0JEFrieifkUUh/6KGGvIJjel63WP6cE4S34UZvJ2O3ddz1dkrmXi6s88ntQAAAACdkCr/EYzTDN+gooyH/0LhTdOIbmFN+YzIozk+MAc6NvlansMQvrfygFCrRuellkLGskRmnT7qAOAmnb2k9iCE= /masterkey:a0b912995883940fac279a75f575c3ead6b037de200e99b52ae2749218568c80a54c602d52a65a991ca93ee830f3d2856c27c7c18ee9f12c657f3e94b6755afd


> Encrypted Key seems to be protected by DPAPI
 * masterkey     : a0b912995883940fac279a75f575c3ead6b037de200e99b52ae2749218568c80a54c602d52a65a991ca93ee830f3d2856c27c7c18ee9f12c657f3e94b6755afd

> AES Key is: 633084eb0dd8e8d89d1d015428bcb873f3f44d1c982502147f318a60a49dd6c1

URL     : https://accounts.santamail.christmas/ ( https://accounts.santamail.christmas/ )
Username: prancer@santamail.christmas
 * using BCrypt with AES-256-GCM
Password: HV24{#?#?#BEST#?#}

Running this command results in a successful decryption!

We discover that Prancer's password and flag component is: HV24{#?#?#BEST#?#}

Exploring Chrome History

Prancer's update mentions they found a suspicious file on another reindeer's machine. They scanned the file with multiple antivirus software programs, which found it to be safe. Nevertheless, they deleted the file. The lyrics YOU'LL GO DOOOOWN IN HISTA, YOU'LL GO DOOOOWN IN HISTA, YOU'LL GO DOOOOWN IN HISTOOORY! hint at looking into the browsing history.

Since we have access to Prancer's Chrome user data directory, we can explore their browsing history. Looking at the Chrome history file at Default/History reveals some interesting visits.

In particular, we see that Prancer visited this URL:
https://www.virustotal.com/gui/file/5e875d3743c7e69c2b09d9b9556e08ff1992012bb25d73e080d8db3b72fbe9bc

This VirusTotal scan corresponds to a file called Silent_Night_by_Comet.mid, which appears to have been deleted from Comet's machine. The community tab on VirusTotal reveals a comment by a user called Prancer_the_reindeer:

Hackvent 2024 - Day 15 - Prancer's VirusTotal Comment

Luckily, the analysis comment by Prancer links to the sample file for download:
https://drive.google.com/file/d/1CEohyNqduw9hA4P0OUmGxo1Zd6uymptg/view?usp=sharing

We visit this link and download the file. We then validate that the SHA256 hash of the file matches both the VirusTotal hash and the hash found in Prancer's comment:

$ sha256sum Silent_Night_by_Comet.mid

5e875d3743c7e69c2b09d9b9556e08ff1992012bb25d73e080d8db3b72fbe9bc *Silent_Night_by_Comet.mid

Comet

Silent Night MIDI file

For Comet, the flag component is hidden in plain sight but may be tricky to discover. Playing the Silent_Night_by_Comet.mid MIDI file produces the normal Silent Night song. However, the clue lies in the name of the song, Silent Night. There are silent notes embedded in the MIDI file that you cannot hear.

We open the MIDI file in a powerful online MIDI editor called Signal. Many other editors may not be able to handle this due to a limited octave designation range, but some local tools, such as Audacity, are capable of solving it.

Using Signal, we upload the MIDI file for analysis. Scrolling down, we observe something unusual in the C0 octave designation.

The flag component is visible in plain sight, as the notes resemble letters:

Hackvent 2024 - Day 15 - Comet Signal MIDI Flag Component

It spells out: HV24{#?#jAM#?#?#}

Note: A jar of jam was also visible in the original update.jpg image.

This provides us with Comet's flag component.

Hidden files in update.jpg

We use binwalk on Comet's update.jpg file, which extracts four images:

  • One showing a location logo.
  • One showing a Rick Roll image.
  • One showing a locked PDF logo.
  • One showing an XML logo.

This serves as a hint for the password to unlock the update.pdf file in Dancer's folder. The hint suggests that the location of the Rick Roll is key to unlocking the PDF. The XML logo suggests the need to locate an XML file hidden within the binary data.

Some versions of binwalk may fail to locate or extract files like XML files. By using tools such as strings, a hex editor, or other utilities, you should be able to identify an XML file embedded in the raw update.jpg binary.

We then extract the XML file:

<?xml version="1.0" encoding="UTF-8"?>
<GeolocationData>
    <Location>2dUURCy1VnuDyonM1oyKq5VoMjApQabrAXxr</Location>
    <TimeStamp>2024-12-12T14:35:00Z</TimeStamp>
    <Address>
        <StreetNumber>REDACTED</StreetNumber>
        <StreetName>REDACTED</StreetName>
		<RegexpPattern>^\d{1,5}\s[A-Z][a-z]+\s(?:St|Rd|Cl)$</RegexpPattern>
    </Address>
    <Details>
        <Accuracy>5 meters</Accuracy>
        <Elevation>25 meters</Elevation>
        <Provider>GPS</Provider>
    </Details>
</GeolocationData>

The string 2dUURCy1VnuDyonM1oyKq5VoMjApQabrAXxr is a Base58-encoded string.

It decodes to: 51°30'45.09"N, 0°13'8.52"W.

By Googling this, we find it is the location where the Rick Roll video was filmed. Google provides the address: 154 Freston Rd. The XML file also includes an RegexpPattern for the Address, which is: ^\d{1,5}\s[A-Z][a-z]+\s(?:St|Rd|Cl)$. This confirms the correct format for the address.

Therefore, the password for the locked update.pdf file in Dancer's folder is: 154 Freston Rd

Dancer

We first unlock the PDF file using the password: 154 Freston Rd.

Dancer's update on the first page of the PDF file is:

Dancer’s Update

Heeeeey Rudolph!

Are you as excited as I am!

For my performance, I will be playing Jingle Bells on the Guitar!
I know everybody just thought I would be dancing instead because of my name
but I do have other talents.

I’m almost ready but today when I was playing from my music sheet,
I noticed that the song did not sound correct at all.

Argh…one of the other reindeer must be pranking me by changing some of my notes.
Serves me right for messing with Blitzen earlier I suppose.

I’ll try to get a new music sheet from Santa as an early Christmas present.
Otherwise, I won’t be able to get more practice in.

Wishing you a wonderful holiday,
Dancer

Dancer mentions that their music sheet, located on the second page of the PDF, has been tampered with:

Hackvent 2024 - Day 15 - Dancer's Music Sheet

By conducting some research (perhaps by comparing other Jingle Bells music sheets online), we discover the following:

  • The treble clef appears normal and accurately represents the Jingle Bells song.
  • The bass clef, however, is unusual. It has a strange 16/4 time signature (compared to the 4/4 treble signature), and the notes appear suspicious. It seems there is hidden encoded data embedded in this section.

This turns out to be a Music Sheet Cipher. We visit this website for decoding and manually enter each character from the bass section into the decoder. We then attempt to decode the hidden message:

Hackvent 2024 - Day 15 - Dancer Music Sheet Cipher dcode.fr

The first few plaintext results appear to be a series of space-separated integers. These likely represent ASCII characters in decimal form.

Using an online Decimal-to-ASCII converter, we decode the message and obtain: HV24{#ReiNd33r#?#?#?}

This provides us with Dancer's flag component.

Final flag

We now have all 4 flag components:

  • Blitzen: HV24{#?#?#?#ToG3th3r#}
  • Prancer: HV24{#?#?#BEST#?#}
  • Comet: HV24{#?#jAM#?#?#}
  • Dancer: HV24{#ReiNd33r#?#?#?#}

We simply combine the flag components in the correct positions to get the final flag.

Flag:

HV24{#ReiNd33r#jAM#BEST#ToG3th3r#}

Bonus Hidden

This challenge also contained the solution to: [HV24.HH] Frosty's Secret


Leave a comment

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

Comments

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