この記事はKogakuin Univ Advent Calendar 20202日目の記事です。

どうも、12月にしか更新されないブログです。

2020/10/3に開催されたTrend Micro CTF 2020 のWriteupをします。

え、何で今更そんな微妙に前のCTFのWriteupをやるかって?
元々書こうと思ってたネタを諸般の事情で延期させたらネタがなくなってしまったので書いていたけど公開はしていない文書を探したらこんくらいしかなかった

では、気を取り直してWriteupします

Overview

チーム KogCoder として参加し、500pts獲得し30位でした

Reversing-I 2問とReversing-II 1問解きました
そろそろ他ジャンルできるようになりたいです

Reversing-I

Reversing-II

Reversing-I 100

100

Ghidraで実行ファイルを調べてみると_MEIPASSというデータが散見される。調べてみるとpyinstallerで使われているデータ形式であると分かる。

pyinstallerはpycファイルとPythonの実行環境を雑にまとめただけなので、ここを参考にデコンパイルした。

# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3391)
# Decompiled from: Python 3.8.5 (default, Sep  5 2020, 10:50:12) 
# [GCC 10.2.0]
# Embedded file name: l11_opy_.py
import sys
l11l11 = sys.version_info[0] == 2
l1ll1ll = 2048
l1lllll = 7

def l1l1(l1ll1l1):
    l1ll11 = ord(l1ll1l1[(-1)])
    ll = l1ll1l1[:-1]
    l11l = l1ll11 % len(ll)
    l11l1l = ll[:l11l] + ll[l11l:]
    if l11l11:
        l1111l = l1lll1l().join([l1(ord(char) - l1ll1ll - (l1l1ll + l1ll11) % l1lllll) for l1l1ll, char in enumerate(l11l1l)])
    else:
        l1111l = str().join([chr(ord(char) - l1ll1ll - (l1l1ll + l1ll11) % l1lllll) for l1l1ll, char in enumerate(l11l1l)])
    return eval(l1111l)

import sys
l11ll = True
if l11ll:

    def to_bytes(l111, length, byteorder):
        return l111.to_bytes(length, byteorder)

    def from_bytes(l11111, byteorder):
        return int.from_bytes(l11111, byteorder)

else:

    def to_bytes(l111, length, byteorder=l1l1('ࠫࡧ\u086fࡧࠨࠀ')):
        h = l1l1('ࠬࠫ\u0878ࠨࠁ') % l111
        s = (l1l1('࠭࠰ࠨࠂ') * (len(h) % 2) + h).zfill(length * 2).decode(l1l1('ࠧࡩࡧ\u087bࠫࠃ'))
        if byteorder == l1l1('ࠨࡤ\u086c\u086bࠬࠄ'):
            return s
        if byteorder == l1l1('ࠩ\u086f\u086d\u0879\u087a\u086cࡦࠩࠅ'):
            return s[::-1]
        raise ValueError(l1l1('ࠥࡦ\u087e\u087aࡥ\u0870\u0874ࡧࡩ\u0877ࠦ\u086d\u0876\u0875\u0877ࠤࡧ\u086bࠠࡦ\u086b\u0877\u086cࡪ\u0878ࠠࠨ\u086e\u086c\u0878\u0879\u0872ࡥࠨࠢ\u0872\u0876ࠥ࠭ࡢࡪࡩࠪࠦࠆ'))

    def from_bytes(l11111, byteorder):
        if len(l11111) == 4:
            size = l1l1('ࠫࡑ࠭ࠇ')
        else:
            if len(l11111) == 8:
                size = l1l1('ࠬࡗࠧࠈ')
        if byteorder == l1l1('࠭ࡢࡪࡩࠪࠉ'):
            return l11l1.l1l1l(l1l1('ࠢ\u083fࠤࠊ') + size, l11111)[0]
        if byteorder == l1l1('ࠨ\u086e\u086c\u0878\u0879\u0872ࡥࠨࠋ'):
            return l11l1.l1l1l(l1l1('ࠤ\u083fࠦࠌ') + size, l11111)[0]
        raise ValueError(l1l1('ࠥࡦ\u087e\u087aࡥ\u0870\u0874ࡧࡩ\u0877ࠦ\u086d\u0876\u0875\u0877ࠤࡧ\u086bࠠࡦ\u086b\u0877\u086cࡪ\u0878ࠠࠨ\u086e\u086c\u0878\u0879\u0872ࡥࠨࠢ\u0872\u0876ࠥ࠭ࡢࡪࡩࠪࠦࠍ'))

l1ll1l = l1l1('ࠦࠧࠨࠍࠋࠏࠍࠤⶍⶎⶈⶉⶊⶋⵛࠥࠦⶈⶉⶊⶋⶌⵜࠦⶈⶉⶊⶋⶌⶍⶎⶈⵘⶊⶋⶌⶍⶎⶈⶉⵙⶋⶌⵜࠦࠠⶉⶊⵚⶌⶍⶎⶈⶉⶊⶋⵛⶍⶎⶈⶉⶊⶋⶌⵜⶎⶈⶉⶊⶋⶌⵜࠦⶈⶉⶊⶋⶌⶍⶎⵗⶉⶊⶋⶌⶍⶎⵗࠡࠏࠍⶌⶍⵚⵐⵑⵒⵓⵡࠥⶎⶈⵕⵒⵓⶌⶍⵝⵚⵑⵒⶋⶌⵙⵖⵐⵞⶊⶋⵘⵕⵖⵐⵑⵟⶋⶌⵖࠦⶈⶉⵖⵠⶌⶍⵚⵐⵑⵒⵓⵡⶍⶎⵔⵑⵒⵓⵔⵢⶎⶈⵕⵒⵓⶌⶍⵝⶈⶉⵖⵓⵔⵕⵖⵝⶉⶊⵗⵔⵕⶎⶈⵘࠏࠍⶌⶍⵗࠠࠡⶊⶋⶌⵜⶎⶈⶉⶊⶋⶌⶍⵗࠠࠡࠢⶋⶌⵖࠦࠠࠡⶊⶋⶌⶍⶎⵗࠡࠢⶋⶌⶍⶎⶈⵕⵟࠣⶌⶍⶎⶈⶉⵙࠣࠤⶍⶎⶈⶉⶊⵚࠤࠥⶎⶈⶉⶊⶋⶌⵙⵣⶈⶉⶊⶋⶌⵜࠦࠠⶉⶊⶋⶌⶍⶎⵔⵞࠏࠍⶌⶍⵗࠠࠡࠢⶋⶌⵖⶎⶈⵕⵒⵓⶌⶍⵗࠠࠡࠢⶋⶌⵖࠦࠠࠡⶊⶋⵘⵕⵖⵝࠡࠢⶋⶌⵙⵖⶈⶉⵙࠣⶌⶍⵚⵐⵑⵟࠣࠤⶍⶎⵔⵑⵒⵠࠤࠥⶎⶈⵕⵒⵓⵔⵢࠦⶈⶉⵖⵓⵔⵢࠦࠠⶉⶊⵗⵔⵕⶎⶈⵘࠏࠍⵞⶍⶎⶈⶉⶊⶋⵘⵢⶎⶈⵒࠢࠣⶌⶍⵗࠠࠡࠢⶋⶌⵖࠦࠠࠡⶊⶋⶌⶍⶎⶈⶉⵙⶋⶌⵖࠦࠠⶉⶊⵚⶌⶍⶎⶈⶉⶊⶋⵛⶍⶎⶈⶉⶊⶋⶌⵜⶎⶈⵒࠢࠣࠤࠥࠦⶈⶉⶊⶋⶌⶍⶎⵗⶉⶊⵔࠤࠥⶎⶈⵒࠏࠍࠤⵟⵖⵐⵑⵒⵓⵡࠥⵠⵐⵞࠢࠣⵞⵕⵣࠠࠡࠢⵝⵔⵢࠦࠠࠡⵜⵓⵔⵕⵖⵐⵑⵟⵝⵔⵢࠦࠠⵛⵒⵠⵞⵕⵖⵐⵑⵒⵓⵡⵟⵖⵐⵑⵒⵓⵔⵢⵠⵐⵞࠢࠣࠤࠥࠦⵚⵑⵒⵓⵔⵕⵖⵝⵛⵒⵠࠤࠥⵠⵐⵞࠏࠍࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠢࠣࠤࠥࠦࠠࠡࠏࠍࠑࠏࠨࠢࠣࠎ')

def l1lll1():
    l1lll11 = [97, 249]
    l1lll11 += [205, 67]
    l1lll11.append(170)
    l1lll11.append(249)
    l1lll11.append(73)
    l1lll11 += [22, 153, 183]
    l1lll11 += [22, 237]
    l1lll11 += [60, 77, 128, 150]
    l1lll11.append(103)
    l1lll11 += [183, 217]
    l1lll11 += [84]
    l1l111 = l1lll11[(-1)]
    for i in range(len(l1lll11) - 1):
        l1lll11[i] ^= l1l111 & 255
        l1l111 = l1l111 + l1lll11[i]

    key = l1l1('ࠬ࠭ࠏ').join((chr(i) for i in l1lll11[:-1]))
    return key

class Counter:

    def __init__(self, l111ll, byteorder=l1l1('࠭ࡢࡪࡩࠪࠐ')):
        self._Counter__11lll = l111ll
        self.byteorder = byteorder
        self._Counter__1111 = from_bytes(self._Counter__11lll, self.byteorder)

    def __call__(self):
        value = to_bytes(self._Counter__1111, 8, self.byteorder)
        self._Counter__1111 += 1
        self._Counter__1111 %= 18446744073709551616
        return value

    def reset(self):
        self._Counter__1111 = from_bytes(self._Counter__11lll, self.byteorder)

print(l1ll1l)

def l1llll1(l11ll1):
    l1ll111 = l1l1('ࠢࠣࠑ')
    l111l1 = 0
    for l1ll11l in l11ll1:
        if l1ll11l.isdigit():
            l1ll111 += l1ll11l
        else:
            l111l1 += int(l1ll111)
            l1ll111 = l1l1('ࠣ࠲ࠥࠒ')

    return l111l1 + int(l1ll111)

l111l = l1l1('ࠩ࠴࠶࠸࠺࠵࠷࠹࠻࠽ࡦࡨࡣࡥࡧࡩ\u086b\u086d\u086fࡪ\u086c\u086f\u0871\u0873\u0875\u0877\u0872\u0874\u0876\u0878\u087a\u087c\u087e\u0879\u087bࡃࡅࡇࡉࡋࡆࡈࡊࡍࡏࡑࡓࡎࡑࡓࡕࡗ࡙࡛ࡖࡘ࡚\u085c࡞ࠬࠓ')
l1ll = len(l111l)
l1l = l1l1('ࠥࠦࠧࠓࠊࠎࠌࠐࠎⴑⴆⴐⴍⴂⴓⴐⴕⴒⴌⴁⴒ\u2d2fⴄⴕⴒⴀⴑⴎ\u2d2fⴔⴱࠦ\u2d2cⴭࠢࠣⴐⴅⴖⴌⴭⴒ\u2d2fⴐⴅⴖⴌⴑⴎⴏⴄⴕࠓࠊⴃࠢࠣⴆࠥⴈⴂⴃⴄⴅࠤⴱⴢ\u2d2cⴙⴞⴃ\u2d28ࠥⴈࠠⴃࠢⴅⴆࠥࠦⴜⴁ\u2d26ࠣⴆࠥⴈⴂࠡⴄⴅⴆⴇⴚⴀⴑࠏࠍⴘⴅⴞⴔⴁⴚⴛⴘⴝⴚⴀⴙⴶⴗⴄⴹࠦⴴࠡⴶࠣⴘⴅⴞⴴⴁⴚⴷࠤⴹࠦⴴࠡⴶⴗⴄⴝⴞⴔⴙⴖⴃⴜࠒࠐࠍࠋࠏࠍࠦࠧࠨࠔ')

def l1l11():
    l1l11l = [0, 46, 200, 65, 44, 226, 136, 39, 237, 123, 67, 201, 119, 244, 189, 22, 253, 140, 63, 242, 77, 32, 139, 221, 82, 247, 134, 32, 241]
    l1l11l = [0, 46]
    l1l11l += [200, 65, 44, 226]
    l1l11l.append(136)
    l1l11l.append(39)
    l1l11l += [237, 123, 67, 201, 119, 244, 189]
    l1l11l.append(22)
    l1l11l.append(253)
    l1l11l.append(140)
    l1l11l += [63, 242, 77]
    l1l11l += [32, 139]
    l1l11l.append(221)
    l1l11l.append(82)
    l1l11l.append(247)
    l1l11l += [134, 32, 241, 54]
    l1l111 = l1l11l[(-1)]
    for i in range(len(l1l11l)):
        l1l11l[i] ^= l1l111 & 255
        l1l111 = l1l111 + l1l11l[i]

    key = l1l1('ࠫࠬࠕ').join((chr(i) for i in l1l11l))
    return key

def l1ll1():
    l1lll11 = [98, 199]
    l1lll11 += [148, 78]
    l1lll11.append(40)
    l1lll11.append(207)
    l1lll11.append(66)
    l1lll11 += [172, 184, 40]
    l1lll11 += [205, 127]
    l1lll11 += [32, 150]
    l1lll11.append(18)
    l1lll11.append(55)
    l1lll11.append(180)
    l1lll11.append(110)
    l1lll11.append(189)
    l1lll11 += [179, 18, 41, 54]
    l1l111 = l1lll11[(-1)]
    for i in range(len(l1lll11) - 1):
        l1lll11[i] ^= l1l111 & 255
        l1l111 = l1l111 + l1lll11[i]

    l1llll = l1l1('ࠬ࠭ࠖ').join((chr(i) for i in l1lll11[:-1]))
    return l1llll

while True:
    print(l1lll1())
    l1l1l1 = input(l1l1('ࠨࡅ\u086f\u0876ࡨ\u0876ࠥ\u087aࡨࡦࠢ\u0873ࡥ\u0878\u0879\u0877\u0870\u0874ࡧ࠾ࠥࠨࠗ'))
    if l1l1l1 == l1lll1():
        print(l1l)
        print(l1l1('࡚ࠢ\u0871\u0878ࠤࡦ\u0878ࡥࠡࡥ\u0872\u0876\u0877\u086bࡣ\u0875ࠣࠥ࠘'))
        print(l1l1('ࠣࡊࡨ\u0876ࡪࠦࡩ\u0874ࠢ\u087c\u0873\u087a\u0878ࠠࡧ\u086eࡤ\u086b\u083fࠨ࠙'), l1ll1())
        input(l1l1('ࠤࡓ\u0876ࡪ\u0879\u0873ࠡࡧ\u0871\u0878ࡪ\u0878ࠠ\u0875\u0871ࠣࡧ\u0874\u0874\u0874ࡪ\u0870\u0878ࡩ࠳࠴\u082eࠣࠚ'))
        break
    else:
        print(l1l1('࡛ࠥࡗࡕࡎࡈࠣࠣࡔ\u0871\u086bࡡ\u0874ࡧࠣ\u0878\u0877\u087fࠠࡢࡩࡤ\u086d\u0873ࠨࠛ'))
# okay decompiling ./gatekeeper.exe_extracted/l11_opy_.pyc

デコンパイル結果から入力とl1lll1()の結果を単純に比較しているだけだと分かるので、print(l1lll1())input()の前に挿入するなどして期待値をリークする。

あとはその期待値を入力すれば、flagが表示される。

TMCTF{m0ther_H4msT3r!}

Reversing-II 200

破損した暗号化ファイルfileと復号ソフトdecrypterの2つが渡された。

decrypterをGhidraで見てみる。
main()は以下のようになっていた

undefined8 main(void)

{
  long lVar1;
  int iVar2;
  int iVar3;
  FILE *__stream;
  __off_t _Var4;
  undefined *__s1;
  undefined *puVar5;
  long in_FS_OFFSET;
  int local_dffc;
  int local_dff8;
  int local_dff4;
  int local_dff0;
  uint local_dfec;
  int local_dfe8;
  int local_dfe4;
  int local_dfe0;
  undefined8 local_df98 [2];
  byte local_df88 [16];
  undefined8 local_df68;
  undefined8 local_df60;
  undefined8 local_df58;
  undefined8 local_df50;
  undefined local_df48;
  undefined8 local_df38;
  undefined8 local_df30;
  undefined8 local_df28;
  undefined8 local_df20;
  undefined local_df18;
  char local_df08 [32];
  undefined local_dee8;
  char data [16];
  char acStack57032 [18992];
  RC4_KEY local_9498 [18];
  byte local_4a58 [16];

  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  __stream = fopen("file","rb");
  if (__stream != (FILE *)0x0) {
    fseek(__stream,0,0);
    fread(data,19000,1,__stream);
    _Var4 = ftello(__stream);
    iVar2 = (int)_Var4;
    local_df88._0_8_ = 0;
    local_df88._8_8_ = 0;
    FUN_00100f32("7dd`8c6dg08068`17845dee1g7b14bdc12345678",0x10,1,local_df88);
    __s1 = FUN_00100c64(data);
    iVar3 = memcmp(__s1,local_df88,0x10);
    if (iVar3 == 0) {
      local_4a58._0_8_ = 0;
      local_4a58._8_8_ = 0;
      FUN_00100f32("7845dee1g7b14bdc12345678",0x10,1,local_4a58);
      __s1 = FUN_00100ce0(data,iVar2);
      iVar3 = memcmp(__s1,local_4a58,0x10);
      if (iVar3 == 0) {
        puts("* File is valid");
      }
      else {
        puts("* File is not valid");
      }
    }
    else {
      puts("File is not valid");
    }
    local_df68 = 0;
    local_df60 = 0;
    local_df58 = 0;
    local_df50 = 0;
    local_df48 = 0;
    local_df38 = 0;
    local_df30 = 0;
    local_df28 = 0;
    local_df20 = 0;
    local_df18 = 0;
    local_dffc = 0;
    while (local_dffc < 0x20) {
      __s1 = FUN_00100ce0(data,iVar2);
      puVar5 = FUN_00100c64(data);
      __s1 = FUN_00100bda((long)puVar5,(long)__s1);
      *(undefined *)((long)&local_df68 + (long)local_dffc) = __s1[local_dffc];
      local_dffc = local_dffc + 1;
    }
    local_dff8 = 0;
    while (local_dff8 < 5) {
      FUN_00100dc0(*(char **)(&DAT_00302080 + (long)local_dff8 * 8),local_df98);
      local_dff4 = 0;
      local_dff0 = 0;
      while (local_dff4 < 0x10) {
        sprintf(local_df08 + local_dff0,"%02x",(ulong)*(byte *)((long)local_df98 + (long)local_dff4)
               );
        local_dff4 = local_dff4 + 1;
        local_dff0 = local_dff0 + 2;
      }
      local_dee8 = 0;
      local_dfec = 0;
      while (local_dfec < 0x10) {
        *(undefined *)((long)&local_df38 + (long)(int)local_dfec) =
             *(undefined *)((long)local_df98 + (long)(int)local_dfec);
        local_dfec = local_dfec + 1;
      }
      iVar3 = strcmp(local_df08,(char *)&local_df68);
      if (iVar3 == 0) {
        local_dfe8 = 0;
        local_dfe4 = 0x10;
        while (local_dfe4 < iVar2 + -0x10) {
          *(char *)((long)&local_9498[0].x + (long)local_dfe8) = data[local_dfe4];
          local_dfe8 = local_dfe8 + 1;
          local_dfe4 = local_dfe4 + 1;
        }
        printf("[+] Decrypted File has been generated. (FinalDecryption.jpeg)");
        memset(local_4a58,0,19000);
        FUN_00100ea4(local_9498,(long)(iVar2 + -0x20),
                     *(uchar **)(&DAT_00302080 + (long)local_dff8 * 8),(uchar *)0xa);
        local_dfe0 = 0;
        while (local_dfe0 < iVar2 + -0x20) {
          local_dfe0 = local_dfe0 + 1;
        }
        FUN_00100ee2(local_4a58,iVar2 + -0x20);
      }
      local_dff8 = local_dff8 + 1;
    }
    puts("\n");
    fclose(__stream);
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 1;
}

FUN_00100f32の引数がいかにもなので詳しく調べた。

void FUN_00100f32(char *param_1,int param_2,byte param_3,byte *param_4)

{
  int local_c;

  local_c = 0;
  while (local_c < param_2) {
    param_4[local_c] = param_1[local_c] ^ param_3;
    local_c = local_c + 1;
  }
  return;
}

第1引数を1Bずつ第3引数でxorしたものを第4引数に代入しているだけのようだ。

この第4引数とmemcmpされているデータを生成していそうな関数、FUN_00100c64FUN_00100ce0を見てみる。

undefined * FUN_00100c64(char *param_1)

{
  long lVar1;
  long in_FS_OFFSET;
  int local_34;
  int local_30;

  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  local_34 = 0;
  local_30 = 0;
  while (local_30 < 0x10) {
    (&DAT_003020f0)[local_34] = param_1[local_30];
    local_34 = local_34 + 1;
    local_30 = local_30 + 1;
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return &DAT_003020f0;
}
undefined * FUN_00100ce0(char *param_1,int param_2)

{
  long lVar1;
  long in_FS_OFFSET;
  int local_34;
  int local_30;

  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  local_30 = param_2 + -0x10;
  local_34 = 0;
  while (local_30 < param_2) {
    (&DAT_003020e0)[local_34] = param_1[local_30];
    local_34 = local_34 + 1;
    local_30 = local_30 + 1;
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return &DAT_003020e0;
}

どちらも第1引数にはファイルの内容が渡されているので、FUN_00100c64はファイルの先頭0x10B、FUN_00100c64はファイルの末尾0x10Bをコピーする関数であると分かる。

先頭と末尾を補完してやれば復号できそうなので、FUN_00100f32の第1引数の各バイトを1でxorしたデータをfileの先頭と末尾に追加したらFinalDecryption.jpegが出力された。

このファイルを調べるとshellcode: \x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6\x52\xe8\x19\x00\x00\x00\x65\x63\x68\x6f\x20\x2d\x6e\x65\x20\x54\x4d\x43\x54\x46\x7b\x67\x30\x30\x64\x77\x30\x72\x6b\x7d\x00\x56\x57\x48\x89\xe6\x0f\x05といういかにも過ぎる文字列がある。この文字列をバイナリに戻すと/bin/sh -c echo -ne TMCTF{g00dw0rk}を実行しそうなアセンブリとなっている。(真面目に読んでいないので確証はない)

TMCTF{g00dw0rk}

Reversing-II 200

trendrunner.gbというファイルが渡される。
問題文とファイルサイズ、拡張子からGameBoyのROMファイルだと推測し、VisualBoyAdvance-Mに投げてみたところ起動した。

画面には START GAME、ENTER CODEの2つのメニュー項目に加え、TMCTF{___-___-___-__}というテキストが書いてあった。
START GAMEを選択するとマリオ風のゲームが始ったが途中で明らかに飛び越えられない高さの壁があり詰んだ。
ENTER CODEを選択するとアルファベット3桁の入力画面が出てきた。flagの最初のほうがアンダーバー3つをハイフンで区切ったものなのでこのコードがflagになっているのだろう。

特にコードのヒントらしきものをもらった記憶もないのでアセンブリを読んで調べた。ディスアセンブリにはmgbdisを用いた。

単純なチェックなら比較命令を3回短い間隔で行っている箇所があるだろうと推測し、cpでアセンブリを検索すると以下のようなコードが見つかった。

Call_000_053e:
    ld a, $15
    cp [hl]
    ret nz

    inc hl
    ld a, $01
    cp [hl]
    ret nz

    inc hl
    ld a, $12
    cp [hl]
    ret

hlレジスタのアドレスの値を直前でロードした値と比較している。比較している値が全て26以下なので、アルファベット順で何文字目かの値であると仮定するとVBSという文字列が得られる。
試しに入力してみるとOK: ABILITY UNLOCKED: HIGH JUMPと表示され、下のflagのアンダーバー3つがVBSに置きかわる。どうやら無事に通ったようだ。

HIGH JUMPを習得したので、もう一度START GAMEを選択してみると壁を越せるようになっているが、そこからしばらく歩くと壁に阻まれて進めなかった。どうやら1コードにつき1障害取り除かれるらしい。

Call_000_053eの呼び出し元を辿ると以下のような記述が見つかる。

Call_000_0520:
    ld hl, $c1aa
    call Call_000_053e
    ld a, $00
    ret z

    ld hl, $c1aa
    call Call_000_054c
    ld a, $01
    ret z

    ld hl, $c1aa
    call Call_000_057b
    ld a, $02
    ret z

    ld a, $ff
    ret

1つ目のcallが1つ目の空欄のチェックルーチンであったことを踏まえるとCall_000_054cが2つ目、Call_000_057bが3つ目の空欄のチェックルーチンであると推測できる。

Call_000_054cは以下のようになっている。

Call_000_054c:
    ld bc, $0575
    ld de, $c1ad
    ld a, [bc]
    xor [hl]
    ld [de], a
    inc hl
    inc de
    inc bc
    ld a, [bc]
    xor [hl]
    ld [de], a
    inc hl
    inc de
    inc bc
    ld a, [bc]
    xor [hl]
    ld [de], a
    inc hl
    inc de
    inc bc
    ld hl, $c1ad
    ld de, $0578
    ld b, $03

jr_000_056c:
    ld a, [de]
    cp [hl]
    ret nz

    inc de
    inc hl
    dec b
    jr nz, jr_000_056c

    ret

$0578のデータと、入力値と$0575のxorを比較している。

$0575$0578のデータはバイナリエディタで実際に確認した所0x1973200x1e6725だった。
あとは(0x1e6725 ^ 0x197320)で入力の期待値が得られる。
HUF

Call_000_057bは以下のようになっている。

Call_000_057b:
    ld de, $0000
    ld c, $03

jr_000_0580:
    ld b, $12
    call Call_000_0594
    ld a, [hl+]
    call Call_000_0b71
    dec c
    jr nz, jr_000_0580

    ld a, d
    cp $19
    ret nz

    ld a, e
    cp $22
    ret

Call_000_0594:
    push hl
    ld a, b
    or a

jr_000_0597:
    jr z, jr_000_05a4

    ld hl, $0000

jr_000_059c:
    add hl, de
    dec b
    jr nz, jr_000_059c

    ld d, h
    ld e, l
    jr jr_000_05a6

jr_000_05a4:
    ld d, a
    ld e, a

jr_000_05a6:
    pop hl
    ret

Call_000_0b71:
    add e
    ld e, a
    ld a, $00
    adc d
    ld d, a
    ret

アセンブリから入力値をhlとした以下の方程式を導出できる

0x1922 == ((hl[0]) * 0x12 + hl[1]) * 0x12 + hl[2] (x <- [0..2] | 0 < h[x] <= 25)

この方程式を解くとTPIという文字列が得られる。

この状態でSTART GAMEを選択すると無事ゲームをクリアすることが出来た。また、flagの残りXMも得られた。

TMCTF{VBS-HUF-TPI-XM}