Try   HackMD
tags: 2023 ctf writeup rev

The Plaid Flag 2023

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Source:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

So what does opening these tags mean to check flags?

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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):

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

But what was that svg background image?

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

and the calculated top position was -300:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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 :

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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 Plaid Flag/solve.py