CodeMash 2018 CTF write-up

01 Do you like my Style?

Every one is talking about styles. Sometimes its about what you wear and sometimes its about what you do.

All I know is that the flag you want definitely has Style.

The Web Developer Tools (available for Firefox and Chrome) easily reveal the flag:

Flag01

The flag:

cm18-te94-1tuJ-ddx9-3dQO

02 Hobo Robo

Hobo Robo prepared a paper chase for you.

Start

A click on the provided link redirects to the C-3PO Wikipedia site. Downloading the site with wget sheds some more light into the dark.

wget https://codemash.hacking-lab.com/codemash/bots/bots.html
<html>
  <head>
        <title>Bots</title>
        <script type="text/javascript">
          eval(String.fromCharCode(105, 102, 32, 40, 33, 40, 110, 97, 118, 105, 103, 97, 116, 111, 114, 46, 117, 115, 101, 114, 65, 103, 101, 110, 116, 32, 61, 61, 61, 32, 39, 72, 111, 98, 111, 82, 111, 98, 111, 39, 41, 41, 32, 123, 32, 108, 111, 99, 97, 116, 105, 111, 110, 46, 114, 101, 112, 108, 97, 99, 101, 40, 39, 104, 116, 116, 112, 58, 47, 47, 101, 110, 46, 119, 105, 107, 105, 112, 101, 100, 105, 97, 46, 111, 114, 103, 47, 119, 105, 107, 105, 47, 67, 45, 51, 80, 79, 39, 41, 59, 125))
    </script>
  </head>
  <body style="background: white; border: 20px solid white;">
    <div style="widht: 100%; height: 100%; background: url('./robotbg.jpg') no-repeat center center fixed; -webkit-background-size: contain; -moz-background-size: contain; -o-background-size: contain; background-size: contain;">&#160;</div>
  </body>
</html>

At a first glance, the string looks promising. The following Python code can be used to decode it:

data = [105, 102, 32, 40, 33, 40, 110, 97, 118, 105, 103, 97, 116, 111, 114, 46, 117, 115, 101, 114, 65, 103, 101, 110, 116, 32, 61, 61, 61, 32, 39, 72, 111, 98, 111, 82, 111, 98, 111, 39, 41, 41, 32, 123, 32, 108, 111, 99, 97, 116, 105, 111, 110, 46, 114, 101, 112, 108, 97, 99, 101, 40, 39, 104, 116, 116, 112, 58, 47, 47, 101, 110, 46, 119, 105, 107, 105, 112, 101, 100, 105, 97, 46, 111, 114, 103, 47, 119, 105, 107, 105, 47, 67, 45, 51, 80,79, 39, 41, 59, 125]

buf = ""
for i in data:
    buf += chr(i)

print buf
python2 sol.py
  if (!(navigator.userAgent === 'HoboRobo')) { location.replace('http://en.wikipedia.org/wiki/C-3PO');}

We already know about the redirection, so let's take a look the mentioned URL (url('./robotbg.jpg')):

wget https://codemash.hacking-lab.com/codemash/bots/robotbg.jp
robotbg.jpg

The words

bama waboki pisal fatatu fomu wosebi seju sowu seju - bamas mufe wafub fomu mowewe

are written in the Robot Interaction Language (ROILA) and decode to:

you must make word of addition two and two - this be name of page
wget https://codemash.hacking-lab.com/codemash/bots/four.html

The file content leads to the next URL and the next image:

<html>
  <head>
        <title>Bots</title>
        <meta name="description" content="Robots talk in ROILA language: eman egap eht esrever tsum">
    <meta name="keywords" content="secret, page, robots, fun, hacky easter, blrt, five, beep">
        <script type="text/javascript">
          eval(String.fromCharCode(105, 102, 32, 40, 33, 40, 110, 97, 118, 105, 103, 97, 116, 111, 114, 46, 117, 115, 101, 114, 65, 103, 101, 110, 116, 32, 61, 61, 61, 32, 39, 72, 111, 98, 111, 82, 111, 98, 111, 39, 41, 41, 32, 123, 32, 108, 111, 99, 97, 116, 105, 111, 110, 46, 114, 101, 112, 108, 97, 99, 101, 40, 39, 104, 116, 116, 112, 58, 47, 47, 101, 110, 46, 119, 105, 107, 105, 112, 101, 100, 105, 97, 46, 111, 114, 103, 47, 119, 105, 107, 105, 47, 67, 45, 51, 80, 79, 39, 41, 59, 125))
    </script>
  </head>
  <body style="background: white; border: 20px solid white;">
    <div style="widht: 100%; height: 100%; background: url('./robotbg2.jpg') no-repeat center center fixed; -webkit-background-size: contain; -moz-background-size: contain; -o-background-size: contain; background-size: contain;">&#160;</div>
  </body>
</html>
wget https://codemash.hacking-lab.com/codemash/bots/robotbg2.jpg
robotbg2.jpg

The image shows the word metae, let's take a second look into four.html and especially the meta data entry:

<meta name="description" content="Robots talk in ROILA language: eman egap eht esrever tsum">
python2 -c 'print "eman egap eht esrever tsum"[::-1]'
   must reverse the page name
wget https://codemash.hacking-lab.com/codemash/bots/ruof.html
wget https://codemash.hacking-lab.com/codemash/bots/robotbg3_1337807.jpg
robotbg3_1337807.jpg

03 - 1337 Riddler

1337 r1ddler h4s a puzzl3 f0r u 2 solve!

H3 1s l1st3n1ng 0n th3 BEST p0r7 on this s3rv3r!

The "BEST p0r7"? Let's don't asume, let's test and verify:

nmap -p- codemash.hacking-lab.com
 PORT     STATE  SERVICE
 22/tcp   open   ssh
 80/tcp   open   http
 443/tcp  open   https
 3544/tcp closed teredo
 8357/tcp open   unknown

Port 8357 seems promising, let's verify using netcat:

nc codemash.hacking-lab.com 8357
  Make an educated guess, dude:
  1
  I need 20 digits, dude!
nc codemash.hacking-lab.com 8357
  Make an educated guess, dude:
  11111111111111111111
  0<
nc codemash.hacking-lab.com 8357
  Make an educated guess, dude:
  91111111111111111111
  0>
nc codemash.hacking-lab.com 8357
  Make an educated guess, dude:
  71111111111111111111
  1<

The task seems to be to guess the 20 digit code. The return value gives the number of already correct digits and a hint if the actual guess is smaller or bigger than the correct value.

The following Python code does not endeavour to be the most clever or elegant solution. It just works in a feasible time.

from pwn import *

context.log_level = 'error'

code = "0" * 20

i = 0
while i < 20:
        for j in range(10):
            conn = remote('codemash.hacking-lab.com', 8357)
            ret = conn.recvline()
            assert('Make an educated guess, dude' in ret)
            tmp = list(code)
            tmp[i] = str(j)
            tmp = ''.join(tmp)
            conn.send(tmp + '\n')
            ret = conn.recvline().rstrip()
            ret = ret.replace('<', '').rstrip()
            ret = ret.replace('>', '').rstrip()
            try:
                ret = int(ret)
            except:
                print j, "\n", tmp
                ret = ""
                while not "cm" in ret:
                    ret = conn.recvline().rstrip()
                    print ret

                conn.close()
                sys.exit()

            conn.close()
            if ret > i:
                code = tmp
                print j,
                i = i + 1
                break
python2 03.py
  7 8 0 2 5 9 2 8 2 3 2 9 2 0 7 1 2 9 6 7
  78025928232920712967
  Congrats! Here's your flag:
  cm18-Glz3-yM2k-h9i9-wntS

04 - Super Eyesight

Can you see what others cannot?

Here is an image to prove your super eyesight.

04_image.jpg

Opening the image in Gimp and playing with the color threshold reveals the flag.

04_flag.jpg

05 - Bools for fools

Calculate this!

((not(a) and b) or c) xor d

boolsforfools_fixed.zip

wget https://codemash.hacking-lab.com/codemash/attachments/boolsforfools_fixed.zip
unzip boolsforfools_fixed.zip
ls -ls
   a.txt
   b.txt
   c.txt
   c.txt
cat a.txt
   0111011001000100111111111
   0100011101001101101110100
   ...

The .zip file reveals four ASCII files (a.txt, b.txt, c.txt and c.txt) containing binary numbers. It seems we have to operate on the content of those files:

# 05 - Bools for fools
# Calculate this!
# ((not(a) and b) or c) xor d
#

import sys

def snot(s):
    ret = ""

    for i in s:
        if i == '1':
            ret += '0'
        elif i == '0':
            ret += '1'
        else:
            return False

    return ret

def sand(s1, s2):
    ret = ""

    if len(s1) != len(s2):
        return False

    for i, j in zip(s1, s2):
        ret += str(int(i) & int(j))

    return ret

def sor(s1, s2):
    ret = ""

    if len(s1) != len(s2):
        return False

    for i, j in zip(s1, s2):
        ret += str(int(i) | int(j))

    return ret

def sxor(s1, s2):
    ret = ""

    if len(s1) != len(s2):
        return False

    for i, j in zip(s1, s2):
        ret += str(int(i) ^ int(j))

    return ret

def get_file_content(fname):
    try:
        with open(fname, 'r') as f:
            cont = f.readlines()
    except Exception as e:
        print e
        sys.exit()

    return cont

# ((not(a) and b) or c) xor d

a = get_file_content('a.txt')
b = get_file_content('b.txt')
c = get_file_content('c.txt')
d = get_file_content('d.txt')

not_a = []
for line in a:
    tmp = snot(line.rstrip()) + '\r\n'
    not_a.append(tmp)

not_a_and_b = []
for i, j in zip(not_a, b):
    tmp = sand(i.rstrip(), j.rstrip())  + '\r\n'
    not_a_and_b.append(tmp)

or_c = []
for i, j in zip(not_a_and_b, c):
    tmp = sor(i.rstrip(), j.rstrip())  + '\r\n'
    or_c.append(tmp)

xor_d = []
for i, j in zip(or_c, d):
    tmp = sxor(i.rstrip(), j.rstrip())  + '\r\n'
    xor_d.append(tmp)

for l in xor_d:
    print l,
python2 05.py
  1111111000100110001111111
  1000001011100011001000001
  1011101010000010101011101
  1011101001001010001011101
  1011101011101100101011101
  ...

And now it gets crazy. The output file looks very similiar to the previous ones. So what now? Let's count lines and characters per line:

python2 05.py | wc -l
  25
python2 05.py | head -n 1 | wc -c
  27

Since the Python script added '\r\n' to each line, the actual count is 25.

echo $(( 25*25 ))
625

Lucky you if you know that a Version 2 QR code contains 25 x 25 pixels and indeed, changing the last print statement of the Python script helps to spot a QR code.

-for l in xor_d:
-    print l,
+for l in xor_d:
+    for c in l:
+        print c,
python2 05.py

Highlighting some of the 1 bits in red, clearly shows the QR code:

codemash2018_05_qr_raw.jpg

Adding the following code to the Python script above takes the content of xor_d and creates a file flag.png which finally contains the QR code (credits go to the author of an old writeup for a similiar challenge).

from PIL import Image
from qrtools import QR

binstring = ""
for line in xor_d:
    binstring += line.rstrip()

outimg = Image.new('RGB', (25, 25), "black")
pixels_out = outimg.load()

count = 0
for bit in binstring:
    i = count % 25
    j = count / 25
    if bit == '0':
        pixels_out[(i, j)] = (255, 255, 255)
    count += 1

outimgname = "flag.png"
outimg = outimg.resize((250, 250))
outimg.save(outimgname, "png")
python2 05.py
zbarimg flag.png
  QR-Code:cm18-eJb2-mfTz-pIMu-oKaV

06 - Witchcraft

I was just messing around with my magic wand trying out some new spells and all of a sudden the flag was gone.

All I was left with is the following:

363336643331333832643438363537373432326436383530

333137323264346337393738333932643635373035343465

Can you undo my spell and get the flag?

Abracadabra? Hocus-pocus? Sim salabim? Whizz whizz? Hex hex? Hex hex!

flag = "363336643331333832643438363537373432326436383530"
flag += "333137323264346337393738333932643635373035343465"

print flag.decode('hex').decode('hex')
python2 06.py
  cm18-HewB-hP1r-Lyx9-epTN

07 - Happy Eyes

Barbara really loves to chat with all friends.

To express joy they use the characters ^^ which represent eyes of a smiling face.

Can you find this happy flag?

Hint: Offset by 2

1xT-Gcm8FV-5cYN-iBc-syHW

The characters ^^ do not only represent eyes of a smiling face, but also a zigzag pattern. Lucky you if you already heard about the Rail Fence (ZigZag) Cipher.

codemash2018_07_flag.png

The flag:

cm18-FxVs-T5yc-YHNG-WicB

08 - Lock

The key for that lock got lost.

If you are good at lock picking you will find the flag.

A good tool to decompile Java code is jad (Java decompiler).

jad Lock.class

The decompiled code looks as follows:

package com.hackinglab.ctf;

import java.io.PrintStream;

class Lock
{

    public Lock()
    {
    }

    public static void main(String args[])
    {
              StringBuffer stringbuffer = new StringBuffer();
              for(int i = 0; i < cipher.length(); i++)
                  stringbuffer.append((char)((key.charAt(i % key.length()) - cipher.charAt(i)) + 54));

              System.out.println(stringbuffer.toString());
    }

    private static String key = "lockpickingisfun";
    private static String cipher = "?8hiyKT5fw*W^J~art3t.47i";
}

For less experienced Java programmers, it might be easier to reimplement the main function's functionality in Python:

cipher = "?8hiyKT5fw*W^J~art3t.47i"
key = "lockpickingisfun"

codeword = ""
for i in range(len(cipher)):
    codeword += chr(ord(key[i % len(key)]) - ord(cipher[i]) +  54)

print codeword
python2 08.py
  cm18-TEl9-sHKR-C01f-xkb8

09 - Meow!

Can you lure the cat out of the hiding place?
pdfimages -all meow.pdf out

The first image (out-000.jpg) contains the flag.

out-000.jpg

Don't worry if you cannot read it, it's not your fault. The flag is actually:

cm18-cxJg-MB4M-Q9E3-ZYlC

with the second last letter being a lower case ell.

10 - Chest

Can you find the flag inside of the chest?
wget https://codemash.hacking-lab.com/codemash/attachments/chest
file chest
  chest: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5042299eb1d095cd4db443de65640c7073668114, not stripped

The file to examine is a x86-64 executable. Before actually running it, let's check if the flag appears in plain text.

strings chest | grep "cm18-"
  cm18-jNiO-bUon-ylon-flag
  cm18-eIdK-bUoC-orna-ment
  ...

Unfortunately, the binary contains a lot of canditates, 998 to be more precise.

strings chest | grep "cm18-" | wc -l
  998

Many of the canditates are very similar and differ only in a few letters. Let's try to sort them:

strings chest | grep "cm18-" | sort -u | less
  cm18-Ml2l-bUoC-vXXE-Fc8c
  cm18-bFaL-bUgo-lden-coin
  cm18-bFdG-bUgo-lden-coin
  cm18-bFgF-bUgo-lden-coin
  ...

The very first hit is unique within the chest and the searched flag.

cm18-Ml2l-bUoC-vXXE-Fc8c

11 - Bacon!

Get the bacon!

1000.zip

I'll skip this for now, be patient, I'll deliver an update, soon.

12 - On-site Challenge

The flag for this challenge can only be found onsite at the conference, in the location seen on top of the backside of the elephant, in this picture.

This challenge was on-site and I was, well, not on-sight.

13 - Alice

Follow the white rabbit.

The only usable information source on the website is an image of a white rabbit:

codemash2018_13_WhiteRabbit.jpg

Let's search for the image URL ...

codemash2018_13_SiteInfo.jpg

... and download the file.

wget https://codemash.hacking-lab.com/codemash/images/banner/challenge_13_2433.jpg

Then check the file for hidden data using binwalk:

binwalk challenge_13_2433.jpg

   DECIMAL       HEXADECIMAL     DESCRIPTION
   --------------------------------------------------------------------------------
   0             0x0             JPEG image data, JFIF standard 1.01
   382           0x17E           Copyright string: "Copyright (c) 1998 Hewlett-Packard Company"
   155385        0x25EF9         Zip archive data, at least v2.0 to extract, compressed size: 167065, uncompressed size: 167258, name: forest.jpg
   322518        0x4EBD6         Zip archive data, at least v2.0 to extract, compressed size: 139956, uncompressed size: 140213, name: meadow.jpg
   462542        0x70ECE         Zip archive data, at least v2.0 to extract, compressed size: 171261, uncompressed size: 171490, name: water.jpg
   634109        0x9ACFD         End of Zip archive

The image contains a .zip archive. binwalk can be used to extract it:

binwalk -e challenge_13_2433.jpg
cd _challenge_13_2433.jpg.extracted

The file contains three images:

  • forest.jpg
  • meadow.jpg
  • water.jpg
strings forest.jpg | head -n 3
  Exif
  2017:12:12 04:01:24
    This image has a protected secret.

strings meadow.jpg | head -n 3
  Exif
  2017:12:12 04:00:30
    This meadow is all dried out. Check the water first.

strings water.jpg | head -n 3
  Exif
  2017:12:12 03:58:52
    steghide was here. With an empty password

Steghide?

pacman -Ss steghide
  community/steghide 0.5.1-8
    Embeds a message in a file by replacing some of the least significant bits

Steghide is a program that can be used to hide data in various kinds of image- and audio-files. Let's install it...

su -c 'pacman -S steghide'

... and use it to extract the hidden data:

steghide extract -sf water.jpg
  Enter passphrase:
    wrote extracted data to "text.txt".

Let's finally view the content:

cat text.txt

  You search the whole place but you can't find anything.
  ...
  Now get out before you get flushed down

Ok, nothing here. Let's check the next file:

steghide extract -sf meadow.jpg
  Enter passphrase:
  the file "text.txt" does already exist. overwrite ? (y/n) y
  wrote extracted data to "text.txt".

And view its content:

cat text.txt
  So you think a mole can speak?!
  ...
  Lucky you, this one can!

  He's name is Fred and he tells you the passphrase:

  The-Mad-Hatter

Let's check the last file:

steghide extract -sf forest.jpg
  Enter passphrase: The-Mad-Hatter
  the file "text.txt" does already exist. overwrite ? (y/n) y
  wrote extracted data to "text.txt".

And finally:

cat text.txt
   Congratulations here is the flag!

   cm18-xZl2-eHC5-axW3-ZkZG

14 - Security Regulations

Due to some new privacy regulations this flag had to be shred. The classification will be secret or topsecret depending on the content.

____-____-____-____-____

Left over shred inside the paper shredder

Clicking the link leeds to a second website with the following URL:

https://codemash.hacking-lab.com/codemash/secret_challenge_shred1.html

Shred 1: Note: the other shreds are classified as <<topsecret>>!

cm18

Two simple changes to the URL lead to the second shred:

https://codemash.hacking-lab.com/codemash/topsecret_challenge_shred2.html

Shred 2

bWh0-VIkC

And one additional change to the third shred:

https://codemash.hacking-lab.com/codemash/topsecret_challenge_shred3.html

Shred 3

cMf4-72jY

The flag sums up to

cm18-bWh0-VIkC-cMf4-72jY

15 - P.A.L.M. Login

Folks at HOBO Authentication Systems implemented a new authentication system named P.A.L.M. Login

Prove that you can break it and find a pair of username and passcode to log on.

Login

codemash2018_15_checkEntries.png
function checkEntries()
{
    var u = document.getElementById('puser').value;
    var p = document.getElementById('ppass').value;
    var used = [0,0,0,0,0,0,0,0,0,0];
    var ok = false;
    if (u === 'cavs') {
        if (p > 0 && p.length == 10) {
            ok = true;
            for (i = 1; i <= 10; i++) {
                var digit = p.charAt(i-1);
                var part = p.substring(0, i);
                if (used[digit] != 0 || part % i != 0) {
                    ok = false
                }
                if (used[digit] == 0) {
                    used[digit] = 1
                }
            }
        }
    }
    if (ok) {
        document.location.href='palm_'+u+'_'+p+'.html'
    } else {
        alert('nope')
    }
}

The username is "cavs" and the password is a 10 digit number with the following characteristics:

  • Each digit appears only once (used[digit] != 0).
  • The first digit is divisible by 1 (part % i != 0).
  • The number build out of the first two digits is divisible by 2.
  • The number build out of the first three digits is divisible by 3.
  • The number build out of the first four digits is divisible by 4.
  • ...
  • The whole number is divisible by 10, so the last digit is 0.

A quick Google search with the search string The number build out of the first two digits is divisible by 2 is sufficiant:

3816547290

Providing cavs as username and 3816547290 as password reveals the flag:

cm18-zbIc-O4Zh-gmxl-r5J6