diff --git a/dbg_fastboot.py b/dbg_fastboot.py index 69d716a..44b686b 100644 --- a/dbg_fastboot.py +++ b/dbg_fastboot.py @@ -29,11 +29,11 @@ with open('challenge.bin', 'rb') as data: i += 1 # Emulate 06bb -for R2 in range(0x17b4, 0x7562): - R1 = SYN_MEM[R2] - R1 ^= pow(R2, 2, 32768) - R1 ^= 0x4154 - SYN_MEM[R2] = R1 +for R1 in range(0x17b4, 0x7562): + R0 = SYN_MEM[R1] + R0 ^= pow(R1, 2, 32768) + R0 ^= 0x4154 + SYN_MEM[R1] = R0 # Jump past self-test SYN_PTR = 0x0377 diff --git a/dbg_teleporter.py b/dbg_teleporter.py index fe2abc7..ebf5bfd 100644 --- a/dbg_teleporter.py +++ b/dbg_teleporter.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# Set R8 to 6486 +# Set R7 to 6486 SYN_REG[7] = 0x6486 # Patch instructions 1571 to 1579 inclusive with nop's diff --git a/electric_boogaloo.md b/electric_boogaloo.md index 817a3e9..057daae 100644 --- a/electric_boogaloo.md +++ b/electric_boogaloo.md @@ -20,7 +20,7 @@ Hex values refer to the instruction lines, not the actual ranges spanned by the * Surprisingly, this is where it is checked that 1 + 1 = 2, but if `add` gives an incorrect non-zero result for 1 + 1, the test will erroneously report that it is `eq` which is not supported! * It would probably have been a better idea to test `eq` first, before `add`, then use `eq` to test `add`. * `024e` to `0261`: Tests `push` and `pop` - * Since only `R1` and `R2` are checked, this would not detect errors involving other registers. + * Since only `R0` and `R1` are checked, this would not detect errors involving other registers. * This test, like the last one, reuses the results of previous tests, since that worked out so well for `eq`… * `0264` to `0276`: Tests `gt` * The tests performed seem quite reasonable, but yet again reuse the results of previous tests… @@ -29,7 +29,7 @@ Hex values refer to the instruction lines, not the actual ranges spanned by the * `02ac` to `02bd`: Tests `not` * Okay, I admit this one was pretty helpful. What the hell is a ‘15-bit bitwise inverse’? Well the test passes if I do just do mod 32768, so that works I guess… * `02c0` to `02e8`: Tests `call` - * Although, notably, not `ret`. The tests operates by `jmp`ing back and forth to test the various values of `R1`. + * Although, notably, not `ret`. The tests operates by `jmp`ing back and forth to test the various values of `R0`. * `02eb` to `0308`: Checks that `add` overflows correctly * `030b` to `0313`: Checks that 6 × 9 ≠ 42. * I suspect there is a mistake in this test. Since Adams (1979) demonstrated unequivocally that 6 × 9 is equal to 42, I believe the `jt` should in fact be a `jf`. @@ -50,36 +50,36 @@ After a wild goose chase examining the code after the self-test, we find that th This is the magic line. Digging into the `06bb` subroutine: - 06bb push R1 - 06bd push R2 - 06bf set R2 17b4 - 06c2 rmem R1 R2 - 06c5 push R2 - 06c7 mult R2 R2 R2 + 06bb push R0 + 06bd push R1 + 06bf set R1 17b4 + 06c2 rmem R0 R1 + 06c5 push R1 + 06c7 mult R1 R1 R1 06cb call 084d - 06cd set R2 4154 + 06cd set R1 4154 06d0 call 084d - 06d2 pop R2 - 06d4 wmem R2 R1 - 06d7 add R2 R2 0001 - 06db eq R1 7562 R2 - 06df jf R1 06c2 - 06e2 pop R2 - 06e4 pop R1 + 06d2 pop R1 + 06d4 wmem R1 R0 + 06d7 add R1 R1 0001 + 06db eq R0 7562 R1 + 06df jf R0 06c2 + 06e2 pop R1 + 06e4 pop R0 06e6 ret -Inspecting the `084d` subroutine reveals that this is simply an XOR function: `R1 XOR R2`. Crypto rating: 1/10 +Inspecting the `084d` subroutine reveals that this is simply an XOR function: `R0 XOR R1`. Crypto rating: 1/10 Rewriting `06bb` function using higher-level syntax reveals that the ‘encryption’ algorithm is really very simple: ```c 06bb() { - R2 = 17b4; - for (R2 = 17b4; R2 != 7562; R2++) { - R1 = [R2]; - R1 ^= R2 * R2; - R1 ^= 4154; - [R2] = R1; + R1 = 17b4; + for (R1 = 17b4; R1 != 7562; R1++) { + R0 = [R1]; + R0 ^= R1 * R1; + R0 ^= 4154; + [R1] = R0; } } ``` @@ -94,19 +94,19 @@ So earlier, we produced a tool-assisted speed-run that would complete and dump t Looking through the code following the self-test, we find: - 0413 set R1 17c0 + 0413 set R0 17c0 0416 call 05ee -Digging deeper, `05ee` calls `05b2` with `R2` set to `05f8`. `05b2` appears to iterate over the characters in a string whose length is stored in address `R1`, and calls `R2` for each character, storing the character in `R1`. `05f8` (the callback provided by `05ee`) simply outputs every character in `R1` it gets. +Digging deeper, `05ee` calls `05b2` with `R1` set to `05f8`. `05b2` appears to iterate over the characters in a string whose length is stored in address `R0`, and calls `R1` for each character, storing the character in `R0`. `05f8` (the callback provided by `05ee`) simply outputs every character in `R0` it gets. Immediately after this call to `05ee`, we find: - 041e set R1 68e3 - 0421 set R2 05fb - 0424 add R3 XXXX XXXX + 041e set R0 68e3 + 0421 set R1 05fb + 0424 add R2 XXXX XXXX 0428 call 05b2 -In other words, a similar string-handling subroutine is called, but instead of `05f8` (which would simply print the string), `05fb` is called. `05fb` also outputs the character, but only after calling `084d` (XOR) with `R2` set to `R3`. +In other words, a similar string-handling subroutine is called, but instead of `05f8` (which would simply print the string), `05fb` is called. `05fb` also outputs the character, but only after calling `084d` (XOR) with `R1` set to `R2`. Now we have everything we need to [extract these encrypted (double-encrypted??) strings](https://github.com/RunasSudo/synacor.py/blob/master/tools/decrypt_strings.py) from the binary! @@ -116,67 +116,67 @@ Only the self-test completion code appears to be stored there, though, so I'm no We may not have the codes themselves, but we can now easily locate where they are printed. The tablet code, for example, is conveniently sandwiched between strings `6ed1` and `6ef1`. Thus the code we are looking for is: - 1290 set R1 1092 - 1293 set R2 650a - 1296 set R3 7fff - 1299 set R4 6eed + 1290 set R0 1092 + 1293 set R1 650a + 1296 set R2 7fff + 1299 set R3 6eed 129c call 0731 Looking into `0731`: - 0731 push R4 - 0733 push R5 - 0735 push R6 - 0737 push R7 - 0739 set R7 0001 - 073c add R5 R4 R7 - 0740 rmem R5 R5 - 0743 add R6 17ed R7 - 0747 wmem R6 R5 - 074a add R7 R7 0001 - 074e rmem R6 17ed - 0751 gt R5 R7 R6 - 0755 jf R5 073c - 0758 set R4 0000 - 075b set R5 0000 - 075e rmem R6 17ed - 0761 mod R6 R5 R6 - 0765 add R6 R6 17ed - 0769 add R6 R6 0001 - 076d rmem R7 R6 - 0770 mult R7 R7 1481 - 0774 add R7 R7 3039 - 0778 wmem R6 R7 - 077b push R1 - 077d push R2 - 077f set R2 R7 + 0731 push R3 + 0733 push R4 + 0735 push R5 + 0737 push R6 + 0739 set R6 0001 + 073c add R4 R3 R6 + 0740 rmem R4 R4 + 0743 add R5 17ed R6 + 0747 wmem R5 R4 + 074a add R6 R6 0001 + 074e rmem R5 17ed + 0751 gt R4 R6 R5 + 0755 jf R4 073c + 0758 set R3 0000 + 075b set R4 0000 + 075e rmem R5 17ed + 0761 mod R5 R4 R5 + 0765 add R5 R5 17ed + 0769 add R5 R5 0001 + 076d rmem R6 R5 + 0770 mult R6 R6 1481 + 0774 add R6 R6 3039 + 0778 wmem R5 R6 + 077b push R0 + 077d push R1 + 077f set R1 R6 0782 call 084d - 0784 set R7 R1 - 0787 pop R2 - 0789 pop R1 - 078b rmem R6 R2 - 078e mod R7 R7 R6 - 0792 add R7 R7 0001 - 0796 gt R6 R7 R3 - 079a jt R6 07a0 - 079d set R4 0001 - 07a0 add R7 R7 R2 - 07a4 rmem R7 R7 - 07a7 add R5 R5 0001 - 07ab add R6 R5 17f1 - 07af wmem R6 R7 - 07b2 rmem R6 17f1 - 07b5 eq R6 R5 R6 - 07b9 jf R6 075e - 07bc jf R4 0758 - 07bf push R1 - 07c1 set R1 17f1 + 0784 set R6 R0 + 0787 pop R1 + 0789 pop R0 + 078b rmem R5 R1 + 078e mod R6 R6 R5 + 0792 add R6 R6 0001 + 0796 gt R5 R6 R2 + 079a jt R5 07a0 + 079d set R3 0001 + 07a0 add R6 R6 R1 + 07a4 rmem R6 R6 + 07a7 add R4 R4 0001 + 07ab add R5 R4 17f1 + 07af wmem R5 R6 + 07b2 rmem R5 17f1 + 07b5 eq R5 R4 R5 + 07b9 jf R5 075e + 07bc jf R3 0758 + 07bf push R0 + 07c1 set R0 17f1 07c4 call 05ee - 07c6 pop R1 - 07c8 pop R7 - 07ca pop R6 - 07cc pop R5 - 07ce pop R4 + 07c6 pop R0 + 07c8 pop R6 + 07ca pop R5 + 07cc pop R4 + 07ce pop R3 07d0 ret Umm… Sorry, could you repeat that? @@ -184,59 +184,59 @@ Umm… Sorry, could you repeat that? Rewriting this again in more friendly terms: ```c -// R1: A seed of sorts - the same for all users -// R2: The length and alphabet to use, usually 650a, but 653f for the mirror -// R3: Usually 7fff, but 0004 for the mirror -// R4: An initialisation vector of sorts - contents different for every user - points to the length, but this is always 3 -0731(R1, R2, R3, R4) { - // copy the string at R4 to 17ed - R7 = 0001; +// R0: A seed of sorts - the same for all users +// R1: The length and alphabet to use, usually 650a, but 653f for the mirror +// R2: Usually 7fff, but 0004 for the mirror +// R3: An initialisation vector of sorts - contents different for every user - points to the length, but this is always 3 +0731(R0, R1, R2, R3) { + // copy the string at R3 to 17ed + R6 = 0001; do { - R5 = R4 + R7; - R5 = [R5]; - R6 = 17ed + R7; - [R6] = R5; - R7 = R7 + 0001; - R6 = [17ed]; // 3, the length - this never seems to change - } while (R7 <= R6); + R4 = R3 + R6; + R4 = [R4]; + R5 = 17ed + R6; + [R5] = R4; + R6 = R6 + 0001; + R5 = [17ed]; // 3, the length - this never seems to change + } while (R6 <= R5); - // the string at 17ed is now what was at R4 + // the string at 17ed is now what was at R3 do { - R4 = 0000; // done flag - R5 = 0000; // index + R3 = 0000; // done flag + R4 = 0000; // index do { - R6 = [17ed]; // 3, the length - R6 = R5 % R6; - R6 = R6 + 17ed; - R6 = R6 + 0001; // will cycle through three addresses of 17ed/R4 string: 17ee, 17ef, 17f0 - R7 = [R6]; - R7 = R7 * 1481; - R7 = R7 + 3039; - [R6] = R7; // mutate that value of the 17ed string - R7 = R1 ^ R7; // combine R1 into R7 - R6 = [R2]; // length of the alphabet - R7 = R7 % R6; - R7 = R7 + 0001; // calculate index in alphabet - if (R7 <= R3) { - R4 = 0001; // we are done with the entire code - this returns immediately for all except the mirror + R5 = [17ed]; // 3, the length + R5 = R4 % R5; + R5 = R5 + 17ed; + R5 = R5 + 0001; // will cycle through three addresses of 17ed/R3 string: 17ee, 17ef, 17f0 + R6 = [R5]; + R6 = R6 * 1481; + R6 = R6 + 3039; + [R5] = R6; // mutate that value of the 17ed string + R6 = R0 ^ R6; // combine R0 into R6 + R5 = [R1]; // length of the alphabet + R6 = R6 % R5; + R6 = R6 + 0001; // calculate index in alphabet + if (R6 <= R2) { + R3 = 0001; // we are done with the entire code - this returns immediately for all except the mirror } - R7 = R7 + R2; // calculate address of letter to use - R7 = [R7]; // the letter to use - R5 = R5 + 0001; // increment the index - R6 = R5 + 17f1; // index of new letter in code - [R6] = R7; // set the letter - R6 = [17f1]; // length of the code: twelve letters - } while (R5 != R6); // loop until we've generated all the letters - } while (!R4); + R6 = R6 + R1; // calculate address of letter to use + R6 = [R6]; // the letter to use + R4 = R4 + 0001; // increment the index + R5 = R4 + 17f1; // index of new letter in code + [R5] = R6; // set the letter + R5 = [17f1]; // length of the code: twelve letters + } while (R4 != R5); // loop until we've generated all the letters + } while (!R3); print(17f1); } ``` Re-implementing this in Python, we can now extract the code for the tablet directly from the binary! -Unfortunately, each of the other codes uses an `R1` based on the solution to the puzzle. In the case of the maze code: +Unfortunately, each of the other codes uses an `R0` based on the solution to the puzzle. In the case of the maze code: - 0f03 rmem R1 0e8e + 0f03 rmem R0 0e8e The value at `0e8e` is derived from which rooms are visited in the maze, as mentioned in the main note file. Armed with our [trusty map](https://github.com/RunasSudo/synacor.py/blob/master/tools/graph.py), and cross-referencing the callbacks with the files, we identify: @@ -247,37 +247,37 @@ The value at `0e8e` is derived from which rooms are visited in the maze, as ment Putting it all together, the final value at `0e8e` is `0008 | 0010 | 0040` = `0058`. -Similarly, the `R1` for the teleporter code is the value of `R8` from the Ackermann function, which we determined earlier to be `6486`: +Similarly, the `R0` for the teleporter code is the value of `R7` from the Ackermann function, which we determined earlier to be `6486`: - 1592 set R1 R8 + 1592 set R0 R7 -The remaining two codes, for the coins and the vault, are more complicated still, but follow the same pattern of determining `R1` based on the player's history. +The remaining two codes, for the coins and the vault, are more complicated still, but follow the same pattern of determining `R0` based on the player's history. -For the coins, the call to `0731` derives an `R1` from the values at memory locations `69de` to `69e2`. One would presume that this relates to the order in which coins are inserted into the puzzle, and following the trail of callbacks for the coins confirms this. The important lines are: +For the coins, the call to `0731` derives an `R0` from the values at memory locations `69de` to `69e2`. One would presume that this relates to the order in which coins are inserted into the puzzle, and following the trail of callbacks for the coins confirms this. The important lines are: - 13c0 rmem R3 099e # nr of coins inserted - 13c3 add R3 R3 69dd - 13c7 add R3 R3 0001 - 13cb wmem R3 R2 # R2 is the number of dots on the coin + 13c0 rmem R2 099e # nr of coins inserted + 13c3 add R2 R2 69dd + 13c7 add R2 R2 0001 + 13cb wmem R2 R1 # R1 is the number of dots on the coin Thus the values should be 9, 2, 5, 7 and 3: the missing numbers in the solved equation. -It is now trivial to generate the correct value of `R1`. Based on the code beginning, `15fd`: +It is now trivial to generate the correct value of `R0`. Based on the code beginning, `15fd`: ```python data_69de = [9, 2, 5, 7, 3] -R1 = 0 +R0 = 0 for i in range(len(data_69de)): - R2 = data_69de[i] - R1 = R1 + R2 - R1 = (R1 * 0x7bac) % 0x8000 - R1 = R1 ^ R2 + R1 = data_69de[i] + R0 = R0 + R1 + R0 = (R0 * 0x7bac) % 0x8000 + R0 = R0 ^ R1 ``` The result is `0b3b`. -On to the final code, now: the vault lock. At `1679`, `R1` is computed as `([0f73] ^ [0f74]) ^ [0f75]`. Searching the code for references to these, we find: +On to the final code, now: the vault lock. At `1679`, `R0` is computed as `([0f73] ^ [0f74]) ^ [0f75]`. Searching the code for references to these, we find: 1236 wmem 0f70 0016 // weight 1239 wmem 0f71 0000 // counter @@ -297,19 +297,19 @@ data_0f74 = 0 data_0f75 = 0 # 08c8 -def rotatey_thing(R1, R2): - while R2 != 0: - R2 = (R2 + 0x7fff) % 0x8000 - R3 = R1 & 0x4000 - R1 = (R1 * 0x0002) % 0x8000 - if R3 == 0: +def rotatey_thing(R0, R1): + while R1 != 0: + R1 = (R1 + 0x7fff) % 0x8000 + R2 = R0 & 0x4000 + R0 = (R0 * 0x0002) % 0x8000 + if R2 == 0: continue - R1 = R1 | 0x0001 - return R1 + R0 = R0 | 0x0001 + return R0 # 11a3 -def mutate(R1val, R2, R3): - return rotatey_thing(R1val, R2) ^ R3 +def mutate(R0val, R1, R2): + return rotatey_thing(R0val, R1) ^ R2 # 1135 def update(value, room): diff --git a/notes.md b/notes.md index 6e1fada..85c8575 100644 --- a/notes.md +++ b/notes.md @@ -5,8 +5,6 @@ Note 1: Codes seem to be unique for every user. Note 2: Numeric values in `monospace` font are in hexadecimal. -Note 3: For some reason, I decided to number the registers `R1` through `R8`, contrary to the spec… - ## The programming codes ### Code 1 @@ -166,7 +164,7 @@ Now, let's see what this `1545` does. C-style, because assembly makes my brain s ```c int 1545() { - if (R8 == 0) + if (R7 == 0) return 15e5(); // Ground state teleport 05b2(70ac, 05fb, 1807 + 585a); // Secure text print for(i = 0; i < 5; i++); // Speed up loop, because why not? @@ -176,7 +174,7 @@ int 1545() { 05b2(7156, 05fb, 1ed6 + 0992); - 0731(R8, 650a, 7fff, 7239); + 0731(R7, 650a, 7fff, 7239); 05b2(723d, 05fb, 7c1f + 0146); @@ -188,49 +186,49 @@ int 1545() { return 1652(); } -int 178b(R1, R2) { +int 178b(R0, R1) { + if (R0 != 0) { + return 1793(R0, R1); + } + return R1 + 0001; +} + +int 1793(R0, R1) { if (R1 != 0) { - return 1793(R1, R2); + return 17a0(R0, R1); } - return R2 + 0001; + R0 = R0 + 7fff; + R1 = R7; + R0 = 178b(R0, R1); + return R0; } -int 1793(R1, R2) { - if (R2 != 0) { - return 17a0(R1, R2); - } +int 17a0(R0, R1) { R1 = R1 + 7fff; - R2 = R8; - R1 = 178b(R1, R2); - return R1; -} - -int 17a0(R1, R2) { - R2 = R2 + 7fff; - R2 = 178b(R1, R2); - R1 = R1 + 7fff; - R1 = 178b(R1, R2); - return R1; + R1 = 178b(R0, R1); + R0 = R0 + 7fff; + R0 = 178b(R0, R1); + return R0; } ``` -Phew, so in other words, we seek an `R8` such that `178b(4, 1, R8)` equals 6. Let's see if we can't rewrite that function. Note that adding 0x7fff = 32767 to a number modulo 32768 is equivalent to subtracting 1. Thinking through the code, then, +Phew, so in other words, we seek an `R7` such that `178b(4, 1, R7)` equals 6. Let's see if we can't rewrite that function. Note that adding 0x7fff = 32767 to a number modulo 32768 is equivalent to subtracting 1. Thinking through the code, then, - A(R1, R2) = R2 + 1 , if R1 = 0 - A(R1 - 1, R8) , if R1 ≠ 0 and R2 = 0 - A(R1 - 1, A(R1, R2 - 1)) , if R1 ≠ 0 and R2 ≠ 0 + A(R0, R1) = R1 + 1 , if R0 = 0 + A(R0 - 1, R7) , if R0 ≠ 0 and R1 = 0 + A(R0 - 1, A(R0, R1 - 1)) , if R0 ≠ 0 and R1 ≠ 0 -Recognise anything? Well, neither did I the first time, and I'd [already seen the video](https://www.youtube.com/watch?v=i7sm9dzFtEI). It's the [Ackermann function](https://en.wikipedia.org/wiki/Ackermann_function)! With the slight twist that instead of the second line being `A(R1 - 1, 1)`, it's `A(R1 - 1, R8)`. +Recognise anything? Well, neither did I the first time, and I'd [already seen the video](https://www.youtube.com/watch?v=i7sm9dzFtEI). It's the [Ackermann function](https://en.wikipedia.org/wiki/Ackermann_function)! With the slight twist that instead of the second line being `A(R0 - 1, 1)`, it's `A(R0 - 1, R7)`. -No mathematical wizardry here, just implementing this and run a brute-force on all possible values of `R8`. And as much as it pains me to admit this, this is a tool for the raw processing efficiency of C, which I am not very proficient in, so I based my solution on [this wonderful code](https://github.com/glguy/synacor-vm/blob/master/teleport.c) by [glguy](https://github.com/glguy). My only contribution is the parallelisation of the computation. (500% speed-up! Whoo!) +No mathematical wizardry here, just implementing this and run a brute-force on all possible values of `R7`. And as much as it pains me to admit this, this is a tool for the raw processing efficiency of C, which I am not very proficient in, so I based my solution on [this wonderful code](https://github.com/glguy/synacor-vm/blob/master/teleport.c) by [glguy](https://github.com/glguy). My only contribution is the parallelisation of the computation. (500% speed-up! Whoo!) gcc ackermann.c -o ackermann -lpthread -O3 && ./ackermann -Running the algorithm, the correct value is revealed to be `0x6486`. Now we simply set `R8` to `0x6486` and patch the code to skip the check, before `use`ing the `teleporter`: +Running the algorithm, the correct value is revealed to be `0x6486`. Now we simply set `R7` to `0x6486` and patch the code to skip the check, before `use`ing the `teleporter`: 1571 call 178b -> nop nop - 1573 eq R2 R1 0006 -> nop nop nop nop - 1577 jf R2 15cb -> nop nop nop + 1573 eq R1 R0 0006 -> nop nop nop nop + 1577 jf R1 15cb -> nop nop nop I've implemented this as a debug function to prepare the teleporter: diff --git a/tools/decrypt_data.py b/tools/decrypt_data.py index 3a76a58..3edcf60 100755 --- a/tools/decrypt_data.py +++ b/tools/decrypt_data.py @@ -34,11 +34,11 @@ else: i += 1 # Emulate 06bb - for R2 in range(0x17b4, 0x7562): - R1 = SYN_MEM[R2] - R1 ^= pow(R2, 2, 32768) - R1 ^= 0x4154 - SYN_MEM[R2] = R1 + for R1 in range(0x17b4, 0x7562): + R0 = SYN_MEM[R1] + R0 ^= pow(R1, 2, 32768) + R0 ^= 0x4154 + SYN_MEM[R1] = R0 # Write with open(sys.argv[2], 'wb') as f: diff --git a/tools/decrypt_strings.py b/tools/decrypt_strings.py index 793a6df..95a147b 100755 --- a/tools/decrypt_strings.py +++ b/tools/decrypt_strings.py @@ -31,11 +31,11 @@ with open(sys.argv[1], 'rb') as data: i += 1 # Emulate 06bb -for R2 in range(0x17b4, 0x7562): - R1 = SYN_MEM[R2] - R1 ^= pow(R2, 2, 32768) - R1 ^= 0x4154 - SYN_MEM[R2] = R1 +for R1 in range(0x17b4, 0x7562): + R0 = SYN_MEM[R1] + R0 ^= pow(R1, 2, 32768) + R0 ^= 0x4154 + SYN_MEM[R1] = R0 class OpLiteral: def __init__(self, value): @@ -133,22 +133,22 @@ while SYN_PTR < len(SYN_MEM): readOp() elif word == 17: #CALL if readWord() == 0x05b2: - if (SYN_MEM[SYN_PTR-9:SYN_PTR-6] == [1, 32769, 0x05fb] # set R2 05fb - and SYN_MEM[SYN_PTR-12:SYN_PTR-10] == [1, 32768] # set R1 XXXX - and SYN_MEM[SYN_PTR-6:SYN_PTR-4] == [9, 32770]): # add R3 XXXX XXXX + if (SYN_MEM[SYN_PTR-9:SYN_PTR-6] == [1, 32769, 0x05fb] # set R1 05fb + and SYN_MEM[SYN_PTR-12:SYN_PTR-10] == [1, 32768] # set R0 XXXX + and SYN_MEM[SYN_PTR-6:SYN_PTR-4] == [9, 32770]): # add R2 XXXX XXXX # Got an encrypted string! - R1 = SYN_MEM[SYN_PTR-10] - R3 = (SYN_MEM[SYN_PTR-4] + SYN_MEM[SYN_PTR-3]) % 32768 - #print('{:04x} {:04x}'.format(R1, R3)) + R0 = SYN_MEM[SYN_PTR-10] + R2 = (SYN_MEM[SYN_PTR-4] + SYN_MEM[SYN_PTR-3]) % 32768 + #print('{:04x} {:04x}'.format(R0, R2)) - strlen = SYN_MEM[R1] + strlen = SYN_MEM[R0] strbuf = '' for i in range(strlen): - encrypted = SYN_MEM[R1 + 1 + i] - decrypted = encrypted ^ R3 + encrypted = SYN_MEM[R0 + 1 + i] + decrypted = encrypted ^ R2 strbuf += escapeChar(chr(decrypted)) - print('{:04x}: "{}"'.format(R1, strbuf)) + print('{:04x}: "{}"'.format(R0, strbuf)) elif word == 18: #RET pass elif word == 19: #OUT diff --git a/tools/disasm.py b/tools/disasm.py index b6e21e8..b02a89a 100755 --- a/tools/disasm.py +++ b/tools/disasm.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # synacor.py - An implementation of the Synacor Challenge -# Copyright © 2016 RunasSudo +# Copyright © 2017 RunasSudo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -30,9 +30,9 @@ class OpRegister: def __init__(self, register): self.register = register; def get(self): - return 'R{}'.format(self.register + 1); + return 'R{}'.format(self.register); def set(self): - return 'R{}'.format(self.register + 1); + return 'R{}'.format(self.register); def readWord(): byteData = data.read(2) diff --git a/tools/generate_codes.py b/tools/generate_codes.py index 98fd132..f0c4eec 100755 --- a/tools/generate_codes.py +++ b/tools/generate_codes.py @@ -24,27 +24,27 @@ IV_LEN = 3 CODE_LEN = 12 # Emulate 0731 -def generate_code(R1, R2, R3, R4): - R2data = SYN_MEM[R2+1:R2+1+SYN_MEM[R2]] - R4data = SYN_MEM[R4+1:R4+1+SYN_MEM[R4]] +def generate_code(R0, R1, R2, R3): + R1data = SYN_MEM[R1+1:R1+1+SYN_MEM[R1]] + R3data = SYN_MEM[R3+1:R3+1+SYN_MEM[R3]] - assert len(R4data) == IV_LEN + assert len(R3data) == IV_LEN done = False while not done: code = '' for i in range(CODE_LEN): - R6 = i % IV_LEN - R7 = R4data[R6] - R7 = (R7 * 0x1481) % 0x8000 - R7 = (R7 + 0x3039) % 0x8000 - R4data[R6] = R7 - R7 = R1 ^ R7 - R7 = R7 % len(R2data) - if R7 + 1 <= R3: + R5 = i % IV_LEN + R6 = R3data[R5] + R6 = (R6 * 0x1481) % 0x8000 + R6 = (R6 + 0x3039) % 0x8000 + R3data[R5] = R6 + R6 = R0 ^ R6 + R6 = R6 % len(R1data) + if R6 + 1 <= R2: done = True - R7 = R2data[R7] - code += chr(R7) + R6 = R1data[R6] + code += chr(R6) return code @@ -77,33 +77,33 @@ with tarfile.open(sys.argv[1], 'r:gz') as challenge_file: print(re.search(r"Here's a code for the challenge website: (............)", spec_data).group(1)) # Emulate 06bb -for R2 in range(0x17b4, 0x7562): - R1 = SYN_MEM[R2] - R1 ^= pow(R2, 2, 32768) - R1 ^= 0x4154 - SYN_MEM[R2] = R1 +for R1 in range(0x17b4, 0x7562): + R0 = SYN_MEM[R1] + R0 ^= pow(R1, 2, 32768) + R0 ^= 0x4154 + SYN_MEM[R1] = R0 # Basic codes print(bytes(SYN_MEM[0x00f5:0x010c:2]).decode('utf-8')) -R1 = 0x68e3 -R3 = (SYN_MEM[0x0426] + SYN_MEM[0x0427]) % 0x8000 -strlen = SYN_MEM[R1] +R0 = 0x68e3 +R2 = (SYN_MEM[0x0426] + SYN_MEM[0x0427]) % 0x8000 +strlen = SYN_MEM[R0] strbuf = '' for i in range(strlen): - encrypted = SYN_MEM[R1 + 1 + i] - decrypted = encrypted ^ R3 + encrypted = SYN_MEM[R0 + 1 + i] + decrypted = encrypted ^ R2 strbuf += chr(decrypted) print(re.match(r"The self-test completion code is: (............)", strbuf).group(1)) # Generated codes # Calls to 0731 CODE_PARAMS = [ - (0x0058, 0x650a, 0x7fff, 0x6e8b), # R1 from the maze + (0x0058, 0x650a, 0x7fff, 0x6e8b), # R0 from the maze (0x1092, 0x650a, 0x7fff, 0x6eed), - (0x6486, 0x650a, 0x7fff, 0x7239), # R1 is R8 from Ackermann - (0x0b3b, 0x650a, 0x7fff, 0x73df), # R1 from the dots on the coins - (0x7714, 0x653f, 0x0004, 0x74f6), # R1 based on solution to vaults + (0x6486, 0x650a, 0x7fff, 0x7239), # R0 is R7 from Ackermann + (0x0b3b, 0x650a, 0x7fff, 0x73df), # R0 from the dots on the coins + (0x7714, 0x653f, 0x0004, 0x74f6), # R0 based on solution to vaults ] for cp in CODE_PARAMS: