Flagvent 2025: Day 20
FV25.20 - SantaOS Communications
Difficulty
medium
Categories
crypto forsensics
Description
Santa's IT team developed a special operating system used to process wishes securely.
In a recent incident, the key for decrypting messages got lost and all backups were destroyed.
The only thing left are the following two files - Can you help Santa?
Author
lu161Flagvent 2025 - Day 20 - santaos-communications.tar.gzSolution
Initially, we are given three files: intercepted_SantaOS.txt, intercepted_message.txt and santaOS_logo.jpeg.
Analysing the intercepted SantaOS
The intercepted_SantaOS.txt contains a Base64-encoded payload. Here’s some truncated output once decoded from Base64:
$timescale 10 us $end
$var wire 1 ! D0 $end
$var wire 1 " D1 $end
$var wire 1 # D2 $end
$var wire 1 $ D3 $end
$var wire 1 % D4 $end
$var wire 1 & D5 $end
$var wire 1 ' D6 $end
$var wire 1 ( D7 $end
$upscope $end
$enddefinitions $end
#0 0! 1" 0# 1$ 1% 1& 1' 1(
#415477 1! 1#
#480392 0!
#480396 1!
#586717 0#
#586759 1#
#586769 0#
#586790 1#
#586800 0#
#586811 1#
...After some research, we discovered this is a VCD (Value Change Dump) file generated by a Verilog simulation.
A .vcd file is essentially a massive timeline of voltage changes. The file logs raw timestamps of voltage changes, which we mapped to characters using standard UART decoding logic. UART transmits the least significant bit (LSB) first. We collect 8 bits and reverse them to form a byte.
By examining the shortest pulses in the file (10 units apart), we determined a bit width of roughly 10.4 units. We also identified wire # as the active data line.
Here’s an example of decoding the first character (starting at time #586717):
- Start Sequence (#586717 to #586759):
- Line drops to 0 and stays there for 42 units.
- Calculation:
42 / 10.4 ≈ 4 bits. - This covers the Start Bit plus Bits 0, 1, and 2.
- Read:
Start(0), 0, 0, 0
- Bit 3 (#586759 to #586769):
- Line rises to 1 for 10 units.
- Calculation:
10 / 10.4 ≈ 1 bit. - Read:
1
- Bits 4-5 (#586769 to #586790):
- Line drops to 0 for 21 units.
- Calculation:
21 / 10.4 ≈ 2 bits. - Read:
0, 0
- Bit 6 (#586790 to #586800):
- Line rises to 1 for 10 units.
- Calculation:
10 / 10.4 ≈ 1 bit. - Read:
1
- Bit 7 (#586800 to #586811):
- Line drops to 0 for 11 units.
- Calculation:
11 / 10.4 ≈ 1 bit. - Read:
0
- Stop Bit (#586811):
- Line rises to 1 (Idle/Stop state).
Raw Bits (LSB): 00010010
Reversed (MSB): 01001000 (0x48)
Which is the ASCII character H.
Processing the VCD dump via script
It’s time to write a script that can process the entire VCD dump and output the hidden message.
The script reads intercepted_SantaOS.txt (handling Base64 decoding), extracts the timestamped transitions for the target wire, and applies UART timing logic to reconstruct the ASCII string.
import base64
import sys
def solve_uart():
print("[*] Parsing SantaOS VCD for UART data...")
# 1. Load File
try:
with open('intercepted_SantaOS.txt', 'r') as f:
vcd_content = f.read().strip()
# Decode Base64 if needed, else use raw
if "timescale" not in vcd_content:
vcd_text = base64.b64decode(vcd_content).decode('utf-8')
else:
vcd_text = vcd_content
except Exception as e:
print(f"[!] Error reading file: {e}")
return
# 2. Extract Transitions for Wire 2 (Symbol '#')
transitions = []
current_time = 0
# Robust token parsing handles "#123 1#" on same line or separate lines
tokens = vcd_text.replace('\n', ' ').split()
for token in tokens:
if token.startswith('#'):
try:
# Remove '#' and parse time
current_time = int(token[1:])
except ValueError:
continue
elif token.endswith('#'):
# This is a value change for Wire D2
try:
val = int(token[:-1])
transitions.append((current_time, val))
except ValueError:
continue
if not transitions:
print("[!] No transitions found. Check file format.")
return
print(f"[+] Found {len(transitions)} transitions. Decoding...")
# 3. Decode UART
# Parameters derived from trace analysis:
# Smallest pulse is ~10 units. This is our bit period.
bit_period = 10.4
decoded_chars = []
i = 0
# Iterate through transitions to find Start Bits
while i < len(transitions) - 1:
t_current, val = transitions[i]
# UART Start Bit is a falling edge (1 -> 0)
# We assume line is Idle High (1)
if val == 0:
# We found a start bit at t_current.
# Sample the 8 data bits.
# Center of first data bit = Start + 1.5 * period
byte_val = 0
for bit_idx in range(8):
sample_time = t_current + (bit_period * 1.5) + (bit_period * bit_idx)
# Determine state of line at sample_time
# We scan forward from current transition
sample_state = 1 # Default idle
for k in range(i, len(transitions)):
t_k, v_k = transitions[k]
if t_k > sample_time:
# The state is whatever the line was *before* this future transition
# So we look at the previous transition's value
if k > 0:
sample_state = transitions[k-1][1]
break
# If we run out of transitions, the state remains the last known value
sample_state = v_k
# UART sends LSB first
if sample_state == 1:
byte_val |= (1 << bit_idx)
decoded_chars.append(chr(byte_val))
# Move index past this character to search for next start bit
# Character takes ~10 bits (1 start + 8 data + 1 stop)
end_of_char_time = t_current + (bit_period * 10)
# Fast forward loop index
while i < len(transitions) - 1 and transitions[i][0] < end_of_char_time:
i += 1
else:
i += 1
# 4. Print Result
raw_key = "".join(decoded_chars)
print("-" * 40)
print(f"{raw_key}")
print("-" * 40)
if __name__ == "__main__":
solve_uart()
Running the script produces the following output:
[*] Parsing SantaOS VCD for UART data...
[+] Found 10720 transitions. Decoding...
----------------------------------------
Ho! Ho! Ho! Welcome to *SantaOS* - Version 2025.12.25
Command line: BOOT_IMAGE=/boot/vmlinuz-christmas root=UUID=fa1c0f83-a32d-4d4e-bfbf-d2369b17b32e ro quiet
Loading the sleigh... Please hold on while we gather the reindeer...
Kernel: Sleigh bells ring... are you listening?
Booting from the North Pole
ACPI: Reindeer fuel gauge check complete. All systems go!
BIOS: Powered by holiday magic! Christmas Eve 2025
Memory: 8GB of Christmas joy loaded and ready to go!
DMI: Reindeer Inc. SleighMaster 9000, BIOS Snowfall Edition 1.0
e820: Warming up the North Pole... All presents accounted for.
e820: [mem 0x0000000100000000-0x000000024fffffff] usable (Candy Cane Vault)
efi: ELF Loader: Powered by the Christmas Spirit
[Holiday Bug]: the boot CPU is running at full Christmas magic frequency.
Random init: random: twinkling lights initialized.
Random: Christmas cheer generated for all processes.
early console: Sleigh bells heard faintly in the distance...
Ho! Ho! Ho! Booting Christmas Kernel...
Loading the Reindeer Operating System from the North Poleâs initrd...
Freeing unused kernel memory: Wrapping paper discarded.
Write protecting the cookie storage: 8192 cookies saved.
Rudolph: Filesystem reindeer_root mounted successfully.
Starting systemd... The elves are busy assembling presents.
systemd[1]: Time synchronized with Santa's Workshop clock.
systemd[1]: Loaded Encryption Service - **AES-256 with CBC mode**
systemd[1]: Encryption Key (UPPERCASE): ><^v<<<^^v^^^>v>><^v<<<^^vvvv>v>
Starting the Holiday-themed GNOME Display Manager (gdm)...
gdm[456]: **Merry Christmas!** GNOME Display Manager started.
Login: Welcome to SantaOS! Type your secret holiday wish to begin. All messages are encrypted.
----------------------------------------The output gives us some critical information about the encryption service: it uses AES-256 in CBC mode, with ><^v<<<^^v^^^>v>><^v<<<^^vvvv>v> as the encryption key.
Pigpen Cipher
Next, we look at the santaOS_logo.jpeg file:

After some research, we figured out the image is hinting at the Pigpen cipher, a simple substitution cipher. The encryption key retrieved from the OS, ><^v<<<^^v^^^>v>><^v<<<^^vvvv>v>, also looks like it has been encoded using the Pigpen cipher.
We used the dCode.fr Pigpen Cipher Decoder and entered the input above in the UI to generate the following potential solutions:
#0 TUVSUUUVVSVVVTSTTUVSUUUVVSSSSTST
#1 TVUSVVVUUSUUUTSTTVUSVVVUUSSSSTST
#2 KLMJLLLMMJMMMKJKKLMJLLLMMJJJJKJK
#3 GHIFHHHIIFIIIGFGGHIFHHHIIFFFFGFG
#4 BCDACCCDDADDDBABBCDACCCDDAAAABAB
#5 UWYSWWWYYSYYYUSUUWYSWWWYYSSSSUSU
#6 UYWSYYYWWSWWWUSUUYWSYYYWWSSSSUSU
#7 --------------------------------
#8 -V-TVVV--T----T--V-TVVV--TTTT-T-Decrypting the intercepted message
At this stage, we put the intercepted message from intercepted_message.txt into an intercepted_message.bin file, without any surrounding text:
U2FsdGVkX18IDMhwQMG9HpiCBLdo1SuQWoHNWRkzgaNDnDlEBruweUk70Bc/Kmds
DRFkbzUtkWOpG69ULK9Htp8LwINUfnwGQ3cMt/P2tnJfIeRUO0RDFryjUxZgUznL
2VT3wYZli3jWsiqAzEskyfHR8V5qGQjhJwnaV1vsfvxddogdukNBBA1UFYdXK9u+
D87DfWseLg33Zn4eGMy59GOrnB1rq2mXpLYHHKGDOQGpC7gwQ63RQEmlFki5CxS6
5ekYT7jCPG9XuuH+qP9WeHcj7Jjz6zxerz/7Jr+UaHco9h+sRcD0caWTFPu/OYfI
jMts0YZjosFOGTWPOw0Ly2ZlZlwPdgUmnKgG2F2j44YuuIVuBHVuGHoLBrz0mvjT
zcJxoE4wNxuGR3IFFbWDXIncbea/JYSGQ1ORuBIdXAGAlXfezJj+mYkf169nN6Jq
bWwpThzkGvexBMbMG6qAPqCkz3is0KU6QLs/gL+idkUJC96vfXsafMVJ9NACRjD3
HyUqqLJv8FG2sMwCGFZTXOvapHJxqizb7RzmRlv8lZ95oIu7ujqEMYb4p9SI2/MR
TZ+fU3tAPtx20/sFyWjJau0EL8BvQ1VjtTC/IJG6oe0NRlRU6b34vRxBa+AuARHx
GGSugTA0fp3X0pAeLb/R2g==
Base64-decoding the message reveals the Salted__ header, which indicates OpenSSL’s salted enc format.
Next, we attempted to decrypt the message using OpenSSL, making sure to use the -a flag so the input is Base64-decoded before being processed. We tried several different passphrases based on our Pigpen decoding, and TUVSUUUVVSVVVTSTTUVSUUUVVSSSSTST turned out to be the correct one.
openssl enc -d -aes-256-cbc -a -in intercepted_message.bin -k TUVSUUUVVSVVVTSTTUVSUUUVVSSSSTSTRunning it gives:
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
On a snowy night, so crisp and bright,
A challenge arose in the cold moonlight.
With hidden clues, through riddles and rhyme,
You searched for the flag, beyond space and time.
Through encrypted snowflakes and icy streams,
You followed the path of a hacker’s dreams.
In code and ciphers, you found the way,
To uncover the secret that lay in the fray.
Now at last, you’ve cracked the code tight,
And here’s your prize, shining in the night:
FV25{W3lc0m3_t0_S4nt4_OS}This gives us our daily flag!
Flag:
FV25{W3lc0m3_t0_S4nt4_OS}