TE: SSCTF 2016 - Chain Rule (Crypto & Exploit 200pts)

For this challenge, we're given a zip file along with the description, "Try 'start'". Upon unzipping the zip file, we see a whole mass of other zip files:


What's interesting about these zip files is that they're password protected. Since we're given the hint to try 'start', I tried the password 'start' on every one of those zip files using the command:


unzip -P start '*.zip'

With that, I was able to easily unzip a single file and extract a "1.txt" file from within. The text file merely said "Next password is [hh.M5Px4U%8]*2z". Therefore, I guessed that we would have to unzip a whole chain of zip files using the password obtained at each iteration. Writing a script to do this was not difficult and I soon came up with one to unzip the whole chain of zip files:


#!/usr/bin/env python

import zipfile
import os

all_files = os.listdir('.')
zip_filenames = filter(lambda filename: '.zip' in filename, all_files)
zip_files = map(lambda filename: zipfile.ZipFile(filename), zip_filenames)
magic_string = "Next password is "
pw_list = []

def get_pw(content):
    return content[len(magic_string)::]

curr_pw = 'start'
while len(zip_files) > 0:
    for zip_file in zip_files:
        try:
            f = zip_file.open('1.txt', pwd=curr_pw)
            content = f.read()
            if magic_string in content:
                curr_pw = get_pw(content)
                print "Got password:", curr_pw
                pw_list.append(curr_pw)
                zip_files.remove(zip_file)
                break
        except:
            pass


However, after executing the script, I only found the text, "This is the end , but NOTHING here. Find "flag" and "pwd"." in the last file. This means that I probably missed out something. Perhaps some of the zip files contained more than the expected "1.txt" file. Therefore, I modified my script to keep a lookout for these extra files and I eventually arrived at this script:


#!/usr/bin/env python

import zipfile
import os

all_files = os.listdir('.')
zip_filenames = filter(lambda filename: '.zip' in filename, all_files)
zip_files = map(lambda filename: zipfile.ZipFile(filename), zip_filenames)
magic_string = "Next password is "
pw_list = []
s_files = []

def get_pw(content):
    return content[len(magic_string)::]

curr_pw = 'start'
while len(zip_files) > 0:
    for zip_file in zip_files:
        try:
            f = zip_file.open('1.txt', pwd=curr_pw)
            if len(zip_file.namelist()) > 1:
                print "Suspicious file found"
                s_files.append([curr_pw, zip_file])
            content = f.read()
            if magic_string in content:
                curr_pw = get_pw(content)
                print "Got password:", curr_pw
                pw_list.append(curr_pw)
                zip_files.remove(zip_file)
                break
        except:
            pass

pw1, zip1 = s_files[0]
pw2, zip2 = s_files[1]

flag_zip = zip1.open('flag.zip', pwd=pw1)
pwd_zip = zip2.open('pwd.zip', pwd=pw2)


It seems like "flag.zip" is a password protected zip that contains our flag and "pwd.zip" contains thousands of text files. From the name "pwd.zip", I inferred that the password of "flag.zip" would have to be found somewhere in "pwd.zip".

Again, the zip file contained a "start.txt" that contains the text "Game begin. next is 184128". The number 184128 seems like a reference to the next file name. It seems like it is going to be a chain of files again. However, before I began chaining the files, I briefly glanced at the content of several files and I found them to be mostly similar except for some files which contained two file references instead of one.

Since the files mostly looked the same and contained the same keywords, I thought I could find the password by simply filtering out the usual keywords. To my surprise, I only got this message, "Follow the path, collect the comments. Avoid the BLACKHOLE!". The message gave me a few clues: (1) there is probably 1 correct path, (2) there exists comments somewhere along the path and (3) the chain probably contains loops (which they termed as a "blackhole").

Using these clues and with an idea from my friend, we decided to construct the path backwards from the "blackhole". I later realised that the "comments" referred to the comment text associated with each text file in "pwd.zip". Looking through the comments associated with all the text files, we realised that there were only two types of comments: "\t" and empty " " comments. After associating each "\t" with the binary digit 1 and " " with the binary digit 0, I got a rather long bitstring which after translation into ASCII characters looked like this:


When I am dead, my dearest,
Sing no sad songs for me;
Plant thou no roses at my head,
Nor shady cypress tree:
Be the green grass above me
With showers and dewdrops wet:
And if thou wilt, remember,
And if thou wilt, forget.

password part1:Thispasswordistoolong

I shall not see the shadows,
I shall not see the rain;
I shall not hear the nightingle
Sing on as if in pain:
And dreaming through the twilight
That doth not rise nor set,
Haply I may remember,
And haply I may forget.

password part2:andyoudon'twanttocrackitbybruteforce

That's all.

From there, we construct the password "Thispasswordistoolongandyoudon'twanttocrackitbybruteforce" and extracted the content in "flag.zip":


Flag is SSCTF{Somewhere_Over_The_Rainbow}

Hurray! We got the flag! Here's the final script that I used:


 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
33
34
35
36
37
#!/usr/bin/env python

import zipfile

def partition(string, l):
    return [string[i*l:(i+1)*l:] for i in range(len(string)/l)]

pwd_zip = zipfile.ZipFile('pwd.zip')
all_txt = {}
for txt in pwd_zip.filelist:
    all_txt[txt.filename[:-4:]] = ('1' if txt.comment == '\t' else '0', pwd_zip.open(txt.filename).read())

comments = []
curr = '376831'
while True:
    if curr == 'start':
        break
    for filenum in all_txt.keys():
        comment, content = all_txt[filenum]
        found = [st for st in content.split() if st.isdigit()]
        if curr not in found:
            continue
        if filenum != 'start':
            comments.append(comment)
        all_txt.pop(filenum)
        curr = filenum
        break

comments.reverse()
bitstrings = partition("".join(comments), 8)
text = "".join(map(lambda bitstring: chr(int(bitstring, 2)), bitstrings))
print text

flag_pw = "Thispasswordistoolongandyoudon'twanttocrackitbybruteforce"
flag_zip = zipfile.ZipFile('flag.zip')
flag_file = flag_zip.open('1.txt', pwd=flag_pw)
print flag_file.read()


Comments

I have no idea why this challenge is classified as crypto because it simply didn't feel like a crypto challenge. However, it was somewhat fun to learn about zip files and revisit graphs in a different context.

Comments