This is a walk-through for some challenges of the SANS Holiday Hack Challenge 2024.
See my profile 🙂 I wish I had more time to play. My final score was 548th out of more than 19K people.
Greetings, holiday travelers! Welcome to the 2024 SANS Holiday Hack Challenge.
As you explore Christmas Island and the North Pole, make sure you stop by the elf Jingle Ringford along the way. He’ll give you your orientation and snowball badge. Your snowball badge on the center of your avatar will be populated with a series of objectives for the Holiday Hack Challenge. Just click on your badge to see your objectives and more.
Also, please do keep an eye on your badge for updates on the narrative and various happenings around Christmas Island and the North Pole during the event!
Useful Links
- The 2024 SANS Holiday Hack Challenge (SANS)
- SANS Holiday Hack Challenge 2024 (Holiday Hack)
- Status page (Holiday Hack)
- KringleCon (Youtube)
Challenges
Prologue
Welcome back to the Geese Islands! Let’s help the elves pack up to return to the North Pole.
Talk to Jingle, Angel, and Poinsettia about their challenges.Go to Prologue
Holiday Hack Orientation (Gold)
Difficulty: 1/5, Dock: Orientation
Talk to Jingle Ringford on Christmas Island and get your bearings at Geese Islands
Click on Jingle Ringford at Frosty’s Beach.
- Click on the Cranberry pie terminal.
- Click in the upper pane of the terminal.
- Type
answer
and press Enter.
Elf Connect (Gold)
Difficulty: 1/5, Dock: Orientation
Click on Angel Candysalt.
Help Angel Candysalt connect the dots in a game of connections.
Click on the Elf Connect cranberry pie terminal.
Open the browser’s DevTools. Copy the URL from the iframe and open it in a new tab (https://hhc24-elfconnect.holidayhackchallenge.com?…).
<iframe title="challenge" src="https://hhc24-elfconnect.holidayhackchallenge.com?&challenge=termElfConnect&username=[...]"></iframe>
https://hhc24-elfconnect.holidayhackchallenge.com?challenge=termElfConnect&username=<YOUR USERNAME>&id=<ID>&area=staging&location=29,17&tokens=&dna=ATAT...TTAAT
In the DevTools, display the source code of the index page.
let urlParams = new URLSearchParams(window.location.search);
const roundCheck = urlParams.get('round');
if (!roundCheck) { // If 'round' is absent or has no value
sessionStorage.clear();
}
The round is taken from the GET parameter “round”. If not set, the session storage is cleared. Add “&round=4” in the URL and reload the page.
The score (“score”) and the last completed round (“roundCompleted”) are stored in session storage.
let score = parseInt(sessionStorage.getItem('score') || '0'); // Initialize score
let scoreText; // Text object for score display
let highScore = 50000;
let highScoreText; // text object for high score
let roundComplete = sessionStorage.getItem('roundComplete');
if (roundComplete == null) {
roundComplete = 0;
}
Set the score and round in the Console of the browser’s DevTools.
sessionStorage.setItem("score", 60000);
sessionStorage.setItem("roundComplete", 4);
Reload the page (keeping “&round=4” in the URL).
The game answers are in the code.
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 wordBoxes = [];
let selectedBoxes = [];
let correctSets = [
[0, 5, 10, 14], // Set 1
[1, 3, 7, 9], // Set 2
[2, 6, 11, 12], // Set 3
[4, 8, 13, 15] // Set 4
];
- Set 1: [0, 5, 10, 14] : “Tinsel”, “Garland”, “Star”, “Lights”
- Set 2: [1, 3, 7, 9]: “burp”, “OWASP Zap”, “Nikto”, “wfuzz”
- Set 3: [2, 6, 11, 12]: “Symmetric”, “Asymmetric”, “hash”, “hybrid”
- Set 4: [4, 8, 13, 15]: “HTTP”, “FTP”, “SMTP”, “DNS”
Click on the right answers for the 4th round and the challenge should be completed.
Act I
With Santa away, Wombley Cube and Alabaster Snowball have each tried to lead. Surely they won’t mess up the naughty and nice list…
Help Bow, Morcel, and Jewell solve their challenges.
Curling (Silver + Gold)
Difficulty: 1/5, Dock: The Front Yard
Click on Bow Ninecandle.
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!
Silver
Click on the curling game.
Are you ready to begin? [y]es
yes
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.
curl http://curlingfun:8080
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/
curl -k https://curlingfun:9090/
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.
curl -k https://curlingfun:9090/ --data "skip=alabaster"
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.
curl -k https://curlingfun:9090/ -H "Cookie:end=3"
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/
curl -k --head https://curlingfun:9090/
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".
curl -k https://curlingfun:9090/ -H "Stone:Granite"
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
curl -k --path-as-is https://curlingfun:9090/../../etc/hacks
Close the terminal. Click on Bow Ninecandle
Gold
You know… rumor has it you can breeze through this with just three commands. Why don’t you give it a whirl?
Click on the curling game.
Are you ready to begin? [y]es
yes
ls -la
total 32
drwxr-x--- 1 alabaster alabaster 4096 Oct 30 18:03 .
drwxr-xr-x 1 root root 4096 Oct 30 18:02 ..
-rw-r--r-- 1 alabaster alabaster 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 alabaster alabaster 3129 Oct 30 18:02 .bashrc
-rw-r--r-- 1 alabaster alabaster 807 Jan 6 2022 .profile
-rw-r--r-- 1 root root 234 Oct 30 18:02 HARD-MODE.txt
-rw-r--r-- 1 root root 168 Oct 25 16:04 HELP
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"
curl -k https://curlingfun:9090/ --data "skip=bow" -H "Cookie:end=10" -H "Hack:12ft"
Excellent! Now, use curl to access this URL: https://curlingfun:9090/../../etc/button
curl -k --path-as-is https://curlingfun:9090/../../etc/button
Great! Finally, use curl to access the page that this URL redirects to: https://curlingfun:9090/GoodSportsmanship
curl --head -k https://curlingfun:9090/GoodSportsmanship
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0 (Ubuntu)
Date: Mon, 18 Nov 2024 20:22:23 GMT
Content-Type: text/html
Content-Length: 178
Location: https://curlingfun:9090/SpiritOfCurling.php
Connection: keep-alive
curl -k https://curlingfun:9090/SpiritOfCurling.php
Excellent work, you have solved hard mode! You may close this terminal once HHC grants your achievement.
Hardware Hacking 101 (Gold)
Difficulty: 1/5, Dock: The Front Yard
Click on Jewel Loggins.
Ready your tools and sharpen your wits—only the cleverest can untangle the wires and unlock Santa’s hidden secrets!
Part 1 (Gold)
Jingle all the wires and connect to Santa’s Little Helper to reveal the merry secrets locked in his chest!
Click on the Hardware Part 1 box. Inspect code from main.js.
/js/main.js
[...]
create() {
this.dev = false;
[...]
// Method to check the conditions
async checkConditions() {
if (this.uV === 3 && this.allConnectedUp && !this.usbIsAtOriginalPosition || this.dev) {
[...]
Intercept requests and responses in Burp Suite. For /js/main.js, change this.dev to true and forward the request.
this.dev = false;
this.dev = true;
- Connect the wires to the right pins.
- V->VCC
- T->TX
- R->RX
- G->GND
- Connect the USB-C cable.
- On the bottom right card, click on the tiny button to switch to 3V. This was shown in the manual.
- Click on the Power button (green “P”)
/js/main.js
[...]
// PDA Screen Text Stuff
const baudRates = [300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200];
const dataBits = [5, 6, 7, 8];
const parityOptions = ["None", "odd", "even"];
const stopBits = [1, 2];
const flowControlOptions = ["None", "RTS/CTS", "Xon/Xoff", "RTS"];
const ports = ["COM1", "COM2", "COM3", "USB0"];
[...]
async function checkit(serial, uV) {
// Retrieve the request ID from the URL query parameters
const requestID = getResourceID(); // Replace 'paramName' with the actual parameter name you want to retrieve
if (!requestID) {
requestID = "00000000-0000-0000-0000-000000000000";
}
// 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 })
});
// Check if the request was successful
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
}
const data = await response.json();
console.log("Data", data)
// Return true if the response is true
return data === true;
} catch (error) {
console.error('There has been a problem with your fetch operation:', error);
return false;
}
}
[...]
We find a comment saying that we can brute-force the v1 API. Send this request to the Intruder module in Burp Suite.
POST /api/v1/complete HTTP/2
Host: hhc24-hardwarehacking.holidayhackchallenge.com
Content-Type: application/json
[...]
{"requestID":"<YOUR REQUEST ID FROM URL>","serial":[3,§0§,§0§,§0§,§0§,§0§],"voltage":3}
Use a Cluster bomb attack with payload type “Numbers” from 0 to 10 (or find the right range for each option).
POST /api/v1/complete HTTP/2
Host: hhc24-hardwarehacking.holidayhackchallenge.com
Content-Type: application/json
[...]
{"requestID":"<YOUR REQUEST ID FROM URL>","serial":[3,9,2,2,0,3],"voltage":3}
HTTP/2 200 OK
Content-Type: application/json
Date: Tue, 19 Nov 2024 13:27:13 GMT
Content-Length: 5
Via: 1.1 google
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
true
From /js/main.js, find corresponding options for [3,9,2,2,0,3], [‘port’, ‘baud’, ‘parity’, ‘bits’, ‘stopbits’, ‘flow ctrl’].
[...]
// PDA Screen Text Stuff
const baudRates = [300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200];
const dataBits = [5, 6, 7, 8];
const parityOptions = ["None", "odd", "even"];
const stopBits = [1, 2];
const flowControlOptions = ["None", "RTS/CTS", "Xon/Xoff", "RTS"];
const ports = ["COM1", "COM2", "COM3", "USB0"];
[...]
const options = ['port', 'baud', 'parity', 'bits', 'stopbits', 'flow ctrl'];
[...]
On the PDA, use the Up/Down arrows and Left/right to change the options.
- Port: USB0
- Baud Rate: 115200
- Parity: even
- Data: 7
- Stop bits: 1
- Flow Ctrl: RTS
success! loading bootloader
Go speak with Jewel Loggins for the next step!
Speak with Jewel Loggins for the next step.
Next, we need to access the terminal and modify the access database. We’re looking to grant access to card number 42.
Start by using the slh application – that’s the key to getting into the access database. Problem is, the ‘slh’ tool is password-protected, so we need to find it first.
Search the terminal thoroughly; passwords sometimes get left out in the open.
Once you’ve found it, modify the entry card number 42 to grant access. Sounds simple, right? Let’s get to it!
Part 2 (Silver)
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!
Click on the Hardware Part 2 terminal.
*** U-Boot Boot Menu ***
> 1. Startup system (Default)
2. U-Boot console
Press UP/DOWN to move, ENTER to select
Select option 1.
history
1 cd /var/www/html
2 ls -l
3 sudo nano index.html
4 cd ..
5 rm -rf repo
6 sudo apt update
7 sudo apt upgrade -y
8 ping 1.1.1.1
9 slh --help
10 slg --config
11 slh --passcode CandyCaneCrunch77 --set-access 1 --id 143
12 df -h
13 top
14 ps aux | grep apache
15 sudo systemctl restart apache2
16 history | grep ssh
17 clear
18 whoami
19 crontab -e
20 crontab -l
21 alias ll='ls -lah'
22 unalias ll
23 echo "Hello, World!"
24 cat /etc/passwd
25 sudo tail -f /var/log/syslog
26 mv archive.tar.gz /backup/
27 rm archive.tar.gz
28 find / -name "*.log"
29 grep "error" /var/log/apache2/error.log
30 history
We find password CandyCaneCrunch77
slh --passcode CandyCaneCrunch77 --set-access 1 --id 143
Modify the entry card number 42 to grant access.
slh --passcode CandyCaneCrunch77 --set-access 1 --id 42
Go talk to Jewel Loggins.
Part 2 (Gold)
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.
I know you can do it—come back once you’ve cracked it!
From the Hints, there is a link to CyberChef with HMAC SHA256.
I seem to remember there being a handy HMAC generator included in CyberChef.
Click on the Hardware Part 2 terminal.
*** U-Boot Boot Menu ***
> 1. Startup system (Default)
2. U-Boot console
Press UP/DOWN to move, ENTER to select
Select option 1. Look at information for card ID 42.
slh --view-card 42
Details of card with ID: 42
(42, 'c06018b6-5e80-4395-ab71-ae5124560189', 0, 'ecb9de15a057305e5887502d46d434c9394f5ed7ef1a51d2930ad786b02f6ffd')
Take a look at the database file.
ls -la
total 156
drwxrwxr-t 1 slh slh 4096 Nov 13 14:44 .
drwxr-xr-x 1 root root 4096 Nov 13 14:44 ..
-r--r--r-- 1 slh slh 518 Oct 16 23:52 .bash_history
-r--r--r-- 1 slh slh 3897 Sep 23 20:02 .bashrc
-r--r--r-- 1 slh slh 807 Sep 23 20:02 .profile
-rw-r--r-- 1 root root 131072 Nov 13 14:44 access_cards
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
It is a SQLite database. Access it.
sqlite3 access_cards
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
.fullschema
CREATE TABLE access_cards (
id INTEGER PRIMARY KEY,
uuid TEXT,
access INTEGER,
sig TEXT
);
CREATE TABLE config (
id INTEGER PRIMARY KEY,
config_key TEXT UNIQUE,
config_value TEXT
);
/* No STAT tables available */
Look at the config table.
select * from config;
1|hmac_secret|9ed1515819dec61fd361d5fdabb57f41ecce1a5fe1fe263b98c0d6943b9b232e
2|hmac_message_format|{access}{uuid}
3|admin_password|3a40ae3f3fd57b2a4513cca783609589dbe51ce5e69739a33141c5717c20c9c1
4|app_version|1.0
.quit
BONUS: Use Hashcat on Kali Linux to crack the hmac_secret. It is not needed for this challenge.
echo "9ed1515819dec61fd361d5fdabb57f41ecce1a5fe1fe263b98c0d6943b9b232e" > hash.txt
hashcat -m 1400 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
hashcat -m 1400 -a 0 hash.txt --show
9ed1515819dec61fd361d5fdabb57f41ecce1a5fe1fe263b98c0d6943b9b232e:pizza
We find hmac_secret is “pizza”.
Now using CyberChef with HMAC SHA256 and Key “9ed1515819dec61fd361d5fdabb57f41ecce1a5fe1fe263b98c0d6943b9b232e”, create the correct HMAC for card 42. The format (“hmac_message_format”): {access}{uuid}.
1c06018b6-5e80-4395-ab71-ae5124560189
135a32d5026c5628b1753e6c67015c0f04e26051ef7391c2552de2816b1b7096
Update the card ID 42.
sqlite3 access_cards
UPDATE access_cards SET access=1, sig='135a32d5026c5628b1753e6c67015c0f04e26051ef7391c2552de2816b1b7096' WHERE id=42;
Wait 5 seconds and a message “Access granted” will appear.
Frosty Keypad (Silver + Gold)
Difficulty: 1/5, Dock: The Front Yard
Click on Morcel Nougat.
In a swirl of shredded paper, lies the key. Can you unlock the shredder’s code and uncover Santa’s lost secrets?
Walk in the surroundings to find Frosty Book.
A snow-covered book lies forgotten on the ground, its pages slightly ajar as if inviting the curious to unlock the secret hidden within its well-worn spine. Read the Book…
Explore the book using Burp Suite.
GET /images/ HTTP/2
Host: frost-y-book.com
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Last-Modified: Mon, 04 Nov 2024 16:46:36 GMT
Date: Tue, 19 Nov 2024 20:22:23 GMT
Content-Length: 113
Via: 1.1 google
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
<pre>
<a href="open_book.png">open_book.png</a>
<a href="shredder_note_pwc.png">shredder_note_pwc.png</a>
</pre>
We find image shredder_note_pwc.png
https://frost-y-book.com/images/shredder_note_pwc.png
We can see within the image:
2:6:1
4:19:3
6:1:1
3:10:4
14:8:3
It looks like it could be the “Page number”:”word number”:”letter number” from main.js.
/js/main.js
[...]
class GameScene extends Phaser.Scene {
constructor() {
super("scene-game");
this.dev = true
this.currentPage = 0;
this.pages = [
[...]
"Page 2: \n\rThe children were nestled all snug in their beds;\n\rWhile visions of sugar-plums danced in their heads;\n\rAnd mamma in her 'kerchief, and I in my cap,\n\rHad just settled our brains for a long winter's nap,",
"Page 3: \n\rWhen out on the lawn there arose such a clatter,\n\rI sprang from my bed to see what was the matter.\n\rAway to the window I flew like a flash,\n\rTore open the shutters and threw up the sash.",
"Page 4: \n\rThe moon on the breast of the new-fallen snow,\n\rGave a lustre of midday to objects below,\n\rWhen what to my wondering eyes did appear,\n\rBut a miniature sleigh and eight tiny rein-deer,",
[...]
"Page 6: \n\r\"Now, Dasher! now, Dancer! now Prancer and Vixen!\n\rOn, Comet! on, Cupid! on, Donder and Blitzen!\n\rTo the top of the porch! to the top of the wall!\n\rNow dash away! dash away! dash away all!\"",
[...]
"Page 14: \n\rHe sprang to his sleigh, to his team gave a whistle,\n\rAnd away they all flew like the down of a thistle.\n\rBut I heard him exclaim, ere he drove out of sight—\n\r“Happy Christmas to all, and to all a good night!”"
];
[...]
“Page number”:”word number”:”letter number”:
2:6:1 = snug = s
4:19:3 = what = a
6:1:1 = Now = N
3:10:4 = clatter = t
14:8:3 = team = a
We find “saNta”.
Click on the Frosty Keypad.
Intercept requests and responses in Burp Suite.
// check if flashlight is over special keys
function checkOverlap() {
clearOverlays();
const keysToCheck = ['2', '7', '6', '8', 'Enter'];
We find keys 2, 7, 6, 8 and Enter.
We can also display the flashlight by using the Math and replace rules in the Proxy tab.
- Click on Proxy -> Match and replace
- Under HTTP match and replace rules, click Add.
- Type: Response body
- Match: uvLight.setVisible(false);
- Replace: uvLight.setVisible(true);
- Comment: Flashlight
- Do NOT select the Regex match checkbox.
Reload the page and the flashlight will appear. We can see these keys with fingerprints: 2, 6, 7, 8 and Enter.
Enter any combination on the keypad and click Enter. Send the request to the Intruder module.
POST /submit?id=<YOUR REQUEST ID> HTTP/2
Host: hhc24-frostykeypad.holidayhackchallenge.com
[...]
{"answer":"§ReplaceMe§"}
Use payload type Brute forcer (Burp Pro version, or create one variable per placeholder and use Simple list), with characters “2678”, length 5 (guess from the “saNta” code found).
POST /submit?id=<YOUR REQUEST ID> HTTP/2
Host: hhc24-frostykeypad.holidayhackchallenge.com
[...]
{"answer":"22786"}
POST /submit?id=<YOUR REQUEST ID> HTTP/2
Host: hhc24-frostykeypad.holidayhackchallenge.com
[...]
{"answer":"72682"}
HTTP/2 200 OK
Server: gunicorn
Date: Tue, 19 Nov 2024 22:50:41 GMT
Content-Type: application/json
Content-Length: 21
Via: 1.1 google
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
{"output":"success"}
These 2 codes result in “success”: 22786 and 72682. Enter them on the keypad. Talk with Morcel Nougat.
Your help has been absolutely essential, especially now with Santa missing. Wombley and Alabaster will want to hear all about it—go share the news with Jewel Loggins!
Go talk to Jewel Loggins.
Brilliant work! We now have access to… the Wish List! I couldn’t have done it without you—thank you so much!
Act II
Wombley’s getting desparate. Out-elved by Alabaster’s faction, he’s planning a gruesome snowball fight to take over present delivery!
Mobile Analysis (Silver + Gold)
Difficulty: 2/5, Dock: The Front Yard
Talk to Eve Snowshoes.
Help find who has been left out of the naughty AND nice list this Christmas. Please speak with Eve Snowshoes for more information.
Silver
Debug version:
https://www.holidayhackchallenge.com/2024/SantaSwipe.apk
Download the Debug version on Kali Linux.
Use the Mobile-Security-Framework (MobSF) to analyze the code.
sudo service docker start
sudo docker pull opensecurity/mobile-security-framework-mobsf
sudo docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest
- Access http://127.0.0.1:8000/ with default credentials mobsf/mobsf
- Upload the SantaSwipe.apk file.
- Click on Information on the left menu.
- Under Decompiled code, click on View Source.
com/northpole/santaswipe/DatabaseHelper.java
package com.northpole.santaswipe;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
/* compiled from: DatabaseHelper.kt */
@Metadata(d1 = {"\u0000(\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0003\b\u0007\u0018\u0000 \r2\u00020\u0001:\u0001\rB\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\u0010\u0010\u0005\u001a\u00020\u00062\u0006\u0010\u0007\u001a\u00020\bH\u0016J \u0010\t\u001a\u00020\u00062\u0006\u0010\u0007\u001a\u00020\b2\u0006\u0010\n\u001a\u00020\u000b2\u0006\u0010\f\u001a\u00020\u000bH\u0016¨\u0006\u000e"}, d2 = {"Lcom/northpole/santaswipe/DatabaseHelper;", "Landroid/database/sqlite/SQLiteOpenHelper;", "context", "Landroid/content/Context;", "(Landroid/content/Context;)V", "onCreate", "", "db", "Landroid/database/sqlite/SQLiteDatabase;", "onUpgrade", "oldVersion", "", "newVersion", "Companion", "app_debug"}, k = 1, mv = {1, 9, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class DatabaseHelper extends SQLiteOpenHelper {
public static final int $stable = 0;
private static final String DATABASE_NAME = "naughtynicelist.db";
private static final int DATABASE_VERSION = 3;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, (SQLiteDatabase.CursorFactory) null, 3);
Intrinsics.checkNotNullParameter(context, "context");
}
@Override // android.database.sqlite.SQLiteOpenHelper
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("DELETE FROM NiceList;");
db.execSQL("DELETE FROM NaughtyList;");
db.execSQL("DELETE FROM NormalList;");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Carlos, Madrid, Spain');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Aiko, Tokyo, Japan');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Maria, Rio de Janeiro, Brazil');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Liam, Dublin, Ireland');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Emma, New York, USA');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Chen, Beijing, China');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Fatima, Casablanca, Morocco');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Hans, Berlin, Germany');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Olga, Moscow, Russia');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ravi, Mumbai, India');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Amelia, Sydney, Australia');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Juan, Buenos Aires, Argentina');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Sofia, Rome, Italy');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ahmed, Cairo, Egypt');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Yuna, Seoul, South Korea');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ellie, Alabama, USA');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Lucas, Paris, France');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Mia, Toronto, Canada');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Sara, Stockholm, Sweden');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ali, Tehran, Iran');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Nina, Lima, Peru');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Anna, Vienna, Austria');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Leo, Helsinki, Finland');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Elena, Athens, Greece');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Davi, Sao Paulo, Brazil');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Marta, Warsaw, Poland');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Noah, Zurich, Switzerland');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ibrahim, Ankara, Turkey');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Emily, Wellington, New Zealand');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Omar, Oslo, Norway');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Fatou, Dakar, Senegal');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Olivia, Vancouver, Canada');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ethan, Cape Town, South Africa');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Santiago, Bogota, Colombia');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Isabella, Barcelona, Spain');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ming, Shanghai, China');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Chloe, Singapore, Singapore');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Mohammed, Dubai, UAE');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Ava, Melbourne, Australia');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Luca, Milan, Italy');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Sakura, Kyoto, Japan');");
db.execSQL("INSERT INTO NormalList (Item) VALUES ('Edward, New Jersey, USA');");
}
@Override // android.database.sqlite.SQLiteOpenHelper
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Intrinsics.checkNotNullParameter(db, "db");
db.execSQL("DROP TABLE IF EXISTS NiceList");
db.execSQL("DROP TABLE IF EXISTS NaughtyList");
db.execSQL("DROP TABLE IF EXISTS NormalList");
onCreate(db);
}
}
com/northpole/santaswipe/MainActivity.java
[...]
public final void getNormalList() {
[...]
Cursor cursor = sQLiteDatabase.rawQuery("SELECT Item FROM NormalList WHERE Item NOT LIKE '%Ellie%'", null);
[...]
Answer: Ellie
Gold
Release version:
https://www.holidayhackchallenge.com/2024/SantaSwipeSecure.aab
Download the Release version on Kali Linux.
Use the Mobile-Security-Framework (MobSF) to analyze the code.
- Access http://127.0.0.1:8000/ with default credentials mobsf/mobsf
- Upload the SantaSwipeSecure.aab file.
- Click on Information on the left menu.
- Under Decompiled code, click on View Source.
com/northpole/santaswipe/MainActivity.java
[...]
public final class MainActivity extends ComponentActivity {
public static final int $stable = 8;
private SQLiteDatabase database;
private WebView myWebView;
private SecretKey secretKey;
private byte[] staticIv;
private final void initializeEncryption() {
}
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
String string = getString(R.string.iv);
Intrinsics.checkNotNullExpressionValue(string, "getString(...)");
byte[] decode = Base64.decode(StringsKt.trim((CharSequence) string).toString(), 0);
Intrinsics.checkNotNullExpressionValue(decode, "decode(...)");
this.staticIv = decode;
String string2 = getString(R.string.ek);
Intrinsics.checkNotNullExpressionValue(string2, "getString(...)");
byte[] decode2 = Base64.decode(StringsKt.trim((CharSequence) string2).toString(), 0);
this.secretKey = new SecretKeySpec(decode2, 0, decode2.length, "AES");
initializeDatabase();
initializeWebView();
initializeEncryption();
} catch (IllegalArgumentException e) {
Log.e("MainActivity", "Error during initialization: " + e.getMessage());
}
}
[...]
private final String decryptData(String encryptedData) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] bArr = MainActivity.this.staticIv;
if (bArr == null) {
Intrinsics.throwUninitializedPropertyAccessException("staticIv");
bArr = null;
}
GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, bArr);
SecretKey secretKey = MainActivity.this.secretKey;
if (secretKey == null) {
Intrinsics.throwUninitializedPropertyAccessException("secretKey");
secretKey = null;
}
cipher.init(2, secretKey, gCMParameterSpec);
byte[] doFinal = cipher.doFinal(Base64.decode(encryptedData, 0));
Intrinsics.checkNotNull(doFinal);
return new String(doFinal, Charsets.UTF_8);
} catch (Exception unused) {
return null;
}
}
[...]
com/northpole/santaswipe/DatabaseHelper.java
[...]
public final class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "naughtynicelist.db";
private static final int DATABASE_VERSION = 1;
private final byte[] encryptionKey;
private final byte[] iv;
private final SecretKeySpec secretKeySpec;
public static final int $stable = 8;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, (SQLiteDatabase.CursorFactory) null, 1);
Intrinsics.checkNotNullParameter(context, "context");
String string = context.getString(R.string.ek);
Intrinsics.checkNotNullExpressionValue(string, "getString(...)");
String obj = StringsKt.trim((CharSequence) string).toString();
String string2 = context.getString(R.string.iv);
Intrinsics.checkNotNullExpressionValue(string2, "getString(...)");
String obj2 = StringsKt.trim((CharSequence) string2).toString();
byte[] decode = Base64.decode(obj, 0);
Intrinsics.checkNotNullExpressionValue(decode, "decode(...)");
this.encryptionKey = decode;
byte[] decode2 = Base64.decode(obj2, 0);
Intrinsics.checkNotNullExpressionValue(decode2, "decode(...)");
this.iv = decode2;
this.secretKeySpec = new SecretKeySpec(decode, "AES");
}
@Override // android.database.sqlite.SQLiteOpenHelper
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);
}
@Override // android.database.sqlite.SQLiteOpenHelper
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Intrinsics.checkNotNullParameter(db, "db");
db.execSQL("DROP TABLE IF EXISTS NiceList");
db.execSQL("DROP TABLE IF EXISTS NaughtyList");
db.execSQL("DROP TABLE IF EXISTS NormalList");
onCreate(db);
}
private final void insertInitialData(SQLiteDatabase db) {
Iterator it = CollectionsKt.listOf((Object[]) new String[]{"L2HD1a45w7EtSN41J7kx/hRgPwR8lDBg9qUicgz1qhRgSg==", [...], "J2zf2/B95PJmf9RuJ7k1/AZr259XFLll5xvRfxu/YDOTFR460RwK+Q=="}).iterator();
while (it.hasNext()) {
db.execSQL("INSERT INTO NormalList (Item) VALUES ('" + StringsKt.trim((CharSequence) it.next()).toString() + "');");
}
}
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;
}
[...]
}
The list of names is encrypted. In MobSF, under Decompiled code, click on Download APK (save as SantaSwipeSecure.apk).
Under Decompiled code, click on View Smali. Smali is a human-readable representation of the intermediate bytecode format used by Android applications. Search for the iv.
com/northpole/santaswipe/R$string.smali
.class public final Lcom/northpole/santaswipe/R$string;
.super Ljava/lang/Object;
# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lcom/northpole/santaswipe/R;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x19
name = "string"
.end annotation
# static fields
.field public static app_name:I = 0x7f090001
.field public static ek:I = 0x7f090033
.field public static iv:I = 0x7f090037
# direct methods
.method private constructor <init>()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
Decompile the APK.
sudo apt install apktool
apktool d SantaSwipeSecure.apk
cd SantaSwipeSecure
cat res/values/strings.xml
We find the IV (initialization vector) and EK (encryption key).
[...]
<string name="ek">rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw=</string>
[...]
<string name="iv">Q2hlY2tNYXRlcml4</string>
[...]
- Key (base64): rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw=
- IV (base64): Q2hlY2tNYXRlcml4
Write Java code to decrypt the data based on the mobile application code.
Main.java:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.nio.charset.StandardCharsets;
class Main {
private byte[] encryptionKey;
private byte[] iv;
private SecretKeySpec secretKeySpec;
public static void main(String[] args) {
String string = "rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw="; // Base64 encoded encryption key
String obj = string.trim();
String string2 = "Q2hlY2tNYXRlcml4"; // Base64 encoded IV
String obj2 = string2.trim();
String encryptedData = "IVrt+9Zct4oUePZeQqFwyhBix8cSCIxtsa+lJZkMNpNFBgoHeJlwp73l2oyEh1Y6AfqnfH7gcU9Yfov6u70cUA2/OwcxVt7Ubdn0UD2kImNsclEQ9M8PpnevBX3mXlW2QnH8+Q+SC7JaMUc9CIvxB2HYQG2JujQf6skpVaPAKGxfLqDj+2UyTAVLoeUlQjc18swZVtTQO7Zwe6sTCYlrw7GpFXCAuI6Ex29gfeVIeB7pK7M4kZGy3OIaFxfTdevCoTMwkoPvJuRupA6ybp36vmLLMXaAWsrDHRUbKfE6UKvGoC9d5vqmKeIO9elASuagxjBJ";
// Found after decrypting encryptedData
String encryptedName = "KGfb0vd4u/4EWMN0bp035hRjjpMiL4NQurjgHIQHNaRaDnIYbKQ9JusGaa1aAkGEVV8=";
// Decode Base64 to byte arrays
byte[] decode = Base64.getDecoder().decode(obj);
byte[] decode2 = Base64.getDecoder().decode(obj2);
// Initialize encryption key and IV
Main main = new Main();
main.encryptionKey = decode;
main.iv = decode2;
main.secretKeySpec = new SecretKeySpec(decode, "AES");
System.out.println("Encryption key and IV initialized successfully.");
System.out.println(main.decryptData(encryptedData));
System.out.println(main.decryptData(encryptedName));
}
private final String decryptData(String encryptedData) {
try {
// AES GCM decryption requires 12-byte IV and GCMParameterSpec
if (this.iv.length != 12) {
throw new IllegalArgumentException("IV must be 12 bytes long for AES GCM mode.");
}
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, this.secretKeySpec, new GCMParameterSpec(128, this.iv));
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData)); // Decoding and decrypting
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
System.err.println("Decryption failed: " + e.getMessage());
return null;
}
}
}
Compile and execute code.
javac Main.java
java Main
Encryption key and IV initialized successfully.
CREATE TRIGGER DeleteIfInsertedSpecificValue
AFTER INSERT ON NormalList
FOR EACH ROW
BEGIN
DELETE FROM NormalList WHERE Item = 'KGfb0vd4u/4EWMN0bp035hRjjpMiL4NQurjgHIQHNaRaDnIYbKQ9JusGaa1aAkGEVV8=';
END;
Joshua, Birmingham, United Kingdom
Answer: Joshua