Skip to content

picoCTF 2024 Writeup - General Skills (1/5)

Posted on:3 April 2024

This year's picoCTF competition took place from March 12th to March 26th. Our team of 2 solved 43/47 challenges, with a total score of 7325/9225. We achieved 111th place out of 6957 teams globally. team profile screenshot leaderboard position

This is the first post out of 5 for picoCTF 2024, one for each of the categories in this year's competition.

Table of contents

Open Table of contents

END_TOC

We can use a simple ssh command to connect to the server:

$ ssh [email protected] -p55352
Host key fingerprint is SHA256:4S9EbTSSRZm32I+cdM5TyzthpQryv5kudRP9PIKT7XQ
+--[ED25519 256]--+
|        .+=o     |
|        .+o..    |
|        o o+ . . |
|       o o. + o +|
|        S  o+B.=+|
|       ....+=+OEo|
|        .o.o+o+o+|
|         .o .+ o |
|           +*.  .|
+----[SHA256]-----+
[email protected]'s password:
Welcome ctf-player, here's your flag: picoCTF{s3cur3_c0nn3ct10n_8969f7d3}
Connection to titan.picoctf.net closed.

Unzipping the challenge file shows us a directory with a .git subdirectory:

$ unzip challenge.zip
Archive:  challenge.zip
   creating: drop-in/
   creating: drop-in/.git/
   creating: drop-in/.git/branches/
  inflating: drop-in/.git/description
<...>
   creating: drop-in/.git/refs/
   creating: drop-in/.git/refs/heads/
 extracting: drop-in/.git/refs/heads/master
   creating: drop-in/.git/refs/tags/
 extracting: drop-in/.git/HEAD
  inflating: drop-in/.git/config
   creating: drop-in/.git/objects/
   creating: drop-in/.git/objects/pack/
   creating: drop-in/.git/objects/info/
   creating: drop-in/.git/objects/ed/
 extracting: drop-in/.git/objects/ed/5937346e2f3d04b2481120ac1eb6fc92e9f975
   creating: drop-in/.git/objects/eb/
 extracting: drop-in/.git/objects/eb/fc561e7c4130f03f3a668ce0609195788f1592
   creating: drop-in/.git/objects/66/
 extracting: drop-in/.git/objects/66/03cb4ff0c4ea293798c03a32e0d78d5ab12ca2
   creating: drop-in/.git/objects/d5/
 extracting: drop-in/.git/objects/d5/52d1ecd2d83fa2e65b6724d1ff73b45a7d59b7
   creating: drop-in/.git/objects/0c/
 extracting: drop-in/.git/objects/0c/1ab266b7a3a1cd099bb509f82b7a2d03aecd03
   creating: drop-in/.git/objects/38/
 extracting: drop-in/.git/objects/38/99edb7f3110d613c72ad40083fd8feeef703d0
  inflating: drop-in/.git/index
 extracting: drop-in/.git/COMMIT_EDITMSG
   creating: drop-in/.git/logs/
  inflating: drop-in/.git/logs/HEAD
   creating: drop-in/.git/logs/refs/
   creating: drop-in/.git/logs/refs/heads/
  inflating: drop-in/.git/logs/refs/heads/master
 extracting: drop-in/message.txt

We can see that there is a message.txt, and multiple objects referenced in the .git/objects directory. message.txt's contents aren't useful:

$ bat message.txt -p
TOP SECRET

Let's check git log to see what's happening:

$ git log
commit 3899edb7f3110d613c72ad40083fd8feeef703d0 (HEAD -> master)
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:58 2024 +0000

    remove sensitive info

commit 6603cb4ff0c4ea293798c03a32e0d78d5ab12ca2
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:58 2024 +0000

    create flag

We can checkout the first commit and then get the flag:

$ git checkout 6603cb4ff0c4ea293798c03a32e0d78d5ab12ca2
Note: switching to '6603cb4ff0c4ea293798c03a32e0d78d5ab12ca2'.
$ bat message.txt -p
picoCTF{s@n1t1z3_9539be6b}

Once again, the zip has a message.txt and a .git directory:

$ unzip challenge.zip
Archive:  challenge.zip
   creating: drop-in/
  inflating: drop-in/message.txt
   creating: drop-in/.git/
<...>
$ cd drop-in
$ bat message.txt -p
This is what I was working on, but I'd need to look at my commit history to know why...

Based on the message, we know we need to use git log:

$ git log
commit 3339c144a0c78dc2fbd3403d2fb37d3830be5d94 (HEAD -> master)
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:10:22 2024 +0000

    picoCTF{t1m3m@ch1n3_d3161c0f}

This time instead of a message.txt, we get a message.py:

$ unzip challenge.zip
Archive:  challenge.zip
   creating: drop-in/
 extracting: drop-in/message.py
   creating: drop-in/.git/
<...>
$ cd drop-in
$ bat -p message.py
print("Hello, World!"

Let's check git log:

$ git log
commit 8cc3930896bb01ae046bc08c382bd30772918ff5 (HEAD -> master)
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:06 2024 +0000

    important business work

commit 6dbd8d326a2f0c9fe7f0011c8e60448b9accc6ff
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:06 2024 +0000

    important business work

commit 2e8970529c41058a68aae8bc04ef7a2d53ce0d8a
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:06 2024 +0000

    important business work

commit 135020e8b96565248b604cb42ae54e256e8fc48a
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:06 2024 +0000

    important business work

commit a95fbac033f190b3fb1066727ea01d7d4be362b5
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:06 2024 +0000

    important business work

commit e6b8b174bf1ce6361ff29096579d2752616cb6f2
Author: picoCTF <[email protected]m>
Date:   Sat Mar 9 21:09:06 2024 +0000

    important business work
...

That's a lot of commits! Let's try to grep for the flag:

$ git log | grep picoCTF{
Author: picoCTF{@sk_th3_1nt3rn_b64c4705} <[email protected]m>

This time, there are multiple branches:

$ unzip challenge.zip
Archive:  challenge.zip
   creating: drop-in/
   creating: drop-in/.git/
<...>
   creating: drop-in/.git/refs/
   creating: drop-in/.git/refs/heads/
 extracting: drop-in/.git/refs/heads/main
   creating: drop-in/.git/refs/heads/feature/
 extracting: drop-in/.git/refs/heads/feature/part-1
 extracting: drop-in/.git/refs/heads/feature/part-2
 extracting: drop-in/.git/refs/heads/feature/part-3
<...>
   creating: drop-in/.git/logs/
  inflating: drop-in/.git/logs/HEAD
   creating: drop-in/.git/logs/refs/
   creating: drop-in/.git/logs/refs/heads/
  inflating: drop-in/.git/logs/refs/heads/main
   creating: drop-in/.git/logs/refs/heads/feature/
  inflating: drop-in/.git/logs/refs/heads/feature/part-1
  inflating: drop-in/.git/logs/refs/heads/feature/part-2
  inflating: drop-in/.git/logs/refs/heads/feature/part-3
  inflating: drop-in/flag.py
$ cd drop-in
$ bat -p flag.py
print("Printing the flag...")

Let's check the contents of flag.py in each of the branches:

$ git show feature/part-{1..3}:flag.py
print("Printing the flag...")
print("picoCTF{t3@mw0rk_", end='')print("Printing the flag...")

print("m@k3s_th3_dr3@m_", end='')print("Printing the flag...")

print("w0rk_6c06cec1}")

Combining the flag strings, we get picoCTF{t3@mw0rk_m@k3s_th3_dr3@m_w0rk_6c06cec1}.

We can connect to the server and play the game manually:

$ ssh -p 54716 [email protected]
The authenticity of host '[atlas.picoctf.net]:54716 ([18.217.83.136]:54716)' can't be established.
ED25519 key fingerprint is SHA256:M8hXanE8l/Yzfs8iuxNsuFL4vCzCKEIlM/3hpO13tfQ.
+--[ED25519 256]--+
|                 |
|         .   .   |
|  .     . = +    |
| + o + . = + .   |
|  = * + S. o  +  |
| . . + o.ooo+. o |
|.   .o   +ooo.. .|
|. . ..o.oo+.oE.o.|
| . .  ...oo.o= .o|
+----[SHA256]-----+
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[atlas.picoctf.net]:54716' (ED25519) to the list of known hosts.
[email protected]'s password:
Welcome to the Binary Search Game!
I'm thinking of a number between 1 and 1000.
Enter your guess: 500
Higher! Try again.
Enter your guess: 750
Higher! Try again.
Enter your guess: 875
Higher! Try again.
Enter your guess: 940
Higher! Try again.
Enter your guess: 970
Higher! Try again.
Enter your guess: 990
Higher! Try again.
Enter your guess: 1000
Lower! Try again.
Enter your guess: 995
Lower! Try again.
Enter your guess: 992
Higher! Try again.
Enter your guess: 993
Congratulations! You guessed the correct number: 993
Here's your flag: picoCTF{g00d_gu355_ee8225d0}
Connection to atlas.picoctf.net closed.

Again, we can just play the game with the help of cyberchef: using cyberchef to get little endian representation

$ nc titan.picoctf.net 62401
Welcome to the Endian CTF!
You need to find both the little endian and big endian representations of a word.
If you get both correct, you will receive the flag.
Word: netnd
Enter the Little Endian representation: 646e74656e
Correct Little Endian representation!
Enter the Big Endian representation: 6e65746e64
Correct Big Endian representation!
Congratulations! You found both endian representations correctly!
Your Flag is: picoCTF{3ndi4n_sw4p_su33ess_817b7cfe}

Not sure why this is 200 points.

Let's see what information is being leaked:

$ nc tethys.picoctf.net 59710
SSH-2.0-OpenSSH_7.6p1 My_Passw@rd_@1234

Seems like the SSH server header and a password. Let's connect to the application:

$ nc tethys.picoctf.net 50678
*************************************
**************WELCOME****************
*************************************

what is the password?
My_Passw@rd_@1234
What is the top cyber security conference in the world?
DEFCON
the first hacker ever was known for phreaking(making free phone calls), who was it?
John Draper
player@challenge:~$ ls -la
ls -la
total 20
drwxr-xr-x 1 player player   20 Mar  9 16:39 .
drwxr-xr-x 1 root   root     20 Mar  9 16:39 ..
-rw-r--r-- 1 player player  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 player player 3771 Apr  4  2018 .bashrc
-rw-r--r-- 1 player player  807 Apr  4  2018 .profile
-rw-r--r-- 1 player player  114 Feb  7 17:25 banner
-rw-r--r-- 1 root   root     13 Feb  7 17:25 text

After quickly googling for the first phreaker, we can get a shell on the server. Let's see what we can find:

player@challenge:~$ cat banner
cat banner
*************************************
**************WELCOME****************
*************************************
player@challenge:~$ cat text
cat text
keep digging
player@challenge:~$ cd /root
cd /root
player@challenge:/root$ ls
ls
flag.txt  script.py
player@challenge:/root$ cat flag.txt
cat flag.txt
cat: flag.txt: Permission denied
player@challenge:/root$ ls -la
ls -la
total 16
drwxr-xr-x 1 root root    6 Mar  9 16:39 .
drwxr-xr-x 1 root root   29 Mar 12 18:06 ..
-rw-r--r-- 1 root root 3106 Apr  9  2018 .bashrc
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-rwx------ 1 root root   46 Mar  9 16:39 flag.txt
-rw-r--r-- 1 root root 1317 Feb  7 17:25 script.py

Seems like we're meant to read /root/flag.txt. Unfortunately, only root has permission to read it. Let's see if there's anything interesting in script.py:

player@challenge:/root$ cat script.py
cat script.py

import os
import pty

incorrect_ans_reply = "Lol, good try, try again and good luck\n"

if __name__ == "__main__":
    try:
      with open("/home/player/banner", "r") as f:
        print(f.read())
    except:
      print("*********************************************")
      print("***************DEFAULT BANNER****************")
      print("*Please supply banner in /home/player/banner*")
      print("*********************************************")

try:
    request = input("what is the password? \n").upper()
    while request:
        if request == 'MY_PASSW@RD_@1234':
            text = input("What is the top cyber security conference in the world?\n").upper()
            if text == 'DEFCON' or text == 'DEF CON':
                output = input(
                    "the first hacker ever was known for phreaking(making free phone calls), who was it?\n").upper()
                if output == 'JOHN DRAPER' or output == 'JOHN THOMAS DRAPER' or output == 'JOHN' or output== 'DRAPER':
                    scmd = 'su - player'
                    pty.spawn(scmd.split(' '))

                else:
                    print(incorrect_ans_reply)
            else:
                print(incorrect_ans_reply)
        else:
            print(incorrect_ans_reply)
            break

except:
    KeyboardInterrupt

The program opens /home/player/banner and outputs it at the start. Based on the hint, we know that we are meant to use symlinks. If we can change /home/player/banner to be a symlink, we can point it to /root/flag.txt and cause the program to read it for us.

player@challenge:/proc$ cd ~
cd ~
player@challenge:~$ ls -l
ls -l
total 8
-rw-r--r-- 1 player player 114 Feb  7 17:25 banner
-rw-r--r-- 1 root   root    13 Feb  7 17:25 text
player@challenge:~$ rm banner
rm banner
player@challenge:~$ ln -s /root/flag.txt banner
ln -s /root/flag.txt banner
player@challenge:~$ ll
ll
total 20
drwxr-xr-x 1 player player   41 Mar 12 18:16 ./
drwxr-xr-x 1 root   root     20 Mar  9 16:39 ../
-rw------- 1 player player    5 Mar 12 18:11 .bash_history
-rw-r--r-- 1 player player  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 player player 3771 Apr  4  2018 .bashrc
-rw-r--r-- 1 player player  807 Apr  4  2018 .profile
lrwxrwxrwx 1 player player   14 Mar 12 18:16 banner -> /root/flag.txt*
-rw-r--r-- 1 root   root     13 Feb  7 17:25 text
player@challenge:~$ ^[

And now if we connect to the server again:

$ nc tethys.picoctf.net 50678
picoCTF{b4nn3r_gr4bb1n9_su((3sfu11y_68ca8b23}

what is the password?
^C

Connecting to the server puts us into a bash jail, with all letters being blocked:

SansAlpha$ a
SansAlpha: Unknown character detected
SansAlpha$ A
SansAlpha: Unknown character detected
SansAlpha$ ""
bash: : command not found

Let's see what files are in the current directory:

SansAlpha$ **
bash: blargh: command not found

SansAlpha$ **/*
bash: blargh/flag.txt: Permission denied

Seems like we will need to read blargh/flag.txt somehow. But how can we use cat without using the letters c, a, and t? A quick google search brings us to a page called launch bash without using any letters. The second answer uses a redirection of a bash "command not found" error message to stdout, then using brace expressions to extract the bash string from the start. Using this method, we can get a bunch of letters into a variable:

SansAlpha$ _1="$(- 2>&1)"
bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)

SansAlpha$ "$_1"
bash: bash: -: command not found: command not found

Now that we have bash: -: command not found: command not found, we have enough letters to use cat. We can use "Substring Expansion" in order to extract each letter that we need, in the form ${parameter:offset:length}.

Here are the characters we need:

Putting it together:

SansAlpha$ _2="${_1:9:1}${_1:1:1}${_1:19:1}"
SansAlpha$ $_2 **/*
return 0 picoCTF{7h15_mu171v3r53_15_m4dn355_36a674c0}Alpha-9, a distinctive layer within the Calastran multiverse, stands as a
sanctuary realm offering individuals a rare opportunity for rebirth and
introspection. Positioned as a serene refuge between the higher and lower
Layers, Alpha-9 serves as a cosmic haven where beings can start anew,
unburdened by the complexities of their past lives. The realm is characterized
by ethereal landscapes and soothing energies that facilitate healing and
self-discovery. Quantum Resonance Wells, unique to Alpha-9, act as conduits for
individuals to reflect on their past experiences from a safe and contemplative
distance. Here, time flows differently, providing a respite for those seeking
solace and renewal. Residents of Alpha-9 find themselves surrounded by an
atmosphere of rejuvenation, encouraging personal growth and the exploration of
untapped potential. While the layer offers a haven for introspection, it is not
without its challenges, as individuals must confront their past and navigate
the delicate equilibrium between redemption and self-acceptance within this
tranquil cosmic retreat.

And this is proof that any device is good enough for CTFs:

screenshot of termius on mobile solved

Conclusion

Overall, this year's General Skills challenges were very underwhelming. The only one that I enjoyed was SansAlpha, which taught me how to use bash error strings to construct commands. I felt that this category was acting like a git tutorial this year and I would loved to have see some more creative and interesting challenges. I also felt that a lot of the challenges in this category suffered from point inflation, and did not accurately reflect the difficulty of these challenges.