sed programming
There’s a big sed script that consists of a list of regular expressions (actually just simple string replacements) that are tried in order. When one is successful, it loops back and starts over. There’s two stop-states (replacements exits the loop).
It looks like this:
#!/bin/sed -f
# Check flag format
# Some characters are used internally
/^SECCON{[02-9A-HJ-Z_a-km-z]*}/!{
cINVALID FORMAT
b
}
:t
s/1Illl11IlIl1/1IlIl11Illl1/;tt
s/1Illl11III1/1III11Illl1/;tt
s/1Ill11IlIl1/1IlIl11Ill1/;tt
s/1Illl11l1/1l11Illl1/;tt
s/1Ill11IIII1/1IIII11Ill1/;tt
s/1Ill11III1/1III11Ill1/;tt
s/1Ill11IIll1/1IIll11Ill1/;tt
s/1Illl11IIll1/1IIll11Illl1/;tt
s/1Illl11IIII1/1IIII11Illl1/;tt
s/1Ill11l1/1l11Ill1/;tt
s/G1II1/GR1II1/;tt
s/1II11IIll11IIll11IIll11IIll11IlIl11IlIl11IlIl11II11IIll1/1II11IIll11IIll11IIll11IIll11IlIl11IlIl11IIll11II1/;tt
s/1II11IIll11IlIl11IlIl11IIll11IlIl11II11IlIl1/1IIIl1/;tt
s/1II11IIll11IIll11IIll11IlIl11IIll11IlIl11IIll11II11IlIl1/1IIIl1/;tt
s/1II11IIll11IlIl11IlIl11IlIl11IlIl11IlIl11IIll11II11IIll1/1II11IIll11IlIl11IlIl11IlIl11IlIl11IIll11IlIl11II1/;tt
s/1II11IIll11IlIl11IIll11IIll11IlIl11IlIl11IlIl11IlIl11II11IlIl1/1II11IIll11IlIl11IIll11IIll11IlIl11IlIl11IlIl11IIll11II1/;tt
s/1II11IIll11IlIl11IIll11IlIl11IlIl11II11IIll1/1II11IIll11IlIl11IIll11IlIl11IIll11II1/;tt
s/1II11IIll11IlIl11IlIl11IlIl11IIll11IIll11IIll11IIll11II11IIll1/1IIIl1/;tt
# ... + tons more ...
Like in qchecker I did this one also naively and manually, even though it clearly invites the construction of state machines that can be more easily manipulated. There’s been a number of similar CTF tasks before, too, and I’ve written bits and pieces, but it’s a messy ad-hoc mess that there’s little hope of salvaging anything. It would probably take me as much time to collect it as it would if I just “brute forced” it like it was a puzzle. Besides, it’s fun to work “with my hands” as it were.
First thing is of course to clean it up so we have something to look at. I’m
simply replacing string-chunks with my best guess for its role in the underlying
state machine. For example it looks like 1
is a separator of some kind,
repeating at a regular interval in many strings. From the stop-states I found a
string that likely represents an error-state, and there’s tons of places where
two symbol-blocks seem to go hand-in-hand like they are binary…
|Illl|:0: /:0:|Illl|
|Illl||III| /|III||Illl|
|Ill|:0: /:0:|Ill|
|Illl||l| /|l||Illl|
|Ill||IIII| /|IIII||Ill|
|Ill||III| /|III||Ill|
|Ill|:1: /:1:|Ill|
|Illl|:1: /:1:|Illl|
|Illl||IIII| /|IIII||Illl|
|Ill||l| /|l||Ill|
G+ / GR+
+:1::1::1::1::0::0::0:+:1: / +:1::1::1::1::0::0::1:+
+:1::0::0::1::0:+:0: / <NO>
+:1::1::1::0::1::0::1:+:0: / <NO>
+:1::0::0::0::0::0::1:+:1: / +:1::0::0::0::0::1::0:+
+:1::0::1::1::0::0::0::0:+:0:/ +:1::0::1::1::0::0::0::1:+
+:1::0::1::0::0:+:1: / +:1::0::1::0::1:+
+:1::0::0::0::1::1::1::1:+:1:/ <NO>
+:1::0::1::1::1::1::1::1:+:0:/ <NO>
+:1::0::1::1::1::1::0:+:0: / <NO>
+:1::0::0::1::1:+:1: / <NO>
+:1::0::1::1::1::0::0:+:0: / <NO>
W<NO> /WR<NO>
+:1::0::0::1::1::1::1:+:1: / +:1::0::1::0::0::0::0:+
+:1::1::0::0::1::0::0:+:1: / <NO>
+:1::0::1::1::0::0::1::0:+:0:/ <NO>
+:1::0::1::0::0::0::1::1:+:0:/ <NO>
+:1::1::0::1::0::1::0:+:1: / <NO>
+:1::0::1::1::0::0::1:+:0: / <NO>
+:1::1::1::1::1::0:+:1: / <NO>
+:1::0::0::1::0::1::0::0:+:0:/ <NO>
+:1::0::0::1::1::0::0::1:+:1:/ <NO>
+:1::1::1::0::1:+:1: / +:1::1::1::1::0:+
+:1::0::1::0::0::0::0:+:0: / <NO>
+:1::1::0::1::1::0:+:0: / +:1::1::0::1::1::1:+
+:1::0::1:+:0: / <NO>
+:1::0::1::1::0::1::1::1:+:0:/ +:1::0::1::1::1::0::0::0:+
+:1::0::1::1::0::1::0::0:+:1:/ <NO>
+:1::1::0::1::1::1:+:1: / +:1::1::1::0::0::0:+
+:1::0::1::1::1::0:+:0: / +:1::0::1::1::1::1:+
+:1::1::1::0::0::0::0:+:1: / +:1::1::1::0::0::0::1:+
+:1::1::1::1:+:1: / <NO>
+:1::0::0::1::1::1:+:1: / <NO>
# ...<snip>...
...
This (the start of the script) seems to be some count-and-check logic. I.e. it counts the binary characters while breaking off with an error if it encounters a given binary digit at a given position. Basically it compares some binary string to a solution?
Further down there’s also two other set of blocks that looked to me like binary digits, though here with just transfer-logic between them.
I wrote a quick Python script to simply dump the state while replacing certain blocks with symbols to watch what was happening visually.
First, apparently is translated to a binary, e.g. 45678
eventually ends up as
its binary string with some initial state:
step 37: §§∅§:I::IIlI::IllI:∅∅§∅∅∅∅∅§∅§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:
Then the “main loop” seems to start. Each iteration seems to transfer the
starting string bit by bit to the other side of the :l:
separator symbol, but
manipulated in some way, and now rendered in new symbols:
step 372: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅:Illl:∅∅§∅∅:l:¶¶¶°°°¶¶°
step 373: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅:Illl:∅§∅∅:l:¶¶¶°°°¶¶°
step 374: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅:Illl:§∅∅:l:¶¶¶°°°¶¶°
step 375: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§:Illl:∅∅:l:¶¶¶°°°¶¶°
step 376: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅:Illl:∅:l:¶¶¶°°°¶¶°
step 377: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:Illl::l:¶¶¶°°°¶¶°
step 378: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l::Illl:¶¶¶°°°¶¶°
step 379: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶:Illl:¶¶°°°¶¶°
step 380: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶:Illl:¶°°°¶¶°
step 381: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶:Illl:°°°¶¶°
step 382: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°:Illl:°°¶¶°
step 383: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°:Illl:°¶¶°
step 384: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°:Illl:¶¶°
step 385: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶:Illl:¶°
step 386: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶¶:Illl:°
step 387: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶¶°:Illl:
step 388: §§∅∅:IIlI::IllI:§∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶¶°¶
step 389: §§∅∅:IIlI::IllI::Illl:∅∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶¶°¶
step 390: §§∅∅:IIlI::IllI:∅:Illl:∅∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶¶°¶
step 391: §§∅∅:IIlI::IllI:∅∅:Illl:∅∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶¶°¶
step 392: §§∅∅:IIlI::IllI:∅∅∅:Illl:∅§∅∅§∅∅∅∅§§∅§∅∅∅§∅∅:l:¶¶¶°°°¶¶°¶
(Notice also that the counter on the far left has decreased by 1 now, it does this every full cycle of the “main loop,” but its symbols change to something weird in the end. The original count is accurate though, of 13 iterations.) Once the original binary string is gone, it translates the new binary string into the alphabet of the old one:
step 1157: §§∅∅:IIlI::IlI:¶¶¶°°°¶¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1158: §§∅∅:IIlI:§:IlI:¶¶°°°¶¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1159: §§∅∅:IIlI:§§:IlI:¶°°°¶¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1160: §§∅∅:IIlI:§§§:IlI:°°°¶¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1161: §§∅∅:IIlI:§§§∅:IlI:°°¶¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1162: §§∅∅:IIlI:§§§∅∅:IlI:°¶¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1163: §§∅∅:IIlI:§§§∅∅∅:IlI:¶¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1164: §§∅∅:IIlI:§§§∅∅∅§:IlI:¶°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1165: §§∅∅:IIlI:§§§∅∅∅§§:IlI:°¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1166: §§∅∅:IIlI:§§§∅∅∅§§∅:IlI:¶¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1167: §§∅∅:IIlI:§§§∅∅∅§§∅§:IlI:¶°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1168: §§∅∅:IIlI:§§§∅∅∅§§∅§§:IlI:°°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1169: §§∅∅:IIlI:§§§∅∅∅§§∅§§∅:IlI:°¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1170: §§∅∅:IIlI:§§§∅∅∅§§∅§§∅∅:IlI:¶¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
step 1171: §§∅∅:IIlI:§§§∅∅∅§§∅§§∅∅§:IlI:¶¶¶¶¶°°¶°°°¶¶°¶¶¶°
Then the loop repeats, doing the same sort of translation. The final string after these iterations is the binary string that is compared in the “error checker” we saw in the beginning.
I also save and print out the values of the binary strings after each “pass” over the strings:
1094861636: 0 :: 41424344: 0 :: b'ABCD':b'' []
1094861636: 0 :: 41424344: 0 :: b'ABCD':b'' []
1094861636: 0 :: 41424344: 0 :: b'ABCD':b'' [12]
1094861636: 0 :: 41424344: 0 :: b'ABCD':b'' [12]
21119812: 0 :: 1424344: 0 :: b'\x01BCD':b'' [12]
...
4: 29983161 :: 4: 1c981b9 :: b'\x04':b'\x01\xc9\x81\xb9' [12]
4: 59966322 :: 4: 3930372 :: b'\x04':b'\x03\x93\x03r' [12]
4: 119932644 :: 4: 72606e4 :: b'\x04':b'\x07&\x06\xe4' [12]
0: 239865288 :: 0: e4c0dc8 :: b'':b'\x0eL\r\xc8' [12]
3815236718: 0 :: e367e46e: 0 :: b'\xe3g\xe4n':b'' []
3815236718: 1 :: e367e46e: 1 :: b'\xe3g\xe4n':b'\x01' [12]
1667753070: 2 :: 6367e46e: 2 :: b'cg\xe4n':b'\x02' [12]
1667753070: 2 :: 6367e46e: 2 :: b'cg\xe4n':b'\x02' [6, 1]
...
Knowing the values must have a pretty simple relation, I just stare at them
really hard until some unknown neurons finally gets it and inform me the loop
does x[i+1] = (x[i] << 1 ^ x[i] ^ x[i] >> 1) % 256**L
. This repeats 13 times,
then the result is checked by the count-and-check thing I described above, so,
reversing, we get SECCON{mARkOV_4Lg0Ri7hM}
, which, looking it up, is the name
for this kind of construction I guess?