この記事は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
- 100
- pyinstaller問
- 200
- 普通rev+ちょっとエスパー
Reversing-II
- 200
- GameBoy rev, Z80アセンブリとあそぼう!
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_00100c64
とFUN_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
のデータはバイナリエディタで実際に確認した所0x197320
、0x1e6725
だった。
あとは(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}