Write up Reversing Plaid CTF 2023

Maulvi Alfansuri
8 min readApr 17, 2023


This week, I played two CTFs: first was Hackpack CTF 2023, and after competition ended, I moved on to Plaid CTF. Today, I want to share my write-up about the reverse engineering challenges in Plaid CTF 2023.

All challenges can be downloaded here

Table of Content

· Treasure
· Epilogue


challenges banner

This challenge provided one URL. Upon accessing the page, we see a simple prompt to input a valid flag.

Treasure URL page contains simple input form

Looking at the source, we find files 0.js, fail.js, pirate-map.png, and index.html itself.

Challenges resource list

Below is the JavaScript code of index.html

Js code of index.html

When we examine the arrow pointed at the script, it imports 0.js, checks the flag length, and slices the flag. The flag format is deleted, and the flag is saved in the window.buffer. The program then calls the function ‘go’. Now let’s inspect 0.js.

Complex routine on 0.js

In 0.js, we see a complex encryption routine that gets executed. In the end, the complex encryption routine is saved to variable ‘fl’, and variable ‘vl’ is loaded as a JS module that calls the ‘go’ function.

Now let’s try to input a valid flag format: PCTF{<25 characters>}.

Input valid format form

When we examine the source after the user inputs a valid flag format, we see that the program loads many JS files.

Program load many js (and jsmap) if input with correct form

To debug this program, I modified the program code and added logging for every parameter that was created.

Add logger
  1. Parameter ‘bti’ is a base64 list mapping with integer enumeration.
  2. Parameter ‘upc’ is a character of the flag.
  3. Parameter ‘moi’ is the source code of the current JS executed.
  4. Parameter ‘tg’ is JSON-parsed data of the JS map.
  5. Parameter ‘fl’ is the next JS file that will be loaded.
Parameter explained — 1
Parameter explained — 2
Parameter explained — 3

From the log, we know that the program performs some mapping based on our input to determine the next JS file that needs to be executed. For example, if we are on 0.js and input “a”, the program maps the next JS file to be loaded as 60.js. This loop continues until all input characters are processed.

View map output

To solve this, we know that if our input flag is valid, the program will call success.js in the end. I attempted to brute force the valid flag from success.js. I tried to find valid characters that mapped to success.js from all 0–200 JS files, then printed all concurrences and manually added valid character flags. Below is the script that implements my idea.

After running for a few minutes, the result is shown below.

Flag output wrong on the initial

The program failed to guess the initial flag because there are two or more valid characters. I manually looped and analyzed the rest of the flag and found the valid flag: Need+a+map/How+about+200!


css banner

The next challenge is named ‘css’. An URL is provided for the challenge. As the name suggests, this challenge is related to HTML and CSS. Below is a screenshot of the challenge.

css page

We can see the flag format with an arrow above and below each letter. When we click the arrow, the flag character changes.

click the arrow changed flag character

Now let’s look for fun (cursed) parts. If we examine the source, we find no JS included in this challenge.

resources only contains html files

This also applies to the HTML files. All 8318 lines consist only of CSS and HTML without JavaScript logic. But we wonder how the program performs logic to compare the right or true flag that we input.

8318 files only contains html and css

If we look at line 8, we find that in the HTML code, there’s a segment that shows “correct” but is hidden with z-index:0.

correct div hidden

If we inspect the object, the location of the “correct” div exists below the PCTF characters.

correct div position

Now let’s reverse-engineer how the program “hides” this correct div. If we look at the HTML code, there are several divs listed below

flag segment div

Each div consists of three characters of the prompt, including all the elements inside, such as arrows and so on. If we delete the first div, the application would look like the image below. I will refer to these as flag segments.

If first flag segment deleted, this would happened

Now let’s explore the segment div. The program has many element details. Each flag segment has 26 * 3 segment details, because flag segments consist of three characters.

Each flag segments contains 26 * 3 details

When we click the arrow, for example, on the last flag character, a few element details in the last flag segment have the property ‘open’.

few details would get opened, when arrow clicked

These details also contain a summary and a div with a CSS height involving complex calculations

Summary details had complex height calculation

At the end of each flag segment, below all the details, we should find a div like the one shown below.

blocker class consist of 3 parts

This div is a blocker element that blocks the correct character

The blocker element consists of three parts: the outer ‘blocker’ class, a ‘blocker container’ that acts as a container for the SVG image, and a ‘blocker SVG’ that serves as the image blocking our flag. Each flag segment has four blocker classes. We should see four SVGs in the code below

4 svg on every flag segments

After I enumerated the pages, I found the following results.

There are transparent blocks on the middle of PCTF{

In this experiment, I modified the default background to grey, deleted all segments except the last one, deleted three of the four blocker classes, and enumerated the flag characters.

In the middle of PCTF, we should see a grey section, and the PCTF section itself has a white color. I also inspected the ‘blocker container’, which has a blue box in the image.

Now let’s check the SVG to understand what the page does

Convert base64 svg to real svg

If we view it in the SVG viewer, we should see the image below. The program has a white bar in the middle, indicating that this rectangle is transparent, and only the area above and below the rectangle is filled. This rectangle is crafted to ensure we can view the correct div.

White blocks do as transparent rectangle

We know that the program has four SVGs based on the four blocker containers

For every flag segments there are 4 svg and 4 blocker container

The rectangle of the blocker SVG can form a position based on our input on the flag segment. Below are a few examples:

  1. Blocker optimal
blocker rectangle is on optimal position

2. Blocker splitted

blocker rectangle is splitted

3. Blocker stacked

blocker rectangle is stacked

We need all blocker rectangles to have a position and craft new blocks that make the correct bar viewable.

We need form blocker rectangle to all overlaped to correct div

To calculate the real location of the SVG blocks, we should be able to get the SVG dimensions based on the blocker container. The SVG dimensions based on the blocker container can be represented as ‘h’ for the top and ‘b’ for the bottom. We should also see that the blocker container has property ‘H’, the top position of this container relative to the viewport. The real rectangle position can be calculated as H + h for the top SVG and H + b for the bottom SVG.

Illustration of finding real svg location

Below is the code for checking the overlapped rectangle with the correct segment.

Check overlaped logic

Now let’s create a strategy to solve this:

  1. Divide flag segments and loop through the segments from the last to the first. Each segment consists of three characters.
  2. Brute-force the three characters of the segment.
  3. Get four blocker classes and parse the SVG to get rectangle properties.
  4. Get correct flag properties.
  5. Compare rectangle properties to correct flag properties. If there are no overlapped positions, return false.
  6. If all rectangles overlap with correct flag properties, return true. All rectangle overlaps indicate that we can see the correct flag.
  7. If step 6 returns true, break the brute-force loop and go to segment — 1, then do step 2.
  8. If step 7 returns false, continue the brute-force of the three characters.

Below is the JavaScript code that implements these ideas.

After running for approximately 20–30 minutes, all input on the webpages returns valid correct results.

After bruteforce completed, this result would come up


I am glad I was able to complete one of the Plaid CTF challenges this year. Plaid CTF is always a difficult competition. I had fun and enjoyed the CTF, especially the CSS challenge. The process of reverse engineering the challenges and understanding the underlying logic was both engaging and rewarding. These types of challenges not only improve problem-solving skills but also enhance one’s understanding of web technologies and their intricacies.