Hack The Planet CTF 2026
On March 20th and 21st, SAIT hosted their winter 2026 CTF, themed around the movie Hackers: Hack The Planet.
Just like last semester, I wasn’t competing and instead built some challenges for the two day competition.
In this post I’m going to share the challenges, my intended solution for each, and my experience and thoughts on making them.
Challenges
For anyone interested in checking out these challenges, any of the previous ones I made, or any future ones, check out this GitHub repository.
I have uploaded everything there as a way to more easily store them for myself but will also add to it as I build more.
Magic Communication - Easy Forensics
Your team is worried that their messages are being intercepted and have devised a plan to hide them through unconventional means. Can you uncover what they were trying to say?
For this challenge the user was provided with one image file, the above description, and one optional hint.
Hint 1 - 0 points
Did you check the file metadata?Challenge Walkthrough
If you have any experience with CTFs you may have seen challenges like this before.
Attempting to open the image once it was downloaded presents the user with an error that there is something wrong with the file.
Running a tool like xxd, we can see that the magic numbers of this supposed PNG image are not the right ones.
─$ xxd flag.png | head
00000000: 9805 e474 d0a0 a1a0 0000 000d 4948 4452 ...t........IHDR
00000010: 0000 05c8 0000 05c8 0100 0000 00a6 4c10 ..............L.
00000020: d600 0008 4f49 4441 5478 9ced dd41 8edb ....OIDATx...A..
I made it even easier for the player by actually just swapping the bytes around.
Using the tool hexeditor or anything similar and replacing the first four bytes with the correct ones will fix the format of the image, allowing for it to be properly viewed.
─$ xxd flag.png | head
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 05c8 0000 05c8 0100 0000 00a6 4c10 ..............L.
00000020: d600 0008 4f49 4441 5478 9ced dd41 8edb ....OIDATx...A..
Once fixed and properly viewable, the image reveals a QR code. Scanning it presents the user with the challenge flag.
HTPCTF{w0w_y0u_f1x3d_th3_m4g1c_numb3r5}
My Thoughts and Experience
A few months ago I was messing around with an old photo of myself that had a scannable QR code in it. However, since I used a “free” online site to generate it, anytime anyone scanned it once it expired, I would get sent an email.
So as a little side project, I generated my own QR code to my blog, that I then photoshopped over the original QR code for whenever I reuse that photo.
That side project and the many challenges I have experienced from other competitions with QR codes in them inspired me to build this very beginner friendly challenge this time around.
This was not my original idea for this challenge though. While scrolling Reddit a few weeks back I stumbled across this post.
I thought it would be super cool to take a photo, put a transparent image of QR code onto it or something, and then mess around with the metadata enough that it was still visible but would appear like that photo.
I didn’t think too much into it though and was running out of time to fully flesh out the idea, maybe one day in the future though I will get something working because I think it could be fun.
Hidden Layers - Hard Forensics
A USB was left behind at Cyberdelia containing a few images. Nothing unusual at first, two appear nearly identical and the third seemingly random.
For this challenge the user was provided with three image files, the above description, and two optional hints.
Hint 1 - 0 points
The metadata from one of the images will provide a hintHint 1 - 50 points
Every image you were given is important and necessaryChallenge Walkthrough
Two of the images, like mentioned in the description, do look the same and are stills from the movie Hackers. The third image is the FBI logo.
Running exiftool or zsteg on the image cyberdelia-2.png provides a hint about the LSB of the image.
Using a quick Python script, the player can extract the info hidden in the LSB:
from PIL import Image
import numpy as np
img = Image.open("cyberdelia-2.png").convert("L") # load image, force into grayscale
arr = np.array(img, dtype=np.uint8) # turns into a NumPy array, each pixel is a byte
hidden_bits = arr & 1 # keep only the LSB
hidden_img = (hidden_bits * 255).astype(np.uint8) # make the bit visible, 1=white and 0=black
Image.fromarray(hidden_img).save("recovered.png") # save image
Viewing the recovered image reveals the following binary: 01111010 00110011 01110010 00110000 00101101 01100011 00110000 00110000 01101100.
Using CyberChef or any binary decoder reveals: z3r0-c00l.
Then using steghide on the image of the FBI logo with the command steghide extract -sf fbi.jpg, and entering the passphrase found above, you can extract flag.txt from the image.
Viewing the flag reveals the challenge flag.
HTPCTF{cyb3rd3l14_15_pr3tty_c00l}
My Thoughts and Experience
For this challenge I had taken inspiration from the challenge Tricky Communications from CyberSci Regionals 2024.
I’m not going to really try and explain it here because I don’t think I could do it any justice, but I did link to the challenge on GitHub which includes an entire walkthrough.
I didn’t really get close to what this challenge was but it is what gave me the original idea for creating this challenge and was one of the first ones I thought of.
It was fun to create and honestly I want to try making it again in the future, just like the last one, it was close to my original vision but I think I could improve it a lot more.
Locked Terminal - Medium Reverse Engineering
After Crash Override stole that file from the gibson, the employee terminal has been disabled for the unforeseeable future. Only a select few have access as needed and some info has been redacted from output for extra caution. Good luck getting anything you’re looking for.
For this challenge the user was provided an executable and the above description.
Challenge Walkthrough
Running strings on the provided executable reveals the encoded flag and key.
Loading the file into Ghidra and locating the function h4ckthepl4net (2nd function listed of the three that look similar) in the symbols tree reveals some key info.
It does show the encoded flag and key again, as well as the modular arithmetic used by the Vigenere cipher on each character in the flag, which helps identify the cipher used.
Using CyberChef or another Vigenere cipher decoder, inputting the key cerealkiller and the encoded flag JXGGTQ{i0c_mc0o3_1ev0_k1sw0n}, revals the challenge flag.
HTPCTF{y0u_br0k3_1nt0_g1bs0n}
My Thoughts and Experience
Originally for this challenge I was also going to provide the source code but when doing some last minute testing and writing out the intended solution for the other testers, I realized it would have been too easy.
This is what the source code looks like for anyone curious:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
__attribute__((used))
void h4ckthepl4net() {
unsigned char h4Ckthepl4net[] = {
74,88,71,71,84,81,123,105,48,99,95,109,99,48,111,51,95,49,101,118,48,95,120,121,51,95,107,49,98,100,48,120,125
};
char h4cKthepl4net[] = "cerealkiller";
int h4cKthep14net = strlen(h4cKthepl4net);
int h4ckthep14net = sizeof(h4Ckthepl4net)/sizeof(h4Ckthepl4net[0]);
char hackth3p1an3t[h4ckthep14net+1];
int h4cktHepl4net = 0;
for (int i = 0; i < h4ckthep14net; i++) {
char c = h4Ckthepl4net[i];
if (c >= 'A' && c <= 'Z') {
int shift = h4cKthepl4net[h4cktHepl4net % h4cKthep14net] - 'a';
hackth3p1an3t[i] = ((c - 'A' - shift + 26) % 26) + 'A';
h4cktHepl4net++;
}
else if (c >= 'a' && c <= 'z') {
int shift = h4cKthepl4net[h4cktHepl4net % h4cKthep14net] - 'a';
hackth3p1an3t[i] = ((c - 'a' - shift + 26) % 26) + 'a';
h4cktHepl4net++;
}
else {
hackth3p1an3t[i] = c;
}
}
hackth3p1an3t[h4ckthep14net] = '\0';
printf("\t[encoded flag]\n");
// printf("%s\n", hackth3p1an3t);
}
void hackth3plan3t() {
int h4cKth3p14n3t;
printf("\t1) Corporate Secrets\n");
printf("\t2) Ultimate Question of Life\n");
printf("\t3) Challenge Flag\n");
printf("\t4) Exit\n");
printf("Please select an option to access: ");
scanf("%d", &h4cKth3p14n3t);
if (h4cKth3p14n3t == 1) {
printf("After the breach you think we would still hide our secrets here?\n\n");
hackth3plan3t();
}
else if (h4cKth3p14n3t == 2) {
printf("Unfortunately I don't have the answer to that but you might like this: \n");
h4ckthepl4net();
printf("Due to the breach though we can no longer decode it or print the encoded version\n\n");
hackth3plan3t();
}
else if (h4cKth3p14n3t == 3) {
printf("You really think I was going to make it that easy?\n\n");
hackth3plan3t();
}
else if (h4cKth3p14n3t == 4) {
exit(0);
}
else {
printf("INCORRECT OPTION, TRY AGAIN\n\n");
hackth3plan3t();
}
}
void h4ckth3pl4n3t(int h4Ckth3p14n3t) {
char h4ckTh3p14n3T[50];
char h4ckTh3pl4n3T[50];
printf("Enter the username: ");
scanf("%49s", h4ckTh3p14n3T);
printf("Enter the password: ");
scanf("%49s", h4ckTh3pl4n3T);
if (strcmp(h4ckTh3p14n3T, "maintenance") == 0 && strcmp(h4ckTh3pl4n3T, "password") == 0) {
printf("\nWelcome back MAINTENANCE\n");
hackth3plan3t();
}
else if (h4Ckth3p14n3t == 2) {
printf("INCORRECT LOGIN\n");
exit(0);
}
else {
printf("INCORRECT LOGIN, TRY AGAIN\n\n");
h4ckth3pl4n3t(h4Ckth3p14n3t + 1);
}
}
int main(int argc, char *argv[]) {
int h4ckth3p14n3t = 0;
if (argc > 1 &&
(strcmp(argv[1], "-b") == 0 || strcmp(argv[1], "--bypass") == 0)) {
h4ckth3p14n3t = 1;
}
if (h4ckth3p14n3t) {
printf("\nWELCOME to the Corporate Employee Terminal\n");
h4ckth3pl4n3t(0);
} else {
printf("\nCorporate Employee Terminal is currently OUT OF ORDER\n");
printf(" ------ Please come back and try again later ------ \n\n");
exit(0);
}
return 0;
}
I went through and attempted to obfuscate a bunch of the functions and variables so it would be harder to sift through it but I didn’t set it up well for the users to be given the source code.
The effort did slightly help though on the executable side, so I’m glad.
Like the other challenges though, I would really want to try building this again, and honestly testing it out myself multiple ways gave me lots of practice with Ghidra.
Conclusion
I enjoyed building these challenges and it was fun trying to step up the difficulty this time around for a few of them, as well as switching it up with the categories I was building for.
I’m grateful to have gotten the chance to work with some of these people again at this event, I always enjoy talking about the challenges with them.
Not sure when my next chance to help build challenges will be but I’m looking forward to some other future events already on my calendar.