picoCTF 'Advanced Potion Making' Write Up

Enumeration

I started off by doing some basic file searching in case the flag was hidden in plain sight.

  1. I ran the strings command and grepped 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. I then opened the file in VS Code as text to see if there was any secret hidden in between. Also no luck.

  3. I then moved on to viewing the file in a Hex Editor. I had a feeling it was an image, but didn’t know for sure. Also, nothing seemed to be hidden in the Hex Array.

File Format Discovery

The challenge description hinted at the file being corrupted by a spell, so I started with trying to see what format it could be.

  1. I tried using exiftool to see what metadata I could get.

    ┌─[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. I used the Hex Editor in VS Code to take a deeper look at the bytes. I used this link to match the file with a header signature List of File Signatures.

    Hex Array of advanced-potion-making file

    I then searched for the first few bytes 89 50 to find 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. I fixed them using the Hex Editor to get this result.

    Fixed PNG Header Byte Array

  2. Using another PNG Image Another way I double checked the validity of my fix was comparing the header structure between another random PNG image and my file. I used this image and inspected the header.

    Either way, the end result is a fixed PNG image. I added the .png extension and opened the image. This was the result.

    advanced-potion-making.png

No flag yet!

Editing the Image

We’re getting close, but not there yet.

  1. My initial instinct was that there was extra data embedded in the image, but the file height was set too small, so it was unable to show it. I had encountered something similar with a BMP image.

    According to the Wiki page, bytes 16 and 17 seemed to be setting the height. I verified this by converting the height in decimal 1240 to hex 0x4d8. Sure enough, bytes 16 and 17 are 04 and 8D respectively. I changed this to match the 09 and 90 that is set for the width, in bytes 12 and 13 respectively.

    When I did this, I got a checksum error in my image viewer.

    Checksum Error

    I could’ve tried to investigate this further and fix the checksum, but this seemed like I was going down a rabbit hole.

  2. I pivoted to try and take a simpler approach. I used Python and OpenCV to quickly view the layers of the image and noticed something obvious.

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 was up with all of these pixel values of [29, 18, 238]. I created a mask to filter out these pixels and set them to black. I then exported the image and boom!

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

Our flag is visible! picoCTF{w1z4dry}