picoCTF 'Advanced Potion Making' Write Up
Enumeration
We can start off by doing some basic file searching in case the flag was hidden in plain sight.
-
We can run the
strings
command and grep the output on ‘pico’, ‘ctf’, ‘flag’, ignoring the case . No luck!┌─[parrot@parrot]─[~/Downloads] └──╼ $strings advanced-potion-making | grep -i pico ┌─[✗]─[parrot@parrot]─[~/Downloads] └──╼ $strings advanced-potion-making | grep -i ctf ┌─[✗]─[parrot@parrot]─[~/Downloads] └──╼ $strings advanced-potion-making | grep -i flag ┌─[✗]─[parrot@parrot]─[~/Downloads] └──╼ $
-
We can open the file in VS Code as text to see if there was any secret hidden in between. Also no luck.
-
Let’s try viewing the file in a Hex Editor. I have a feeling it’s an image, but don’t know for sure. Also, nothing seems to be hidden in the Hex Array.
File Format Discovery
The challenge description hinted at the file being corrupted by a spell, so we can start by trying to see what format it could be.
-
Let’s try using exiftool to see the metadata.
┌─[parrot@parrot]─[~/Downloads] └──╼ $exiftool advanced-potion-making ExifTool Version Number : 12.57 File Name : advanced-potion-making Directory : . File Size : 30 kB File Modification Date/Time : 2024:04:07 21:35:15+01:00 File Access Date/Time : 2024:04:07 21:35:15+01:00 File Inode Change Date/Time : 2024:04:07 21:35:19+01:00 File Permissions : -rw-r--r-- Error : Unknown file type
No luck! Let’s keep going.
-
We can use the Hex Editor in VS Code to take a deeper look at the bytes. We can use this link to match the file with a header signature List of File Signatures.
Searching for the first few bytes
89 50
could give us a match.Looks like we found it! It’s also the only one on that page with this pattern.
Fixing the Corrupted Bytes
We now have to fix the messed up bytes in order to get a working image.
-
Using the PNG Wiki Page The PNG Wikipedia Page has great information on the file header structure. We can use this to fix our byte array.
If you scroll down to Examples, the page shows the expected file header for a singular pixel.
Looking at this chart, we can see that bytes
02, 03, 09, 0A, 0B
are all corrupted. We can fix them using the Hex Editor to get this result. -
Using another PNG Image Another way we can double check the validity of the fix is to compare the header structure between another random PNG image and our file. We can use this image and inspect the header.
Either way, the end result is a fixed PNG image. We can add the
.png
extension and opened the image. This is the result.
No flag yet!
Editing the Image
We’re getting close, but not there yet.
-
It’s possible that there is extra data embedded in the image, but the file height is set too small, so the file viewer is unable to show it. BMP images can run into similar issues.
According to the Wiki page, bytes
16
and17
seemed to be setting the height. We can verify this by converting the height in decimal1240
to hex0x4d8
. Sure enough, bytes16
and17
are04
and8D
respectively. We can change this to match the09
and90
that is set for the width, in bytes12
and13
respectively.However, we get a checksum error in the image viewer.
This seemed like we’re going down a rabbit hole.
-
Let’s pivot and try and take a simpler approach. We can used Python and OpenCV to quickly view the layers of the image. Something seems strange!
import cv2
img = cv2.imread('advanced-potion-making.png')
img2 = img.copy()
img2[:,:,0]
Output:
array([[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29],
...,
[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29]], dtype=uint8)
img2[:,:,1]
Output:
array([[18, 18, 18, ..., 18, 18, 18],
[18, 18, 18, ..., 18, 18, 18],
[18, 18, 18, ..., 18, 18, 18],
...,
[18, 18, 18, ..., 18, 18, 18],
[18, 18, 18, ..., 18, 18, 18],
[18, 18, 18, ..., 18, 18, 18]], dtype=uint8)
img2[:,:,2]
Output:
array([[238, 238, 238, ..., 238, 238, 238],
[238, 238, 238, ..., 238, 238, 238],
[238, 238, 238, ..., 238, 238, 238],
...,
[238, 238, 238, ..., 238, 238, 238],
[238, 238, 238, ..., 238, 238, 238],
[238, 238, 238, ..., 238, 238, 238]], dtype=uint8)
Clearly something is up with these specific pixels with values of [29, 18, 238]
. They are likely obfuscating our flag. We can create a mask to filter out these pixels and set them to black. Let’s export the image!
mask = (img2[:,:,0] == 29) & (img2[:,:,1] == 18) & (img2[:,:,2] == 238)
img2[:,:,0][mask] = 0
img2[:,:,1][mask] = 0
img2[:,:,2][mask] = 0
cv2.imwrite('new.png', img2)
Boom, our flag is visible! picoCTF{w1z4dry}