Skip to main content
  1. Posts/

The Plaid Flag CTF 2023

·4 mins
Jinn
Writeup Reverse Honors CTF Top tier

Rev/CSS-crimes - 200pts #

Description:

I found this locked chest at the bottom o’ the ocean, but the lock seems downright… criminal. Think you can open it? We recommend chrome at 100% zoom. Other browsers may be broken.

The challenge link here

First look #

At the first look, it seems like a simple flag checker, but after I inspect this page, there is no <script> tag here. That why this challenge little hard.

Page:

Source:

Approach #

After I did a quick check, I found the message “Correct” here, but this z-index:0. That mean it’s covered by some things.

Also, there is no <button> tag here, I was figout what how we can choose the letter, I found this:

There are 27*3 <details> tags and followed by 4 svg

So, when we choose letter, those <details> tag will open, it was check 3 characters 1 time. This mean, if we choosed 3 character _, all details tag will open:

So what does opening these tags mean to check flags?

When these details have different size, so, when we open one of these, the page size was changed, that mean it affect on the svg position (top position):

But what was that svg background image?

I changed the fill from #fff to #111(black). As we can see, it’s a image with a part missing in the middle.

I changed z-index of the correct message to 1, and it matches the missing part of our svg image.

We know we can move the image up and down, so how did it move?

Let me example with the first image, this is base positon of this image:

and the calculated top position was -300:

I changed the first letter from a to b, and here is the result:

It was changed from -300 to -160, call first letter as x we now coefficient of x is 140

Reset that letter to a, do the same with the 2nd character (y), we can see the top position was change fromm -300 to -140, that mean coefficient of y is 160

and coefficient of z is 20

And we know exactly the pixel was change of the first image:

d = 140x + 160y + 20z

but when the top position >= 80, they will reset to base top positon (-300 as example), that mean we need modulo right side by 80 - (-300) = 380px.

But how we know what is the goal of top position in that case? I was do it by hand but it little hard, so I created the formula :

As we can see 62 was the first position the missing part was appear. And it same with the correct message.

So the correct top position of this image was (60 - 60) = 0

And the formula for this goal was: goal = 60 - start_postition

Do the same thing with 3 orther image we have this equations:

####################-------------------------  test, first brute
start = -300
goal = 0
mod = (goal - start) + 100
import string
m = string.ascii_lowercase + '_'
for x in range(27):
    for y in range(27):
        for z in range(27):
            if (140*x+160*y+20*z)%380 == (0 - (-300))\
            and (60*x +100*y+20*z)%220 == (20 - (-140))\
            and (320*x+80*y+ 20*z)%460 == (380 - 380)\
            and ( 300*x+200*y+20*z)%340 == (-60 - (-260)):
                print_chr(x,y,z) 
                
#output : "you"

Solve this stuff #

So we confirm that the 3 letter was correct, all you need to do is find the remaining characters

Each 3 letter, we need to find the following number:

4 start top position of 4 image

start = [-180,-300,-140,-260]

The top position of image when we change the first letter from a to b is present in p1[0], p2[0],… in order to caculate coefficient of x later. And similar with the second letter. Also, cofficient of z alway 20.

p1,p2,p3,p4 = [
    [-160,-160,20],
    [-160,-140,20],
    [-80,-40,20],
    [40,-60,20],
    ]

And the goal are the first position the missing part was appear. (rounded)

goal = [80,300,200,160]

Put things together:


def brute(start,p1,p2,p3,p4,goal):
    p1[0],p1[1] = p1[0] - start[0], p1[1] - start[0] # calc the coefficient of x, y of equation 1
    p2[0],p2[1] = p2[0] - start[1], p2[1] - start[1]
    p3[0],p3[1] = p3[0] - start[2], p3[1] - start[2]
    p4[0],p4[1] = p4[0] - start[3], p4[1] - start[3]
    goal = [60 - i for i in goal] # calc goal of top positio
    mod1,mod2,mod3,mod4 = [0 - start[i]+80 for i in range(4)] # find the mod
    dis1,dis2,dis3,dis4 = [goal[i] - start[i] for i in range(4)] #find the correct distance
    for x in range(27):
        for y in range(27):
            for z in range(27): 
                if (p1[0]*x+p1[1]*y+p1[2]*z)%mod1 == dis1\
                and (p2[0]*x+p2[1]*y+p2[2]*z)%mod2 == dis2\
                and (p3[0]*x+p3[1]*y+p3[2]*z)%mod3 == dis3\
                and (p4[0]*x+p4[1]*y+p4[2]*z)%mod4 == dis4:
                    print_chr(x,y,z)

Do the same with the remaining characters we will get flag.

Full script here: https://github.com/lephuduc/CTFs-Honors/blob/main/The%20Plaid%20Flag/solve.py