Hack.lu 2013: FluxArchiv

FluxArchiv (Part 1) (Category: Reversing) Author(s): sqall

These funny humans try to exclude us from the delicious beer of the Oktoberfest! They made up a passcode for everyone who wants to enter the Festzelt. Sadly, our human informant friend could not learn the passcode for us. But he heard a conversation between two drunken humans, that they were using the same passcode for this intercepted archive file. They claimed that the format is is absolutely secure and solves any kind of security issue. It’s written by this funny hacker group named FluxFingers. Real jerks if you ask me. Anyway, it seems that the capability of drunken humans to remember things is limited. So they just used a 6 character passcode with only numbers and upper-case letters. So crack this passcode and get our ticket to their delicious german beer!

Here is the challenge:
https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz

FluxArchiv (Part 2) (Category: Reversing) Author(s): sqall

These sneaky humans! They do not just use one passcode, but two to enter the Festzelt. We heard that the passcode is hidden inside the archive file. It seems that the FluxFingers overrated their programming skill and had a major logical flaw in the archive file structure. Some of the drunken Oktoberfest humans found it and abused this flaw in order to transfer hidden messages. Find this passcode so we can finally drink their beer!

(only solvable when FluxArchiv (Part 1) was solved)

Here is the challenge:
https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz

Solution

When trying to solve this challenge, we accidently forgot to read the challenge description so we ended up solving part2 before part1. For this challenge, we get a program and an archive created by this program. Starting the program gives:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./archiv

################################################################################

FluxArchiv - solved security since 2007!
Written by sqall - leading expert in social-kernel-web-reverse-engineering.

################################################################################

Unknown or invalid command.

Usage: ./archiv <command> <archiv> <password> <file>
commands:
-l <archiv> <password> - lists all files in the archiv.
-a <archiv> <password> <file> - adds a file to the archiv (when archiv does not exist create a new archiv).
-x <archiv> <password> <filename> - extracts the given filename from the archiv.
-d <archiv> <password> <filename> - delete the given filename from the archiv.

So the programm requires a password for every operation on an archive file. After some reversing, we found out, that the password is hashed with sha1 and the result is used as seed for a rc4 prng. Everytime a chunk ob data should be encrypted, the prng is reset with the sha1sum and the data is xored with the prng data. So to find out more about the fileformat, we created two archive files with different password:

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
$ dd if=/dev/urandom of=1mb bs=1024 count=1024
1024+0 Datensätze ein
1024+0 Datensätze aus
1048576 Bytes (1,0 MB) kopiert, 0,0755623 s, 13,9 MB/s
$ ./archiv -a test1 pw1 1mb

################################################################################

FluxArchiv - solved security since 2007!
Written by sqall - leading expert in social-kernel-web-reverse-engineering.

################################################################################

Archiv test1 successfully created.

Progress:
0% ... 10% ... 20% ... 30% ... 40% ... 50% ... 60% ... 70% ... 80% ... 90% ... 100%

File 1mb successfully added to the archiv.
$ ./archiv -a test2 pw2 1mb

################################################################################

FluxArchiv - solved security since 2007!
Written by sqall - leading expert in social-kernel-web-reverse-engineering.

################################################################################

Archiv test2 successfully created.

Progress:
0% ... 10% ... 20% ... 30% ... 40% ... 50% ... 60% ... 70% ... 80% ... 90% ... 100%

File 1mb successfully added to the archiv.

The result should be two files with completely the same content only the cipherstream is different, so with the following script, we can find the cipherstream starts and reverse the format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python2
import Crypto.Cipher.ARC4
import Crypto.Hash.SHA
xor = lambda a,b: "".join(chr(ord(i)^ord(j)) for i,j in zip(a,b))

f1 = open("test1").read()
f2 = open("test2").read()
assert(len(f1) == len(f2))
stream1 = Crypto.Cipher.ARC4.new(Crypto.Hash.SHA.new("pw1").digest()).encrypt("\x00" * 0x10000)
stream2 = Crypto.Cipher.ARC4.new(Crypto.Hash.SHA.new("pw2").digest()).encrypt("\x00" * 0x10000)

xored_stream = xor(stream1, stream2)
xored_files = xor(f1, f2)

pos = 0
while pos < len(f1):
        subpos = 0
        while pos + subpos < len(f1) and xored_files[pos+subpos] == xored_stream[subpos]:
                subpos += 1
        if subpos != 0:
                print "%06x: %r (%d bytes)" % (pos, xor(f1[pos:pos+subpos], stream1), subpos)
                pos += subpos
        else:
                pos += 1

The script spits out something like this:

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
38
39
40
41
42
43
44
$ ./find_offsets.py
000020: 'FluXL1sT' (8 bytes)
000028: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000030: '\x01\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000038: '\xf9\x03\x00\x00\x00\x00\x00\x00' (8 bytes)
000040: '(\xd7\xf8\xd0\x98\xfc~\x8a\xe3\xc2<\x01\x0f9X\x84' (16 bytes)
000050: '1mb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0000b0: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0000b8: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0000c0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
0000d0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
000130: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000138: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000140: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
000150: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0001b0: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0001b8: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0001c0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
0001d0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
000230: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000238: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000240: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
000250: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0002b0: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0002b8: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0002c0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
0002d0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
000330: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000338: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000340: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
000350: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0003b0: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0003b8: '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0003c0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
0003d0: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
000430: '\x02\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000438: '[random junk]' (1032 bytes)
000840: '\x03\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000848: '[random junk]' (1032 bytes)
101fa0: '\xf9\x03\x00\x00\x00\x00\x00\x00' (8 bytes)
[snip]
101fa8: '[random junk]' (1032 bytes)
1023b0: '\x00\x00\x10\x00\x00\x00\x00\x00' (8 bytes)
1023b8: '[random junk]' (64 bytes)

Looks like the format is as follows:

  • 12byte unencrypted “FluXArChiV13” header (can be seen when looking at the hexdump)
  • 20byte unencrypted sha1 hash of scrambled input pw (found while reversing)
  • 8byte “FluXL1sT” header
  • 8byte of unknown data
  • 8 file entries:
    • 2x 8bytes of unknown data
    • 16bytes md5sum (found while reversing and checked against 1mb file)
    • 96 byte filename, padded with null bytes
  • many data chunks:
    • 8bytes of unknown data
    • 1032byte file data

What is interesting, is the encrypted “FluXL1sT” header and the zero padded filenames, because we can reconstruct the keystream from these, lets see what we can find out about the original archive when using what we already know about the format:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python2
import sys
xor = lambda a,b: "".join(chr(ord(i)^ord(j)) for i,j in zip(a,b))

f = open(sys.argv[1]).read()
assert(f[:12] == "FluXArChiV13")
pos = 32
keystream = xor(f[pos:pos+8], "FluXL1sT")
pos += 16
for _ in xrange(8):
        pos += 32
        print "%06x %r" % (pos, xor(keystream, f[pos:pos + len(keystream)]))
        pos += 96

This first version only prints the first 8 byte of the filenames:

1
2
3
4
5
6
7
8
9
$ ./parser.py FluxArchiv.arc
000050 'attentio'
0000d0 'Did_You_'
000150 '\x00\x00\x00\x00\x00\x00\x00\x00'
0001d0 'fluxfing'
000250 '\x00\x00\x00\x00\x00\x00\x00\x00'
0002d0 '\x00\x00\x00\x00\x00\x00\x00\x00'
000350 '\x00\x00\x00\x00\x00\x00\x00\x00'
0003d0 '\x00\x00\x00\x00\x00\x00\x00\x00'

Looks good, especially the entries without filename - we can use them to reconstruct the whole file list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python2
import sys
xor = lambda a,b: "".join(chr(ord(i)^ord(j)) for i,j in zip(a,b))

f = open(sys.argv[1]).read()
assert(f[:12] == "FluXArChiV13")
keystream = f[0x3d0:0x3d0+96]
pos = 0x20
lengths = [
        8, 8,
        8,8,16,96, 8,8,16,96, 8,8,16,96, 8,8,16,96,
        8,8,16,96, 8,8,16,96, 8,8,16,96, 8,8,16,96,
]
for len_ in lengths:
        print "%06x %r (%d bytes)" % (pos, xor(keystream, f[pos:pos+len_]), len_)
        pos += len_

This is the result:

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
$ ./parser.py FluxArchiv.arc
000020 'FluXL1sT' (8 bytes)
000028 '\xad\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000030 '\x01\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000038 '\x16\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000040 '\xff\xea:RT\xc0Xz\xd5\xb2\xec\xe8\x1b\xe8\xba\xb5' (16 bytes)
000050 'attentionzombie.mp3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0000b0 '\x02\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0000b8 '\x86\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0000c0 '\xecV8\xdaH\x10\xf7^\x075\xa3\xdf\x9e0\xc1\xb0' (16 bytes)
0000d0 'Did_You_Know.jpg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
000130 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000138 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000140 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
000150 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0001b0 '\x89\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0001b8 '\t\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0001c0 '+\xee\x8b\xecl\x1a\x8a\xd6X\xdf\xfb\xe1K\xc1G\x92' (16 bytes)
0001d0 'fluxfingers.png\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
000230 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000238 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000240 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
000250 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0002b0 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0002b8 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0002c0 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
0002d0 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
000330 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000338 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
000340 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
000350 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)
0003b0 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0003b8 '\x00\x00\x00\x00\x00\x00\x00\x00' (8 bytes)
0003c0 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (16 bytes)
0003d0 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' (96 bytes)

Looks like we got the whole file listing and it looks consistent, so we can assume the keystream is correct. But we still need more bytes of the keystream to decompress the whole data because we only have 96 bytes at the moment, but while going through what we already have, we found a new file listing at 0x2bef0 with this script:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python2
import sys
xor = lambda a,b: "".join(chr(ord(i)^ord(j)) for i,j in zip(a,b))

f = open(sys.argv[1]).read()
assert(f[:12] == "FluXArChiV13")
keystream = f[0x3d0:0x3d0+96]
pos = 0x3d0 + 96
while pos < len(f):
        print "%06x %r" % (pos, xor(keystream, f[pos:pos+8]))
        pos += 8
        print "%06x %r" % (pos, xor(keystream, f[pos:pos+len(keystream)]))
        pos += 1032

Small modifications to the directory listing parser spit out another filelisting with just one file th_oh-noes-everybody-panic.gif and an md5sum of f1df033f0179fe101d11700fe796b4fd, luckily we could find the original (we couldn’t find the original of any other file until now) with a matching md5sum and could reconstruct enough bytes to parse the whole file with this script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python2
import sys
xor = lambda a,b: "".join(chr(ord(i)^ord(j)) for i,j in zip(a,b))

f = open(sys.argv[1]).read()
assert(f[:12] == "FluXArChiV13")
img = open("th_oh-noes-everybody-panic.gif").read()
keystream = f[0x3d0:0x3d0+96]
pos = f.find(xor(keystream, img))
keystream = xor(img, f[pos:])

pos = 0x3d0 + 96
while pos < len(f):
        print "%06x %r" % (pos, xor(keystream, f[pos:pos+8]))
        pos += 8
        print "%06x %r" % (pos, xor(keystream, f[pos:pos+len(keystream)]))
        pos += 1032

Some searching in the output revealed this: Flag: D3letinG-1nd3x_F4iL.

We entered the flag into the Part1 field but it didn’t work - it was the flag for Part2 :)

Solving part1 now was easy because we had the rc4 keystream and could bruteforce against it with a very simple bruteforce script:

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
38
39
40
41
42
43
44
45
#!/usr/bin/env python2
import sys
xor = lambda a,b: "".join(chr(ord(i)^ord(j)) for i,j in zip(a,b))

f = open(sys.argv[1]).read()
assert(f[:12] == "FluXArChiV13")
keystream = f[0x3d0:0x3d0+6]

import Crypto.Cipher.ARC4
import Crypto.Hash.SHA
import itertools
import string
import threading
import os

alphabet = string.uppercase + string.digits
threads = 4
class T(threading.Thread):
        def __init__(self, t):
                self.t = t
                threading.Thread.__init__(self)
                self.daemon = True
                self.start()
        def run(self):
                t = self.t
                for first in alphabet[t::threads]:
                        for k in itertools.product(alphabet, repeat = 5):
                                res = Crypto.Cipher.ARC4.new(Crypto.Hash.SHA.new(first + "".join(k)).digest()).encrypt(keystream)
                                if ord(res[0]) != 0:
                                        continue
                                if ord(res[1]) != 0:
                                        continue
                                if ord(res[2]) != 0:
                                        continue
                                if ord(res[3]) != 0:
                                        continue
                                if ord(res[4]) != 0:
                                        continue
                                if ord(res[5]) != 0:
                                        continue
                                sys.stdout.write("%s\n" % (first + "".join(k)))
                                os.kill(os.getpid(), 11)
threads = [T(t) for t in xrange(threads)]
for t in threads:
        t.join()

And this was the result:

1
2
3
$ ./password_bf.py FluxArchiv.arc
PWF41L
Segmentation fault

Flag for Part1 is: PWF41L