vsCTF 2023 Writeup

この大会は2023/9/24 1:00(JST)~2023/9/25 1:00(JST)に開催されました。
今回もチームで参戦。結果は2033点で600チーム中60位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (Web)

Webページで右クリックやF12キーは効かない。

$ curl https://challs.vsc.tf/sanity-check  
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sanity Check</title>
    <style>
        body {
            background-color: #000021;
            font-family: "JetBrains Mono", monospace;
        }

        h1 {
            background: linear-gradient(to right, #fe5d00, #fe8c00);
            background-clip: text;
            -webkit-background-clip: text;
            color: transparent;
            text-align: center;
        }

        .centered {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    </style>
    <script>
        setInterval("debugger", 10)
    </script>
</head>

<body oncontextmenu="return false">
    <script>
        document.addEventListener('contextmenu', (e) => {
            e.preventDefault();
        }
        );
        document.onkeydown = function (e) {
            if (event.keyCode == 123) {
                return false;
            }
            if (e.ctrlKey && e.shiftKey && e.keyCode == 'I'.charCodeAt(0)) {
                return false;
            }
            if (e.ctrlKey && e.shiftKey && e.keyCode == 'C'.charCodeAt(0)) {
                return false;
            }
            if (e.ctrlKey && e.shiftKey && e.keyCode == 'J'.charCodeAt(0)) {
                return false;
            }
            if (e.ctrlKey && e.keyCode == 'U'.charCodeAt(0)) {
                return false;
            }
        }
    </script>
    <div class="centered">
        <h1>you know what to do.</h1>
        <!-- vsctf{c0ngratulati0ns_y0u_viewed_the_s0urc3!...welcome_to_vsctf!} -->
    </div>
</body>

</html>
vsctf{c0ngratulati0ns_y0u_viewed_the_s0urc3!...welcome_to_vsctf!}

Discord (Misc)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。

vsctf{w3lc0m3_t0_vsctf_2023!}

x0rr3al?!! (Reverse)

Ghidraでデコンパイルする。

undefined8 FUN_001019a4(void)

{
  char cVar1;
  int iVar2;
  int iVar3;
  long lVar4;
  size_t sVar5;
  long in_FS_OFFSET;
  int local_58;
  char local_48 [56];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  iVar2 = FUN_001017a4();
  if (iVar2 != 0) {
    FUN_001016ad();
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  lVar4 = ptrace(PTRACE_TRACEME,0,1,0);
  if (lVar4 == -1) {
    FUN_001016ad();
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  iVar2 = FUN_00101483(FUN_001019a4,0x200);
  FUN_00101355();
  FUN_001013f8();
  iVar3 = FUN_00101483(FUN_001019a4,0x200);
  if (iVar2 != iVar3) {
    FUN_001016ad();
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  printf("p4ss m3 th3 fl4g: ");
  __isoc99_scanf(&DAT_0010205d,local_48);
  sVar5 = strlen(local_48);
  if (sVar5 == 0x35) {
    for (local_58 = 0; local_58 < 0x35; local_58 = local_58 + 1) {
      cVar1 = FUN_0010150a((int)local_48[local_58],0);
      iVar3 = FUN_001014f7((int)cVar1);
      if (iVar3 != *(int *)(&DAT_001040a0 + (long)local_58 * 4)) {
        FUN_0010156a();
        goto LAB_00101b6c;
      }
      iVar3 = FUN_00101483(FUN_001019a4,0x200);
      if (iVar2 != iVar3) {
        FUN_001016ad();
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
    }
    FUN_00101614();
  }
  else {
    FUN_0010156a();
  }
LAB_00101b6c:
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

byte FUN_0010150a(byte param_1,int param_2)

{
  if (param_2 < 4) {
    param_1 = FUN_0010150a((int)(char)(param_1 ^ (&DAT_00104180)[(long)param_2 * 0xb]),param_2 + 1);
  }
  return param_1;
}

uint FUN_001014f7(uint param_1)

{
  return param_1 ^ 0x12;
}

void FUN_001013f8(void)

{
  int local_10;
  int local_c;
  
  for (local_10 = 0; local_10 < 0x1a; local_10 = local_10 + 1) {
    *(int *)(&DAT_001040a0 + (long)local_10 * 4) = (int)(char)(&DAT_00104040)[local_10];
  }
  for (local_c = 0; local_c < 0x1b; local_c = local_c + 1) {
    *(int *)(&DAT_001040a0 + (long)(local_c + 0x1a) * 4) = (int)(char)(&DAT_00104060)[local_c];
  }
  return;
}

                             DAT_00104040                                    XREF[2]:     FUN_001013f8:0010140e(*), 
                                                                                          FUN_001013f8:00101415(*)  
        00104040 7e              ??         7Eh    ~
        00104041 7b              ??         7Bh    {
        00104042 6b              ??         6Bh    k
        00104043 7c              ??         7Ch    |
        00104044 6e              ??         6Eh    n
        00104045 73              ??         73h    s
        00104046 7f              ??         7Fh    
        00104047 3b              ??         3Bh    ;
        00104048 3c              ??         3Ch    <
        00104049 63              ??         63h    c
        0010404a 57              ??         57h    W
        0010404b 3c              ??         3Ch    <
        0010404c 66              ??         66h    f
        0010404d 7c              ??         7Ch    |
        0010404e 39              ??         39h    9
        0010404f 57              ??         57h    W
        00104050 6c              ??         6Ch    l
        00104051 3b              ??         3Bh    ;
        00104052 6a              ??         6Ah    j
        00104053 7d              ??         7Dh    }
        00104054 6f              ??         6Fh    o
        00104055 6f              ??         6Fh    o
        00104056 3b              ??         3Bh    ;
        00104057 7a              ??         7Ah    z
        00104058 7b              ??         7Bh    {
        00104059 57              ??         57h    W
        0010405a 00              ??         00h
        0010405b 00              ??         00h
        0010405c 00              ??         00h
        0010405d 00              ??         00h
        0010405e 00              ??         00h
        0010405f 00              ??         00h
                             DAT_00104060                                    XREF[2]:     FUN_001013f8:0010144c(*), 
                                                                                          FUN_001013f8:00101453(*)  
        00104060 3c              ??         3Ch    <
        00104061 7a              ??         7Ah    z
        00104062 3b              ??         3Bh    ;
        00104063 57              ??         57h    W
        00104064 66              ??         66h    f
        00104065 38              ??         38h    8
        00104066 57              ??         57h    W
        00104067 65              ??         65h    e
        00104068 3c              ??         3Ch    <
        00104069 7c              ??         7Ch    |
        0010406a 6b              ??         6Bh    k
        0010406b 60              ??         60h    `
        0010406c 57              ??         57h    W
        0010406d 6e              ??         6Eh    n
        0010406e 38              ??         38h    8
        0010406f 7a              ??         7Ah    z
        00104070 57              ??         57h    W
        00104071 7c              ??         7Ch    |
        00104072 60              ??         60h    `
        00104073 3b              ??         3Bh    ;
        00104074 57              ??         57h    W
        00104075 3b              ??         3Bh    ;
        00104076 39              ??         39h    9
        00104077 3b              ??         3Bh    ;
        00104078 3b              ??         3Bh    ;
        00104079 3f              ??         3Fh    ?
        0010407a 75              ??         75h    u

void FUN_00101355(void)

{
  strcpy(&DAT_00104180,&DAT_00104010);
  strcat(&DAT_00104180,&DAT_00104020);
  strcpy(&DAT_0010418a,&DAT_00104015);
  strcat(&DAT_0010418a,s_fvsctiamfrnow0kkeyw0wkeyw_00104025);
  strcpy(&DAT_00104194,s_fvsctiamfrnow0kkeyw0wkeyw_00104025 + 5);
  strcat(&DAT_00104194,s_fvsctiamfrnow0kkeyw0wkeyw_00104025 + 10);
  strcpy(&DAT_0010419e,s_fvsctiamfrnow0kkeyw0wkeyw_00104025 + 0xf);
  strcat(&DAT_0010419e,s_fvsctiamfrnow0kkeyw0wkeyw_00104025 + 0x14);
  return;
}

データ領域のデータをFUN_00101355に従って、書いてみる。

・2行目まで
00104180 s3cRvsct3ts3

・4行目まで
00104180 s3cRvsct3tvsctfv
00104190 sctiamfrnow0kkey
001041a0 w0wkeyw

・6行目まで
00104180 s3cRvsct3tvsctfv
00104190 sctiiamfrnow0kke
001041a0 yw0wkeywnow0kkey
001041b0 w0wkeyw

・8行目まで
00104180 s3cRvsct3tvsctfv
00104190 sctiiamfrnow0kke
001041a0 yw0wkeywwkeyw\0ey
001041b0 w0wkeyw

入力文字0文字目の場合、以下のような処理をしている。

・cVar1 = FUN_0010150a((int)local_48[0],0)
 ・param_1 = FUN_0010150a((int)(char)((int)local_48[0] ^ (&DAT_00104180)[0]),1)
  ・param_1 = FUN_0010150a((int)(char)((int)local_48[0] ^ (&DAT_00104180)[0]) ^ (&DAT_00104180)[11]),2)
   ・param_1 = FUN_0010150a((int)(char)((int)local_48[0] ^ (&DAT_00104180)[0]) ^ (&DAT_00104180)[11]) ^ (&DAT_00104180)[22]),3)
    ・param_1 = FUN_0010150a((int)(char)((int)local_48[0] ^ (&DAT_00104180)[0]) ^ (&DAT_00104180)[11]) ^ (&DAT_00104180)[22]) ^ (&DAT_00104180)[33]),4)
 ・cVar1 =param_1
・iVar3 = FUN_001014f7((int)cVar1)
 ・iVar3 = cVar1 ^ 0x12
・001040a0の先頭と比較

これを元に入力文字列を割り出す。

#!/usr/bin/env python3

data = b's3cRvsct3tvsctfvsctiiamfrnow0kkeyw0wkeywwkeyw\x00eyw0wkeyw'
target = b'~{k|ns\x7f;<cW<f|9Wl;j}oo;z{W<z;Wf8We<|k`Wn8zW|`;W;9;;?u'

flag = ''
for i in range(0x35):
    v = target[i] ^ 0x12
    c = v ^ data[0] ^ data[11] ^ data[22] ^ data[33]
    flag += chr(c)
print(flag)
vsctf{w34k_4nt1_d3bugg3rs_4r3_n0_m4tch_f0r_th3_31337}

Redundancy (Crypto)

RSA暗号。2つのe(GCDは5)でフラグを含むメッセージが暗号がされている。Common Modulus Attackでメッセージの5乗の値を復号する。
flagの"{}"の中以外のメッセージがわかっており、"{}"の中は16バイト未満であることから、"{}"の中の長さを総当たりしてCoppersmithの定理で復号する。

#!/usr/bin/env sage
import gmpy2
from Crypto.Util.number import *

def commom_modulus_attack(c1, c2, e1, e2, n):
    gcd, s1, s2 = gmpy2.gcdext(e1, e2)
    if s1 < 0:
        s1 = -s1
        c1 = gmpy2.invert(c1, n)
    elif s2 < 0:
        s2 = -s2
        c2 = gmpy2.invert(c2, n)

    v = pow(c1, s1, n)
    w = pow(c2, s2, n)
    x = (v*w) % n
    return int(x)

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

n = int(params[0].split(' ')[-1])
c1 = int(params[1].split(' ')[-1])
c2 = int(params[2].split(' ')[-1])
e1 = 2
e2 = 3

_m = commom_modulus_attack(c1, c2, e1, e2, n)

e = 5

for l in range(1, 16):
    mbar = "Wow good job the flag is (omg hype hype): vsctf{" + "\x00" * l +"}"
    mbar = int(bytes_to_long(mbar.encode()))

    PR.<x> = PolynomialRing(Zmod(n))
    f = (mbar + x)^e - _m
    try:
        x0 = f.small_roots(X=256^(l + 1), beta=1)[0]
        break
    except:
        continue

m = int(mbar + x0)
msg = long_to_bytes(m).decode()
print(msg)

復号結果は以下の通り。

Wow good job the flag is (omg hype hype): vsctf{WE<3COPPERSMITH}
vsctf{WE<3COPPERSMITH}

Survey (Misc)

アンケートに答えていくと、最後にbase64でフラグが書かれていた。

dnNjdGZ7dGhhbmtzX2Zvcl9wbGF5aW5nX3V3dX0=

base64デコードする。

$ echo dnNjdGZ7dGhhbmtzX2Zvcl9wbGF5aW5nX3V3dX0= | base64 -d        
vsctf{thanks_for_playing_uwu}
vsctf{thanks_for_playing_uwu}