Hack.lu 2013: Roboparty

Robot LHCH is happy. He made it into the castings for the tenth roman musical. He even is so happy that he went on the Oktoberfest to drink some beer. Unfortunately it seems that he drank too much so now he is throwing up part of his source code. Can you decipher the secret he knows?

Warning: Viewing this page is not recommended for people that suffer from epilepsy. We are dead serious.

And here is your totally eye-friendly challenge:
https://ctf.fluxfingers.net/static/downloads/roboparty/index.html

Besides the eye cancer, the page contains a midi file linked in the html. Smells like an esolang already. Some digging yields Musical X, which is a perfect match for an unimplemented lang taking notes as its input. “Tenth Roman Musical”, get it?

We can extract notes from the midi file using lilypad. All that’s left is implementing the language:

Musical X
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import collections
import sys

order = [ "e4", "f4", "g4", "a4", "b4", "c5", "d5", "e5", "f5", "g5", "a5", "b5", "c6", "d6", "e6", "f6", "g6", "a6", "b6" ]

lilycode = [
        "c5", "d5", "e5", "f5", "g5", "a5", "b5", "c6", "d6", "a5", "b5", "c5", "c6",
        "g5", "c5", "g5", "a5", "b5", "c6", "d6", "e6", "b5", "d6", "d5", "e5", "f5",
        "c5", "c6", "f5", "c6", "b5", "c5", "c6", "g5", "a5", "e5", "d5", "c6", "f5",
        "c5", "g5", "f5", "c5", "c6", "g5", "d6", "d5", "e5", "f5", "g5", "d5", "c5",
        "c6", "g5", "f5", "c6", "d6", "a5", "g5", "d5", "c5", "g5", "a5", "e5", "g5",
        "f5", "c5", "c6", "a5", "g5", "c5", "c6", "f5", "c6", "d6", "e6", "b5", "a5",
        "g5", "d5", "a5", "b5", "e5", "e6", "b5", "c5", "c6", "g5", "b5", "c6", "c5",
        "d5", "e5", "f5", "g5", "a5", "b5", "c5", "c6", "g5", "f5", "d5", "d6", "a5",
        "e5", "g5", "d5", "c5", "g5", "d5", "f5", "g5", "a5", "b5", "c6", "d6", "d5",
        "e5", "f5", "g5", "d5", "a5", "f5", "c5", "c6", "b5", "c5", "c6", "g5", "a5",
        "b5", "c5", "c6", "g5", "f5", "c5", "c6", "g5", "b5", "c6", "d6", "a5", "e6",
        "e5", "e5", "e6", "e5", "e6", "e5", "e6", "e5", "f5", "g5", "a5", "b5", "c6",
        "d6", "e6", "b5", "a5", "e5", "f5", "c6", "c5", "g5", "d6", "d5", "e5", "f5",
        "c5", "b4", "b5", "c5", "c6", "g5", "a5", "e6", "b5", "e5", "b4", "b5", "c5",
        "c6", "f5", "a5", "d5", "a5", "f5", "c5", "c6", "g5", "b5", "c5", "c6", "g5",
        "e5", "f5", "c6", "d6", "e6", "b5", "d6", "a5", "b5", "c6", "c5", "g5", "e5",
        "e6", "b5", "d6", "a5", "e6", "d6", "d5", "a5", "g5", "d5", "c5", "g4", "b4",
        "b5", "c5", "c6", "g5", "d6", "a5", "e5", "b5", "a5", "g5", "d6", "a5", "e5",
        "b5", "a5", "g5", "d6", "a5", "e5"
    ]

commands = []
output = []

last = order.index(lilycode[0])
for i in lilycode[1:]:

    cur = order.index(i)
    interval = cur-last
    if interval != 0:
        interval += (1 if interval > 0 else -1)
    # if abs(interval) == 8:
        # continue

    commands.append([interval,[last,cur]])
    last = cur

# print commands
# sys.exit(0)

cur = 0

# There are many infinite tapes as in Brainfuck with values 0-255, each tape
# has a separate pointer, and each tape is named with one note.

# The initial tape is the note at the beginning of the program.
tapes = {}
tps = collections.defaultdict(int)

pointer = commands[0][1][0]
# initial tape
tapes[pointer] = collections.defaultdict(int)

print "set of commands:", set(map(lambda x: x[0], commands))
# exit()

while True:

    if cur >= len(commands):
        print "ok!"
        break

    cmd, firstsecond = commands[cur]
    first, second = firstsecond
    cur += 1

    # print "pointer: ", pointer, "tps[pointer]", tps[pointer]
    # print tps

    print str(cur).rjust(4), " ", cmd, first, second

    # skip unisons and octaves
    if cmd == 0:
        continue

    if abs(cmd) == 8:
        pass
    elif cmd == 2:
        # Increment value at pointer (mod 256).
        tapes[pointer][tps[pointer]] = (tapes[pointer][tps[pointer]]+1)%256
    elif cmd == -2:
        # Decrement value at pointer (mod 256).
        tapes[pointer][tps[pointer]] = (tapes[pointer][tps[pointer]]-1)%256
    elif cmd == 3:
        # Move tape pointer one space forward.
        tps[pointer] += 1
    elif cmd == -3:
        # Move tape pointer one space backward.
        tps[pointer] -= 1

    elif cmd == 4:
        # Input value at pointer.
        print "hum? input required..."
    elif cmd == -4:
        print tapes
        # Output value at pointer.
        print "output tape", pointer, "pos", tps[pointer], "dec", tapes[pointer][tps[pointer]], "chr" # chr(ord('a')+tapes[pointer][tps[pointer]])
        output.append(tapes[pointer][tps[pointer]])

    elif cmd == 5:
        # Select tape indicated by second note.
        pointer = second
        pointer = pointer % 7
        if pointer not in tapes:
            tapes[pointer] = collections.defaultdict(int)
    elif cmd == -5:
        # Move to initial position of current tape.
        tps[pointer] = 0

    # those don't occur!
    elif cmd == 6:
        # If value at pointer is non-zero, then search backward to command with
        # a first note the same note as the second note of this command.
        if tapes[pointer][tps[pointer]] != 0:
            cur -= 2
            while commands[cur][1][0] != second:
                cur -= 1
                if cur < 0:
                    print "error!"
                    break

    # those don't occur!
    elif cmd == -6:
        # If value at pointer is zero, then search forward to command with a
        # first note the same note as the first note of this command.
        if tapes[pointer][tps[pointer]] == 0:
            while commands[cur][1][0] != first:
                cur += 1
                if cur >= len(commands):
                    print "error!"
                    break

    # only one occurence, with -5 (skip)
    elif cmd == 7:

        cmd, firstsecond = commands[cur]
        first, second = firstsecond
        cur += 1

        # skip unisons and octaves
        if cmd == 0 or cmd == 8:
            continue

        print str(cur).rjust(4), "aux", cmd, first, second

        if cmd == 2:
            # tape value under pointer +64 mod 256
            tapes[pointer][tps[pointer]] = (tapes[pointer][tps[pointer]]+64)%256
        elif cmd == -2:
            # tape value under pointer -64 mod 256
            tapes[pointer][tps[pointer]] = (tapes[pointer][tps[pointer]]-64)%256

        elif cmd == 3:
            # tape pointer +64
            tps[pointer] += 64
        elif cmd == -3:
            # tape pointer -64
            tps[pointer] -= 64

        elif cmd == 4:
            # Change notes not of this key to round up instead of down.
            print "+7/4 not implemented"
        elif cmd == -4:
            # Change notes not of this key to round down instead of up.
            print "+7/5 not implemented"

        elif cmd == 5:
            # Select tape indicated by first note.
            pointer = first
        elif cmd == -5:
            # skip next command
            cur += 1

        elif cmd == 6:
            # If value at pointer is even, then search backward to command with
            # a first note the same note as the second note of this command.
            if tapes[pointer][tps[pointer]] % 2 == 0:
                cur -= 1
                while commands[cur][1][0] != second:
                    cur -= 1
                    if cur < 0:
                        print "error!"
                        break
                print "jumped to ", cur


        elif cmd == -6:
            # If value at pointer is odd, then search forward to command with a
            # first note the same note as the first note of this command.
            if tapes[pointer][tps[pointer]] % 2 != 0:
                while commands[cur][1][0] != first:
                    cur += 1
                    if cur >= len(commands):
                        print "error!"
                        break
                print "jumped to ", cur


        elif cmd == 7:
            print "auxiliary +7 is (RESERVED)"
            pass
        elif cmd == -7:
            print "auxiliary -7 is (RESERVED)"
            pass


    elif cmd == -7:
        if second != 5:
            print "!!!!!!!!!!! bad -7"
        # change "key"? doesn't matter.
        pass

    else:
        print "unknown interval:", cmd

print output
print ''.join(map(str, output))
# print ''.join(map(lambda x: chr( (ord('a')+x)), output))

Half that stuff isn’t even needed, the midi file doesn’t implement any loops. We got the hint that all key changes should have the C from the beginning as second note.

Output yields 52 digits: 8952894432985197117116491021177632771179049107333333 As the 333333 at the end suggests, this is (variable length) decimal ascii.

flag: Y4Y, b3aut1fuL MuZ1k!!!