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.

  1. 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]
    └──╼ $
    
  2. We can open the file in VS Code as text to see if there was any secret hidden in between. Also no luck.

  3. 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.

  1. 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.

  2. 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.

    Hex Array of advanced-potion-making file

    Searching for the first few bytes 89 50 could give us a match.

    Wikipedia File Signature Entry for PNG

    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.

  1. 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.

    PNG Header Structure

    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.

    Fixed PNG Header Byte Array

  2. 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.

    advanced-potion-making.png

No flag yet!

Editing the Image

We’re getting close, but not there yet.

  1. 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 and 17 seemed to be setting the height. We can verify this by converting the height in decimal 1240 to hex 0x4d8. Sure enough, bytes 16 and 17 are 04 and 8D respectively. We can change this to match the 09 and 90 that is set for the width, in bytes 12 and 13 respectively.

    However, we get a checksum error in the image viewer.

    Checksum Error

    This seemed like we’re going down a rabbit hole.

  2. 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)

Flag Image

Boom, our flag is visible! picoCTF{w1z4dry}