v(*'='*)v かに

PlaidCTF 2019 writeup

I played PlaidCTF 2019 as a member of insecure. Our team got 261pts and reached the 116th place. I don't feel it's a good result. All the challenges I tried were very difficult but also a lot of fun.

[Misc 10pts(368 solves)] docker

docker pull whowouldeverguessthis/public

I ran docker pull and docker run --it public bash as the description says. Then I found the file flag in /root but the content was I'm sorry, but your princess is in another castle. I wondered that I'd get the solution if I could see how this container image was created. So, I ran docker image history and found that the flag was overwritten during the container creation.

$ docker image history whowouldeverguessthis/public --no-trunc
IMAGE                                                                     CREATED             CREATED BY                                                                                          SIZE                COMMENT
sha256:969996089570ead17d586e6b940c8cb0375aba7bd329076cbe2a2fc18653b8d9   6 hours ago         /bin/sh -c echo "I'm sorry, but your princess is in another castle" > /flag                         50B                 
<missing>                                                                 6 hours ago         /bin/sh -c echo "PCTF{well_it_isnt_many_points_what_did_you_expect}" > /flag                        51B                 
<missing>                                                                 2 months ago        /bin/sh -c #(nop)  CMD ["bash"]                                                                     0B                  
<missing>                                                                 2 months ago        /bin/sh -c #(nop) ADD file:34b9952e66cb98287bc41fab82739375fe6c43f38ed3b893e98a99035b494770 in /    68.9MB             

[Misc 100pts (306 solves)]can you guess me

Here's the source to a guessing game: here

You can access the server at

As I read the distributed python script, I found that I had to construct a python code with 10 or less than 10 types of characters which must be evaluated as the same value of secret_value_for_password in order to get the flag. Unfortunately print(flag) was invalid because it had 11 types of characters. I tried some other ways to dump the variables and found print(vars()). It has just 10 types of characters and it'll dump all the variables.

$ nc canyouguessme.pwni.ng 12349

  ____         __   __           ____                     __  __       
 / ___|__ _ _ _\ \ / /__  _   _ / ___|_   _  ___  ___ ___|  \/  | ___  
| |   / _` | '_ \ V / _ \| | | | |  _| | | |/ _ \/ __/ __| |\/| |/ _ \ 
| |__| (_| | | | | | (_) | |_| | |_| | |_| |  __/\__ \__ \ |  | |  __/ 
 \____\__,_|_| |_|_|\___/ \__,_|\____|\__,_|\___||___/___/_|  |_|\___| 

Input value: print(vars())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fe9664399e8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/guessme/can-you-guess-me.py', '__cached__': None, 'exit': <built-in function exit>, 'secret_value_for_password': 'not even a number; this is a damn string; and it has all 26 characters of the alphabet; abcdefghijklmnopqrstuvwxyz; lol', 'flag': 'PCTF{hmm_so_you_were_Able_2_g0lf_it_down?_Here_have_a_flag}', 'exec': <function exec at 0x7fe966382158>, 'val': 0, 'inp': 'print(vars())', 'count_digits': 10}
Nope. Better luck next time.

Well done.

[Misc 150pts(97 solves)]A Whaley Good Joke

You'll have a whale of a time with this one! I couldn't decide what I wanted the flag to be so I alternated adding and removing stuff in waves until I got something that looked good. Can you dive right in and tell me what was so punny?

We were given a tar.gz file which had various sha256-string name jsons/directories, manifest.json, and repositories. After doing a search, I found they were created by docker save. So, I tried docker load but it failed because some layer names were filled with ???. When I checked the contents archived in layer.tar for each layer directories, I found /root/flag.sh as shown below.


for i in {1..32}
    test -f $i
    if [[ $? -ne 0 ]]
        echo "Missing file $i - no flag for you!"

echo pctf{1_b3t$(cat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32)}

In order to get the flag, we have to concatenate the files from 1 to 32. Other layer.tar files contained some files such as /root/1, /root/20 and so on. However, they were duplicated since there were some processes which overwrote some files. So, I decided to do a search for the candidates of the flag by brute force.

Thus I decided to search the candidates of the flag by brute force.

for d in `ls`; do
    tar xf "$d/layer.tar" -C "$d"
import glob
import re

paths = [p for p in glob.glob("**", recursive=True) if re.search(r'root/[0-9]+$', p) and not p.startswith('workspace')]

table = {}
for p in paths:
    n = int(re.findall(r'root/([0-9]+)$', p)[0])
    with open(p) as f:
        x = f.read()

    if n not in table:
        table[n] = set()

def f(i, e, s):
    if i > e:

    for x in table[i]:
        f(i+1, e, s+x)

Of course it'd be hard to do a search for 32 characters so I did a search word by word.

Eventually I got the most plausible flag: pctf{1_b3t_4_couldnt_c0nt4in3r_ur_l4ught3r} but it didn't work. So, I posted the previous script to our team Slack. Then my teammate id:ptr-yudai submitted the flag pctf{1_b3t_u_couldnt_c0nt4in3r_ur_l4ught3r} and it was correct.