TISC20

Stage 1

We're given an encrypted zip file and told that the password is a hex string. Generating a wordlist and feeding it to fcrackzip solves this.

import itertools

with open("hex_wordlist.txt", "w") as f:
    for c in itertools.product("0123456789abcdef", repeat=6):
        pw = "".join(c)
        f.write(pw + "\n")
$ fcrackzip -D -p hex_wordlist.txt stage1.zip -u -v
found file 'temp.mess', (size cp/uc  39970/ 39943, flags 3, chk 37bc)
checking pw 98967f

PASSWORD FOUND!!!!: pw == 9ea08c

Unzipping the file reveals...more archives. We're given temp.mess - manually unzipping and decoding the file got too tedious. Another script solves this quickly:

import magic
import zlib
import base64
import binascii
import lzma
import bz2

with open("temp.mess", "rb") as f:
    data = f.read()

try:
    while True:
        typ = magic.from_buffer(data)
        if "gzip" in typ or "zlib" in typ:
            # https://stackoverflow.com/a/6124315
            data = zlib.decompress(data, 15 + 32)
        elif "XZ" in typ:
            data = lzma.decompress(data)
        elif "bzip2" in typ:
            data = bz2.decompress(data)
        elif "ASCII text" in typ:
            unique_chars = set(data.decode("utf8"))
            if len(unique_chars) > 16:
                # base64
                data = base64.b64decode(data)
            else:
                data = binascii.unhexlify(data)
        elif "JSON" in typ:
            print(data.decode("utf8"))
            break
        else:
            print(f"unable to handle {typ}")
            print(data[0:10])
            break
except:
    print("error!")
    with open("stop", "wb") as f:
        f.write(data)

This gets us the flag for stage 1: {"anoroc": "v1.320", "secret": "TISC20{q1_946324372636474e071b767f4a567bb2}", "desc": "Submit this.secret to the TISC grader to complete challenge", "constants": [1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221], "sign": "KQ4QYnHLM_4"}

Stage 2

We're asked to extract a public key from a Golang binary. Opening it in Ghidra, we find something interesting in main.main:

<snip>
local_378 = &DAT_006fd9d7;
local_370 = (undefined **)0x7c4;
local_368 = (long **)&DAT_006fcc8d;
local_360 = (long **)0x64;
main.EncryptDecrypt();
local_378 = (undefined1 *)encoding/base64.StdEncoding;
local_370 = (undefined **)local_358;
local_368 = local_350;
encoding/base64.();
local_368 = local_350;
local_370 = (undefined **)local_358;
local_378 = (undefined1 *)local_360;
if (local_348 != (long **)0x0) {
  local_320 = local_350;
  local_328 = local_358;
  local_288 = local_360;
  local_1d8 = &DAT_00689a80;
  local_1d0 = &PTR_DAT_0074c330;
  local_378 = go.itab.*os.File,io.Writer;
  local_370 = (undefined **)os.Stdout;
  local_368 = (long **)&local_1d8;
  local_360 = (long **)0x1;
  local_358 = (long **)0x1;
  fmt.Fprint();
  local_368 = local_320;
  local_370 = (undefined **)local_328;
  local_378 = (undefined1 *)local_288;
}
encoding/pem.Decode();
local_368 = (long **)local_360[5];
local_370 = (undefined **)local_360[4];
local_378 = (undefined1 *)local_360[3];
crypto/x509.ParsePKIXPublicKey();
<snip>
main.main
void main.EncryptDecrypt(long param_1,long param_2,long param_3,ulong param_4) {
  ulong *puVar1;
  long lVar2;
  undefined8 uVar3;
  ulong uVar4;
  undefined8 uVar5;
  long in_FS_OFFSET;
  undefined8 local_58;
  ulong local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  long local_20;
  undefined8 local_18;
  undefined8 local_10;
  
  puVar1 = (ulong *)(*(long *)(in_FS_OFFSET + 0xfffffff8) + 0x10);
  if ((undefined *)*puVar1 <= register0x00000020 &&
      (undefined *)register0x00000020 != (undefined *)*puVar1) {
    register0x00000020 = (BADSPACEBASE *)&local_58;
    lVar2 = 0;
    uVar3 = 0;
    uVar5 = 0;
    while( true ) {
      if (param_2 <= lVar2) {
        return;
      }
      if (param_4 == 0) break;
      uVar4 = (lVar2 * lVar2) % param_4;
      if (param_4 <= uVar4) {
                    /* WARNING: Subroutine does not return */
        runtime.panicIndex();
      }
      local_58 = 0;
      local_50 = (ulong)(*(byte *)(param_1 + lVar2) ^ *(byte *)(param_3 + uVar4));
      local_20 = lVar2;
      local_18 = uVar5;
      local_10 = uVar3;
      runtime.intstring();
      local_38 = local_40;
      local_40 = local_48;
      local_58 = 0;
      local_50 = local_10;
      local_48 = local_18;
      runtime.concatstring2();
      lVar2 = local_20 + 1;
      uVar3 = local_30;
      uVar5 = local_28;
    }
    runtime.panicdivide();
  }
  *(undefined8 *)((undefined *)register0x00000020 + -8) = 0x661942;
  runtime.morestack_noctxt();
  main.EncryptDecrypt();
  return;
}
main.EncryptDecrypt

We see two large chunks of data get passed to main.EncryptDecrypt - I wonder what's inside? Carving out the data and reimplementing the decryption algorithm in Python gets us our key:

coronaware = open("anorocware", "rb").read()

i = coronaware.find(b"\x4e\x5b\x37\x76")
data = coronaware[i:i+0x7c4]

i = coronaware.find(b"\x02\x08\x04\x03")
key = coronaware[i:i+0x64]

out = ""
for i, c in enumerate(data):
    out += chr(c ^ key[(i * i) % 0x64])

print(out, end="")
$ python3 decrypt_full.py  | base64 -d
-----BEGIN PUBLIC KEY-----
MIIEIDANBgkqhkiG9w0BAQEFAAOCBA0AMIIECAKCBAEAm99b2pvtrViW+jN/3NFf
w8g36dQR6iJr+cyRe+k8XFzuHUO4LN3tk76tFS8DbaCcYFiuf8GsugcRmQDErPZf
qgkvXZpufffTfjTB+je/Wi43bwLqtw0W4cXoPW33uGVaWZX0oLzKC/Axg7kwItmG
xnn321TAjEZgTbL+OaNkcHzfQ7UzwaEp9UPtT8pGYoNJHlX3fkFq2iVy77uI4gRK
Mf8ujTfkIHHjQ7BEzgEgk8kqxGaSPlINQs65P4tvOpihqpwUVpAjPLNBTt9Hz1F/
fR+aDsJQRKZNMrWRLuMYiO2Mx9cZBnwzL9KuFRvHelO7BWayU9f0XOpg/zybEQOL
ux+jmsUsTsQbjK9cB67Ma21D+XJHyKgKuP9u14mVCZgCBk9lybS1bxdvFDQPgkyc
M3z9vuucCU1Eu2D0lhFmJ3FQfZkAY++XHUpiwui9NO3A9UG7amyXbOSclF2X9kRq
0CwmqOtBRBEWISe5rdzc/ATOP3PqDjGwySXxWZDCH8rrgnzWpv2LriYQTnf2cE0G
/iI8RwjYoGLWzeLVRr1hhZ8Y5s4R/sR497WenkRcpOLOkDVge7MusTOWh4eNi4go
PldsiYTqTndA1wV67r09ujpp8VvpdLuo+4h+7p/pfpXMsx8dALom4sfkYcJHhObk
xt5CpNCkVXh5tsGheFb7v85GiNFy17zualMda32BinPeEbFrqKwD2Z4R5QgQuB8u
IwjqSTgNo9Uvvch6lWCbj9e+80ugV4o7jHCd/56FkuvhCqiINdZDUU4ZB37hdelf
eE9NbxDjKG8V7aCdwqJJDYGiz/3jmuCfB/k5FkoHSANgbLE0A5Smk3T8tuv8Sz+f
v4rrPxmpn8X2Sm1Foz+U0BWzP+VLmpLnnyXkrOHyn8lJFbn/U5NWGRLn+ev2CSkw
AI/TfHALqTvjqlGQxTTaY7Znkn5i+D1LztK8cpSZXdDVoRh+/vMIEiNuk8++/s6a
HNd7wuFkY/Z8jjJ1jH/csF37mGYAUxp32nRk5wRp/c6eWZPM+zGibfEnmFW5yUEU
YbX4hzzGr5Q6f/sysuzhaylWi3XCvIrH6LBjFNu3UJ0VIzcJN0kxaABaXY8JUDYX
tXULipvUOqkttOqJSxOXWg72SWKLKv/QvfDRVXedUk066k7RL1okpbMnwYlfYg7J
mpZZR2CNNwbMkQm2TmrA/MZudvqtsX9PpkgJI+ZWjUwVtGRUTdDMxZWx4H3neJiy
8m8udk42RN0j3n0wVXsWt6Qmy7bQsHYXIHUgkBXYzdy/u+NodKAjhdVpiJbzIncz
SdolXiniKwNULW8VjjS9KTSRwidqeOkkpNeIqiRnWT3TTMAMzb5j0jEGF7L3DOMP
6QIBAw==
-----END PUBLIC KEY-----

Reflections

This is where I stopped due to time constraints (and the lack of patience to RE the Golang binary). It turns out that I placed 11th - perhaps I should have stuck around for longer...

Other Far More Detailed Writeups:

  1. Calvin (First place) - https://blog.idiot.sg/2020-09-18/tisc-ctf-2020/
  2. Jeremy (Third place) - https://nandynarwhals.org/tisc-2020-writeups/
  3. Eugene (Sixth place) - https://spaceraccoon.dev/beat-the-clock-the-csit-infosecurity-challenge